mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
parent
052e3472b1
commit
4d6eeff3e0
84 changed files with 1035 additions and 712 deletions
|
@ -30,9 +30,16 @@ export function plugin() {
|
|||
export { DataStart };
|
||||
|
||||
export { Field, FieldType, IFieldList, IndexPattern } from './index_patterns';
|
||||
export { SavedQuery, SavedQueryTimeFilter } from '../../../../plugins/data/public';
|
||||
export { EsQuerySortValue, FetchOptions, ISearchSource, SortDirection } from './search/types';
|
||||
export { SearchSourceFields } from './search/types';
|
||||
export {
|
||||
SavedQueryAttributes,
|
||||
SavedQuery,
|
||||
SavedQueryTimeFilter,
|
||||
} from '../../../../plugins/data/public';
|
||||
|
||||
/** @public static code */
|
||||
export * from '../common';
|
||||
export { FilterStateManager } from './filter/filter_manager';
|
||||
export { getFromSavedObject, getRoutes, flattenHitWrapper } from './index_patterns';
|
||||
export { getRequestInspectorStats, getResponseInspectorStats } from './search';
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import { CoreSetup, CoreStart, Plugin } from 'kibana/public';
|
||||
import { SearchService, SearchStart } from './search';
|
||||
import { DataPublicPluginStart } from '../../../../plugins/data/public';
|
||||
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
|
@ -32,8 +33,9 @@ export interface DataPluginStartDependencies {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface DataStart {}
|
||||
export interface DataStart {
|
||||
search: SearchStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data Plugin - public
|
||||
|
@ -48,13 +50,20 @@ export interface DataStart {}
|
|||
*/
|
||||
|
||||
export class DataPlugin implements Plugin<void, DataStart, {}, DataPluginStartDependencies> {
|
||||
private readonly search = new SearchService();
|
||||
|
||||
public setup(core: CoreSetup) {}
|
||||
|
||||
public start(core: CoreStart, { data }: DataPluginStartDependencies): DataStart {
|
||||
// This is required for when Angular code uses Field and FieldList.
|
||||
setFieldFormats(data.fieldFormats);
|
||||
return {};
|
||||
|
||||
return {
|
||||
search: this.search.start(core),
|
||||
};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
public stop() {
|
||||
this.search.stop();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import {
|
|||
import { npStart } from 'ui/new_platform';
|
||||
import {
|
||||
SearchSource,
|
||||
SearchSourceContract,
|
||||
ISearchSource,
|
||||
getRequestInspectorStats,
|
||||
getResponseInspectorStats,
|
||||
} from '../../../../../ui/public/courier';
|
||||
|
@ -51,7 +51,7 @@ import { PersistedState } from '../../../../../ui/public/persisted_state';
|
|||
import { Adapters } from '../../../../../../plugins/inspector/public';
|
||||
|
||||
export interface RequestHandlerParams {
|
||||
searchSource: SearchSourceContract;
|
||||
searchSource: ISearchSource;
|
||||
aggs: AggConfigs;
|
||||
timeRange?: TimeRange;
|
||||
query?: Query;
|
||||
|
|
|
@ -181,7 +181,7 @@ exports[`ShardFailureModal renders matching snapshot given valid properties 1`]
|
|||
<FormattedMessage
|
||||
defaultMessage="Close"
|
||||
description="Closing the Modal"
|
||||
id="common.ui.courier.fetch.shardsFailedModal.close"
|
||||
id="data.search.searchSource.fetch.shardsFailedModal.close"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
|
@ -19,7 +19,7 @@
|
|||
import React from 'react';
|
||||
import { EuiCodeBlock, EuiDescriptionList, EuiSpacer } from '@elastic/eui';
|
||||
import { ShardFailure } from './shard_failure_types';
|
||||
import { getFlattenedObject } from '../../../../../../legacy/utils/get_flattened_object';
|
||||
import { getFlattenedObject } from '../../../../../../../legacy/utils/get_flattened_object';
|
||||
import { ShardFailureDescriptionHeader } from './shard_failure_description_header';
|
||||
|
||||
/**
|
|
@ -35,7 +35,7 @@ export function getFailureSummaryText(failure: ShardFailure, failureDetails?: st
|
|||
const displayDetails =
|
||||
typeof failureDetails === 'string' ? failureDetails : getFailureSummaryDetailsText(failure);
|
||||
|
||||
return i18n.translate('common.ui.courier.fetch.shardsFailedModal.failureHeader', {
|
||||
return i18n.translate('data.search.searchSource.fetch.shardsFailedModal.failureHeader', {
|
||||
defaultMessage: '{failureName} at {failureDetails}',
|
||||
values: { failureName, failureDetails: displayDetails },
|
||||
description: 'Summary of shard failures, e.g. "IllegalArgumentException at shard 0 node xyz"',
|
|
@ -59,15 +59,18 @@ export function ShardFailureModal({ request, response, title, onClose }: Props)
|
|||
const tabs = [
|
||||
{
|
||||
id: 'table',
|
||||
name: i18n.translate('common.ui.courier.fetch.shardsFailedModal.tabHeaderShardFailures', {
|
||||
defaultMessage: 'Shard failures',
|
||||
description: 'Name of the tab displaying shard failures',
|
||||
}),
|
||||
name: i18n.translate(
|
||||
'data.search.searchSource.fetch.shardsFailedModal.tabHeaderShardFailures',
|
||||
{
|
||||
defaultMessage: 'Shard failures',
|
||||
description: 'Name of the tab displaying shard failures',
|
||||
}
|
||||
),
|
||||
content: <ShardFailureTable failures={failures} />,
|
||||
},
|
||||
{
|
||||
id: 'json-request',
|
||||
name: i18n.translate('common.ui.courier.fetch.shardsFailedModal.tabHeaderRequest', {
|
||||
name: i18n.translate('data.search.searchSource.fetch.shardsFailedModal.tabHeaderRequest', {
|
||||
defaultMessage: 'Request',
|
||||
description: 'Name of the tab displaying the JSON request',
|
||||
}),
|
||||
|
@ -79,7 +82,7 @@ export function ShardFailureModal({ request, response, title, onClose }: Props)
|
|||
},
|
||||
{
|
||||
id: 'json-response',
|
||||
name: i18n.translate('common.ui.courier.fetch.shardsFailedModal.tabHeaderResponse', {
|
||||
name: i18n.translate('data.search.searchSource.fetch.shardsFailedModal.tabHeaderResponse', {
|
||||
defaultMessage: 'Response',
|
||||
description: 'Name of the tab displaying the JSON response',
|
||||
}),
|
||||
|
@ -104,7 +107,7 @@ export function ShardFailureModal({ request, response, title, onClose }: Props)
|
|||
{copy => (
|
||||
<EuiButtonEmpty onClick={copy}>
|
||||
<FormattedMessage
|
||||
id="common.ui.courier.fetch.shardsFailedModal.copyToClipboard"
|
||||
id="data.search.searchSource.fetch.shardsFailedModal.copyToClipboard"
|
||||
defaultMessage="Copy response to clipboard"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
@ -112,7 +115,7 @@ export function ShardFailureModal({ request, response, title, onClose }: Props)
|
|||
</EuiCopy>
|
||||
<EuiButton onClick={() => onClose()} fill data-test-sub="closeShardFailureModal">
|
||||
<FormattedMessage
|
||||
id="common.ui.courier.fetch.shardsFailedModal.close"
|
||||
id="data.search.searchSource.fetch.shardsFailedModal.close"
|
||||
defaultMessage="Close"
|
||||
description="Closing the Modal"
|
||||
/>
|
|
@ -22,7 +22,7 @@ import { npStart } from 'ui/new_platform';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiButton, EuiTextAlign } from '@elastic/eui';
|
||||
|
||||
import { toMountPoint } from '../../../../../../plugins/kibana_react/public';
|
||||
import { toMountPoint } from '../../../../../../../plugins/kibana_react/public';
|
||||
import { ShardFailureModal } from './shard_failure_modal';
|
||||
import { ResponseWithShardFailure, Request } from './shard_failure_types';
|
||||
|
||||
|
@ -57,7 +57,7 @@ export function ShardFailureOpenModalButton({ request, response, title }: Props)
|
|||
data-test-subj="openShardFailureModalBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="common.ui.courier.fetch.shardsFailedModal.showDetails"
|
||||
id="data.search.searchSource.fetch.shardsFailedModal.showDetails"
|
||||
defaultMessage="Show details"
|
||||
description="Open the modal to show details"
|
||||
/>
|
|
@ -44,7 +44,7 @@ export function ShardFailureTable({ failures }: { failures: ShardFailure[] }) {
|
|||
render: (item: ListItem) => {
|
||||
const failureSummeryText = getFailureSummaryText(item);
|
||||
const collapseLabel = i18n.translate(
|
||||
'common.ui.courier.fetch.shardsFailedModal.tableRowCollapse',
|
||||
'data.search.searchSource.fetch.shardsFailedModal.tableRowCollapse',
|
||||
{
|
||||
defaultMessage: 'Collapse {rowDescription}',
|
||||
description: 'Collapse a row of a table with failures',
|
||||
|
@ -53,7 +53,7 @@ export function ShardFailureTable({ failures }: { failures: ShardFailure[] }) {
|
|||
);
|
||||
|
||||
const expandLabel = i18n.translate(
|
||||
'common.ui.courier.fetch.shardsFailedModal.tableRowExpand',
|
||||
'data.search.searchSource.fetch.shardsFailedModal.tableRowExpand',
|
||||
{
|
||||
defaultMessage: 'Expand {rowDescription}',
|
||||
description: 'Expand a row of a table with failures',
|
||||
|
@ -81,7 +81,7 @@ export function ShardFailureTable({ failures }: { failures: ShardFailure[] }) {
|
|||
},
|
||||
{
|
||||
field: 'shard',
|
||||
name: i18n.translate('common.ui.courier.fetch.shardsFailedModal.tableColShard', {
|
||||
name: i18n.translate('data.search.searchSource.fetch.shardsFailedModal.tableColShard', {
|
||||
defaultMessage: 'Shard',
|
||||
}),
|
||||
sortable: true,
|
||||
|
@ -90,7 +90,7 @@ export function ShardFailureTable({ failures }: { failures: ShardFailure[] }) {
|
|||
},
|
||||
{
|
||||
field: 'index',
|
||||
name: i18n.translate('common.ui.courier.fetch.shardsFailedModal.tableColIndex', {
|
||||
name: i18n.translate('data.search.searchSource.fetch.shardsFailedModal.tableColIndex', {
|
||||
defaultMessage: 'Index',
|
||||
}),
|
||||
sortable: true,
|
||||
|
@ -98,7 +98,7 @@ export function ShardFailureTable({ failures }: { failures: ShardFailure[] }) {
|
|||
},
|
||||
{
|
||||
field: 'node',
|
||||
name: i18n.translate('common.ui.courier.fetch.shardsFailedModal.tableColNode', {
|
||||
name: i18n.translate('data.search.searchSource.fetch.shardsFailedModal.tableColNode', {
|
||||
defaultMessage: 'Node',
|
||||
}),
|
||||
sortable: true,
|
||||
|
@ -106,7 +106,7 @@ export function ShardFailureTable({ failures }: { failures: ShardFailure[] }) {
|
|||
},
|
||||
{
|
||||
field: 'reason.type',
|
||||
name: i18n.translate('common.ui.courier.fetch.shardsFailedModal.tableColReason', {
|
||||
name: i18n.translate('data.search.searchSource.fetch.shardsFailedModal.tableColReason', {
|
||||
defaultMessage: 'Reason',
|
||||
}),
|
||||
truncateText: true,
|
|
@ -17,9 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SearchError } from '../../courier';
|
||||
import { KbnError } from '../../../../../plugins/kibana_utils/public';
|
||||
import { SearchError } from '../search_strategy';
|
||||
import { KbnError } from '../../../../../../plugins/kibana_utils/public';
|
||||
import { SearchResponse } from '../types';
|
||||
|
||||
/**
|
||||
* Request Failure - When an entire multi request fails
|
||||
* @param {Error} err - the Error that came back
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import { fetchSoon } from './fetch_soon';
|
||||
import { callClient } from './call_client';
|
||||
import { IUiSettingsClient } from '../../../../../core/public';
|
||||
import { IUiSettingsClient } from '../../../../../../core/public';
|
||||
import { FetchHandlers, FetchOptions } from './types';
|
||||
import { SearchRequest, SearchResponse } from '../types';
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { getMSearchParams, getSearchParams } from './get_search_params';
|
||||
import { IUiSettingsClient } from '../../../../../core/public';
|
||||
import { IUiSettingsClient } from '../../../../../../core/public';
|
||||
|
||||
function getConfigStub(config: any = {}) {
|
||||
return {
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IUiSettingsClient } from '../../../../../core/public';
|
||||
import { IUiSettingsClient } from '../../../../../../core/public';
|
||||
|
||||
const sessionId = Date.now();
|
||||
|
|
@ -18,9 +18,9 @@
|
|||
*/
|
||||
|
||||
import { handleResponse } from './handle_response';
|
||||
import { toastNotifications } from '../../notify/toasts';
|
||||
import { toastNotifications } from 'ui/notify/toasts';
|
||||
|
||||
jest.mock('../../notify/toasts', () => {
|
||||
jest.mock('ui/notify/toasts', () => {
|
||||
return {
|
||||
toastNotifications: {
|
||||
addWarning: jest.fn(),
|
|
@ -20,23 +20,23 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { toastNotifications } from '../../notify/toasts';
|
||||
import { toastNotifications } from 'ui/notify/toasts';
|
||||
import { ShardFailureOpenModalButton } from './components/shard_failure_open_modal_button';
|
||||
import { Request, ResponseWithShardFailure } from './components/shard_failure_types';
|
||||
import { SearchRequest, SearchResponse } from '../types';
|
||||
import { toMountPoint } from '../../../../../plugins/kibana_react/public';
|
||||
import { toMountPoint } from '../../../../../../plugins/kibana_react/public';
|
||||
|
||||
export function handleResponse(request: SearchRequest, response: SearchResponse) {
|
||||
if (response.timed_out) {
|
||||
toastNotifications.addWarning({
|
||||
title: i18n.translate('common.ui.courier.fetch.requestTimedOutNotificationMessage', {
|
||||
title: i18n.translate('data.search.searchSource.fetch.requestTimedOutNotificationMessage', {
|
||||
defaultMessage: 'Data might be incomplete because your request timed out',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (response._shards && response._shards.failed) {
|
||||
const title = i18n.translate('common.ui.courier.fetch.shardsFailedNotificationMessage', {
|
||||
const title = i18n.translate('data.search.searchSource.fetch.shardsFailedNotificationMessage', {
|
||||
defaultMessage: '{shardsFailed} of {shardsTotal} shards failed',
|
||||
values: {
|
||||
shardsFailed: response._shards.failed,
|
||||
|
@ -44,7 +44,7 @@ export function handleResponse(request: SearchRequest, response: SearchResponse)
|
|||
},
|
||||
});
|
||||
const description = i18n.translate(
|
||||
'common.ui.courier.fetch.shardsFailedNotificationDescription',
|
||||
'data.search.searchSource.fetch.shardsFailedNotificationDescription',
|
||||
{
|
||||
defaultMessage: 'The data you are seeing might be incomplete or wrong.',
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IUiSettingsClient } from '../../../../../core/public';
|
||||
import { IUiSettingsClient } from '../../../../../../core/public';
|
||||
import { SearchRequest, SearchResponse } from '../types';
|
||||
|
||||
export interface ApiCaller {
|
|
@ -16,3 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { SearchService, SearchSetup, SearchStart } from './search_service';
|
||||
|
||||
export { getRequestInspectorStats, getResponseInspectorStats } from './utils';
|
||||
|
|
51
src/legacy/core_plugins/data/public/search/search_service.ts
Normal file
51
src/legacy/core_plugins/data/public/search/search_service.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 { Plugin, CoreSetup, CoreStart } from '../../../../../core/public';
|
||||
import { SearchSource } from './search_source';
|
||||
import { defaultSearchStrategy } from './search_strategy';
|
||||
import { SearchStrategyProvider } from './search_strategy/types';
|
||||
|
||||
export interface SearchSetup {} // eslint-disable-line @typescript-eslint/no-empty-interface
|
||||
|
||||
export interface SearchStart {
|
||||
defaultSearchStrategy: SearchStrategyProvider;
|
||||
SearchSource: typeof SearchSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* The contract provided here is a new platform shim for ui/courier.
|
||||
*
|
||||
* Once it has been refactored to work with new platform services,
|
||||
* it will move into the existing search service in src/plugins/data/public/search
|
||||
*/
|
||||
export class SearchService implements Plugin<SearchSetup, SearchStart> {
|
||||
public setup(core: CoreSetup): SearchSetup {
|
||||
return {};
|
||||
}
|
||||
|
||||
public start(core: CoreStart): SearchStart {
|
||||
return {
|
||||
defaultSearchStrategy,
|
||||
SearchSource,
|
||||
};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './search_source';
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 { ISearchSource } from './search_source';
|
||||
|
||||
export const searchSourceMock: MockedKeys<ISearchSource> = {
|
||||
setPreferredSearchStrategyId: jest.fn(),
|
||||
setFields: jest.fn().mockReturnThis(),
|
||||
setField: jest.fn().mockReturnThis(),
|
||||
getId: jest.fn(),
|
||||
getFields: jest.fn(),
|
||||
getField: jest.fn(),
|
||||
getOwnField: jest.fn(),
|
||||
create: jest.fn().mockReturnThis(),
|
||||
createCopy: jest.fn().mockReturnThis(),
|
||||
createChild: jest.fn().mockReturnThis(),
|
||||
setParent: jest.fn(),
|
||||
getParent: jest.fn().mockReturnThis(),
|
||||
fetch: jest.fn().mockResolvedValue({}),
|
||||
onRequestStart: jest.fn(),
|
||||
getSearchRequestBody: jest.fn(),
|
||||
destroy: jest.fn(),
|
||||
history: [],
|
||||
};
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import { normalizeSortRequest } from './normalize_sort_request';
|
||||
import { SortDirection } from './types';
|
||||
import { IndexPattern } from '../../../../core_plugins/data/public/index_patterns';
|
||||
import { IIndexPattern } from '../../../../../../plugins/data/public';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
|
@ -40,7 +40,7 @@ describe('SearchSource#normalizeSortRequest', function() {
|
|||
};
|
||||
const indexPattern = {
|
||||
fields: [scriptedField, murmurScriptedField],
|
||||
} as IndexPattern;
|
||||
} as IIndexPattern;
|
||||
|
||||
it('should return an array', function() {
|
||||
const sortable = { someField: SortDirection.desc };
|
|
@ -17,12 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IndexPattern } from '../../../../core_plugins/data/public';
|
||||
import { IIndexPattern } from '../../../../../../plugins/data/public';
|
||||
import { EsQuerySortValue, SortOptions } from './types';
|
||||
|
||||
export function normalizeSortRequest(
|
||||
sortObject: EsQuerySortValue | EsQuerySortValue[],
|
||||
indexPattern: IndexPattern | string | undefined,
|
||||
indexPattern: IIndexPattern | string | undefined,
|
||||
defaultSortOptions: SortOptions = {}
|
||||
) {
|
||||
const sortArray: EsQuerySortValue[] = Array.isArray(sortObject) ? sortObject : [sortObject];
|
||||
|
@ -38,7 +38,7 @@ export function normalizeSortRequest(
|
|||
*/
|
||||
function normalize(
|
||||
sortable: EsQuerySortValue,
|
||||
indexPattern: IndexPattern | string | undefined,
|
||||
indexPattern: IIndexPattern | string | undefined,
|
||||
defaultSortOptions: any
|
||||
) {
|
||||
const [[sortField, sortOrder]] = Object.entries(sortable);
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { SearchSource } from '../search_source';
|
||||
import { IndexPattern } from '../../../../core_plugins/data/public';
|
||||
import { IndexPattern } from '../../../../../../plugins/data/public';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
|
@ -26,7 +26,7 @@ jest.mock('../fetch', () => ({
|
|||
fetchSoon: jest.fn().mockResolvedValue({}),
|
||||
}));
|
||||
|
||||
jest.mock('../../chrome', () => ({
|
||||
jest.mock('ui/chrome', () => ({
|
||||
dangerouslyGetActiveInjector: () => ({
|
||||
get: jest.fn(),
|
||||
}),
|
|
@ -0,0 +1,410 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @name SearchSource
|
||||
*
|
||||
* @description A promise-based stream of search results that can inherit from other search sources.
|
||||
*
|
||||
* Because filters/queries in Kibana have different levels of persistence and come from different
|
||||
* places, it is important to keep track of where filters come from for when they are saved back to
|
||||
* the savedObject store in the Kibana index. To do this, we create trees of searchSource objects
|
||||
* that can have associated query parameters (index, query, filter, etc) which can also inherit from
|
||||
* other searchSource objects.
|
||||
*
|
||||
* At query time, all of the searchSource objects that have subscribers are "flattened", at which
|
||||
* point the query params from the searchSource are collected while traversing up the inheritance
|
||||
* chain. At each link in the chain a decision about how to merge the query params is made until a
|
||||
* single set of query parameters is created for each active searchSource (a searchSource with
|
||||
* subscribers).
|
||||
*
|
||||
* That set of query parameters is then sent to elasticsearch. This is how the filter hierarchy
|
||||
* works in Kibana.
|
||||
*
|
||||
* Visualize, starting from a new search:
|
||||
*
|
||||
* - the `savedVis.searchSource` is set as the `appSearchSource`.
|
||||
* - The `savedVis.searchSource` would normally inherit from the `appSearchSource`, but now it is
|
||||
* upgraded to inherit from the `rootSearchSource`.
|
||||
* - Any interaction with the visualization will still apply filters to the `appSearchSource`, so
|
||||
* they will be stored directly on the `savedVis.searchSource`.
|
||||
* - Any interaction with the time filter will be written to the `rootSearchSource`, so those
|
||||
* filters will not be saved by the `savedVis`.
|
||||
* - When the `savedVis` is saved to elasticsearch, it takes with it all the filters that are
|
||||
* defined on it directly, but none of the ones that it inherits from other places.
|
||||
*
|
||||
* Visualize, starting from an existing search:
|
||||
*
|
||||
* - The `savedVis` loads the `savedSearch` on which it is built.
|
||||
* - The `savedVis.searchSource` is set to inherit from the `saveSearch.searchSource` and set as
|
||||
* the `appSearchSource`.
|
||||
* - The `savedSearch.searchSource`, is set to inherit from the `rootSearchSource`.
|
||||
* - Then the `savedVis` is written to elasticsearch it will be flattened and only include the
|
||||
* filters created in the visualize application and will reconnect the filters from the
|
||||
* `savedSearch` at runtime to prevent losing the relationship
|
||||
*
|
||||
* Dashboard search sources:
|
||||
*
|
||||
* - Each panel in a dashboard has a search source.
|
||||
* - The `savedDashboard` also has a searchsource, and it is set as the `appSearchSource`.
|
||||
* - Each panel's search source inherits from the `appSearchSource`, meaning that they inherit from
|
||||
* the dashboard search source.
|
||||
* - When a filter is added to the search box, or via a visualization, it is written to the
|
||||
* `appSearchSource`.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { npSetup } from 'ui/new_platform';
|
||||
import chrome from 'ui/chrome';
|
||||
import { fieldWildcardFilter } from 'ui/field_wildcard';
|
||||
import { normalizeSortRequest } from './normalize_sort_request';
|
||||
import { fetchSoon } from '../fetch';
|
||||
import { getHighlightRequest, esFilters, esQuery } from '../../../../../../plugins/data/public';
|
||||
import { RequestFailure } from '../fetch/errors';
|
||||
import { filterDocvalueFields } from './filter_docvalue_fields';
|
||||
import { SearchSourceOptions, SearchSourceFields, SearchRequest } from './types';
|
||||
import { FetchOptions, ApiCaller } from '../fetch/types';
|
||||
|
||||
const esShardTimeout = npSetup.core.injectedMetadata.getInjectedVar('esShardTimeout') as number;
|
||||
const config = npSetup.core.uiSettings;
|
||||
|
||||
export type ISearchSource = Pick<SearchSource, keyof SearchSource>;
|
||||
|
||||
export class SearchSource {
|
||||
private id: string = _.uniqueId('data_source');
|
||||
private searchStrategyId?: string;
|
||||
private parent?: SearchSource;
|
||||
private requestStartHandlers: Array<
|
||||
(searchSource: ISearchSource, options?: FetchOptions) => Promise<unknown>
|
||||
> = [];
|
||||
private inheritOptions: SearchSourceOptions = {};
|
||||
public history: SearchRequest[] = [];
|
||||
|
||||
constructor(private fields: SearchSourceFields = {}) {}
|
||||
|
||||
/** ***
|
||||
* PUBLIC API
|
||||
*****/
|
||||
|
||||
setPreferredSearchStrategyId(searchStrategyId: string) {
|
||||
this.searchStrategyId = searchStrategyId;
|
||||
}
|
||||
|
||||
setFields(newFields: SearchSourceFields) {
|
||||
this.fields = newFields;
|
||||
return this;
|
||||
}
|
||||
|
||||
setField<K extends keyof SearchSourceFields>(field: K, value: SearchSourceFields[K]) {
|
||||
if (value == null) {
|
||||
delete this.fields[field];
|
||||
} else {
|
||||
this.fields[field] = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
getFields() {
|
||||
return { ...this.fields };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fields from the fields
|
||||
*/
|
||||
getField<K extends keyof SearchSourceFields>(field: K, recurse = true): SearchSourceFields[K] {
|
||||
if (!recurse || this.fields[field] !== void 0) {
|
||||
return this.fields[field];
|
||||
}
|
||||
const parent = this.getParent();
|
||||
return parent && parent.getField(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field from our own fields, don't traverse up the chain
|
||||
*/
|
||||
getOwnField<K extends keyof SearchSourceFields>(field: K): SearchSourceFields[K] {
|
||||
return this.getField(field, false);
|
||||
}
|
||||
|
||||
create() {
|
||||
return new SearchSource();
|
||||
}
|
||||
|
||||
createCopy() {
|
||||
const newSearchSource = new SearchSource();
|
||||
newSearchSource.setFields({ ...this.fields });
|
||||
// when serializing the internal fields we lose the internal classes used in the index
|
||||
// pattern, so we have to set it again to workaround this behavior
|
||||
newSearchSource.setField('index', this.getField('index'));
|
||||
newSearchSource.setParent(this.getParent());
|
||||
return newSearchSource;
|
||||
}
|
||||
|
||||
createChild(options = {}) {
|
||||
const childSearchSource = new SearchSource();
|
||||
childSearchSource.setParent(this, options);
|
||||
return childSearchSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a searchSource that this source should inherit from
|
||||
* @param {SearchSource} parent - the parent searchSource
|
||||
* @param {SearchSourceOptions} options - the inherit options
|
||||
* @return {this} - chainable
|
||||
*/
|
||||
setParent(parent?: ISearchSource, options: SearchSourceOptions = {}) {
|
||||
this.parent = parent as SearchSource;
|
||||
this.inheritOptions = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent of this SearchSource
|
||||
* @return {undefined|searchSource}
|
||||
*/
|
||||
getParent() {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch this source and reject the returned Promise on error
|
||||
*
|
||||
* @async
|
||||
*/
|
||||
async fetch(options: FetchOptions = {}) {
|
||||
const $injector = await chrome.dangerouslyGetActiveInjector();
|
||||
const es = $injector.get('es') as ApiCaller;
|
||||
|
||||
await this.requestIsStarting(options);
|
||||
|
||||
const searchRequest = await this.flatten();
|
||||
this.history = [searchRequest];
|
||||
|
||||
const response = await fetchSoon(
|
||||
searchRequest,
|
||||
{
|
||||
...(this.searchStrategyId && { searchStrategyId: this.searchStrategyId }),
|
||||
...options,
|
||||
},
|
||||
{ es, config, esShardTimeout }
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
throw new RequestFailure(null, response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a handler that will be notified whenever requests start
|
||||
* @param {Function} handler
|
||||
* @return {undefined}
|
||||
*/
|
||||
onRequestStart(
|
||||
handler: (searchSource: ISearchSource, options?: FetchOptions) => Promise<unknown>
|
||||
) {
|
||||
this.requestStartHandlers.push(handler);
|
||||
}
|
||||
|
||||
async getSearchRequestBody() {
|
||||
const searchRequest = await this.flatten();
|
||||
return searchRequest.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely destroy the SearchSource.
|
||||
* @return {undefined}
|
||||
*/
|
||||
destroy() {
|
||||
this.requestStartHandlers.length = 0;
|
||||
}
|
||||
|
||||
/** ****
|
||||
* PRIVATE APIS
|
||||
******/
|
||||
|
||||
/**
|
||||
* Called by requests of this search source when they are started
|
||||
* @param {Courier.Request} request
|
||||
* @param options
|
||||
* @return {Promise<undefined>}
|
||||
*/
|
||||
private requestIsStarting(options: FetchOptions = {}) {
|
||||
const handlers = [...this.requestStartHandlers];
|
||||
// If callParentStartHandlers has been set to true, we also call all
|
||||
// handlers of parent search sources.
|
||||
if (this.inheritOptions.callParentStartHandlers) {
|
||||
let searchSource = this.getParent();
|
||||
while (searchSource) {
|
||||
handlers.push(...searchSource.requestStartHandlers);
|
||||
searchSource = searchSource.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(handlers.map(fn => fn(this, options)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to merge properties into the data within ._flatten().
|
||||
* The data is passed in and modified by the function
|
||||
*
|
||||
* @param {object} data - the current merged data
|
||||
* @param {*} val - the value at `key`
|
||||
* @param {*} key - The key of `val`
|
||||
* @return {undefined}
|
||||
*/
|
||||
private mergeProp<K extends keyof SearchSourceFields>(
|
||||
data: SearchRequest,
|
||||
val: SearchSourceFields[K],
|
||||
key: K
|
||||
) {
|
||||
val = typeof val === 'function' ? val(this) : val;
|
||||
if (val == null || !key) return;
|
||||
|
||||
const addToRoot = (rootKey: string, value: any) => {
|
||||
data[rootKey] = value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the key and val to the body of the request
|
||||
*/
|
||||
const addToBody = (bodyKey: string, value: any) => {
|
||||
// ignore if we already have a value
|
||||
if (data.body[bodyKey] == null) {
|
||||
data.body[bodyKey] = value;
|
||||
}
|
||||
};
|
||||
|
||||
switch (key) {
|
||||
case 'filter':
|
||||
return addToRoot('filters', (data.filters || []).concat(val));
|
||||
case 'query':
|
||||
return addToRoot(key, (data[key] || []).concat(val));
|
||||
case 'fields':
|
||||
const fields = _.uniq((data[key] || []).concat(val));
|
||||
return addToRoot(key, fields);
|
||||
case 'index':
|
||||
case 'type':
|
||||
case 'highlightAll':
|
||||
return key && data[key] == null && addToRoot(key, val);
|
||||
case 'searchAfter':
|
||||
return addToBody('search_after', val);
|
||||
case 'source':
|
||||
return addToBody('_source', val);
|
||||
case 'sort':
|
||||
const sort = normalizeSortRequest(val, this.getField('index'), config.get('sort:options'));
|
||||
return addToBody(key, sort);
|
||||
default:
|
||||
return addToBody(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk the inheritance chain of a source and return its
|
||||
* flat representation (taking into account merging rules)
|
||||
* @returns {Promise}
|
||||
* @resolved {Object|null} - the flat data of the SearchSource
|
||||
*/
|
||||
private mergeProps(root = this, searchRequest: SearchRequest = { body: {} }) {
|
||||
Object.entries(this.fields).forEach(([key, value]) => {
|
||||
this.mergeProp(searchRequest, value, key as keyof SearchSourceFields);
|
||||
});
|
||||
if (this.parent) {
|
||||
this.parent.mergeProps(root, searchRequest);
|
||||
}
|
||||
return searchRequest;
|
||||
}
|
||||
|
||||
private flatten() {
|
||||
const searchRequest = this.mergeProps();
|
||||
|
||||
searchRequest.body = searchRequest.body || {};
|
||||
const { body, index, fields, query, filters, highlightAll } = searchRequest;
|
||||
|
||||
const computedFields = index ? index.getComputedFields() : {};
|
||||
|
||||
body.stored_fields = computedFields.storedFields;
|
||||
body.script_fields = body.script_fields || {};
|
||||
_.extend(body.script_fields, computedFields.scriptFields);
|
||||
|
||||
const defaultDocValueFields = computedFields.docvalueFields
|
||||
? computedFields.docvalueFields
|
||||
: [];
|
||||
body.docvalue_fields = body.docvalue_fields || defaultDocValueFields;
|
||||
|
||||
if (!body.hasOwnProperty('_source') && index) {
|
||||
body._source = index.getSourceFiltering();
|
||||
}
|
||||
|
||||
if (body._source) {
|
||||
// exclude source fields for this index pattern specified by the user
|
||||
const filter = fieldWildcardFilter(body._source.excludes, config.get('metaFields'));
|
||||
body.docvalue_fields = body.docvalue_fields.filter((docvalueField: any) =>
|
||||
filter(docvalueField.field)
|
||||
);
|
||||
}
|
||||
|
||||
// if we only want to search for certain fields
|
||||
if (fields) {
|
||||
// filter out the docvalue_fields, and script_fields to only include those that we are concerned with
|
||||
body.docvalue_fields = filterDocvalueFields(body.docvalue_fields, fields);
|
||||
body.script_fields = _.pick(body.script_fields, fields);
|
||||
|
||||
// request the remaining fields from both stored_fields and _source
|
||||
const remainingFields = _.difference(fields, _.keys(body.script_fields));
|
||||
body.stored_fields = remainingFields;
|
||||
_.set(body, '_source.includes', remainingFields);
|
||||
}
|
||||
|
||||
const esQueryConfigs = esQuery.getEsQueryConfig(config);
|
||||
body.query = esQuery.buildEsQuery(index, query, filters, esQueryConfigs);
|
||||
|
||||
if (highlightAll && body.query) {
|
||||
body.highlight = getHighlightRequest(body.query, config.get('doc_table:highlight'));
|
||||
delete searchRequest.highlightAll;
|
||||
}
|
||||
|
||||
const translateToQuery = (filter: esFilters.Filter) => filter && (filter.query || filter);
|
||||
|
||||
// re-write filters within filter aggregations
|
||||
(function recurse(aggBranch) {
|
||||
if (!aggBranch) return;
|
||||
Object.keys(aggBranch).forEach(function(id) {
|
||||
const agg = aggBranch[id];
|
||||
|
||||
if (agg.filters) {
|
||||
// translate filters aggregations
|
||||
const { filters: aggFilters } = agg.filters;
|
||||
Object.keys(aggFilters).forEach(filterId => {
|
||||
aggFilters[filterId] = translateToQuery(aggFilters[filterId]);
|
||||
});
|
||||
}
|
||||
|
||||
recurse(agg.aggs || agg.aggregations);
|
||||
});
|
||||
})(body.aggs || body.aggregations);
|
||||
|
||||
return searchRequest;
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { NameList } from 'elasticsearch';
|
||||
import { esFilters, Query, IndexPattern } from '../../../../../plugins/data/public';
|
||||
import { esFilters, IndexPattern, Query } from '../../../../../../plugins/data/public';
|
||||
|
||||
export type EsQuerySearchAfter = [string | number, string | number];
|
||||
|
||||
|
@ -54,7 +54,7 @@ export interface SearchSourceOptions {
|
|||
callParentStartHandlers?: boolean;
|
||||
}
|
||||
|
||||
export { SearchSourceContract } from './search_source';
|
||||
export { ISearchSource } from './search_source';
|
||||
|
||||
export interface SortOptions {
|
||||
mode?: 'min' | 'max' | 'sum' | 'avg' | 'median';
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { defaultSearchStrategy } from './default_search_strategy';
|
||||
import { IUiSettingsClient } from '../../../../../core/public';
|
||||
import { IUiSettingsClient } from '../../../../../../core/public';
|
||||
import { SearchStrategySearchParams } from './types';
|
||||
|
||||
const { search } = defaultSearchStrategy;
|
|
@ -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 { SearchStrategyProvider, SearchStrategySearchParams } from './types';
|
||||
import { isDefaultTypeIndexPattern } from './is_default_type_index_pattern';
|
||||
import {
|
||||
getSearchParams,
|
||||
getMSearchParams,
|
||||
getPreference,
|
||||
getTimeout,
|
||||
} from '../fetch/get_search_params';
|
||||
|
||||
export const defaultSearchStrategy: SearchStrategyProvider = {
|
||||
id: 'default',
|
||||
|
||||
search: params => {
|
||||
return params.config.get('courier:batchSearches') ? msearch(params) : search(params);
|
||||
},
|
||||
|
||||
isViable: indexPattern => {
|
||||
return indexPattern && isDefaultTypeIndexPattern(indexPattern);
|
||||
},
|
||||
};
|
||||
|
||||
function msearch({ searchRequests, es, config, esShardTimeout }: SearchStrategySearchParams) {
|
||||
const inlineRequests = searchRequests.map(({ index, body, search_type: searchType }) => {
|
||||
const inlineHeader = {
|
||||
index: index.title || index,
|
||||
search_type: searchType,
|
||||
ignore_unavailable: true,
|
||||
preference: getPreference(config),
|
||||
};
|
||||
const inlineBody = {
|
||||
...body,
|
||||
timeout: getTimeout(esShardTimeout),
|
||||
};
|
||||
return `${JSON.stringify(inlineHeader)}\n${JSON.stringify(inlineBody)}`;
|
||||
});
|
||||
|
||||
const searching = es.msearch({
|
||||
...getMSearchParams(config),
|
||||
body: `${inlineRequests.join('\n')}\n`,
|
||||
});
|
||||
return {
|
||||
searching: searching.then(({ responses }) => responses),
|
||||
abort: searching.abort,
|
||||
};
|
||||
}
|
||||
|
||||
function search({ searchRequests, es, config, esShardTimeout }: SearchStrategySearchParams) {
|
||||
const abortController = new AbortController();
|
||||
const searchParams = getSearchParams(config, esShardTimeout);
|
||||
const promises = searchRequests.map(({ index, body }) => {
|
||||
const searching = es.search({ index: index.title || index, body, ...searchParams });
|
||||
abortController.signal.addEventListener('abort', searching.abort);
|
||||
return searching.catch(({ response }) => JSON.parse(response));
|
||||
});
|
||||
return {
|
||||
searching: Promise.all(promises),
|
||||
abort: () => abortController.abort(),
|
||||
};
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export {
|
||||
addSearchStrategy,
|
||||
hasSearchStategyForIndexPattern,
|
||||
getSearchStrategyById,
|
||||
getSearchStrategyForSearchRequest,
|
||||
} from './search_strategy_registry';
|
||||
|
||||
export { defaultSearchStrategy } from './default_search_strategy';
|
||||
|
||||
export { isDefaultTypeIndexPattern } from './is_default_type_index_pattern';
|
||||
|
||||
export { SearchError, getSearchErrorType } from './search_error';
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IndexPattern } from '../../../../core_plugins/data/public';
|
||||
import { IndexPattern } from '../../../../../../plugins/data/public';
|
||||
|
||||
export const isDefaultTypeIndexPattern = (indexPattern: IndexPattern) => {
|
||||
// Default index patterns don't have `type` defined.
|
|
@ -27,11 +27,14 @@ export const noOpSearchStrategy: SearchStrategyProvider = {
|
|||
search: () => {
|
||||
const searchError = new SearchError({
|
||||
status: '418', // "I'm a teapot" error
|
||||
title: i18n.translate('common.ui.courier.noSearchStrategyRegisteredErrorMessageTitle', {
|
||||
defaultMessage: 'No search strategy registered',
|
||||
}),
|
||||
title: i18n.translate(
|
||||
'data.search.searchSource.noSearchStrategyRegisteredErrorMessageTitle',
|
||||
{
|
||||
defaultMessage: 'No search strategy registered',
|
||||
}
|
||||
),
|
||||
message: i18n.translate(
|
||||
'common.ui.courier.noSearchStrategyRegisteredErrorMessageDescription',
|
||||
'data.search.searchSource.noSearchStrategyRegisteredErrorMessageDescription',
|
||||
{
|
||||
defaultMessage: `Couldn't find a search strategy for the search request`,
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
interface SearchErrorOptions {
|
||||
status: string;
|
||||
title: string;
|
||||
message: string;
|
||||
path: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export class SearchError extends Error {
|
||||
public name: string;
|
||||
public status: string;
|
||||
public title: string;
|
||||
public message: string;
|
||||
public path: string;
|
||||
public type: string;
|
||||
|
||||
constructor({ status, title, message, path, type }: SearchErrorOptions) {
|
||||
super(message);
|
||||
this.name = 'SearchError';
|
||||
this.status = status;
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.path = path;
|
||||
this.type = type;
|
||||
|
||||
// captureStackTrace is only available in the V8 engine, so any browser using
|
||||
// a different JS engine won't have access to this method.
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, SearchError);
|
||||
}
|
||||
|
||||
// Babel doesn't support traditional `extends` syntax for built-in classes.
|
||||
// https://babeljs.io/docs/en/caveats/#classes
|
||||
Object.setPrototypeOf(this, SearchError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export function getSearchErrorType({ message }: Pick<SearchError, 'message'>) {
|
||||
const msg = message.toLowerCase();
|
||||
if (msg.indexOf('unsupported query') > -1) {
|
||||
return 'UNSUPPORTED_QUERY';
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IndexPattern } from '../../../../core_plugins/data/public';
|
||||
import { IndexPattern } from '../../../../../../plugins/data/public';
|
||||
import { noOpSearchStrategy } from './no_op_search_strategy';
|
||||
import {
|
||||
searchStrategies,
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IndexPattern } from '../../../../core_plugins/data/public';
|
||||
import { IndexPattern } from '../../../../../../plugins/data/public';
|
||||
import { SearchStrategyProvider } from './types';
|
||||
import { noOpSearchStrategy } from './no_op_search_strategy';
|
||||
import { SearchResponse } from '../types';
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IndexPattern } from '../../../../core_plugins/data/public';
|
||||
import { IndexPattern } from '../../../../../../plugins/data/public';
|
||||
import { FetchHandlers } from '../fetch/types';
|
||||
import { SearchRequest, SearchResponse } from '../types';
|
||||
|
23
src/legacy/core_plugins/data/public/search/types.ts
Normal file
23
src/legacy/core_plugins/data/public/search/types.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './fetch/types';
|
||||
export * from './search_source/types';
|
||||
export * from './search_strategy/types';
|
||||
export * from './utils/types';
|
|
@ -26,28 +26,28 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { SearchSourceContract, RequestInspectorStats } from '../types';
|
||||
import { ISearchSource, RequestInspectorStats } from '../types';
|
||||
|
||||
export function getRequestInspectorStats(searchSource: SearchSourceContract) {
|
||||
export function getRequestInspectorStats(searchSource: ISearchSource) {
|
||||
const stats: RequestInspectorStats = {};
|
||||
const index = searchSource.getField('index');
|
||||
|
||||
if (index) {
|
||||
stats.indexPattern = {
|
||||
label: i18n.translate('common.ui.courier.indexPatternLabel', {
|
||||
label: i18n.translate('data.search.searchSource.indexPatternLabel', {
|
||||
defaultMessage: 'Index pattern',
|
||||
}),
|
||||
value: index.title,
|
||||
description: i18n.translate('common.ui.courier.indexPatternDescription', {
|
||||
description: i18n.translate('data.search.searchSource.indexPatternDescription', {
|
||||
defaultMessage: 'The index pattern that connected to the Elasticsearch indices.',
|
||||
}),
|
||||
};
|
||||
stats.indexPatternId = {
|
||||
label: i18n.translate('common.ui.courier.indexPatternIdLabel', {
|
||||
label: i18n.translate('data.search.searchSource.indexPatternIdLabel', {
|
||||
defaultMessage: 'Index pattern ID',
|
||||
}),
|
||||
value: index.id!,
|
||||
description: i18n.translate('common.ui.courier.indexPatternIdDescription', {
|
||||
description: i18n.translate('data.search.searchSource.indexPatternIdDescription', {
|
||||
defaultMessage: 'The ID in the {kibanaIndexPattern} index.',
|
||||
values: { kibanaIndexPattern: '.kibana' },
|
||||
}),
|
||||
|
@ -58,7 +58,7 @@ export function getRequestInspectorStats(searchSource: SearchSourceContract) {
|
|||
}
|
||||
|
||||
export function getResponseInspectorStats(
|
||||
searchSource: SearchSourceContract,
|
||||
searchSource: ISearchSource,
|
||||
resp: SearchResponse<unknown>
|
||||
) {
|
||||
const lastRequest = searchSource.history && searchSource.history[searchSource.history.length - 1];
|
||||
|
@ -66,14 +66,14 @@ export function getResponseInspectorStats(
|
|||
|
||||
if (resp && resp.took) {
|
||||
stats.queryTime = {
|
||||
label: i18n.translate('common.ui.courier.queryTimeLabel', {
|
||||
label: i18n.translate('data.search.searchSource.queryTimeLabel', {
|
||||
defaultMessage: 'Query time',
|
||||
}),
|
||||
value: i18n.translate('common.ui.courier.queryTimeValue', {
|
||||
value: i18n.translate('data.search.searchSource.queryTimeValue', {
|
||||
defaultMessage: '{queryTime}ms',
|
||||
values: { queryTime: resp.took },
|
||||
}),
|
||||
description: i18n.translate('common.ui.courier.queryTimeDescription', {
|
||||
description: i18n.translate('data.search.searchSource.queryTimeDescription', {
|
||||
defaultMessage:
|
||||
'The time it took to process the query. ' +
|
||||
'Does not include the time to send the request or parse it in the browser.',
|
||||
|
@ -83,21 +83,21 @@ export function getResponseInspectorStats(
|
|||
|
||||
if (resp && resp.hits) {
|
||||
stats.hitsTotal = {
|
||||
label: i18n.translate('common.ui.courier.hitsTotalLabel', {
|
||||
label: i18n.translate('data.search.searchSource.hitsTotalLabel', {
|
||||
defaultMessage: 'Hits (total)',
|
||||
}),
|
||||
value: `${resp.hits.total}`,
|
||||
description: i18n.translate('common.ui.courier.hitsTotalDescription', {
|
||||
description: i18n.translate('data.search.searchSource.hitsTotalDescription', {
|
||||
defaultMessage: 'The number of documents that match the query.',
|
||||
}),
|
||||
};
|
||||
|
||||
stats.hits = {
|
||||
label: i18n.translate('common.ui.courier.hitsLabel', {
|
||||
label: i18n.translate('data.search.searchSource.hitsLabel', {
|
||||
defaultMessage: 'Hits',
|
||||
}),
|
||||
value: `${resp.hits.hits.length}`,
|
||||
description: i18n.translate('common.ui.courier.hitsDescription', {
|
||||
description: i18n.translate('data.search.searchSource.hitsDescription', {
|
||||
defaultMessage: 'The number of documents returned by the query.',
|
||||
}),
|
||||
};
|
||||
|
@ -105,14 +105,14 @@ export function getResponseInspectorStats(
|
|||
|
||||
if (lastRequest && (lastRequest.ms === 0 || lastRequest.ms)) {
|
||||
stats.requestTime = {
|
||||
label: i18n.translate('common.ui.courier.requestTimeLabel', {
|
||||
label: i18n.translate('data.search.searchSource.requestTimeLabel', {
|
||||
defaultMessage: 'Request time',
|
||||
}),
|
||||
value: i18n.translate('common.ui.courier.requestTimeValue', {
|
||||
value: i18n.translate('data.search.searchSource.requestTimeValue', {
|
||||
defaultMessage: '{requestTime}ms',
|
||||
values: { requestTime: lastRequest.ms },
|
||||
}),
|
||||
description: i18n.translate('common.ui.courier.requestTimeDescription', {
|
||||
description: i18n.translate('data.search.searchSource.requestTimeDescription', {
|
||||
defaultMessage:
|
||||
'The time of the request from the browser to Elasticsearch and back. ' +
|
||||
'Does not include the time the requested waited in the queue.',
|
20
src/legacy/core_plugins/data/public/search/utils/index.ts
Normal file
20
src/legacy/core_plugins/data/public/search/utils/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './courier_inspector_utils';
|
|
@ -17,13 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SearchSource as SearchSourceClass } from 'ui/courier';
|
||||
import { SearchSource as SearchSourceClass, ISearchSource } from 'ui/courier';
|
||||
import { Class } from '@kbn/utility-types';
|
||||
|
||||
export { Vis, VisParams } from 'ui/vis';
|
||||
export { VisOptionsProps } from 'ui/vis/editors/default';
|
||||
export { ValidatedDualRange } from 'ui/validated_range';
|
||||
export { SearchSourceFields } from 'ui/courier/types';
|
||||
export { SearchSourceFields } from '../../data/public';
|
||||
|
||||
export type SearchSource = Class<SearchSourceClass>;
|
||||
export type SearchSource = Class<ISearchSource>;
|
||||
export const SearchSource = SearchSourceClass;
|
||||
|
|
|
@ -24,6 +24,8 @@ import { findTestSubject } from '@elastic/eui/lib/test';
|
|||
import { flattenHitWrapper } from '../../../../data/public/';
|
||||
import { DocViewTable } from './table';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
// @ts-ignore
|
||||
const indexPattern = {
|
||||
fields: {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { SearchSourceContract } from 'ui/courier';
|
||||
import { ISearchSource } from 'ui/courier';
|
||||
import { SavedObject, SavedObjectKibanaServices } from 'ui/saved_objects/types';
|
||||
import { createSavedObjectClass } from 'ui/saved_objects/saved_object';
|
||||
import { extractReferences, injectReferences } from './saved_dashboard_references';
|
||||
|
@ -36,7 +36,7 @@ export interface SavedObjectDashboard extends SavedObject {
|
|||
uiStateJSON?: string;
|
||||
lastSavedTitle: string;
|
||||
refreshInterval?: RefreshInterval;
|
||||
searchSource: SearchSourceContract;
|
||||
searchSource: ISearchSource;
|
||||
getQuery(): Query;
|
||||
getFilters(): esFilters.Filter[];
|
||||
}
|
||||
|
|
|
@ -65,8 +65,8 @@ export {
|
|||
SearchSource,
|
||||
EsQuerySortValue,
|
||||
SortDirection,
|
||||
SearchSourceContract,
|
||||
} from '../../../../ui/public/courier';
|
||||
ISearchSource,
|
||||
} from 'ui/courier';
|
||||
// @ts-ignore
|
||||
export { intervalOptions } from 'ui/agg_types/buckets/_interval_options';
|
||||
// @ts-ignore
|
||||
|
|
|
@ -16,11 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
EsQuerySortValue,
|
||||
SortDirection,
|
||||
SearchSourceContract,
|
||||
} from '../../../../../kibana_services';
|
||||
import { EsQuerySortValue, SortDirection, ISearchSource } from '../../../../../kibana_services';
|
||||
import { convertTimeValueToIso } from './date_conversion';
|
||||
import { EsHitRecordList } from '../context';
|
||||
import { IntervalValue } from './generate_intervals';
|
||||
|
@ -40,7 +36,7 @@ interface RangeQuery {
|
|||
* and filters set.
|
||||
*/
|
||||
export async function fetchHitsInInterval(
|
||||
searchSource: SearchSourceContract,
|
||||
searchSource: ISearchSource,
|
||||
timeField: string,
|
||||
sort: [EsQuerySortValue, EsQuerySortValue],
|
||||
sortDir: SortDirection,
|
||||
|
|
|
@ -50,7 +50,7 @@ import {
|
|||
getServices,
|
||||
IndexPattern,
|
||||
RequestAdapter,
|
||||
SearchSourceContract,
|
||||
ISearchSource,
|
||||
} from '../../kibana_services';
|
||||
import { SEARCH_EMBEDDABLE_TYPE } from './constants';
|
||||
|
||||
|
@ -89,7 +89,7 @@ export class SearchEmbeddable extends Embeddable<SearchInput, SearchOutput>
|
|||
private inspectorAdaptors: Adapters;
|
||||
private searchScope?: SearchScope;
|
||||
private panelTitle: string = '';
|
||||
private filtersSearchSource?: SearchSourceContract;
|
||||
private filtersSearchSource?: ISearchSource;
|
||||
private searchInstance?: JQLite;
|
||||
private autoRefreshFetchSubscription?: Subscription;
|
||||
private subscription?: Subscription;
|
||||
|
|
|
@ -17,14 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SearchSourceContract } from '../kibana_services';
|
||||
import { ISearchSource } from '../kibana_services';
|
||||
import { SortOrder } from './angular/doc_table/components/table_header/helpers';
|
||||
export { SortOrder } from './angular/doc_table/components/table_header/helpers';
|
||||
|
||||
export interface SavedSearch {
|
||||
readonly id: string;
|
||||
title: string;
|
||||
searchSource: SearchSourceContract;
|
||||
searchSource: ISearchSource;
|
||||
description?: string;
|
||||
columns: string[];
|
||||
sort: SortOrder[];
|
||||
|
|
|
@ -29,7 +29,7 @@ import { getTableAggs } from 'ui/visualize/loader/pipeline_helpers/utilities';
|
|||
import { AppState } from 'ui/state_management/app_state';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { IExpressionLoaderParams } from 'src/plugins/expressions/public';
|
||||
import { SearchSourceContract } from 'ui/courier';
|
||||
import { ISearchSource } from 'ui/courier';
|
||||
import { VISUALIZE_EMBEDDABLE_TYPE } from './constants';
|
||||
import {
|
||||
IIndexPattern,
|
||||
|
@ -54,7 +54,7 @@ const getKeys = <T extends {}>(o: T): Array<keyof T> => Object.keys(o) as Array<
|
|||
export interface VisSavedObject extends SavedObject {
|
||||
vis: Vis;
|
||||
description?: string;
|
||||
searchSource: SearchSourceContract;
|
||||
searchSource: ISearchSource;
|
||||
title: string;
|
||||
uiStateJSON?: string;
|
||||
destroy: () => void;
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
createFormat,
|
||||
} from '../../../legacy_imports';
|
||||
// eslint-disable-next-line
|
||||
import { SearchSourceContract } from '../../../../../../ui/public/courier/search_source/search_source';
|
||||
import { ISearchSource } from '../../../../../../ui/public/courier/search_source/search_source';
|
||||
import { Vis, VisParams, VisState } from '..';
|
||||
|
||||
interface SchemaConfigParams {
|
||||
|
@ -466,7 +466,7 @@ export const buildVislibDimensions = async (
|
|||
// take a Vis object and decorate it with the necessary params (dimensions, bucket, metric, etc)
|
||||
export const getVisParams = async (
|
||||
vis: Vis,
|
||||
params: { searchSource: SearchSourceContract; timeRange?: any; abortSignal?: AbortSignal }
|
||||
params: { searchSource: ISearchSource; timeRange?: any; abortSignal?: AbortSignal }
|
||||
) => {
|
||||
const schemas = getSchemas(vis, params.timeRange);
|
||||
let visConfig = cloneDeep(vis.params);
|
||||
|
@ -484,7 +484,7 @@ export const getVisParams = async (
|
|||
export const buildPipeline = async (
|
||||
vis: Vis,
|
||||
params: {
|
||||
searchSource: SearchSourceContract;
|
||||
searchSource: ISearchSource;
|
||||
timeRange?: any;
|
||||
}
|
||||
) => {
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
import _ from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { SearchSourceContract, FetchOptions } from '../courier/types';
|
||||
import { ISearchSource, FetchOptions } from '../courier/types';
|
||||
import { AggType } from './agg_type';
|
||||
import { AggGroupNames } from '../vis/editors/default/agg_groups';
|
||||
import { writeParams } from './agg_params';
|
||||
|
@ -236,7 +236,7 @@ export class AggConfig {
|
|||
* @param {Courier.FetchOptions} options
|
||||
* @return {Promise<undefined>}
|
||||
*/
|
||||
onSearchRequestStart(searchSource: SearchSourceContract, options?: FetchOptions) {
|
||||
onSearchRequestStart(searchSource: ISearchSource, options?: FetchOptions) {
|
||||
if (!this.type) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import { Schema } from '../vis/editors/default/schemas';
|
|||
import { AggConfig, AggConfigOptions } from './agg_config';
|
||||
import { AggGroupNames } from '../vis/editors/default/agg_groups';
|
||||
import { IndexPattern } from '../../../core_plugins/data/public';
|
||||
import { SearchSourceContract, FetchOptions } from '../courier/types';
|
||||
import { ISearchSource, FetchOptions } from '../courier/types';
|
||||
|
||||
type Schemas = Record<string, any>;
|
||||
|
||||
|
@ -306,7 +306,7 @@ export class AggConfigs {
|
|||
return _.find(reqAgg.getResponseAggs(), { id });
|
||||
}
|
||||
|
||||
onSearchRequestStart(searchSource: SearchSourceContract, options?: FetchOptions) {
|
||||
onSearchRequestStart(searchSource: ISearchSource, options?: FetchOptions) {
|
||||
return Promise.all(
|
||||
// @ts-ignore
|
||||
this.getRequestAggs().map((agg: AggConfig) => agg.onSearchRequestStart(searchSource, options))
|
||||
|
|
|
@ -24,7 +24,7 @@ import { initParams } from './agg_params';
|
|||
|
||||
import { AggConfig } from '../vis';
|
||||
import { AggConfigs } from './agg_configs';
|
||||
import { SearchSource } from '../courier';
|
||||
import { ISearchSource } from '../courier';
|
||||
import { Adapters } from '../inspector';
|
||||
import { BaseParamType } from './param_types/base';
|
||||
import { AggParamType } from '../agg_types/param_types/agg';
|
||||
|
@ -51,7 +51,7 @@ export interface AggTypeConfig<
|
|||
resp: any,
|
||||
aggConfigs: AggConfigs,
|
||||
aggConfig: TAggConfig,
|
||||
searchSource: SearchSource,
|
||||
searchSource: ISearchSource,
|
||||
inspectorAdapters: Adapters,
|
||||
abortSignal?: AbortSignal
|
||||
) => Promise<any>;
|
||||
|
@ -180,7 +180,7 @@ export class AggType<
|
|||
resp: any,
|
||||
aggConfigs: AggConfigs,
|
||||
aggConfig: TAggConfig,
|
||||
searchSource: SearchSource,
|
||||
searchSource: ISearchSource,
|
||||
inspectorAdapters: Adapters,
|
||||
abortSignal?: AbortSignal
|
||||
) => Promise<any>;
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import { noop } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SearchSource, getRequestInspectorStats, getResponseInspectorStats } from '../../courier';
|
||||
import { ISearchSource, getRequestInspectorStats, getResponseInspectorStats } from '../../courier';
|
||||
import { BucketAggType } from './_bucket_agg_type';
|
||||
import { BUCKET_TYPES } from './bucket_agg_types';
|
||||
import { IBucketAggConfig } from './_bucket_agg_type';
|
||||
|
@ -90,7 +90,7 @@ export const termsBucketAgg = new BucketAggType({
|
|||
resp: any,
|
||||
aggConfigs: AggConfigs,
|
||||
aggConfig: IBucketAggConfig,
|
||||
searchSource: SearchSource,
|
||||
searchSource: ISearchSource,
|
||||
inspectorAdapters: Adapters,
|
||||
abortSignal?: AbortSignal
|
||||
) => {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import { AggConfigs } from '../agg_configs';
|
||||
import { AggConfig } from '../../vis';
|
||||
import { SearchSourceContract, FetchOptions } from '../../courier/types';
|
||||
import { ISearchSource, FetchOptions } from '../../courier/types';
|
||||
|
||||
export class BaseParamType<TAggConfig extends AggConfig = AggConfig> {
|
||||
name: string;
|
||||
|
@ -54,7 +54,7 @@ export class BaseParamType<TAggConfig extends AggConfig = AggConfig> {
|
|||
*/
|
||||
modifyAggConfigOnSearchRequestStart: (
|
||||
aggConfig: TAggConfig,
|
||||
searchSource?: SearchSourceContract,
|
||||
searchSource?: ISearchSource,
|
||||
options?: FetchOptions
|
||||
) => void;
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
@import './fetch/components/shard_failure_modal';
|
||||
@import '../../../core_plugins/data/public/search/fetch/components/shard_failure_modal';
|
|
@ -17,31 +17,46 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { SearchSource } from './search_source';
|
||||
/**
|
||||
* Nothing to see here!
|
||||
*
|
||||
* Courier / SearchSource has moved to the data plugin, and is being
|
||||
* re-exported from ui/courier for backwards compatibility.
|
||||
*/
|
||||
|
||||
import { start as dataStart } from '../../../core_plugins/data/public/legacy';
|
||||
|
||||
// runtime contracts
|
||||
export const { defaultSearchStrategy, SearchSource } = dataStart.search;
|
||||
|
||||
// types
|
||||
export {
|
||||
ISearchSource,
|
||||
EsQuerySortValue, // used externally by Discover
|
||||
FetchOptions, // used externally by AggTypes
|
||||
SortDirection, // used externally by Discover
|
||||
} from '../../../core_plugins/data/public';
|
||||
|
||||
// static code
|
||||
export {
|
||||
getRequestInspectorStats,
|
||||
getResponseInspectorStats,
|
||||
} from '../../../core_plugins/data/public';
|
||||
|
||||
// TODO: Exporting this mock outside of jest tests causes errors because
|
||||
// jest is undefined. Need to refactor the mock to be consistent with
|
||||
// other NP-style mocks.
|
||||
// export { searchSourceMock } from './search_source/mocks';
|
||||
|
||||
// Most these can probably be made internal to the search
|
||||
// service, so we are temporarily deeply importing them
|
||||
// until we relocate them to a longer-term home.
|
||||
/* eslint-disable @kbn/eslint/no-restricted-paths */
|
||||
export {
|
||||
addSearchStrategy, // used externally by Rollups
|
||||
getSearchErrorType, // used externally by Rollups
|
||||
hasSearchStategyForIndexPattern, // used externally by Discover
|
||||
isDefaultTypeIndexPattern, // used externally by Discover
|
||||
SearchError, // used externally by Visualizations & Rollups
|
||||
} from './search_strategy';
|
||||
|
||||
export {
|
||||
getRequestInspectorStats,
|
||||
getResponseInspectorStats,
|
||||
} from './utils/courier_inspector_utils';
|
||||
|
||||
// types
|
||||
export { SearchSourceContract } from './search_source';
|
||||
|
||||
export {
|
||||
EsQuerySortValue, // used externally by Discover
|
||||
FetchOptions, // used externally by AggTypes
|
||||
SortDirection, // used externally by Discover
|
||||
} from './types';
|
||||
} from '../../../core_plugins/data/public/search/search_strategy';
|
||||
/* eslint-enable @kbn/eslint/no-restricted-paths */
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export * from './search_source';
|
||||
export { SearchSource, ISearchSource } from '../index';
|
||||
|
|
|
@ -36,9 +36,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SearchSourceContract } from './search_source';
|
||||
// This mock is here for BWC, but will be left behind and replaced by
|
||||
// the data service mock in the new platform.
|
||||
import { ISearchSource } from '../index';
|
||||
|
||||
export const searchSourceMock: MockedKeys<SearchSourceContract> = {
|
||||
export const searchSourceMock: MockedKeys<ISearchSource> = {
|
||||
setPreferredSearchStrategyId: jest.fn(),
|
||||
setFields: jest.fn().mockReturnThis(),
|
||||
setField: jest.fn().mockReturnThis(),
|
||||
|
|
|
@ -17,394 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @name SearchSource
|
||||
*
|
||||
* @description A promise-based stream of search results that can inherit from other search sources.
|
||||
*
|
||||
* Because filters/queries in Kibana have different levels of persistence and come from different
|
||||
* places, it is important to keep track of where filters come from for when they are saved back to
|
||||
* the savedObject store in the Kibana index. To do this, we create trees of searchSource objects
|
||||
* that can have associated query parameters (index, query, filter, etc) which can also inherit from
|
||||
* other searchSource objects.
|
||||
*
|
||||
* At query time, all of the searchSource objects that have subscribers are "flattened", at which
|
||||
* point the query params from the searchSource are collected while traversing up the inheritance
|
||||
* chain. At each link in the chain a decision about how to merge the query params is made until a
|
||||
* single set of query parameters is created for each active searchSource (a searchSource with
|
||||
* subscribers).
|
||||
*
|
||||
* That set of query parameters is then sent to elasticsearch. This is how the filter hierarchy
|
||||
* works in Kibana.
|
||||
*
|
||||
* Visualize, starting from a new search:
|
||||
*
|
||||
* - the `savedVis.searchSource` is set as the `appSearchSource`.
|
||||
* - The `savedVis.searchSource` would normally inherit from the `appSearchSource`, but now it is
|
||||
* upgraded to inherit from the `rootSearchSource`.
|
||||
* - Any interaction with the visualization will still apply filters to the `appSearchSource`, so
|
||||
* they will be stored directly on the `savedVis.searchSource`.
|
||||
* - Any interaction with the time filter will be written to the `rootSearchSource`, so those
|
||||
* filters will not be saved by the `savedVis`.
|
||||
* - When the `savedVis` is saved to elasticsearch, it takes with it all the filters that are
|
||||
* defined on it directly, but none of the ones that it inherits from other places.
|
||||
*
|
||||
* Visualize, starting from an existing search:
|
||||
*
|
||||
* - The `savedVis` loads the `savedSearch` on which it is built.
|
||||
* - The `savedVis.searchSource` is set to inherit from the `saveSearch.searchSource` and set as
|
||||
* the `appSearchSource`.
|
||||
* - The `savedSearch.searchSource`, is set to inherit from the `rootSearchSource`.
|
||||
* - Then the `savedVis` is written to elasticsearch it will be flattened and only include the
|
||||
* filters created in the visualize application and will reconnect the filters from the
|
||||
* `savedSearch` at runtime to prevent losing the relationship
|
||||
*
|
||||
* Dashboard search sources:
|
||||
*
|
||||
* - Each panel in a dashboard has a search source.
|
||||
* - The `savedDashboard` also has a searchsource, and it is set as the `appSearchSource`.
|
||||
* - Each panel's search source inherits from the `appSearchSource`, meaning that they inherit from
|
||||
* the dashboard search source.
|
||||
* - When a filter is added to the search box, or via a visualization, it is written to the
|
||||
* `appSearchSource`.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { npSetup } from 'ui/new_platform';
|
||||
import { normalizeSortRequest } from './normalize_sort_request';
|
||||
import { fetchSoon } from '../fetch';
|
||||
import { fieldWildcardFilter } from '../../field_wildcard';
|
||||
import { getHighlightRequest, esFilters, esQuery } from '../../../../../plugins/data/public';
|
||||
import chrome from '../../chrome';
|
||||
import { RequestFailure } from '../fetch/errors';
|
||||
import { filterDocvalueFields } from './filter_docvalue_fields';
|
||||
import { SearchSourceOptions, SearchSourceFields, SearchRequest } from './types';
|
||||
import { FetchOptions, ApiCaller } from '../fetch/types';
|
||||
|
||||
const esShardTimeout = npSetup.core.injectedMetadata.getInjectedVar('esShardTimeout') as number;
|
||||
const config = npSetup.core.uiSettings;
|
||||
|
||||
export type SearchSourceContract = Pick<SearchSource, keyof SearchSource>;
|
||||
|
||||
export class SearchSource {
|
||||
private id: string = _.uniqueId('data_source');
|
||||
private searchStrategyId?: string;
|
||||
private parent?: SearchSource;
|
||||
private requestStartHandlers: Array<
|
||||
(searchSource: SearchSourceContract, options?: FetchOptions) => Promise<unknown>
|
||||
> = [];
|
||||
private inheritOptions: SearchSourceOptions = {};
|
||||
public history: SearchRequest[] = [];
|
||||
|
||||
constructor(private fields: SearchSourceFields = {}) {}
|
||||
|
||||
/** ***
|
||||
* PUBLIC API
|
||||
*****/
|
||||
|
||||
setPreferredSearchStrategyId(searchStrategyId: string) {
|
||||
this.searchStrategyId = searchStrategyId;
|
||||
}
|
||||
|
||||
setFields(newFields: SearchSourceFields) {
|
||||
this.fields = newFields;
|
||||
return this;
|
||||
}
|
||||
|
||||
setField<K extends keyof SearchSourceFields>(field: K, value: SearchSourceFields[K]) {
|
||||
if (value == null) {
|
||||
delete this.fields[field];
|
||||
} else {
|
||||
this.fields[field] = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
getFields() {
|
||||
return { ...this.fields };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fields from the fields
|
||||
*/
|
||||
getField<K extends keyof SearchSourceFields>(field: K, recurse = true): SearchSourceFields[K] {
|
||||
if (!recurse || this.fields[field] !== void 0) {
|
||||
return this.fields[field];
|
||||
}
|
||||
const parent = this.getParent();
|
||||
return parent && parent.getField(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field from our own fields, don't traverse up the chain
|
||||
*/
|
||||
getOwnField<K extends keyof SearchSourceFields>(field: K): SearchSourceFields[K] {
|
||||
return this.getField(field, false);
|
||||
}
|
||||
|
||||
create() {
|
||||
return new SearchSource();
|
||||
}
|
||||
|
||||
createCopy() {
|
||||
const newSearchSource = new SearchSource();
|
||||
newSearchSource.setFields({ ...this.fields });
|
||||
// when serializing the internal fields we lose the internal classes used in the index
|
||||
// pattern, so we have to set it again to workaround this behavior
|
||||
newSearchSource.setField('index', this.getField('index'));
|
||||
newSearchSource.setParent(this.getParent());
|
||||
return newSearchSource;
|
||||
}
|
||||
|
||||
createChild(options = {}) {
|
||||
const childSearchSource = new SearchSource();
|
||||
childSearchSource.setParent(this, options);
|
||||
return childSearchSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a searchSource that this source should inherit from
|
||||
* @param {SearchSource} parent - the parent searchSource
|
||||
* @param {SearchSourceOptions} options - the inherit options
|
||||
* @return {this} - chainable
|
||||
*/
|
||||
setParent(parent?: SearchSourceContract, options: SearchSourceOptions = {}) {
|
||||
this.parent = parent as SearchSource;
|
||||
this.inheritOptions = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent of this SearchSource
|
||||
* @return {undefined|searchSource}
|
||||
*/
|
||||
getParent() {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch this source and reject the returned Promise on error
|
||||
*
|
||||
* @async
|
||||
*/
|
||||
async fetch(options: FetchOptions = {}) {
|
||||
const $injector = await chrome.dangerouslyGetActiveInjector();
|
||||
const es = $injector.get('es') as ApiCaller;
|
||||
|
||||
await this.requestIsStarting(options);
|
||||
|
||||
const searchRequest = await this.flatten();
|
||||
this.history = [searchRequest];
|
||||
|
||||
const response = await fetchSoon(
|
||||
searchRequest,
|
||||
{
|
||||
...(this.searchStrategyId && { searchStrategyId: this.searchStrategyId }),
|
||||
...options,
|
||||
},
|
||||
{ es, config, esShardTimeout }
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
throw new RequestFailure(null, response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a handler that will be notified whenever requests start
|
||||
* @param {Function} handler
|
||||
* @return {undefined}
|
||||
*/
|
||||
onRequestStart(
|
||||
handler: (searchSource: SearchSourceContract, options?: FetchOptions) => Promise<unknown>
|
||||
) {
|
||||
this.requestStartHandlers.push(handler);
|
||||
}
|
||||
|
||||
async getSearchRequestBody() {
|
||||
const searchRequest = await this.flatten();
|
||||
return searchRequest.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely destroy the SearchSource.
|
||||
* @return {undefined}
|
||||
*/
|
||||
destroy() {
|
||||
this.requestStartHandlers.length = 0;
|
||||
}
|
||||
|
||||
/** ****
|
||||
* PRIVATE APIS
|
||||
******/
|
||||
|
||||
/**
|
||||
* Called by requests of this search source when they are started
|
||||
* @param {Courier.Request} request
|
||||
* @param options
|
||||
* @return {Promise<undefined>}
|
||||
*/
|
||||
private requestIsStarting(options: FetchOptions = {}) {
|
||||
const handlers = [...this.requestStartHandlers];
|
||||
// If callParentStartHandlers has been set to true, we also call all
|
||||
// handlers of parent search sources.
|
||||
if (this.inheritOptions.callParentStartHandlers) {
|
||||
let searchSource = this.getParent();
|
||||
while (searchSource) {
|
||||
handlers.push(...searchSource.requestStartHandlers);
|
||||
searchSource = searchSource.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(handlers.map(fn => fn(this, options)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to merge properties into the data within ._flatten().
|
||||
* The data is passed in and modified by the function
|
||||
*
|
||||
* @param {object} data - the current merged data
|
||||
* @param {*} val - the value at `key`
|
||||
* @param {*} key - The key of `val`
|
||||
* @return {undefined}
|
||||
*/
|
||||
private mergeProp<K extends keyof SearchSourceFields>(
|
||||
data: SearchRequest,
|
||||
val: SearchSourceFields[K],
|
||||
key: K
|
||||
) {
|
||||
val = typeof val === 'function' ? val(this) : val;
|
||||
if (val == null || !key) return;
|
||||
|
||||
const addToRoot = (rootKey: string, value: any) => {
|
||||
data[rootKey] = value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the key and val to the body of the request
|
||||
*/
|
||||
const addToBody = (bodyKey: string, value: any) => {
|
||||
// ignore if we already have a value
|
||||
if (data.body[bodyKey] == null) {
|
||||
data.body[bodyKey] = value;
|
||||
}
|
||||
};
|
||||
|
||||
switch (key) {
|
||||
case 'filter':
|
||||
return addToRoot('filters', (data.filters || []).concat(val));
|
||||
case 'query':
|
||||
return addToRoot(key, (data[key] || []).concat(val));
|
||||
case 'fields':
|
||||
const fields = _.uniq((data[key] || []).concat(val));
|
||||
return addToRoot(key, fields);
|
||||
case 'index':
|
||||
case 'type':
|
||||
case 'highlightAll':
|
||||
return key && data[key] == null && addToRoot(key, val);
|
||||
case 'searchAfter':
|
||||
return addToBody('search_after', val);
|
||||
case 'source':
|
||||
return addToBody('_source', val);
|
||||
case 'sort':
|
||||
const sort = normalizeSortRequest(val, this.getField('index'), config.get('sort:options'));
|
||||
return addToBody(key, sort);
|
||||
default:
|
||||
return addToBody(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk the inheritance chain of a source and return its
|
||||
* flat representation (taking into account merging rules)
|
||||
* @returns {Promise}
|
||||
* @resolved {Object|null} - the flat data of the SearchSource
|
||||
*/
|
||||
private mergeProps(root = this, searchRequest: SearchRequest = { body: {} }) {
|
||||
Object.entries(this.fields).forEach(([key, value]) => {
|
||||
this.mergeProp(searchRequest, value, key as keyof SearchSourceFields);
|
||||
});
|
||||
if (this.parent) {
|
||||
this.parent.mergeProps(root, searchRequest);
|
||||
}
|
||||
return searchRequest;
|
||||
}
|
||||
|
||||
private flatten() {
|
||||
const searchRequest = this.mergeProps();
|
||||
|
||||
searchRequest.body = searchRequest.body || {};
|
||||
const { body, index, fields, query, filters, highlightAll } = searchRequest;
|
||||
|
||||
const computedFields = index ? index.getComputedFields() : {};
|
||||
|
||||
body.stored_fields = computedFields.storedFields;
|
||||
body.script_fields = body.script_fields || {};
|
||||
_.extend(body.script_fields, computedFields.scriptFields);
|
||||
|
||||
const defaultDocValueFields = computedFields.docvalueFields
|
||||
? computedFields.docvalueFields
|
||||
: [];
|
||||
body.docvalue_fields = body.docvalue_fields || defaultDocValueFields;
|
||||
|
||||
if (!body.hasOwnProperty('_source') && index) {
|
||||
body._source = index.getSourceFiltering();
|
||||
}
|
||||
|
||||
if (body._source) {
|
||||
// exclude source fields for this index pattern specified by the user
|
||||
const filter = fieldWildcardFilter(body._source.excludes, config.get('metaFields'));
|
||||
body.docvalue_fields = body.docvalue_fields.filter((docvalueField: any) =>
|
||||
filter(docvalueField.field)
|
||||
);
|
||||
}
|
||||
|
||||
// if we only want to search for certain fields
|
||||
if (fields) {
|
||||
// filter out the docvalue_fields, and script_fields to only include those that we are concerned with
|
||||
body.docvalue_fields = filterDocvalueFields(body.docvalue_fields, fields);
|
||||
body.script_fields = _.pick(body.script_fields, fields);
|
||||
|
||||
// request the remaining fields from both stored_fields and _source
|
||||
const remainingFields = _.difference(fields, _.keys(body.script_fields));
|
||||
body.stored_fields = remainingFields;
|
||||
_.set(body, '_source.includes', remainingFields);
|
||||
}
|
||||
|
||||
const esQueryConfigs = esQuery.getEsQueryConfig(config);
|
||||
body.query = esQuery.buildEsQuery(index, query, filters, esQueryConfigs);
|
||||
|
||||
if (highlightAll && body.query) {
|
||||
body.highlight = getHighlightRequest(body.query, config.get('doc_table:highlight'));
|
||||
delete searchRequest.highlightAll;
|
||||
}
|
||||
|
||||
const translateToQuery = (filter: esFilters.Filter) => filter && (filter.query || filter);
|
||||
|
||||
// re-write filters within filter aggregations
|
||||
(function recurse(aggBranch) {
|
||||
if (!aggBranch) return;
|
||||
Object.keys(aggBranch).forEach(function(id) {
|
||||
const agg = aggBranch[id];
|
||||
|
||||
if (agg.filters) {
|
||||
// translate filters aggregations
|
||||
const { filters: aggFilters } = agg.filters;
|
||||
Object.keys(aggFilters).forEach(filterId => {
|
||||
aggFilters[filterId] = translateToQuery(aggFilters[filterId]);
|
||||
});
|
||||
}
|
||||
|
||||
recurse(agg.aggs || agg.aggregations);
|
||||
});
|
||||
})(body.aggs || body.aggregations);
|
||||
|
||||
return searchRequest;
|
||||
}
|
||||
}
|
||||
export { SearchSource, ISearchSource } from '../index';
|
||||
|
|
|
@ -17,65 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SearchStrategyProvider, SearchStrategySearchParams } from './types';
|
||||
import { addSearchStrategy } from './search_strategy_registry';
|
||||
import { isDefaultTypeIndexPattern } from './is_default_type_index_pattern';
|
||||
import {
|
||||
getSearchParams,
|
||||
getMSearchParams,
|
||||
getPreference,
|
||||
getTimeout,
|
||||
} from '../fetch/get_search_params';
|
||||
|
||||
export const defaultSearchStrategy: SearchStrategyProvider = {
|
||||
id: 'default',
|
||||
|
||||
search: params => {
|
||||
return params.config.get('courier:batchSearches') ? msearch(params) : search(params);
|
||||
},
|
||||
|
||||
isViable: indexPattern => {
|
||||
return indexPattern && isDefaultTypeIndexPattern(indexPattern);
|
||||
},
|
||||
};
|
||||
|
||||
function msearch({ searchRequests, es, config, esShardTimeout }: SearchStrategySearchParams) {
|
||||
const inlineRequests = searchRequests.map(({ index, body, search_type: searchType }) => {
|
||||
const inlineHeader = {
|
||||
index: index.title || index,
|
||||
search_type: searchType,
|
||||
ignore_unavailable: true,
|
||||
preference: getPreference(config),
|
||||
};
|
||||
const inlineBody = {
|
||||
...body,
|
||||
timeout: getTimeout(esShardTimeout),
|
||||
};
|
||||
return `${JSON.stringify(inlineHeader)}\n${JSON.stringify(inlineBody)}`;
|
||||
});
|
||||
|
||||
const searching = es.msearch({
|
||||
...getMSearchParams(config),
|
||||
body: `${inlineRequests.join('\n')}\n`,
|
||||
});
|
||||
return {
|
||||
searching: searching.then(({ responses }) => responses),
|
||||
abort: searching.abort,
|
||||
};
|
||||
}
|
||||
|
||||
function search({ searchRequests, es, config, esShardTimeout }: SearchStrategySearchParams) {
|
||||
const abortController = new AbortController();
|
||||
const searchParams = getSearchParams(config, esShardTimeout);
|
||||
const promises = searchRequests.map(({ index, body }) => {
|
||||
const searching = es.search({ index: index.title || index, body, ...searchParams });
|
||||
abortController.signal.addEventListener('abort', searching.abort);
|
||||
return searching.catch(({ response }) => JSON.parse(response));
|
||||
});
|
||||
return {
|
||||
searching: Promise.all(promises),
|
||||
abort: () => abortController.abort(),
|
||||
};
|
||||
}
|
||||
import { addSearchStrategy, defaultSearchStrategy } from '../index';
|
||||
|
||||
addSearchStrategy(defaultSearchStrategy);
|
||||
|
||||
export { defaultSearchStrategy };
|
||||
|
|
|
@ -20,10 +20,6 @@
|
|||
export {
|
||||
addSearchStrategy,
|
||||
hasSearchStategyForIndexPattern,
|
||||
getSearchStrategyById,
|
||||
getSearchStrategyForSearchRequest,
|
||||
} from './search_strategy_registry';
|
||||
|
||||
export { isDefaultTypeIndexPattern } from './is_default_type_index_pattern';
|
||||
|
||||
export { SearchError, getSearchErrorType } from './search_error';
|
||||
isDefaultTypeIndexPattern,
|
||||
SearchError,
|
||||
} from '../index';
|
||||
|
|
|
@ -17,46 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
interface SearchErrorOptions {
|
||||
status: string;
|
||||
title: string;
|
||||
message: string;
|
||||
path: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export class SearchError extends Error {
|
||||
public name: string;
|
||||
public status: string;
|
||||
public title: string;
|
||||
public message: string;
|
||||
public path: string;
|
||||
public type: string;
|
||||
|
||||
constructor({ status, title, message, path, type }: SearchErrorOptions) {
|
||||
super(message);
|
||||
this.name = 'SearchError';
|
||||
this.status = status;
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.path = path;
|
||||
this.type = type;
|
||||
|
||||
// captureStackTrace is only available in the V8 engine, so any browser using
|
||||
// a different JS engine won't have access to this method.
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, SearchError);
|
||||
}
|
||||
|
||||
// Babel doesn't support traditional `extends` syntax for built-in classes.
|
||||
// https://babeljs.io/docs/en/caveats/#classes
|
||||
Object.setPrototypeOf(this, SearchError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export function getSearchErrorType({ message }: Pick<SearchError, 'message'>) {
|
||||
const msg = message.toLowerCase();
|
||||
if (msg.indexOf('unsupported query') > -1) {
|
||||
return 'UNSUPPORTED_QUERY';
|
||||
}
|
||||
}
|
||||
export { SearchError } from '../index';
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export * from './fetch/types';
|
||||
export * from './search_source/types';
|
||||
export * from './search_strategy/types';
|
||||
export * from './utils/types';
|
||||
export {
|
||||
ISearchSource,
|
||||
EsQuerySortValue, // used externally by Discover
|
||||
FetchOptions, // used externally by AggTypes
|
||||
SortDirection, // used externally by Discover
|
||||
} from './index';
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ChromeStart,
|
||||
OverlayStart,
|
||||
|
@ -23,7 +24,7 @@ import {
|
|||
SavedObjectAttributes,
|
||||
SavedObjectReference,
|
||||
} from 'kibana/public';
|
||||
import { SearchSource, SearchSourceContract } from 'ui/courier';
|
||||
import { ISearchSource } from 'ui/courier';
|
||||
import { IIndexPattern, IndexPatternsContract } from '../../../../plugins/data/public';
|
||||
|
||||
export interface SavedObject {
|
||||
|
@ -46,7 +47,7 @@ export interface SavedObject {
|
|||
lastSavedTitle: string;
|
||||
migrationVersion?: Record<string, any>;
|
||||
save: (saveOptions: SavedObjectSaveOpts) => Promise<string>;
|
||||
searchSource?: SearchSourceContract;
|
||||
searchSource?: ISearchSource;
|
||||
showInRecentlyAccessed: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
@ -88,7 +89,7 @@ export interface SavedObjectConfig {
|
|||
mapping?: any;
|
||||
migrationVersion?: Record<string, any>;
|
||||
path?: string;
|
||||
searchSource?: SearchSource | boolean;
|
||||
searchSource?: ISearchSource | boolean;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,13 +24,13 @@ import { toastNotifications } from 'ui/notify';
|
|||
import { AggConfig } from 'ui/vis';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
import { Vis } from '../../../vis';
|
||||
import { SearchSource, SearchSourceContract } from '../../../courier';
|
||||
import { SearchSource, ISearchSource } from '../../../courier';
|
||||
import { esFilters, Query } from '../../../../../../plugins/data/public';
|
||||
|
||||
interface QueryGeohashBoundsParams {
|
||||
filters?: esFilters.Filter[];
|
||||
query?: Query;
|
||||
searchSource?: SearchSourceContract;
|
||||
searchSource?: ISearchSource;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,8 @@ import moment from 'moment';
|
|||
import { mergeTables } from './merge_tables';
|
||||
import { KibanaDatatable } from 'src/plugins/expressions/public';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
describe('lens_merge_tables', () => {
|
||||
it('should produce a row with the nested table as defined', () => {
|
||||
const sampleTable1: KibanaDatatable = {
|
||||
|
|
|
@ -14,19 +14,35 @@ import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
|
|||
import { createMockedIndexPattern } from '../../mocks';
|
||||
import { IndexPatternPrivateState } from '../../types';
|
||||
|
||||
jest.mock('ui/new_platform', () => ({
|
||||
npStart: {
|
||||
core: {
|
||||
uiSettings: {
|
||||
get: (path: string) => {
|
||||
if (path === 'histogram:maxBars') {
|
||||
return 10;
|
||||
}
|
||||
jest.mock('ui/new_platform', () => {
|
||||
// Due to the way we are handling shims in the NP migration, we need
|
||||
// to mock core here so that upstream services don't cause these
|
||||
// tests to fail. Ordinarly `jest.mock('ui/new_platform')` would be
|
||||
// sufficient, however we need to mock one of the `uiSettings` return
|
||||
// values for this suite, so we must manually assemble the mock.
|
||||
// Because babel hoists `jest` we must use an inline `require`
|
||||
// to ensure the core mocks are available (`jest.doMock` doesn't
|
||||
// work in this case). This mock should be able to be replaced
|
||||
// altogether once Lens has migrated to the new platform.
|
||||
const { coreMock } = require('src/core/public/mocks'); // eslint-disable-line @typescript-eslint/no-var-requires
|
||||
return {
|
||||
npSetup: {
|
||||
core: coreMock.createSetup(),
|
||||
},
|
||||
npStart: {
|
||||
core: {
|
||||
...coreMock.createStart(),
|
||||
uiSettings: {
|
||||
get: (path: string) => {
|
||||
if (path === 'histogram:maxBars') {
|
||||
return 10;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
||||
const defaultOptions = {
|
||||
storage: {} as IStorageWrapper,
|
||||
|
|
|
@ -261,24 +261,6 @@
|
|||
"common.ui.aggTypes.timeInterval.scaledHelpText": "現在 {bucketDescription} にスケーリングされています",
|
||||
"common.ui.aggTypes.timeInterval.selectIntervalPlaceholder": "間隔を選択",
|
||||
"common.ui.aggTypes.timeInterval.selectOptionHelpText": "オプションを選択するかカスタム値を作成します。例30s、20m、24h、2d、1w、1M",
|
||||
"common.ui.courier.fetch.requestTimedOutNotificationMessage": "リクエストがタイムアウトしたため、データが不完全な可能性があります",
|
||||
"common.ui.courier.fetch.shardsFailedNotificationMessage": "{shardsTotal} 件中 {shardsFailed} 件のシャードでエラーが発生しました",
|
||||
"common.ui.courier.hitsDescription": "クエリにより返されたドキュメントの数です。",
|
||||
"common.ui.courier.hitsLabel": "ヒット数",
|
||||
"common.ui.courier.hitsTotalDescription": "クエリに一致するドキュメントの数です。",
|
||||
"common.ui.courier.hitsTotalLabel": "ヒット数 (合計)",
|
||||
"common.ui.courier.indexPatternDescription": "Elasticsearch インデックスに接続したインデックスパターンです。",
|
||||
"common.ui.courier.indexPatternIdDescription": "{kibanaIndexPattern} インデックス内の ID です。",
|
||||
"common.ui.courier.indexPatternIdLabel": "インデックスパターン ID",
|
||||
"common.ui.courier.indexPatternLabel": "インデックスパターン",
|
||||
"common.ui.courier.noSearchStrategyRegisteredErrorMessageDescription": "検索リクエストの検索方法が見つかりませんでした",
|
||||
"common.ui.courier.noSearchStrategyRegisteredErrorMessageTitle": "検索方法が登録されていません",
|
||||
"common.ui.courier.queryTimeDescription": "クエリの処理の所要時間です。リクエストの送信やブラウザでのパースの時間は含まれません。",
|
||||
"common.ui.courier.queryTimeLabel": "クエリ時間",
|
||||
"common.ui.courier.queryTimeValue": "{queryTime}ms",
|
||||
"common.ui.courier.requestTimeDescription": "ブラウザから Elasticsearch にリクエストが送信され返されるまでの所要時間です。リクエストがキューで待機していた時間は含まれません。",
|
||||
"common.ui.courier.requestTimeLabel": "リクエスト時間",
|
||||
"common.ui.courier.requestTimeValue": "{requestTime}ms",
|
||||
"common.ui.directives.fieldNameIcons.booleanAriaLabel": "ブールフィールド",
|
||||
"common.ui.directives.fieldNameIcons.conflictFieldAriaLabel": "矛盾フィールド",
|
||||
"common.ui.directives.fieldNameIcons.dateFieldAriaLabel": "日付フィールド",
|
||||
|
@ -541,20 +523,6 @@
|
|||
"common.ui.visualize.queryGeohashBounds.unableToGetBoundErrorTitle": "バウンドを取得できませんでした",
|
||||
"common.ui.welcomeErrorMessage": "Kibana が正常に読み込まれませんでした。詳細はサーバーアウトプットを確認してください。",
|
||||
"common.ui.welcomeMessage": "Kibana を読み込み中",
|
||||
"common.ui.courier.fetch.shardsFailedModal.close": "閉じる",
|
||||
"common.ui.courier.fetch.shardsFailedModal.copyToClipboard": "応答をクリップボードにコピー",
|
||||
"common.ui.courier.fetch.shardsFailedModal.failureHeader": "{failureName} で {failureDetails}",
|
||||
"common.ui.courier.fetch.shardsFailedModal.showDetails": "詳細を表示",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tabHeaderRequest": "リクエスト",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tabHeaderResponse": "応答",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tabHeaderShardFailures": "シャードエラー",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tableColIndex": "インデックス",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tableColNode": "ノード",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tableColReason": "理由",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tableColShard": "シャード",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tableRowCollapse": "{rowDescription} を折りたたむ",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tableRowExpand": "{rowDescription} を展開する",
|
||||
"common.ui.courier.fetch.shardsFailedNotificationDescription": "表示されているデータは不完全か誤りの可能性があります。",
|
||||
"common.ui.directives.fieldNameIcons.geoShapeFieldAriaLabel": "地理情報図形",
|
||||
"common.ui.vis.editors.agg.errorsAriaLabel": "集約にエラーがあります",
|
||||
"common.ui.vislib.heatmap.maxBucketsText": "定義された数列が多すぎます ({nr}).構成されている最高値は {max} です。",
|
||||
|
@ -873,6 +841,38 @@
|
|||
"data.search.searchBar.savedQueryPopoverSavedQueryListItemDescriptionAriaLabel": "{savedQueryName} の説明",
|
||||
"data.search.searchBar.savedQueryPopoverSavedQueryListItemSelectedButtonAriaLabel": "選択されたクエリボタン {savedQueryName} を保存しました。変更を破棄するには押してください。",
|
||||
"data.search.searchBar.savedQueryPopoverTitleText": "保存されたクエリ",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.close": "閉じる",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.copyToClipboard": "応答をクリップボードにコピー",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.failureHeader": "{failureName} で {failureDetails}",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.showDetails": "詳細を表示",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tabHeaderRequest": "リクエスト",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tabHeaderResponse": "応答",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tabHeaderShardFailures": "シャードエラー",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tableColIndex": "インデックス",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tableColNode": "ノード",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tableColReason": "理由",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tableColShard": "シャード",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tableRowCollapse": "{rowDescription} を折りたたむ",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tableRowExpand": "{rowDescription} を展開する",
|
||||
"data.search.searchSource.fetch.shardsFailedNotificationDescription": "表示されているデータは不完全か誤りの可能性があります。",
|
||||
"data.search.searchSource.fetch.requestTimedOutNotificationMessage": "リクエストがタイムアウトしたため、データが不完全な可能性があります",
|
||||
"data.search.searchSource.fetch.shardsFailedNotificationMessage": "{shardsTotal} 件中 {shardsFailed} 件のシャードでエラーが発生しました",
|
||||
"data.search.searchSource.hitsDescription": "クエリにより返されたドキュメントの数です。",
|
||||
"data.search.searchSource.hitsLabel": "ヒット数",
|
||||
"data.search.searchSource.hitsTotalDescription": "クエリに一致するドキュメントの数です。",
|
||||
"data.search.searchSource.hitsTotalLabel": "ヒット数 (合計)",
|
||||
"data.search.searchSource.indexPatternDescription": "Elasticsearch インデックスに接続したインデックスパターンです。",
|
||||
"data.search.searchSource.indexPatternIdDescription": "{kibanaIndexPattern} インデックス内の ID です。",
|
||||
"data.search.searchSource.indexPatternIdLabel": "インデックスパターン ID",
|
||||
"data.search.searchSource.indexPatternLabel": "インデックスパターン",
|
||||
"data.search.searchSource.noSearchStrategyRegisteredErrorMessageDescription": "検索リクエストの検索方法が見つかりませんでした",
|
||||
"data.search.searchSource.noSearchStrategyRegisteredErrorMessageTitle": "検索方法が登録されていません",
|
||||
"data.search.searchSource.queryTimeDescription": "クエリの処理の所要時間です。リクエストの送信やブラウザでのパースの時間は含まれません。",
|
||||
"data.search.searchSource.queryTimeLabel": "クエリ時間",
|
||||
"data.search.searchSource.queryTimeValue": "{queryTime}ms",
|
||||
"data.search.searchSource.requestTimeDescription": "ブラウザから Elasticsearch にリクエストが送信され返されるまでの所要時間です。リクエストがキューで待機していた時間は含まれません。",
|
||||
"data.search.searchSource.requestTimeLabel": "リクエスト時間",
|
||||
"data.search.searchSource.requestTimeValue": "{requestTime}ms",
|
||||
"data.filter.filterEditor.operatorSelectPlaceholderSelect": "選択してください",
|
||||
"data.filter.filterEditor.operatorSelectPlaceholderWaiting": "待機中",
|
||||
"data.filter.filterEditor.rangeInputLabel": "範囲",
|
||||
|
|
|
@ -261,24 +261,6 @@
|
|||
"common.ui.aggTypes.timeInterval.scaledHelpText": "当前缩放至 {bucketDescription}",
|
||||
"common.ui.aggTypes.timeInterval.selectIntervalPlaceholder": "选择时间间隔",
|
||||
"common.ui.aggTypes.timeInterval.selectOptionHelpText": "选择选项或创建定制值示例:30s、20m、24h、2d、1w、1M",
|
||||
"common.ui.courier.fetch.requestTimedOutNotificationMessage": "由于您的请求超时,因此数据可能不完整",
|
||||
"common.ui.courier.fetch.shardsFailedNotificationMessage": "{shardsTotal} 个分片有 {shardsFailed} 个失败",
|
||||
"common.ui.courier.hitsDescription": "查询返回的文档数目。",
|
||||
"common.ui.courier.hitsLabel": "命中",
|
||||
"common.ui.courier.hitsTotalDescription": "匹配查询的文档数目。",
|
||||
"common.ui.courier.hitsTotalLabel": "命中(总计)",
|
||||
"common.ui.courier.indexPatternDescription": "连接到 Elasticsearch 索引的索引模式。",
|
||||
"common.ui.courier.indexPatternIdDescription": "{kibanaIndexPattern} 索引中的 ID。",
|
||||
"common.ui.courier.indexPatternIdLabel": "索引模式 ID",
|
||||
"common.ui.courier.indexPatternLabel": "索引模式",
|
||||
"common.ui.courier.noSearchStrategyRegisteredErrorMessageDescription": "无法为该搜索请求找到搜索策略",
|
||||
"common.ui.courier.noSearchStrategyRegisteredErrorMessageTitle": "未注册任何搜索策略",
|
||||
"common.ui.courier.queryTimeDescription": "处理查询所花费的时间。不包括发送请求或在浏览器中解析它的时间。",
|
||||
"common.ui.courier.queryTimeLabel": "查询时间",
|
||||
"common.ui.courier.queryTimeValue": "{queryTime}ms",
|
||||
"common.ui.courier.requestTimeDescription": "请求从浏览器到 Elasticsearch 以及返回的时间。不包括请求在队列中等候的时间。",
|
||||
"common.ui.courier.requestTimeLabel": "请求时间",
|
||||
"common.ui.courier.requestTimeValue": "{requestTime}ms",
|
||||
"common.ui.directives.fieldNameIcons.booleanAriaLabel": "布尔字段",
|
||||
"common.ui.directives.fieldNameIcons.conflictFieldAriaLabel": "冲突字段",
|
||||
"common.ui.directives.fieldNameIcons.dateFieldAriaLabel": "日期字段",
|
||||
|
@ -542,20 +524,6 @@
|
|||
"common.ui.visualize.queryGeohashBounds.unableToGetBoundErrorTitle": "无法获取边界",
|
||||
"common.ui.welcomeErrorMessage": "Kibana 未正确加载。检查服务器输出以了解详情。",
|
||||
"common.ui.welcomeMessage": "正在加载 Kibana",
|
||||
"common.ui.courier.fetch.shardsFailedModal.close": "关闭",
|
||||
"common.ui.courier.fetch.shardsFailedModal.copyToClipboard": "将响应复制到剪贴板",
|
||||
"common.ui.courier.fetch.shardsFailedModal.failureHeader": "{failureDetails} 时为 {failureName}",
|
||||
"common.ui.courier.fetch.shardsFailedModal.showDetails": "显示详情",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tabHeaderRequest": "请求",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tabHeaderResponse": "响应",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tabHeaderShardFailures": "分片错误",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tableColIndex": "索引",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tableColNode": "节点",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tableColReason": "原因",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tableColShard": "分片",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tableRowCollapse": "折叠 {rowDescription}",
|
||||
"common.ui.courier.fetch.shardsFailedModal.tableRowExpand": "展开 {rowDescription}",
|
||||
"common.ui.courier.fetch.shardsFailedNotificationDescription": "您正在查看的数据可能不完整或有错误。",
|
||||
"common.ui.directives.fieldNameIcons.geoShapeFieldAriaLabel": "几何形状字段",
|
||||
"common.ui.vis.editors.agg.errorsAriaLabel": "聚合有错误",
|
||||
"common.ui.vislib.heatmap.maxBucketsText": "定义了过多的序列 ({nr})。配置的最大值为 {max}。",
|
||||
|
@ -874,6 +842,38 @@
|
|||
"data.search.searchBar.savedQueryPopoverSavedQueryListItemDescriptionAriaLabel": "{savedQueryName} 描述",
|
||||
"data.search.searchBar.savedQueryPopoverSavedQueryListItemSelectedButtonAriaLabel": "已保存查询按钮已选择 {savedQueryName}。按下可清除任何更改。",
|
||||
"data.search.searchBar.savedQueryPopoverTitleText": "已保存查询",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.close": "关闭",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.copyToClipboard": "将响应复制到剪贴板",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.failureHeader": "{failureDetails} 时为 {failureName}",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.showDetails": "显示详情",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tabHeaderRequest": "请求",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tabHeaderResponse": "响应",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tabHeaderShardFailures": "分片错误",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tableColIndex": "索引",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tableColNode": "节点",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tableColReason": "原因",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tableColShard": "分片",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tableRowCollapse": "折叠 {rowDescription}",
|
||||
"data.search.searchSource.fetch.shardsFailedModal.tableRowExpand": "展开 {rowDescription}",
|
||||
"data.search.searchSource.fetch.shardsFailedNotificationDescription": "您正在查看的数据可能不完整或有错误。",
|
||||
"data.search.searchSource.fetch.requestTimedOutNotificationMessage": "由于您的请求超时,因此数据可能不完整",
|
||||
"data.search.searchSource.fetch.shardsFailedNotificationMessage": "{shardsTotal} 个分片有 {shardsFailed} 个失败",
|
||||
"data.search.searchSource.hitsDescription": "查询返回的文档数目。",
|
||||
"data.search.searchSource.hitsLabel": "命中",
|
||||
"data.search.searchSource.hitsTotalDescription": "匹配查询的文档数目。",
|
||||
"data.search.searchSource.hitsTotalLabel": "命中(总计)",
|
||||
"data.search.searchSource.indexPatternDescription": "连接到 Elasticsearch 索引的索引模式。",
|
||||
"data.search.searchSource.indexPatternIdDescription": "{kibanaIndexPattern} 索引中的 ID。",
|
||||
"data.search.searchSource.indexPatternIdLabel": "索引模式 ID",
|
||||
"data.search.searchSource.indexPatternLabel": "索引模式",
|
||||
"data.search.searchSource.noSearchStrategyRegisteredErrorMessageDescription": "无法为该搜索请求找到搜索策略",
|
||||
"data.search.searchSource.noSearchStrategyRegisteredErrorMessageTitle": "未注册任何搜索策略",
|
||||
"data.search.searchSource.queryTimeDescription": "处理查询所花费的时间。不包括发送请求或在浏览器中解析它的时间。",
|
||||
"data.search.searchSource.queryTimeLabel": "查询时间",
|
||||
"data.search.searchSource.queryTimeValue": "{queryTime}ms",
|
||||
"data.search.searchSource.requestTimeDescription": "请求从浏览器到 Elasticsearch 以及返回的时间。不包括请求在队列中等候的时间。",
|
||||
"data.search.searchSource.requestTimeLabel": "请求时间",
|
||||
"data.search.searchSource.requestTimeValue": "{requestTime}ms",
|
||||
"data.filter.filterEditor.operatorSelectPlaceholderSelect": "选择",
|
||||
"data.filter.filterEditor.operatorSelectPlaceholderWaiting": "正在等候",
|
||||
"data.filter.filterEditor.rangeInputLabel": "范围",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue