mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
De-angularize discover doc (#44660)
* Add react component * Remove angular * Refactor component data fetching * Add tests * Remove translation that need to be retranslated
This commit is contained in:
parent
39ee90b6b4
commit
c97811fcfb
12 changed files with 504 additions and 317 deletions
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// Load the kibana app dependencies.
|
||||
import ngMock from 'ng_mock';
|
||||
import expect from '@kbn/expect';
|
||||
import '..';
|
||||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
|
||||
let $scope;
|
||||
let createController;
|
||||
|
||||
const init = function (index, type, id) {
|
||||
|
||||
ngMock.module('kibana');
|
||||
|
||||
// Stub services
|
||||
ngMock.module(function ($provide) {
|
||||
$provide.service('$route', function (Private) {
|
||||
this.current = {
|
||||
locals: {
|
||||
indexPattern: Private(FixturesStubbedLogstashIndexPatternProvider)
|
||||
},
|
||||
params: {
|
||||
index: index || 'myIndex',
|
||||
type: type || 'myType',
|
||||
id: id || 'myId'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
$provide.service('es', function ($q) {
|
||||
this.search = function (config) {
|
||||
const deferred = $q.defer();
|
||||
|
||||
switch (config.index) {
|
||||
case 'goodSearch':
|
||||
deferred.resolve({
|
||||
hits: {
|
||||
total: 1,
|
||||
hits: [{
|
||||
_source: {
|
||||
foo: true
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'badSearch':
|
||||
deferred.resolve({
|
||||
hits: {
|
||||
total: 0,
|
||||
hits: []
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'missingIndex':
|
||||
deferred.reject({ status: 404 });
|
||||
break;
|
||||
case 'badRequest':
|
||||
deferred.reject({ status: 500 });
|
||||
break;
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Create the scope
|
||||
ngMock.inject(function ($rootScope, $controller) {
|
||||
$scope = $rootScope.$new();
|
||||
|
||||
createController = function () {
|
||||
return $controller('doc', {
|
||||
'$scope': $scope
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
createController();
|
||||
};
|
||||
|
||||
|
||||
describe('Doc app controller', function () {
|
||||
|
||||
it('should set status=found if the document was found', function (done) {
|
||||
init('goodSearch');
|
||||
$scope.$digest();
|
||||
expect($scope.status).to.be('found');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should attach the hit to scope', function (done) {
|
||||
init('goodSearch');
|
||||
$scope.$digest();
|
||||
expect($scope.hit).to.be.an(Object);
|
||||
expect($scope.hit._source).to.be.an(Object);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should set status=notFound if the document was missing', function (done) {
|
||||
init('badSearch');
|
||||
$scope.$digest();
|
||||
expect($scope.status).to.be('notFound');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should set status=notFound if the request returns a 404', function (done) {
|
||||
init('missingIndex');
|
||||
$scope.$digest();
|
||||
expect($scope.status).to.be('notFound');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should set status=error if the request fails with any other code', function (done) {
|
||||
init('badRequest');
|
||||
$scope.$digest();
|
||||
expect($scope.status).to.be('error');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should disable the time filter', function (done) {
|
||||
init();
|
||||
expect(timefilter.isAutoRefreshSelectorEnabled).to.be(false);
|
||||
expect(timefilter.isTimeRangeSelectorEnabled).to.be(false);
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
});
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import 'ui/notify';
|
||||
import 'ui/courier';
|
||||
import 'ui/index_patterns';
|
||||
import html from '../index.html';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
import 'plugins/kibana/doc_viewer';
|
||||
import { getRootBreadcrumbs } from 'plugins/kibana/discover/breadcrumbs';
|
||||
|
||||
const app = uiModules.get('apps/doc', [
|
||||
'kibana/courier',
|
||||
'kibana/index_patterns'
|
||||
]);
|
||||
|
||||
|
||||
const resolveIndexPattern = {
|
||||
indexPattern: function (indexPatterns, savedSearches, $route) {
|
||||
return indexPatterns.get($route.current.params.indexPattern);
|
||||
}
|
||||
};
|
||||
|
||||
const k7Breadcrumbs = ($route) => [
|
||||
...getRootBreadcrumbs(),
|
||||
{
|
||||
text: `${$route.current.params.index}#${$route.current.params.id}`
|
||||
}
|
||||
];
|
||||
|
||||
uiRoutes
|
||||
// the old, pre 8.0 route, no longer used, keep it to stay compatible
|
||||
// somebody might have bookmarked his favorite log messages
|
||||
.when('/doc/:indexPattern/:index/:type', {
|
||||
template: html,
|
||||
resolve: resolveIndexPattern,
|
||||
k7Breadcrumbs
|
||||
})
|
||||
//the new route, es 7 deprecated types, es 8 removed them
|
||||
.when('/doc/:indexPattern/:index', {
|
||||
template: html,
|
||||
resolve: resolveIndexPattern,
|
||||
k7Breadcrumbs
|
||||
});
|
||||
|
||||
app.controller('doc', function ($scope, $route, es) {
|
||||
timefilter.disableAutoRefreshSelector();
|
||||
timefilter.disableTimeRangeSelector();
|
||||
|
||||
// Pretty much only need this for formatting, not actually using it for fetching anything.
|
||||
$scope.indexPattern = $route.current.locals.indexPattern;
|
||||
|
||||
const computedFields = $scope.indexPattern.getComputedFields();
|
||||
|
||||
es.search({
|
||||
index: $route.current.params.index,
|
||||
body: {
|
||||
query: {
|
||||
ids: {
|
||||
values: [$route.current.params.id]
|
||||
}
|
||||
},
|
||||
stored_fields: computedFields.storedFields,
|
||||
_source: true,
|
||||
script_fields: computedFields.scriptFields,
|
||||
docvalue_fields: computedFields.docvalueFields
|
||||
}
|
||||
}).then(function (resp) {
|
||||
if (resp.hits) {
|
||||
if (resp.hits.total < 1) {
|
||||
$scope.status = 'notFound';
|
||||
} else {
|
||||
$scope.status = 'found';
|
||||
$scope.hit = resp.hits.hits[0];
|
||||
}
|
||||
}
|
||||
}).catch(function (err) {
|
||||
if (err.status === 404) {
|
||||
$scope.status = 'notFound';
|
||||
} else {
|
||||
$scope.status = 'error';
|
||||
$scope.resp = err;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
100
src/legacy/core_plugins/kibana/public/doc/doc.test.tsx
Normal file
100
src/legacy/core_plugins/kibana/public/doc/doc.test.tsx
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
// @ts-ignore
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { Doc, DocProps } from './doc';
|
||||
|
||||
// Suppress warnings about "act" until we use React 16.9
|
||||
/* eslint-disable no-console */
|
||||
const originalError = console.error;
|
||||
beforeAll(() => {
|
||||
console.error = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.error = originalError;
|
||||
});
|
||||
|
||||
export const waitForPromises = () => new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
/**
|
||||
* this works but logs ugly error messages until we're using React 16.9
|
||||
* should be adapted when we upgrade
|
||||
*/
|
||||
async function mountDoc(search: () => void, update = false, indexPatternGetter: any = null) {
|
||||
const indexPattern = {
|
||||
getComputedFields: () => [],
|
||||
};
|
||||
const indexPatternService = {
|
||||
get: indexPatternGetter ? indexPatternGetter : jest.fn(() => Promise.resolve(indexPattern)),
|
||||
} as any;
|
||||
|
||||
const props = {
|
||||
id: '1',
|
||||
index: 'index1',
|
||||
esClient: { search } as any,
|
||||
indexPatternId: 'xyz',
|
||||
indexPatternService,
|
||||
} as DocProps;
|
||||
let comp!: ReactWrapper;
|
||||
act(() => {
|
||||
comp = mountWithIntl(<Doc {...props} />);
|
||||
if (update) comp.update();
|
||||
});
|
||||
if (update) {
|
||||
await waitForPromises();
|
||||
comp.update();
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
|
||||
describe('Test of <Doc /> of Discover', () => {
|
||||
it('renders loading msg', async () => {
|
||||
const comp = await mountDoc(jest.fn());
|
||||
expect(findTestSubject(comp, 'doc-msg-loading').length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders IndexPattern notFound msg', async () => {
|
||||
const indexPatternGetter = jest.fn(() => Promise.reject({ savedObjectId: '007' }));
|
||||
const comp = await mountDoc(jest.fn(), true, indexPatternGetter);
|
||||
expect(findTestSubject(comp, 'doc-msg-notFoundIndexPattern').length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders notFound msg', async () => {
|
||||
const search = jest.fn(() => Promise.reject({ status: 404 }));
|
||||
const comp = await mountDoc(search, true);
|
||||
expect(findTestSubject(comp, 'doc-msg-notFound').length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders error msg', async () => {
|
||||
const search = jest.fn(() => Promise.reject('whatever'));
|
||||
const comp = await mountDoc(search, true);
|
||||
expect(findTestSubject(comp, 'doc-msg-error').length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders elasticsearch hit ', async () => {
|
||||
const hit = { hits: { total: 1, hits: [{ _id: 1, _source: { test: 1 } }] } };
|
||||
const search = jest.fn(() => Promise.resolve(hit));
|
||||
const comp = await mountDoc(search, true);
|
||||
expect(findTestSubject(comp, 'doc-hit').length).toBe(1);
|
||||
});
|
||||
});
|
145
src/legacy/core_plugins/kibana/public/doc/doc.tsx
Normal file
145
src/legacy/core_plugins/kibana/public/doc/doc.tsx
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPageContent } from '@elastic/eui';
|
||||
import { IndexPatterns } from 'ui/index_patterns';
|
||||
import { metadata } from 'ui/metadata';
|
||||
import { ElasticSearchHit } from 'ui/registry/doc_views_types';
|
||||
import { DocViewer } from '../doc_viewer/doc_viewer';
|
||||
import { ElasticRequestState, useEsDocSearch } from './use_es_doc_search';
|
||||
|
||||
export interface ElasticSearchResult {
|
||||
hits: {
|
||||
hits: [ElasticSearchHit];
|
||||
max_score: number;
|
||||
};
|
||||
timed_out: boolean;
|
||||
took: number;
|
||||
shards: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DocProps {
|
||||
/**
|
||||
* Id of the doc in ES
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Index in ES to query
|
||||
*/
|
||||
index: string;
|
||||
/**
|
||||
* IndexPattern ID used to get IndexPattern entity
|
||||
* that's used for adding additional fields (stored_fields, script_fields, docvalue_fields)
|
||||
*/
|
||||
indexPatternId: string;
|
||||
/**
|
||||
* IndexPatternService to get a given index pattern by ID
|
||||
*/
|
||||
indexPatternService: IndexPatterns;
|
||||
/**
|
||||
* Client of ElasticSearch to use for the query
|
||||
*/
|
||||
esClient: {
|
||||
search: (payload: { index: string; body: Record<string, any> }) => Promise<ElasticSearchResult>;
|
||||
};
|
||||
}
|
||||
|
||||
export function Doc(props: DocProps) {
|
||||
const [reqState, hit, indexPattern] = useEsDocSearch(props);
|
||||
|
||||
return (
|
||||
<EuiPageContent>
|
||||
{reqState === ElasticRequestState.NotFoundIndexPattern && (
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
data-test-subj={`doc-msg-notFoundIndexPattern`}
|
||||
iconType="alert"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.doc.failedToLocateIndexPattern"
|
||||
defaultMessage="No index pattern matches ID {indexPatternId}"
|
||||
values={{ indexPatternId: props.indexPatternId }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{reqState === ElasticRequestState.NotFound && (
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
data-test-subj={`doc-msg-notFound`}
|
||||
iconType="alert"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.doc.failedToLocateDocumentDescription"
|
||||
defaultMessage="Cannot find document"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.doc.couldNotFindDocumentsDescription"
|
||||
defaultMessage="No documents match that ID."
|
||||
/>
|
||||
</EuiCallOut>
|
||||
)}
|
||||
|
||||
{reqState === ElasticRequestState.Error && (
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
data-test-subj={`doc-msg-error`}
|
||||
iconType="alert"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.doc.failedToExecuteQueryDescription"
|
||||
defaultMessage="Cannot run search"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.doc.somethingWentWrongDescription"
|
||||
defaultMessage="{indexName} is missing."
|
||||
values={{ indexName: props.index }}
|
||||
/>{' '}
|
||||
<EuiLink
|
||||
href={`https://www.elastic.co/guide/en/elasticsearch/reference/${metadata.branch}/indices-exists.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.doc.somethingWentWrongDescriptionAddon"
|
||||
defaultMessage="Please ensure the index exists."
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiCallOut>
|
||||
)}
|
||||
|
||||
{reqState === ElasticRequestState.Loading && (
|
||||
<EuiCallOut data-test-subj={`doc-msg-loading`}>
|
||||
<EuiLoadingSpinner size="m" />{' '}
|
||||
<FormattedMessage id="kbn.doc.loadingDescription" defaultMessage="Loading…" />
|
||||
</EuiCallOut>
|
||||
)}
|
||||
|
||||
{reqState === ElasticRequestState.Found && hit !== null && indexPattern && (
|
||||
<div data-test-subj="doc-hit">
|
||||
<DocViewer hit={hit} indexPattern={indexPattern} />
|
||||
</div>
|
||||
)}
|
||||
</EuiPageContent>
|
||||
);
|
||||
}
|
|
@ -16,5 +16,21 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { wrapInI18nContext } from 'ui/i18n';
|
||||
// @ts-ignore
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { Doc } from './doc';
|
||||
|
||||
import './controllers/doc';
|
||||
uiModules.get('apps/discover').directive('discoverDoc', function(reactDirective: any) {
|
||||
return reactDirective(
|
||||
wrapInI18nContext(Doc),
|
||||
[
|
||||
['id', { watchDepth: 'value' }],
|
||||
['index', { watchDepth: 'value' }],
|
||||
['indexPatternId', { watchDepth: 'reference' }],
|
||||
['indexPatternService', { watchDepth: 'reference' }],
|
||||
['esClient', { watchDepth: 'reference' }],
|
||||
],
|
||||
{ restrict: 'E' }
|
||||
);
|
||||
});
|
|
@ -1,61 +1,9 @@
|
|||
<div ng-controller="doc" class="app-container">
|
||||
<div class="kuiViewContent">
|
||||
<!-- no results -->
|
||||
<div class="kuiViewContentItem" ng-if="status === 'notFound'">
|
||||
<div class="kuiInfoPanel kuiInfoPanel--error kuiVerticalRhythm">
|
||||
<div class="kuiInfoPanelHeader">
|
||||
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--error fa-warning"></span>
|
||||
<span
|
||||
class="kuiInfoPanelHeader__title"
|
||||
i18n-id="kbn.doc.failedToLocateDocumentDescription"
|
||||
i18n-default-message="Failed to locate document"
|
||||
></span>
|
||||
</div>
|
||||
<div class="kuiInfoPanelBody">
|
||||
<div
|
||||
class="kuiInfoPanelBody__message"
|
||||
i18n-id="kbn.doc.couldNotFindDocumentsDescription"
|
||||
i18n-default-message="Unfortunately I could not find any documents matching that id, of that type, in that index. I tried really hard. I wanted it to be there. Sometimes I swear documents grow legs and just walk out of the index. Sneaky. I wish I could offer some advice here, something to make you feel better"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- error -->
|
||||
<div class="kuiViewContentItem" ng-if="status === 'error'">
|
||||
<div class="kuiInfoPanel kuiInfoPanel--error kuiVerticalRhythm">
|
||||
<div class="kuiInfoPanelHeader">
|
||||
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--error fa-warning"></span>
|
||||
<span
|
||||
class="kuiInfoPanelHeader__title"
|
||||
i18n-id="kbn.doc.failedToExecuteQueryDescription"
|
||||
i18n-default-message="Failed to execute query"
|
||||
></span>
|
||||
</div>
|
||||
<div class="kuiInfoPanelBody">
|
||||
<div
|
||||
class="kuiInfoPanelBody__message"
|
||||
i18n-id="kbn.doc.somethingWentWrongDescription"
|
||||
i18n-default-message="Oh no. Something went very wrong. Its not just that I couldn't find your document, I couldn't even try. The index was missing, or the type. Go check out Elasticsearch, something isn't quite right here."
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- loading -->
|
||||
<div class="kuiViewContentItem" ng-if="status === undefined">
|
||||
<div class="kuiPanel kuiPanel--centered">
|
||||
<div
|
||||
class="kuiTableInfo"
|
||||
i18n-id="kbn.doc.loadingDescription"
|
||||
i18n-default-message="Loading…"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- result -->
|
||||
<div class="kuiViewContentItem" ng-if="status === 'found'" data-test-subj="doc-hit">
|
||||
<doc-viewer hit="hit" index-pattern="indexPattern"></doc-viewer>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-container">
|
||||
<discover-doc
|
||||
es-client="esClient"
|
||||
id="id"
|
||||
index="index"
|
||||
index-pattern-id="indexPatternId"
|
||||
index-pattern-service="indexPatternService"
|
||||
></discover-doc>
|
||||
</div>
|
||||
|
|
52
src/legacy/core_plugins/kibana/public/doc/index.ts
Normal file
52
src/legacy/core_plugins/kibana/public/doc/index.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { IndexPatterns } from 'ui/index_patterns';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
// @ts-ignore
|
||||
import { getRootBreadcrumbs } from 'plugins/kibana/discover/breadcrumbs';
|
||||
// @ts-ignore
|
||||
import html from './index.html';
|
||||
import './doc_directive';
|
||||
|
||||
uiRoutes
|
||||
// the old, pre 8.0 route, no longer used, keep it to stay compatible
|
||||
// somebody might have bookmarked his favorite log messages
|
||||
.when('/doc/:indexPattern/:index/:type', {
|
||||
redirectTo: '/doc/:indexPattern/:index',
|
||||
})
|
||||
// the new route, es 7 deprecated types, es 8 removed them
|
||||
.when('/doc/:indexPattern/:index', {
|
||||
controller: ($scope: any, $route: any, es: any, indexPatterns: IndexPatterns) => {
|
||||
timefilter.disableAutoRefreshSelector();
|
||||
timefilter.disableTimeRangeSelector();
|
||||
$scope.esClient = es;
|
||||
$scope.id = $route.current.params.id;
|
||||
$scope.index = $route.current.params.index;
|
||||
$scope.indexPatternId = $route.current.params.indexPattern;
|
||||
$scope.indexPatternService = indexPatterns;
|
||||
},
|
||||
template: html,
|
||||
k7Breadcrumbs: ($route: any) => [
|
||||
...getRootBreadcrumbs(),
|
||||
{
|
||||
text: `${$route.current.params.index}#${$route.current.params.id}`,
|
||||
},
|
||||
],
|
||||
});
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { renderHook, act } from 'react-hooks-testing-library';
|
||||
import { buildSearchBody, useEsDocSearch, ElasticRequestState } from './use_es_doc_search';
|
||||
import { DocProps } from './doc';
|
||||
|
||||
// Suppress warnings about "act" until we use React 16.9
|
||||
/* eslint-disable no-console */
|
||||
const originalError = console.error;
|
||||
beforeAll(() => {
|
||||
console.error = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.error = originalError;
|
||||
});
|
||||
|
||||
describe('Test of <Doc /> helper / hook', () => {
|
||||
test('buildSearchBody', () => {
|
||||
const indexPattern = {
|
||||
getComputedFields: () => ({ storedFields: [], scriptFields: [], docvalueFields: [] }),
|
||||
} as any;
|
||||
const actual = buildSearchBody('1', indexPattern);
|
||||
expect(actual).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"_source": true,
|
||||
"docvalue_fields": Array [],
|
||||
"query": Object {
|
||||
"ids": Object {
|
||||
"values": Array [
|
||||
"1",
|
||||
],
|
||||
},
|
||||
},
|
||||
"script_fields": Array [],
|
||||
"stored_fields": Array [],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('useEsDocSearch', () => {
|
||||
const indexPattern = {
|
||||
getComputedFields: () => [],
|
||||
};
|
||||
const indexPatternService = {
|
||||
get: jest.fn(() => Promise.resolve(indexPattern)),
|
||||
} as any;
|
||||
const props = {
|
||||
id: '1',
|
||||
index: 'index1',
|
||||
esClient: { search: jest.fn() },
|
||||
indexPatternId: 'xyz',
|
||||
indexPatternService,
|
||||
} as DocProps;
|
||||
let hook;
|
||||
act(() => {
|
||||
hook = renderHook((p: DocProps) => useEsDocSearch(p), { initialProps: props });
|
||||
});
|
||||
// @ts-ignore
|
||||
expect(hook.result.current).toEqual([ElasticRequestState.Loading, null, null]);
|
||||
expect(indexPatternService.get).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ElasticSearchHit } from 'ui/registry/doc_views_types';
|
||||
import { DocProps } from './doc';
|
||||
import { IndexPattern } from '../../../data/public/index_patterns';
|
||||
|
||||
export enum ElasticRequestState {
|
||||
Loading,
|
||||
NotFound,
|
||||
Found,
|
||||
Error,
|
||||
NotFoundIndexPattern,
|
||||
}
|
||||
|
||||
/**
|
||||
* helper function to build a query body for Elasticsearch
|
||||
* https://www.elastic.co/guide/en/elasticsearch/reference/current//query-dsl-ids-query.html
|
||||
*/
|
||||
export function buildSearchBody(id: string, indexPattern: IndexPattern): Record<string, any> {
|
||||
const computedFields = indexPattern.getComputedFields();
|
||||
|
||||
return {
|
||||
query: {
|
||||
ids: {
|
||||
values: [id],
|
||||
},
|
||||
},
|
||||
stored_fields: computedFields.storedFields,
|
||||
_source: true,
|
||||
script_fields: computedFields.scriptFields,
|
||||
docvalue_fields: computedFields.docvalueFields,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom react hook for querying a single doc in ElasticSearch
|
||||
*/
|
||||
export function useEsDocSearch({
|
||||
esClient,
|
||||
id,
|
||||
index,
|
||||
indexPatternId,
|
||||
indexPatternService,
|
||||
}: DocProps): [ElasticRequestState, ElasticSearchHit | null, IndexPattern | null] {
|
||||
const [indexPattern, setIndexPattern] = useState<IndexPattern | null>(null);
|
||||
const [status, setStatus] = useState(ElasticRequestState.Loading);
|
||||
const [hit, setHit] = useState<ElasticSearchHit | null>(null);
|
||||
|
||||
async function requestData() {
|
||||
try {
|
||||
const indexPatternEntity = await indexPatternService.get(indexPatternId);
|
||||
setIndexPattern(indexPatternEntity);
|
||||
|
||||
const { hits } = await esClient.search({
|
||||
index,
|
||||
body: buildSearchBody(id, indexPatternEntity),
|
||||
});
|
||||
|
||||
if (hits && hits.hits && hits.hits[0]) {
|
||||
setStatus(ElasticRequestState.Found);
|
||||
setHit(hits.hits[0]);
|
||||
} else {
|
||||
setStatus(ElasticRequestState.NotFound);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.savedObjectId) {
|
||||
setStatus(ElasticRequestState.NotFoundIndexPattern);
|
||||
} else if (err.status === 404) {
|
||||
setStatus(ElasticRequestState.NotFound);
|
||||
} else {
|
||||
setStatus(ElasticRequestState.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
requestData();
|
||||
}, []);
|
||||
|
||||
return [status, hit, indexPattern];
|
||||
}
|
|
@ -44,6 +44,12 @@ export function DocViewer(renderProps: DocViewRenderProps) {
|
|||
};
|
||||
});
|
||||
|
||||
if (!tabs.length) {
|
||||
// There there's a minimum of 2 tabs active in Discover.
|
||||
// This condition takes care of unit tests with 0 tabs.
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="kbnDocViewer">
|
||||
<EuiTabbedContent tabs={tabs} />
|
||||
|
|
|
@ -1597,11 +1597,9 @@
|
|||
"kbn.discover.topNav.openSearchPanel.openSearchTitle": "検索を開く",
|
||||
"kbn.discover.valueIsNotConfiguredIndexPatternIDWarningTitle": "{stateVal} は設定されたインデックスパターン ID ではありません",
|
||||
"kbn.discoverTitle": "ディスカバリ",
|
||||
"kbn.doc.couldNotFindDocumentsDescription": "申し訳ございませんが、このインデックスでこの ID と一致するこのタイプのドキュメントは見つかりませんでした。全力を尽くしました。お役に立ちたかったのですが。まるでドキュメントに脚が生えてインボックスから逃げて行ったようです。ずるがしこいやつですね。何かアドバイスでもあれば良いのですが。",
|
||||
"kbn.doc.failedToExecuteQueryDescription": "クエリの実行に失敗しました",
|
||||
"kbn.doc.failedToLocateDocumentDescription": "ドキュメントが見つかりませんでした",
|
||||
"kbn.doc.loadingDescription": "読み込み中…",
|
||||
"kbn.doc.somethingWentWrongDescription": "おっと。何か問題が起こりました。ドキュメントが見つからなかっただけではなくて、探すことすらできませんでした。インボックスかタイプが存在しないようです。Elasticsearch をチェックしてみてください。何かおかしなことが起きています。",
|
||||
"kbn.docTable.limitedSearchResultLabel": "{resultCount} 件の結果に制限。検索結果の絞り込み。",
|
||||
"kbn.docTable.noResultsTitle": "結果が見つかりませんでした",
|
||||
"kbn.docTable.tableHeader.moveColumnLeftButtonAriaLabel": "{columnName} 列を左に移動",
|
||||
|
|
|
@ -1598,11 +1598,9 @@
|
|||
"kbn.discover.topNav.openSearchPanel.openSearchTitle": "打开搜索",
|
||||
"kbn.discover.valueIsNotConfiguredIndexPatternIDWarningTitle": "{stateVal} 不是配置的索引模式 ID",
|
||||
"kbn.discoverTitle": "Discover",
|
||||
"kbn.doc.couldNotFindDocumentsDescription": "抱歉,我无法在该索引中找到任何匹配该 ID 且为该类型的文档。我已进行非常努力的尝试。我希望它存在。有时候,我觉得文件一定长了腿,自行逃离了索引。有点诡异。我希望能够提供一些建议让您会感觉好一点",
|
||||
"kbn.doc.failedToExecuteQueryDescription": "无法执行查询",
|
||||
"kbn.doc.failedToLocateDocumentDescription": "无法找到文档",
|
||||
"kbn.doc.loadingDescription": "正在加载……",
|
||||
"kbn.doc.somethingWentWrongDescription": "哎呦。出了问题。不是我找不到您的文档,而是无法尝试。索引缺失或类型缺失。去问问 Elasticsearch,似乎哪里不对劲。",
|
||||
"kbn.docTable.limitedSearchResultLabel": "仅限 {resultCount} 个结果。优化您的搜索。",
|
||||
"kbn.docTable.noResultsTitle": "找不到结果",
|
||||
"kbn.docTable.tableHeader.moveColumnLeftButtonAriaLabel": "向左移动“{columnName}”列",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue