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:
Matthias Wilhelm 2019-09-19 08:36:42 +02:00 committed by GitHub
parent 39ee90b6b4
commit c97811fcfb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 504 additions and 317 deletions

View file

@ -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();
});
});

View file

@ -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;
}
});
});

View 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);
});
});

View 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>
);
}

View file

@ -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' }
);
});

View file

@ -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>

View 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}`,
},
],
});

View file

@ -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();
});
});

View file

@ -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];
}

View file

@ -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} />

View file

@ -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} 列を左に移動",

View file

@ -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}”列",