Move ui/courier into data shim plugin (#52359) (#53743)

This commit is contained in:
Luke Elmers 2019-12-22 17:17:00 -07:00 committed by GitHub
parent 052e3472b1
commit 4d6eeff3e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
84 changed files with 1035 additions and 712 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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';
/**

View file

@ -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"',

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { IUiSettingsClient } from '../../../../../core/public';
import { IUiSettingsClient } from '../../../../../../core/public';
const sessionId = Date.now();

View file

@ -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(),

View file

@ -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.',
}

View file

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

View file

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

View 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() {}
}

View 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 './search_source';

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,78 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { 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(),
};
}

View file

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

View file

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

View file

@ -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`,
}

View file

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

View file

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

View file

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

View file

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

View 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';

View file

@ -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.',

View 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';

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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
) => {

View file

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

View file

@ -1 +1 @@
@import './fetch/components/shard_failure_modal';
@import '../../../core_plugins/data/public/search/fetch/components/shard_failure_modal';

View file

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

View file

@ -17,4 +17,4 @@
* under the License.
*/
export * from './search_source';
export { SearchSource, ISearchSource } from '../index';

View file

@ -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(),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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": "範囲",

View file

@ -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": "范围",