Remove data_enhanced plugin (#122075)

Code moved into `data` plugin
This commit is contained in:
Anton Dosov 2022-04-29 16:43:59 +02:00 committed by GitHub
parent 6e6c9614f5
commit e603d92552
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
156 changed files with 1878 additions and 2315 deletions

View file

@ -20,7 +20,7 @@ const STORYBOOKS = [
'custom_integrations',
'dashboard_enhanced',
'dashboard',
'data_enhanced',
'data',
'embeddable',
'expression_error',
'expression_image',

1
.github/CODEOWNERS vendored
View file

@ -79,7 +79,6 @@
/src/plugins/inspector/ @elastic/kibana-app-services
/src/plugins/unified_search/ @elastic/kibana-app-services
/x-pack/examples/ui_actions_enhanced_examples/ @elastic/kibana-app-services
/x-pack/plugins/data_enhanced/ @elastic/kibana-app-services
/x-pack/plugins/embeddable_enhanced/ @elastic/kibana-app-services
/x-pack/plugins/ui_actions_enhanced/ @elastic/kibana-app-services
/x-pack/plugins/runtime_fields @elastic/kibana-app-services

View file

@ -259,7 +259,7 @@ export const myEnhancedSearchStrategyProvider = (
await ese.cancel(id, options, deps);
},
extend: async (id, keepAlive, options, deps) => {
// async search results are not stored indefinitely. By default, they expire after 7 days (or as defined by xpack.data_enhanced.search.sessions.defaultExpiration setting in kibana.yml).
// async search results are not stored indefinitely. By default, they expire after 7 days (or as defined by data.search.sessions.defaultExpiration setting in kibana.yml).
// call the extend method of the async strategy you are using or implement your own extend function.
await ese.extend(id, options, deps);
},

View file

@ -412,10 +412,6 @@ The plugin exposes the static DefaultEditorController class to consume.
|Adds drilldown capabilities to dashboard. Owned by the Kibana App team.
|{kib-repo}blob/{branch}/x-pack/plugins/data_enhanced/README.md[dataEnhanced]
|The data_enhanced plugin is the x-pack counterpart to the src/plguins/data plugin.
|{kib-repo}blob/{branch}/x-pack/plugins/data_visualizer/README.md[dataVisualizer]
|The data_visualizer plugin enables you to explore the fields in your data.

View file

@ -104,8 +104,7 @@ pageLoadAssetSize:
fieldFormats: 65209
kibanaReact: 74422
share: 71239
uiActions: 35121
dataEnhanced: 24980
uiActions: 35121
embeddable: 87309
embeddableEnhanced: 22107
uiActionsEnhanced: 38494

View file

@ -40,6 +40,13 @@ kibana_vars=(
csp.report_to
data.autocomplete.valueSuggestions.terminateAfter
data.autocomplete.valueSuggestions.timeout
data.search.sessions.defaultExpiration
data.search.sessions.enabled
data.search.sessions.maxUpdateRetries
data.search.sessions.notTouchedInProgressTimeout
data.search.sessions.notTouchedTimeout
data.search.sessions.pageSize
data.search.sessions.trackingInterval
unifiedSearch.autocomplete.valueSuggestions.terminateAfter
unifiedSearch.autocomplete.valueSuggestions.timeout
unifiedSearch.autocomplete.querySuggestions.enabled

View file

@ -17,7 +17,7 @@ export const storybookAliases = {
custom_integrations: 'src/plugins/custom_integrations/storybook',
dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook',
dashboard: 'src/plugins/dashboard/.storybook',
data_enhanced: 'x-pack/plugins/data_enhanced/.storybook',
data: 'src/plugins/data/.storybook',
discover: 'src/plugins/discover/.storybook',
embeddable: 'src/plugins/embeddable/.storybook',
expression_error: 'src/plugins/expression_error/.storybook',

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
module.exports = require('@kbn/storybook').defaultConfig;

View file

@ -8,21 +8,6 @@
import { schema, TypeOf } from '@kbn/config-schema';
export const configSchema = schema.object({
search: schema.object({
aggs: schema.object({
shardDelay: schema.object({
// Whether or not to register the shard_delay (which is only available in snapshot versions
// of Elasticsearch) agg type/expression function to make it available in the UI for either
// functional or manual testing
enabled: schema.boolean({ defaultValue: false }),
}),
}),
}),
});
export type ConfigSchema = TypeOf<typeof configSchema>;
export const searchSessionsConfigSchema = schema.object({
/**
* Turns the feature on \ off (incl. removing indicator and management screens)
@ -90,4 +75,20 @@ export const searchSessionsConfigSchema = schema.object({
}),
});
export const configSchema = schema.object({
search: schema.object({
aggs: schema.object({
shardDelay: schema.object({
// Whether or not to register the shard_delay (which is only available in snapshot versions
// of Elasticsearch) agg type/expression function to make it available in the UI for either
// functional or manual testing
enabled: schema.boolean({ defaultValue: false }),
}),
}),
sessions: searchSessionsConfigSchema,
}),
});
export type ConfigSchema = TypeOf<typeof configSchema>;
export type SearchSessionsConfigSchema = TypeOf<typeof searchSessionsConfigSchema>;

View file

@ -3,7 +3,17 @@
"version": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["bfetch", "expressions", "uiActions", "share", "inspector", "fieldFormats", "dataViews"],
"requiredPlugins": [
"bfetch",
"expressions",
"uiActions",
"share",
"inspector",
"fieldFormats",
"dataViews",
"screenshotMode",
"management"
],
"serviceFolders": ["search", "query", "ui"],
"optionalPlugins": ["usageCollection", "taskManager", "security"],
"extraPublicDirs": ["common"],

View file

@ -73,6 +73,7 @@ export class DataPublicPlugin
usageCollection,
inspector,
fieldFormats,
management,
}: DataSetupDependencies
): DataPublicPluginSetup {
const startServices = createStartServicesGetter(core.getStartServices);
@ -84,6 +85,7 @@ export class DataPublicPlugin
usageCollection,
expressions,
nowProvider: this.nowProvider,
management,
});
const queryService = this.queryService.setup({
@ -117,7 +119,7 @@ export class DataPublicPlugin
public start(
core: CoreStart,
{ uiActions, fieldFormats, dataViews }: DataStartDependencies
{ uiActions, fieldFormats, dataViews, screenshotMode }: DataStartDependencies
): DataPublicPluginStart {
const { uiSettings, notifications, overlays } = core;
setNotifications(notifications);
@ -131,7 +133,11 @@ export class DataPublicPlugin
uiSettings,
});
const search = this.searchService.start(core, { fieldFormats, indexPatterns: dataViews });
const search = this.searchService.start(core, {
fieldFormats,
indexPatterns: dataViews,
screenshotMode,
});
setSearchService(search);
uiActions.addTriggerAction(

View file

@ -12,6 +12,8 @@ import { CoreSetup, CoreStart } from '@kbn/core/public';
import { SearchService, SearchServiceSetupDependencies } from './search_service';
import { bfetchPluginMock } from '@kbn/bfetch-plugin/public/mocks';
import { managementPluginMock } from '@kbn/management-plugin/public/mocks';
import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks';
describe('Search service', () => {
let searchService: SearchService;
@ -19,7 +21,7 @@ describe('Search service', () => {
let mockCoreStart: MockedKeys<CoreStart>;
const initializerContext = coreMock.createPluginInitializerContext();
initializerContext.config.get = jest.fn().mockReturnValue({
search: { aggs: { shardDelay: { enabled: false } } },
search: { aggs: { shardDelay: { enabled: false } }, sessions: { enabled: true } },
});
beforeEach(() => {
@ -35,6 +37,7 @@ describe('Search service', () => {
packageInfo: { version: '8' },
bfetch,
expressions: { registerFunction: jest.fn(), registerType: jest.fn() },
management: managementPluginMock.createSetupContract(),
} as unknown as SearchServiceSetupDependencies);
expect(setup).toHaveProperty('aggs');
expect(setup).toHaveProperty('usageCollector');
@ -45,9 +48,18 @@ describe('Search service', () => {
describe('start()', () => {
it('exposes proper contract', async () => {
const bfetch = bfetchPluginMock.createSetupContract();
searchService.setup(mockCoreSetup, {
packageInfo: { version: '8' },
bfetch,
expressions: { registerFunction: jest.fn(), registerType: jest.fn() },
management: managementPluginMock.createSetupContract(),
} as unknown as SearchServiceSetupDependencies);
const start = searchService.start(mockCoreStart, {
fieldFormats: {},
indexPatterns: {},
screenshotMode: screenshotModePluginMock.createStartContract(),
} as any);
expect(start).toHaveProperty('aggs');
expect(start).toHaveProperty('search');

View file

@ -7,62 +7,71 @@
*/
import {
Plugin,
CoreSetup,
CoreStart,
Plugin,
PluginInitializerContext,
StartServicesAccessor,
} from '@kbn/core/public';
import { BehaviorSubject } from 'rxjs';
import React from 'react';
import moment from 'moment';
import { BfetchPublicSetup } from '@kbn/bfetch-plugin/public';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { ExpressionsSetup } from '@kbn/expressions-plugin/public';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { ScreenshotModePluginStart } from '@kbn/screenshot-mode-plugin/public';
import { ManagementSetup } from '@kbn/management-plugin/public';
import type { ISearchSetup, ISearchStart } from './types';
import { handleResponse } from './fetch';
import {
kibana,
kibanaContext,
ISearchGeneric,
SearchSourceDependencies,
SearchSourceService,
extendedBoundsFunction,
ipRangeFunction,
kibanaTimerangeFunction,
luceneFunction,
kqlFunction,
fieldFunction,
numericalRangeFunction,
rangeFunction,
cidrFunction,
dateRangeFunction,
esRawResponse,
existsFilterFunction,
extendedBoundsFunction,
fieldFunction,
geoBoundingBoxFunction,
geoPointFunction,
ipRangeFunction,
ISearchGeneric,
kibana,
kibanaContext,
kibanaFilterFunction,
kibanaTimerangeFunction,
kqlFunction,
luceneFunction,
numericalRangeFunction,
phraseFilterFunction,
queryFilterFunction,
rangeFilterFunction,
rangeFunction,
removeFilterFunction,
SearchSourceDependencies,
SearchSourceService,
selectFilterFunction,
kibanaFilterFunction,
phraseFilterFunction,
esRawResponse,
eqlRawResponse,
} from '../../common/search';
import { AggsService, AggsStartDependencies } from './aggs';
import { IKibanaSearchResponse, IndexPatternsContract, SearchRequest } from '..';
import { ISearchInterceptor, SearchInterceptor } from './search_interceptor';
import { SearchUsageCollector, createUsageCollector } from './collectors';
import { createUsageCollector, SearchUsageCollector } from './collectors';
import { getEsaggs, getEsdsl, getEql } from './expressions';
import { ISessionsClient, ISessionService, SessionsClient, SessionService } from './session';
import { ConfigSchema } from '../../config';
import {
SHARD_DELAY_AGG_NAME,
getShardDelayBucketAgg,
SHARD_DELAY_AGG_NAME,
} from '../../common/search/aggs/buckets/shard_delay';
import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn';
import { DataPublicPluginStart, DataStartDependencies } from '../types';
import { NowProviderInternalContract } from '../now_provider';
import { getKibanaContext } from './expressions/kibana_context';
import { createConnectedSearchSessionIndicator } from './session/session_indicator';
import { registerSearchSessionsMgmt } from './session/sessions_mgmt';
/** @internal */
export interface SearchServiceSetupDependencies {
@ -70,12 +79,14 @@ export interface SearchServiceSetupDependencies {
expressions: ExpressionsSetup;
usageCollection?: UsageCollectionSetup;
nowProvider: NowProviderInternalContract;
management: ManagementSetup;
}
/** @internal */
export interface SearchServiceStartDependencies {
fieldFormats: AggsStartDependencies['fieldFormats'];
indexPatterns: IndexPatternsContract;
screenshotMode: ScreenshotModePluginStart;
}
export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
@ -89,9 +100,16 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
constructor(private initializerContext: PluginInitializerContext<ConfigSchema>) {}
public setup(
{ http, getStartServices, notifications, uiSettings, executionContext, theme }: CoreSetup,
{ bfetch, expressions, usageCollection, nowProvider }: SearchServiceSetupDependencies
core: CoreSetup,
{
bfetch,
expressions,
usageCollection,
nowProvider,
management,
}: SearchServiceSetupDependencies
): ISearchSetup {
const { http, getStartServices, notifications, uiSettings, executionContext, theme } = core;
this.usageCollector = createUsageCollector(getStartServices, usageCollection);
this.sessionsClient = new SessionsClient({ http });
@ -173,6 +191,21 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
expressions.registerFunction(aggShardDelay);
}
const config = this.initializerContext.config.get<ConfigSchema>();
if (config.search.sessions.enabled) {
const sessionsConfig = config.search.sessions;
registerSearchSessionsMgmt(
core as CoreSetup<DataStartDependencies>,
{
searchUsageCollector: this.usageCollector!,
sessionsClient: this.sessionsClient,
management,
},
sessionsConfig,
this.initializerContext.env.packageInfo.version
);
}
return {
aggs,
usageCollector: this.usageCollector!,
@ -182,8 +215,8 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
}
public start(
{ http, theme, uiSettings }: CoreStart,
{ fieldFormats, indexPatterns }: SearchServiceStartDependencies
{ http, theme, uiSettings, chrome, application }: CoreStart,
{ fieldFormats, indexPatterns, screenshotMode }: SearchServiceStartDependencies
): ISearchStart {
const search = ((request, options = {}) => {
return this.searchInterceptor.search(request, options);
@ -199,6 +232,28 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
handleResponse(request, response, theme),
};
const config = this.initializerContext.config.get();
if (config.search.sessions.enabled) {
chrome.setBreadcrumbsAppendExtension({
content: toMountPoint(
React.createElement(
createConnectedSearchSessionIndicator({
sessionService: this.sessionService,
application,
basePath: http.basePath,
storage: new Storage(window.localStorage),
disableSaveAfterSessionCompletesTimeout: moment
.duration(config.search.sessions.notTouchedTimeout)
.asMilliseconds(),
usageCollector: this.usageCollector,
tourDisabled: screenshotMode.isScreenshotMode(),
})
),
{ theme$: theme.theme$ }
),
});
}
return {
aggs: this.aggsService.start({ fieldFormats, uiSettings, indexPatterns }),
search,

View file

@ -1,15 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { ReactNode } from 'react';
import { StubBrowserStorage } from '@kbn/test-jest-helpers';
import { render, waitFor, screen, act } from '@testing-library/react';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { dataPluginMock } from '../../../../mocks';
import { createConnectedSearchSessionIndicator } from './connected_search_session_indicator';
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
@ -19,12 +20,12 @@ import {
SearchSessionState,
SearchUsageCollector,
TimefilterContract,
} from '@kbn/data-plugin/public';
} from '../../../..';
import { coreMock } from '@kbn/core/public/mocks';
import { TOUR_RESTORE_STEP_KEY, TOUR_TAKING_TOO_LONG_STEP_KEY } from './search_session_tour';
import userEvent from '@testing-library/user-event';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { createSearchUsageCollectorMock } from '@kbn/data-plugin/public/search/collectors/mocks';
import { createSearchUsageCollectorMock } from '../../../collectors/mocks';
const coreStart = coreMock.createStart();
const application = coreStart.application;

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { useCallback, useEffect, useState } from 'react';
@ -10,12 +11,14 @@ import { debounce, distinctUntilChanged, mapTo, switchMap, tap } from 'rxjs/oper
import { merge, of, timer } from 'rxjs';
import useObservable from 'react-use/lib/useObservable';
import { i18n } from '@kbn/i18n';
import { ISessionService, SearchSessionState, SearchUsageCollector } from '@kbn/data-plugin/public';
import { RedirectAppLinks } from '@kbn/kibana-react-plugin/public';
import { ApplicationStart, IBasePath } from '@kbn/core/public';
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
import { ApplicationStart, IBasePath } from '@kbn/core/public';
import { SearchSessionIndicator, SearchSessionIndicatorRef } from '../search_session_indicator';
import { useSearchSessionTour } from './search_session_tour';
import { SearchUsageCollector } from '../../../collectors';
import { ISessionService } from '../../session_service';
import { SearchSessionState } from '../../search_session_state';
export interface SearchSessionIndicatorDeps {
sessionService: ISessionService;
@ -82,7 +85,7 @@ export const createConnectedSearchSessionIndicator = ({
if (disableSaveAfterSessionCompleteTimedOut) {
saveDisabled = true;
saveDisabledReasonText = i18n.translate(
'xpack.data.searchSessionIndicator.disabledDueToTimeoutMessage',
'data.searchSessionIndicator.disabledDueToTimeoutMessage',
{
defaultMessage: 'Search session results expired.',
}
@ -99,7 +102,7 @@ export const createConnectedSearchSessionIndicator = ({
if (!sessionService.hasAccess()) {
managementDisabled = saveDisabled = true;
managementDisabledReasonText = saveDisabledReasonText = i18n.translate(
'xpack.data.searchSessionIndicator.disabledDueToDisabledGloballyMessage',
'data.searchSessionIndicator.disabledDueToDisabledGloballyMessage',
{
defaultMessage: "You don't have permissions to manage search sessions",
}

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export type { SearchSessionIndicatorDeps } from './connected_search_session_indicator';

View file

@ -1,15 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { useCallback, useEffect } from 'react';
import { once } from 'lodash';
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
import { SearchSessionState, SearchUsageCollector } from '@kbn/data-plugin/public';
import { SearchSessionIndicatorRef } from '../search_session_indicator';
import { SearchSessionState } from '../../search_session_state';
import { SearchUsageCollector } from '../../../collectors';
const TOUR_TAKING_TOO_LONG_TIMEOUT = 10000;
export const TOUR_TAKING_TOO_LONG_STEP_KEY = `data.searchSession.tour.takingTooLong`;

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export enum SearchSessionViewState {

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './connected_search_session_indicator';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './search_session_name';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './search_session_name';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { useEffect } from 'react';
@ -45,7 +46,7 @@ export const SearchSessionName: React.FC<SearchSessionNameProps> = ({ name, edit
autoFocus={true}
iconType={'pencil'}
color={'text'}
aria-label={i18n.translate('xpack.data.searchSessionName.editAriaLabelText', {
aria-label={i18n.translate('data.searchSessionName.editAriaLabelText', {
defaultMessage: 'Edit search session name',
})}
data-test-subj={'searchSessionNameEdit'}
@ -56,14 +57,14 @@ export const SearchSessionName: React.FC<SearchSessionNameProps> = ({ name, edit
<EuiFieldText
autoFocus={true}
compressed={true}
placeholder={i18n.translate('xpack.data.searchSessionName.placeholderText', {
placeholder={i18n.translate('data.searchSessionName.placeholderText', {
defaultMessage: 'Enter a name for the search session',
})}
value={newName}
onChange={(e) => {
setNewName(e.target.value);
}}
aria-label={i18n.translate('xpack.data.searchSessionName.ariaLabelText', {
aria-label={i18n.translate('data.searchSessionName.ariaLabelText', {
defaultMessage: 'Search session name',
})}
data-test-subj={'searchSessionNameInput'}
@ -89,10 +90,7 @@ export const SearchSessionName: React.FC<SearchSessionNameProps> = ({ name, edit
isLoading={isSaving}
data-test-subj={'searchSessionNameSave'}
>
<FormattedMessage
id="xpack.data.searchSessionName.saveButtonText"
defaultMessage="Save"
/>
<FormattedMessage id="data.searchSessionName.saveButtonText" defaultMessage="Save" />
</EuiButtonEmpty>
}
/>

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiDelayRender, EuiLoadingSpinner } from '@elastic/eui';

View file

@ -1,14 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { storiesOf } from '@storybook/react';
import { SearchSessionState } from '@kbn/data-plugin/public';
import { SearchSessionIndicator } from './search_session_indicator';
import { SearchSessionState } from '../../search_session_state';
storiesOf('components/SearchSessionIndicator', module).add('default', () => {
const [searchSessionName, setSearchSessionName] = React.useState('Discover session');

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { ReactNode } from 'react';
@ -10,7 +11,7 @@ import { screen, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { SearchSessionIndicator } from './search_session_indicator';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { SearchSessionState } from '@kbn/data-plugin/public';
import { SearchSessionState } from '../../../..';
function Container({ children }: { children?: ReactNode }) {
return <IntlProvider locale="en">{children}</IntlProvider>;

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { useCallback, useImperativeHandle } from 'react';
@ -22,10 +23,10 @@ import {
import moment from 'moment';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { SearchSessionState } from '@kbn/data-plugin/public';
import { CheckInEmptyCircle, PartialClock } from './custom_icons';
import './search_session_indicator.scss';
import { SearchSessionName } from './components';
import { SearchSessionState } from '../../search_session_state';
export interface SearchSessionIndicatorProps {
state: SearchSessionState;
@ -59,7 +60,7 @@ const CancelButton = ({ onCancel = () => {}, buttonProps = {} }: ActionButtonPro
{...buttonProps}
>
<FormattedMessage
id="xpack.data.searchSessionIndicator.cancelButtonText"
id="data.searchSessionIndicator.cancelButtonText"
defaultMessage="Stop session"
/>
</EuiButtonEmpty>
@ -79,7 +80,7 @@ const ContinueInBackgroundButton = ({
{...buttonProps}
>
<FormattedMessage
id="xpack.data.searchSessionIndicator.continueInBackgroundButtonText"
id="data.searchSessionIndicator.continueInBackgroundButtonText"
defaultMessage="Save session"
/>
</EuiButtonEmpty>
@ -103,7 +104,7 @@ const ViewAllSearchSessionsButton = ({
{...buttonProps}
>
<FormattedMessage
id="xpack.data.searchSessionIndicator.viewSearchSessionsLinkText"
id="data.searchSessionIndicator.viewSearchSessionsLinkText"
defaultMessage="Manage sessions"
/>
</EuiButtonEmpty>
@ -124,7 +125,7 @@ const SaveButton = ({
{...buttonProps}
>
<FormattedMessage
id="xpack.data.searchSessionIndicator.saveButtonText"
id="data.searchSessionIndicator.saveButtonText"
defaultMessage="Save session"
/>
</EuiButtonEmpty>
@ -150,24 +151,22 @@ const searchSessionIndicatorViewStateToProps: {
button: {
color: 'text',
iconType: PartialClock,
'aria-label': i18n.translate(
'xpack.data.searchSessionIndicator.loadingResultsIconAriaLabel',
{ defaultMessage: 'Search session loading' }
),
tooltipText: i18n.translate(
'xpack.data.searchSessionIndicator.loadingResultsIconTooltipText',
{ defaultMessage: 'Search session loading' }
),
'aria-label': i18n.translate('data.searchSessionIndicator.loadingResultsIconAriaLabel', {
defaultMessage: 'Search session loading',
}),
tooltipText: i18n.translate('data.searchSessionIndicator.loadingResultsIconTooltipText', {
defaultMessage: 'Search session loading',
}),
},
popover: {
title: i18n.translate('xpack.data.searchSessionIndicator.loadingResultsTitle', {
title: i18n.translate('data.searchSessionIndicator.loadingResultsTitle', {
defaultMessage: 'Your search is taking a while...',
}),
description: i18n.translate('xpack.data.searchSessionIndicator.loadingResultsDescription', {
description: i18n.translate('data.searchSessionIndicator.loadingResultsDescription', {
defaultMessage: 'Save your session, continue your work, and return to completed results',
}),
whenText: (props: SearchSessionIndicatorProps) =>
i18n.translate('xpack.data.searchSessionIndicator.loadingResultsWhenText', {
i18n.translate('data.searchSessionIndicator.loadingResultsWhenText', {
defaultMessage: 'Started {when}',
values: {
when: props.startedTime ? moment(props.startedTime).format(`L @ LTS`) : '',
@ -181,28 +180,22 @@ const searchSessionIndicatorViewStateToProps: {
button: {
color: 'text',
iconType: 'check',
'aria-label': i18n.translate('xpack.data.searchSessionIndicator.resultsLoadedIconAriaLabel', {
'aria-label': i18n.translate('data.searchSessionIndicator.resultsLoadedIconAriaLabel', {
defaultMessage: 'Search session complete',
}),
tooltipText: i18n.translate('data.searchSessionIndicator.resultsLoadedIconTooltipText', {
defaultMessage: 'Search session complete',
}),
tooltipText: i18n.translate(
'xpack.data.searchSessionIndicator.resultsLoadedIconTooltipText',
{
defaultMessage: 'Search session complete',
}
),
},
popover: {
title: i18n.translate('xpack.data.searchSessionIndicator.resultsLoadedText', {
title: i18n.translate('data.searchSessionIndicator.resultsLoadedText', {
defaultMessage: 'Search session complete',
}),
description: i18n.translate(
'xpack.data.searchSessionIndicator.resultsLoadedDescriptionText',
{
defaultMessage: 'Save your session and return to it later',
}
),
description: i18n.translate('data.searchSessionIndicator.resultsLoadedDescriptionText', {
defaultMessage: 'Save your session and return to it later',
}),
whenText: (props: SearchSessionIndicatorProps) =>
i18n.translate('xpack.data.searchSessionIndicator.resultsLoadedWhenText', {
i18n.translate('data.searchSessionIndicator.resultsLoadedWhenText', {
defaultMessage: 'Completed {when}',
values: {
when: props.completedTime ? moment(props.completedTime).format(`L @ LTS`) : '',
@ -216,30 +209,30 @@ const searchSessionIndicatorViewStateToProps: {
button: {
iconType: EuiLoadingSpinner,
'aria-label': i18n.translate(
'xpack.data.searchSessionIndicator.loadingInTheBackgroundIconAriaLabel',
'data.searchSessionIndicator.loadingInTheBackgroundIconAriaLabel',
{
defaultMessage: 'Saved session in progress',
}
),
tooltipText: i18n.translate(
'xpack.data.searchSessionIndicator.loadingInTheBackgroundIconTooltipText',
'data.searchSessionIndicator.loadingInTheBackgroundIconTooltipText',
{
defaultMessage: 'Saved session in progress',
}
),
},
popover: {
title: i18n.translate('xpack.data.searchSessionIndicator.loadingInTheBackgroundTitleText', {
title: i18n.translate('data.searchSessionIndicator.loadingInTheBackgroundTitleText', {
defaultMessage: 'Saved session in progress',
}),
description: i18n.translate(
'xpack.data.searchSessionIndicator.loadingInTheBackgroundDescriptionText',
'data.searchSessionIndicator.loadingInTheBackgroundDescriptionText',
{
defaultMessage: 'You can return to completed results from Management',
}
),
whenText: (props: SearchSessionIndicatorProps) =>
i18n.translate('xpack.data.searchSessionIndicator.loadingInTheBackgroundWhenText', {
i18n.translate('data.searchSessionIndicator.loadingInTheBackgroundWhenText', {
defaultMessage: 'Started {when}',
values: {
when: props.startedTime ? moment(props.startedTime).format(`L @ LTS`) : '',
@ -254,33 +247,30 @@ const searchSessionIndicatorViewStateToProps: {
color: 'success',
iconType: 'checkInCircleFilled',
'aria-label': i18n.translate(
'xpack.data.searchSessionIndicator.resultLoadedInTheBackgroundIconAriaLabel',
'data.searchSessionIndicator.resultLoadedInTheBackgroundIconAriaLabel',
{
defaultMessage: 'Saved session complete',
}
),
tooltipText: i18n.translate(
'xpack.data.searchSessionIndicator.resultLoadedInTheBackgroundIconTooltipText',
'data.searchSessionIndicator.resultLoadedInTheBackgroundIconTooltipText',
{
defaultMessage: 'Saved session complete',
}
),
},
popover: {
title: i18n.translate(
'xpack.data.searchSessionIndicator.resultLoadedInTheBackgroundTitleText',
{
defaultMessage: 'Search session saved',
}
),
title: i18n.translate('data.searchSessionIndicator.resultLoadedInTheBackgroundTitleText', {
defaultMessage: 'Search session saved',
}),
description: i18n.translate(
'xpack.data.searchSessionIndicator.resultLoadedInTheBackgroundDescriptionText',
'data.searchSessionIndicator.resultLoadedInTheBackgroundDescriptionText',
{
defaultMessage: 'You can return to these results from Management',
}
),
whenText: (props: SearchSessionIndicatorProps) =>
i18n.translate('xpack.data.searchSessionIndicator.resultLoadedInTheBackgroundWhenText', {
i18n.translate('data.searchSessionIndicator.resultLoadedInTheBackgroundWhenText', {
defaultMessage: 'Completed {when}',
values: {
when: props.completedTime ? moment(props.completedTime).format(`L @ LTS`) : '',
@ -293,26 +283,23 @@ const searchSessionIndicatorViewStateToProps: {
button: {
color: 'success',
iconType: CheckInEmptyCircle,
'aria-label': i18n.translate(
'xpack.data.searchSessionIndicator.restoredResultsIconAriaLabel',
{
defaultMessage: 'Saved session restored',
}
),
tooltipText: i18n.translate('xpack.data.searchSessionIndicator.restoredResultsTooltipText', {
'aria-label': i18n.translate('data.searchSessionIndicator.restoredResultsIconAriaLabel', {
defaultMessage: 'Saved session restored',
}),
tooltipText: i18n.translate('data.searchSessionIndicator.restoredResultsTooltipText', {
defaultMessage: 'Search session restored',
}),
},
popover: {
title: i18n.translate('xpack.data.searchSessionIndicator.restoredTitleText', {
title: i18n.translate('data.searchSessionIndicator.restoredTitleText', {
defaultMessage: 'Search session restored',
}),
description: i18n.translate('xpack.data.searchSessionIndicator.restoredDescriptionText', {
description: i18n.translate('data.searchSessionIndicator.restoredDescriptionText', {
defaultMessage:
'You are viewing cached data from a specific time range. Changing the time range or filters will re-run the session',
}),
whenText: (props: SearchSessionIndicatorProps) =>
i18n.translate('xpack.data.searchSessionIndicator.restoredWhenText', {
i18n.translate('data.searchSessionIndicator.restoredWhenText', {
defaultMessage: 'Completed {when}',
values: {
when: props.completedTime ? moment(props.completedTime).format(`L @ LTS`) : '',
@ -325,22 +312,22 @@ const searchSessionIndicatorViewStateToProps: {
button: {
color: 'danger',
iconType: 'alert',
'aria-label': i18n.translate('xpack.data.searchSessionIndicator.canceledIconAriaLabel', {
'aria-label': i18n.translate('data.searchSessionIndicator.canceledIconAriaLabel', {
defaultMessage: 'Search session stopped',
}),
tooltipText: i18n.translate('xpack.data.searchSessionIndicator.canceledTooltipText', {
tooltipText: i18n.translate('data.searchSessionIndicator.canceledTooltipText', {
defaultMessage: 'Search session stopped',
}),
},
popover: {
title: i18n.translate('xpack.data.searchSessionIndicator.canceledTitleText', {
title: i18n.translate('data.searchSessionIndicator.canceledTitleText', {
defaultMessage: 'Search session stopped',
}),
description: i18n.translate('xpack.data.searchSessionIndicator.canceledDescriptionText', {
description: i18n.translate('data.searchSessionIndicator.canceledDescriptionText', {
defaultMessage: 'You are viewing incomplete data',
}),
whenText: (props: SearchSessionIndicatorProps) =>
i18n.translate('xpack.data.searchSessionIndicator.canceledWhenText', {
i18n.translate('data.searchSessionIndicator.canceledWhenText', {
defaultMessage: 'Stopped {when}',
values: {
when: props.canceledTime ? moment(props.canceledTime).format(`L @ LTS`) : '',

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { ReactNode } from 'react';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { CoreSetup } from '@kbn/core/public';
@ -11,24 +12,24 @@ import type {
AppDependencies,
IManagementSectionsPluginsSetup,
IManagementSectionsPluginsStart,
SessionsConfigSchema,
} from '..';
import { APP } from '..';
import { SearchSessionsMgmtAPI } from '../lib/api';
import { AsyncSearchIntroDocumentation } from '../lib/documentation';
import { renderApp } from './render';
import { SearchSessionsConfigSchema } from '../../../../../config';
export class SearchSessionsMgmtApp {
constructor(
private coreSetup: CoreSetup<IManagementSectionsPluginsStart>,
private config: SessionsConfigSchema,
private setupDeps: IManagementSectionsPluginsSetup,
private config: SearchSessionsConfigSchema,
private kibanaVersion: string,
private params: ManagementAppMountParams,
private pluginsSetup: IManagementSectionsPluginsSetup
private params: ManagementAppMountParams
) {}
public async mountManagementSection() {
const { coreSetup, params, pluginsSetup } = this;
const { coreSetup, params, setupDeps } = this;
const [coreStart, pluginsStart] = await coreSetup.getStartServices();
const {
@ -40,24 +41,21 @@ export class SearchSessionsMgmtApp {
uiSettings,
application,
} = coreStart;
const { data, share } = pluginsStart;
const pluginName = APP.getI18nName();
docTitle.change(pluginName);
params.setBreadcrumbs([{ text: pluginName }]);
this.params.setBreadcrumbs([{ text: pluginName }]);
const { sessionsClient } = data.search;
const api = new SearchSessionsMgmtAPI(sessionsClient, this.config, {
const api = new SearchSessionsMgmtAPI(setupDeps.sessionsClient, this.config, {
notifications,
locators: share.url.locators,
locators: pluginsStart.share.url.locators,
application,
usageCollector: pluginsSetup.data.search.usageCollector,
usageCollector: setupDeps.searchUsageCollector,
});
const documentation = new AsyncSearchIntroDocumentation(docLinks);
const dependencies: AppDependencies = {
plugins: pluginsSetup,
config: this.config,
documentation,
core: coreStart,
@ -65,8 +63,9 @@ export class SearchSessionsMgmtApp {
http,
i18n,
uiSettings,
share,
share: pluginsStart.share,
kibanaVersion: this.kibanaVersion,
searchUsageCollector: setupDeps.searchUsageCollector,
};
const { element } = params;

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiConfirmModal } from '@elastic/eui';
@ -26,16 +27,16 @@ const DeleteConfirm = (props: DeleteButtonProps & { onActionDismiss: OnActionDis
const { name, id } = searchSession;
const [isLoading, setIsLoading] = useState(false);
const title = i18n.translate('xpack.data.mgmt.searchSessions.cancelModal.title', {
const title = i18n.translate('data.mgmt.searchSessions.cancelModal.title', {
defaultMessage: 'Delete search session',
});
const confirm = i18n.translate('xpack.data.mgmt.searchSessions.cancelModal.deleteButton', {
const confirm = i18n.translate('data.mgmt.searchSessions.cancelModal.deleteButton', {
defaultMessage: 'Delete',
});
const cancel = i18n.translate('xpack.data.mgmt.searchSessions.cancelModal.cancelButton', {
const cancel = i18n.translate('data.mgmt.searchSessions.cancelModal.cancelButton', {
defaultMessage: 'Cancel',
});
const message = i18n.translate('xpack.data.mgmt.searchSessions.cancelModal.message', {
const message = i18n.translate('data.mgmt.searchSessions.cancelModal.message', {
defaultMessage: `Deleting the search session \'{name}\' deletes all cached results.`,
values: {
name,
@ -68,9 +69,7 @@ export const createDeleteActionDescriptor = (
core: CoreStart
): IClickActionDescriptor => ({
iconType: 'crossInACircleFilled',
label: (
<FormattedMessage id="xpack.data.mgmt.searchSessions.actionDelete" defaultMessage="Delete" />
),
label: <FormattedMessage id="data.mgmt.searchSessions.actionDelete" defaultMessage="Delete" />,
onClick: async () => {
const ref = core.overlays.openModal(
toMountPoint(

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiConfirmModal } from '@elastic/eui';
@ -31,16 +32,16 @@ const ExtendConfirm = ({ ...props }: ExtendButtonProps & { onActionDismiss: OnAc
const newExpiration = moment(expires).add(extendByDuration);
const title = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.title', {
const title = i18n.translate('data.mgmt.searchSessions.extendModal.title', {
defaultMessage: 'Extend search session expiration',
});
const confirm = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.extendButton', {
const confirm = i18n.translate('data.mgmt.searchSessions.extendModal.extendButton', {
defaultMessage: 'Extend expiration',
});
const extend = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.dontExtendButton', {
const extend = i18n.translate('data.mgmt.searchSessions.extendModal.dontExtendButton', {
defaultMessage: 'Cancel',
});
const message = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.extendMessage', {
const message = i18n.translate('data.mgmt.searchSessions.extendModal.extendMessage', {
defaultMessage: "The search session '{name}' expiration would be extended until {newExpires}.",
values: {
name,
@ -75,9 +76,7 @@ export const createExtendActionDescriptor = (
core: CoreStart
): IClickActionDescriptor => ({
iconType: extendSessionIcon,
label: (
<FormattedMessage id="xpack.data.mgmt.searchSessions.actionExtend" defaultMessage="Extend" />
),
label: <FormattedMessage id="data.mgmt.searchSessions.actionExtend" defaultMessage="Extend" />,
onClick: async () => {
const ref = core.overlays.openModal(
toMountPoint(

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { CoreStart } from '@kbn/core/public';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { PopoverActionsMenu } from './popover_actions';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiFlyoutBody, EuiFlyoutHeader, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
@ -58,7 +59,7 @@ const InspectFlyout = ({ uiSettings, searchSession }: InspectFlyoutProps) => {
<EuiTitle size="m">
<h2 id="flyoutTitle">
<FormattedMessage
id="xpack.data.sessions.management.flyoutTitle"
id="data.sessions.management.flyoutTitle"
defaultMessage="Inspect search session"
/>
</h2>
@ -69,7 +70,7 @@ const InspectFlyout = ({ uiSettings, searchSession }: InspectFlyoutProps) => {
<EuiText size="xs">
<p>
<FormattedMessage
id="xpack.data.sessions.management.flyoutText"
id="data.sessions.management.flyoutText"
defaultMessage="Configuration for this search session"
/>
</p>
@ -90,7 +91,7 @@ export const createInspectActionDescriptor = (
iconType: 'document',
label: (
<FormattedMessage
id="xpack.data.mgmt.searchSessions.flyoutTitle"
id="data.mgmt.searchSessions.flyoutTitle"
aria-label="Inspect"
defaultMessage="Inspect"
/>

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import {
@ -49,12 +50,12 @@ export const PopoverActionsMenu = ({
const renderPopoverButton = () => (
<EuiToolTip
content={i18n.translate('xpack.data.mgmt.searchSessions.actions.tooltip.moreActions', {
content={i18n.translate('data.mgmt.searchSessions.actions.tooltip.moreActions', {
defaultMessage: 'More actions',
})}
>
<EuiButtonIcon
aria-label={i18n.translate('xpack.data.mgmt.searchSessions.ariaLabel.moreActions', {
aria-label={i18n.translate('data.mgmt.searchSessions.ariaLabel.moreActions', {
defaultMessage: 'More actions',
})}
color="text"

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import {
@ -41,22 +42,19 @@ const RenameDialog = ({
const [isLoading, setIsLoading] = useState(false);
const [newName, setNewName] = useState(originalName);
const title = i18n.translate('xpack.data.mgmt.searchSessions.renameModal.title', {
const title = i18n.translate('data.mgmt.searchSessions.renameModal.title', {
defaultMessage: 'Edit search session name',
});
const confirm = i18n.translate('xpack.data.mgmt.searchSessions.renameModal.renameButton', {
const confirm = i18n.translate('data.mgmt.searchSessions.renameModal.renameButton', {
defaultMessage: 'Save',
});
const cancel = i18n.translate('xpack.data.mgmt.searchSessions.renameModal.cancelButton', {
const cancel = i18n.translate('data.mgmt.searchSessions.renameModal.cancelButton', {
defaultMessage: 'Cancel',
});
const label = i18n.translate(
'xpack.data.mgmt.searchSessions.renameModal.searchSessionNameInputLabel',
{
defaultMessage: 'Search session name',
}
);
const label = i18n.translate('data.mgmt.searchSessions.renameModal.searchSessionNameInputLabel', {
defaultMessage: 'Search session name',
});
const isNewNameValid = newName && originalName !== newName;
@ -109,9 +107,7 @@ export const createRenameActionDescriptor = (
core: CoreStart
): IClickActionDescriptor => ({
iconType: 'pencil',
label: (
<FormattedMessage id="xpack.data.mgmt.searchSessions.actionRename" defaultMessage="Edit name" />
),
label: <FormattedMessage id="data.mgmt.searchSessions.actionRename" defaultMessage="Edit name" />,
onClick: async () => {
const ref = core.overlays.openModal(
toMountPoint(

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export type OnActionComplete = () => void;

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiLinkProps, EuiText, EuiTextProps } from '@elastic/eui';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { MockedKeys } from '@kbn/utility-types/jest';
@ -12,34 +13,30 @@ import moment from 'moment';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { coreMock } from '@kbn/core/public/mocks';
import { SessionsClient } from '@kbn/data-plugin/public/search';
import { IManagementSectionsPluginsSetup, SessionsConfigSchema } from '..';
import { SearchUsageCollector, SessionsClient } from '../../..';
import { SearchSessionsMgmtAPI } from '../lib/api';
import { AsyncSearchIntroDocumentation } from '../lib/documentation';
import { LocaleWrapper } from '../__mocks__';
import { SearchSessionsMgmtMain } from './main';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { managementPluginMock } from '@kbn/management-plugin/public/mocks';
import { SharePluginStart } from '@kbn/share-plugin/public';
import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
import { SearchSessionsConfigSchema } from '../../../../../config';
import { createSearchUsageCollectorMock } from '../../../collectors/mocks';
let mockCoreSetup: MockedKeys<CoreSetup>;
let mockCoreStart: MockedKeys<CoreStart>;
let mockShareStart: jest.Mocked<SharePluginStart>;
let mockPluginsSetup: IManagementSectionsPluginsSetup;
let mockConfig: SessionsConfigSchema;
let mockConfig: SearchSessionsConfigSchema;
let sessionsClient: SessionsClient;
let api: SearchSessionsMgmtAPI;
let mockSearchUsageCollector: SearchUsageCollector;
describe('Background Search Session Management Main', () => {
beforeEach(() => {
mockCoreSetup = coreMock.createSetup();
mockCoreStart = coreMock.createStart();
mockShareStart = sharePluginMock.createStartContract();
mockPluginsSetup = {
data: dataPluginMock.createSetupContract(),
management: managementPluginMock.createSetupContract(),
};
mockSearchUsageCollector = createSearchUsageCollectorMock();
mockConfig = {
defaultExpiration: moment.duration('7d'),
management: {
@ -80,13 +77,13 @@ describe('Background Search Session Management Main', () => {
<LocaleWrapper>
<SearchSessionsMgmtMain
core={mockCoreStart}
plugins={mockPluginsSetup}
api={api}
http={mockCoreSetup.http}
timezone="UTC"
documentation={new AsyncSearchIntroDocumentation(docLinks)}
config={mockConfig}
kibanaVersion={'8.0.0'}
searchUsageCollector={mockSearchUsageCollector}
/>
</LocaleWrapper>
);

View file

@ -1,19 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiButtonEmpty, EuiPageHeader, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { CoreStart, HttpStart } from '@kbn/core/public';
import React from 'react';
import type { SessionsConfigSchema } from '..';
import { IManagementSectionsPluginsSetup } from '..';
import type { SearchSessionsMgmtAPI } from '../lib/api';
import type { AsyncSearchIntroDocumentation } from '../lib/documentation';
import { SearchSessionsMgmtTable } from './table';
import { SearchSessionsConfigSchema } from '../../../../../config';
import { SearchUsageCollector } from '../../../collectors';
interface Props {
documentation: AsyncSearchIntroDocumentation;
@ -21,9 +22,9 @@ interface Props {
api: SearchSessionsMgmtAPI;
http: HttpStart;
timezone: string;
config: SessionsConfigSchema;
plugins: IManagementSectionsPluginsSetup;
config: SearchSessionsConfigSchema;
kibanaVersion: string;
searchUsageCollector: SearchUsageCollector;
}
export function SearchSessionsMgmtMain({ documentation, ...tableProps }: Props) {
@ -32,13 +33,13 @@ export function SearchSessionsMgmtMain({ documentation, ...tableProps }: Props)
<EuiPageHeader
pageTitle={
<FormattedMessage
id="xpack.data.mgmt.searchSessions.main.sectionTitle"
id="data.mgmt.searchSessions.main.sectionTitle"
defaultMessage="Search Sessions"
/>
}
description={
<FormattedMessage
id="xpack.data.mgmt.searchSessions.main.sectionDescription"
id="data.mgmt.searchSessions.main.sectionDescription"
defaultMessage="Manage your saved search sessions."
/>
}
@ -50,7 +51,7 @@ export function SearchSessionsMgmtMain({ documentation, ...tableProps }: Props)
iconType="help"
>
<FormattedMessage
id="xpack.data.mgmt.searchSessions.main.backgroundSessionsDocsLinkText"
id="data.mgmt.searchSessions.main.backgroundSessionsDocsLinkText"
defaultMessage="Documentation"
/>
</EuiButtonEmpty>,

View file

@ -1,14 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiTextProps, EuiToolTipProps } from '@elastic/eui';
import { mount } from 'enzyme';
import React from 'react';
import { SearchSessionStatus } from '@kbn/data-plugin/common';
import { SearchSessionStatus } from '../../../../../common';
import { UISession } from '../types';
import { LocaleWrapper } from '../__mocks__';
import { getStatusText, StatusIndicator } from './status';

View file

@ -1,14 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { ReactElement } from 'react';
import { SearchSessionStatus } from '@kbn/data-plugin/common';
import { SearchSessionStatus } from '../../../../../common';
import { dateString } from '../lib/date_string';
import { UISession } from '../types';
import { StatusDef as StatusAttributes, TableText } from '.';
@ -17,23 +18,23 @@ import { StatusDef as StatusAttributes, TableText } from '.';
export const getStatusText = (statusType: string): string => {
switch (statusType) {
case SearchSessionStatus.IN_PROGRESS:
return i18n.translate('xpack.data.mgmt.searchSessions.status.label.inProgress', {
return i18n.translate('data.mgmt.searchSessions.status.label.inProgress', {
defaultMessage: 'In progress',
});
case SearchSessionStatus.EXPIRED:
return i18n.translate('xpack.data.mgmt.searchSessions.status.label.expired', {
return i18n.translate('data.mgmt.searchSessions.status.label.expired', {
defaultMessage: 'Expired',
});
case SearchSessionStatus.CANCELLED:
return i18n.translate('xpack.data.mgmt.searchSessions.status.label.cancelled', {
return i18n.translate('data.mgmt.searchSessions.status.label.cancelled', {
defaultMessage: 'Cancelled',
});
case SearchSessionStatus.COMPLETE:
return i18n.translate('xpack.data.mgmt.searchSessions.status.label.complete', {
return i18n.translate('data.mgmt.searchSessions.status.label.complete', {
defaultMessage: 'Complete',
});
case SearchSessionStatus.ERROR:
return i18n.translate('xpack.data.mgmt.searchSessions.status.label.error', {
return i18n.translate('data.mgmt.searchSessions.status.label.error', {
defaultMessage: 'Error',
});
default:
@ -60,7 +61,7 @@ const getStatusAttributes = ({
if (session.expires) {
expireDate = dateString(session.expires!, timezone);
} else {
expireDate = i18n.translate('xpack.data.mgmt.searchSessions.status.expireDateUnknown', {
expireDate = i18n.translate('data.mgmt.searchSessions.status.expireDateUnknown', {
defaultMessage: 'unknown',
});
}
@ -72,13 +73,10 @@ const getStatusAttributes = ({
textColor: 'default',
icon: <EuiLoadingSpinner />,
label: <TableText>{getStatusText(session.status)}</TableText>,
toolTipContent: i18n.translate(
'xpack.data.mgmt.searchSessions.status.message.createdOn',
{
defaultMessage: 'Expires on {expireDate}',
values: { expireDate },
}
),
toolTipContent: i18n.translate('data.mgmt.searchSessions.status.message.createdOn', {
defaultMessage: 'Expires on {expireDate}',
values: { expireDate },
}),
};
} catch (err) {
// eslint-disable-next-line no-console
@ -88,13 +86,10 @@ const getStatusAttributes = ({
case SearchSessionStatus.EXPIRED:
try {
const toolTipContent = i18n.translate(
'xpack.data.mgmt.searchSessions.status.message.expiredOn',
{
defaultMessage: 'Expired on {expireDate}',
values: { expireDate },
}
);
const toolTipContent = i18n.translate('data.mgmt.searchSessions.status.message.expiredOn', {
defaultMessage: 'Expired on {expireDate}',
values: { expireDate },
});
return {
icon: <EuiIcon color="#9AA" type="clock" />,
@ -111,7 +106,7 @@ const getStatusAttributes = ({
return {
icon: <EuiIcon color="#9AA" type="crossInACircleFilled" />,
label: <TableText>{getStatusText(session.status)}</TableText>,
toolTipContent: i18n.translate('xpack.data.mgmt.searchSessions.status.message.cancelled', {
toolTipContent: i18n.translate('data.mgmt.searchSessions.status.message.cancelled', {
defaultMessage: 'Cancelled by user',
}),
};
@ -121,7 +116,7 @@ const getStatusAttributes = ({
textColor: 'danger',
icon: <EuiIcon color="danger" type="crossInACircleFilled" />,
label: <TableText>{getStatusText(session.status)}</TableText>,
toolTipContent: i18n.translate('xpack.data.mgmt.searchSessions.status.message.error', {
toolTipContent: i18n.translate('data.mgmt.searchSessions.status.message.error', {
defaultMessage: 'Error: {error}',
values: { error: (session as any).error || 'unknown' },
}),
@ -129,7 +124,7 @@ const getStatusAttributes = ({
case SearchSessionStatus.COMPLETE:
try {
const toolTipContent = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresOn', {
const toolTipContent = i18n.translate('data.mgmt.searchSessions.status.expiresOn', {
defaultMessage: 'Expires on {expireDate}',
values: { expireDate },
});

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui';
@ -12,7 +13,7 @@ import { UISession } from '../../types';
export const getAppFilter: (tableData: UISession[]) => SearchFilterConfig = (tableData) => ({
type: 'field_value_selection',
name: i18n.translate('xpack.data.mgmt.searchSessions.search.filterApp', {
name: i18n.translate('data.mgmt.searchSessions.search.filterApp', {
defaultMessage: 'App',
}),
field: 'appId',

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { SearchSessionsMgmtTable } from './table';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui';
@ -14,7 +15,7 @@ import { getStatusText } from '../status';
export const getStatusFilter: (tableData: UISession[]) => SearchFilterConfig = (tableData) => ({
type: 'field_value_selection',
name: i18n.translate('xpack.data.mgmt.searchSessions.search.filterStatus', {
name: i18n.translate('data.mgmt.searchSessions.search.filterStatus', {
defaultMessage: 'Status',
}),
field: 'status',

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { MockedKeys } from '@kbn/utility-types/jest';
@ -12,34 +13,29 @@ import { CoreSetup, CoreStart } from '@kbn/core/public';
import moment from 'moment';
import React from 'react';
import { coreMock } from '@kbn/core/public/mocks';
import { SessionsClient } from '@kbn/data-plugin/public/search';
import { SearchSessionStatus } from '@kbn/data-plugin/common';
import { IManagementSectionsPluginsSetup, SessionsConfigSchema } from '../..';
import { SearchUsageCollector, SessionsClient } from '../../../..';
import { SearchSessionStatus } from '../../../../../../common';
import { SearchSessionsMgmtAPI } from '../../lib/api';
import { LocaleWrapper } from '../../__mocks__';
import { SearchSessionsMgmtTable } from './table';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { managementPluginMock } from '@kbn/management-plugin/public/mocks';
import { SharePluginStart } from '@kbn/share-plugin/public';
import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
import { SearchSessionsConfigSchema } from '../../../../../../config';
import { createSearchUsageCollectorMock } from '../../../../collectors/mocks';
let mockCoreSetup: MockedKeys<CoreSetup>;
let mockCoreStart: CoreStart;
let mockShareStart: jest.Mocked<SharePluginStart>;
let mockPluginsSetup: IManagementSectionsPluginsSetup;
let mockConfig: SessionsConfigSchema;
let mockConfig: SearchSessionsConfigSchema;
let sessionsClient: SessionsClient;
let api: SearchSessionsMgmtAPI;
let mockSearchUsageCollector: SearchUsageCollector;
describe('Background Search Session Management Table', () => {
beforeEach(async () => {
mockCoreSetup = coreMock.createSetup();
mockCoreStart = coreMock.createStart();
mockShareStart = sharePluginMock.createStartContract();
mockPluginsSetup = {
data: dataPluginMock.createSetupContract(),
management: managementPluginMock.createSetupContract(),
};
mockConfig = {
defaultExpiration: moment.duration('7d'),
management: {
@ -49,6 +45,7 @@ describe('Background Search Session Management Table', () => {
refreshTimeout: moment.duration(10, 'minutes'),
},
} as any;
mockSearchUsageCollector = createSearchUsageCollectorMock();
sessionsClient = new SessionsClient({ http: mockCoreSetup.http });
api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, {
@ -91,11 +88,11 @@ describe('Background Search Session Management Table', () => {
<LocaleWrapper>
<SearchSessionsMgmtTable
core={mockCoreStart}
plugins={mockPluginsSetup}
api={api}
timezone="UTC"
config={mockConfig}
kibanaVersion={'8.0.0'}
searchUsageCollector={mockSearchUsageCollector}
/>
</LocaleWrapper>
);
@ -124,11 +121,11 @@ describe('Background Search Session Management Table', () => {
<LocaleWrapper>
<SearchSessionsMgmtTable
core={mockCoreStart}
plugins={mockPluginsSetup}
api={api}
timezone="UTC"
config={mockConfig}
kibanaVersion={'8.0.0'}
searchUsageCollector={mockSearchUsageCollector}
/>
</LocaleWrapper>
);
@ -168,11 +165,11 @@ describe('Background Search Session Management Table', () => {
<LocaleWrapper>
<SearchSessionsMgmtTable
core={mockCoreStart}
plugins={mockPluginsSetup}
api={api}
timezone="UTC"
config={mockConfig}
kibanaVersion={'8.0.0'}
searchUsageCollector={mockSearchUsageCollector}
/>
</LocaleWrapper>
);
@ -202,11 +199,11 @@ describe('Background Search Session Management Table', () => {
<LocaleWrapper>
<SearchSessionsMgmtTable
core={mockCoreStart}
plugins={mockPluginsSetup}
api={api}
timezone="UTC"
config={mockConfig}
kibanaVersion={'8.0.0'}
searchUsageCollector={mockSearchUsageCollector}
/>
</LocaleWrapper>
);

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiButton, EuiInMemoryTable, EuiSearchBarProps } from '@elastic/eui';
@ -12,23 +13,24 @@ import moment from 'moment';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import useDebounce from 'react-use/lib/useDebounce';
import useInterval from 'react-use/lib/useInterval';
import { SEARCH_SESSIONS_TABLE_ID } from '@kbn/data-plugin/common';
import { TableText } from '..';
import { IManagementSectionsPluginsSetup, SessionsConfigSchema } from '../..';
import { SEARCH_SESSIONS_TABLE_ID } from '../../../../../../common';
import { SearchSessionsMgmtAPI } from '../../lib/api';
import { getColumns } from '../../lib/get_columns';
import { UISession } from '../../types';
import { OnActionComplete } from '../actions';
import { getAppFilter } from './app_filter';
import { getStatusFilter } from './status_filter';
import { SearchUsageCollector } from '../../../../collectors';
import { SearchSessionsConfigSchema } from '../../../../../../config';
interface Props {
core: CoreStart;
api: SearchSessionsMgmtAPI;
timezone: string;
config: SessionsConfigSchema;
plugins: IManagementSectionsPluginsSetup;
config: SearchSessionsConfigSchema;
kibanaVersion: string;
searchUsageCollector: SearchUsageCollector;
}
export function SearchSessionsMgmtTable({
@ -36,8 +38,8 @@ export function SearchSessionsMgmtTable({
api,
timezone,
config,
plugins,
kibanaVersion,
searchUsageCollector,
...props
}: Props) {
const [tableData, setTableData] = useState<UISession[]>([]);
@ -80,8 +82,8 @@ export function SearchSessionsMgmtTable({
// initial data load
useEffect(() => {
doRefresh();
plugins.data.search.usageCollector?.trackSessionsListLoaded();
}, [doRefresh, plugins]);
searchUsageCollector.trackSessionsListLoaded();
}, [doRefresh, searchUsageCollector]);
useInterval(doRefresh, refreshInterval);
@ -104,7 +106,7 @@ export function SearchSessionsMgmtTable({
data-test-subj="sessionManagementRefreshBtn"
>
<FormattedMessage
id="xpack.data.mgmt.searchSessions.search.tools.refresh"
id="data.mgmt.searchSessions.search.tools.refresh"
defaultMessage="Refresh"
/>
</EuiButton>
@ -121,7 +123,15 @@ export function SearchSessionsMgmtTable({
'data-test-subj': `searchSessionsRow`,
'data-test-search-session-id': `id-${searchSession.id}`,
})}
columns={getColumns(core, plugins, api, config, timezone, onActionComplete, kibanaVersion)}
columns={getColumns(
core,
api,
config,
timezone,
onActionComplete,
kibanaVersion,
searchUsageCollector
)}
items={tableData}
pagination={pagination}
search={search}

View file

@ -1,34 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import type { CoreStart, HttpStart, I18nStart, IUiSettingsClient } from '@kbn/core/public';
import { CoreSetup } from '@kbn/core/public';
import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { ManagementSetup } from '@kbn/management-plugin/public';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import { SEARCH_SESSIONS_MANAGEMENT_ID } from '@kbn/data-plugin/public';
import type { ConfigSchema } from '../../../config';
import type { DataEnhancedStartDependencies } from '../../plugin';
import type { ISessionsClient, SearchUsageCollector } from '../../..';
import { SEARCH_SESSIONS_MANAGEMENT_ID } from '../constants';
import type { SearchSessionsMgmtAPI } from './lib/api';
import type { AsyncSearchIntroDocumentation } from './lib/documentation';
import { SearchSessionsConfigSchema } from '../../../../config';
export interface IManagementSectionsPluginsSetup {
data: DataPublicPluginSetup;
management: ManagementSetup;
searchUsageCollector: SearchUsageCollector;
sessionsClient: ISessionsClient;
}
export interface IManagementSectionsPluginsStart {
data: DataPublicPluginStart;
share: SharePluginStart;
}
export interface AppDependencies {
plugins: IManagementSectionsPluginsSetup;
share: SharePluginStart;
uiSettings: IUiSettingsClient;
documentation: AsyncSearchIntroDocumentation;
@ -36,33 +35,32 @@ export interface AppDependencies {
api: SearchSessionsMgmtAPI;
http: HttpStart;
i18n: I18nStart;
config: SessionsConfigSchema;
config: SearchSessionsConfigSchema;
kibanaVersion: string;
searchUsageCollector: SearchUsageCollector;
}
export const APP = {
id: SEARCH_SESSIONS_MANAGEMENT_ID,
getI18nName: (): string =>
i18n.translate('xpack.data.mgmt.searchSessions.appTitle', {
i18n.translate('data.mgmt.searchSessions.appTitle', {
defaultMessage: 'Search Sessions',
}),
};
export type SessionsConfigSchema = ConfigSchema['search']['sessions'];
export function registerSearchSessionsMgmt(
coreSetup: CoreSetup<DataEnhancedStartDependencies>,
config: SessionsConfigSchema,
kibanaVersion: string,
services: IManagementSectionsPluginsSetup
coreSetup: CoreSetup<IManagementSectionsPluginsStart>,
deps: IManagementSectionsPluginsSetup,
config: SearchSessionsConfigSchema,
kibanaVersion: string
) {
services.management.sections.section.kibana.registerApp({
deps.management.sections.section.kibana.registerApp({
id: APP.id,
title: APP.getI18nName(),
order: 1.75,
mount: async (params) => {
const { SearchSessionsMgmtApp: MgmtApp } = await import('./application');
const mgmtApp = new MgmtApp(coreSetup, config, kibanaVersion, params, services);
const mgmtApp = new MgmtApp(coreSetup, deps, config, kibanaVersion, params);
return mgmtApp.mountManagementSection();
},
});

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { MockedKeys } from '@kbn/utility-types/jest';
@ -11,17 +12,17 @@ import moment from 'moment';
import { coreMock } from '@kbn/core/public/mocks';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import type { SavedObjectsFindResponse } from '@kbn/core/server';
import { SessionsClient } from '@kbn/data-plugin/public/search';
import type { SessionsConfigSchema } from '..';
import { SearchSessionStatus } from '@kbn/data-plugin/common';
import { SessionsClient } from '../../..';
import { SearchSessionStatus } from '../../../../../common';
import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
import { SharePluginStart } from '@kbn/share-plugin/public';
import { SearchSessionsMgmtAPI } from './api';
import { SearchSessionsConfigSchema } from '../../../../../config';
let mockCoreSetup: MockedKeys<CoreSetup>;
let mockCoreStart: MockedKeys<CoreStart>;
let mockShareStart: jest.Mocked<SharePluginStart>;
let mockConfig: SessionsConfigSchema;
let mockConfig: SearchSessionsConfigSchema;
let sessionsClient: SessionsClient;
describe('Search Sessions Management API', () => {

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
@ -12,15 +13,16 @@ import { from, race, timer } from 'rxjs';
import { mapTo, tap } from 'rxjs/operators';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import { SerializableRecord } from '@kbn/utility-types';
import { ISessionsClient, SearchUsageCollector } from '@kbn/data-plugin/public';
import { SearchSessionStatus } from '@kbn/data-plugin/common';
import { ACTION } from '../components/actions';
import {
PersistedSearchSessionSavedObjectAttributes,
UISearchSessionState,
UISession,
} from '../types';
import { SessionsConfigSchema } from '..';
import { ISessionsClient } from '../../sessions_client';
import { SearchUsageCollector } from '../../../collectors';
import { SearchSessionStatus } from '../../../../../common';
import { SearchSessionsConfigSchema } from '../../../../../config';
type LocatorsStart = SharePluginStart['url']['locators'];
@ -73,7 +75,7 @@ function getUrlFromState(locators: LocatorsStart, locatorId: string, state: Seri
// Helper: factory for a function to map server objects to UI objects
const mapToUISession =
(locators: LocatorsStart, config: SessionsConfigSchema) =>
(locators: LocatorsStart, config: SearchSessionsConfigSchema) =>
async (
savedObject: SavedObject<PersistedSearchSessionSavedObjectAttributes>
): Promise<UISession> => {
@ -125,7 +127,7 @@ interface SearchSessionManagementDeps {
export class SearchSessionsMgmtAPI {
constructor(
private sessionsClient: ISessionsClient,
private config: SessionsConfigSchema,
private config: SearchSessionsConfigSchema,
private deps: SearchSessionManagementDeps
) {}
@ -147,7 +149,7 @@ export class SearchSessionsMgmtAPI {
const timeout$ = timer(refreshTimeout.asMilliseconds()).pipe(
tap(() => {
this.deps.notifications.toasts.addDanger(
i18n.translate('xpack.data.mgmt.searchSessions.api.fetchTimeout', {
i18n.translate('data.mgmt.searchSessions.api.fetchTimeout', {
defaultMessage: 'Fetching the Search Session info timed out after {timeout} seconds',
values: { timeout: refreshTimeout.asSeconds() },
})
@ -169,7 +171,7 @@ export class SearchSessionsMgmtAPI {
// eslint-disable-next-line no-console
console.error(err);
this.deps.notifications.toasts.addError(err, {
title: i18n.translate('xpack.data.mgmt.searchSessions.api.fetchError', {
title: i18n.translate('data.mgmt.searchSessions.api.fetchError', {
defaultMessage: 'Failed to refresh the page!',
}),
});
@ -194,13 +196,13 @@ export class SearchSessionsMgmtAPI {
await this.sessionsClient.delete(id);
this.deps.notifications.toasts.addSuccess({
title: i18n.translate('xpack.data.mgmt.searchSessions.api.deleted', {
title: i18n.translate('data.mgmt.searchSessions.api.deleted', {
defaultMessage: 'The search session was deleted.',
}),
});
} catch (err) {
this.deps.notifications.toasts.addError(err, {
title: i18n.translate('xpack.data.mgmt.searchSessions.api.deletedError', {
title: i18n.translate('data.mgmt.searchSessions.api.deletedError', {
defaultMessage: 'Failed to delete the search session!',
}),
});
@ -214,13 +216,13 @@ export class SearchSessionsMgmtAPI {
await this.sessionsClient.extend(id, expires);
this.deps.notifications.toasts.addSuccess({
title: i18n.translate('xpack.data.mgmt.searchSessions.api.extended', {
title: i18n.translate('data.mgmt.searchSessions.api.extended', {
defaultMessage: 'The search session was extended.',
}),
});
} catch (err) {
this.deps.notifications.toasts.addError(err, {
title: i18n.translate('xpack.data.mgmt.searchSessions.api.extendError', {
title: i18n.translate('data.mgmt.searchSessions.api.extendError', {
defaultMessage: 'Failed to extend the search session!',
}),
});
@ -233,13 +235,13 @@ export class SearchSessionsMgmtAPI {
await this.sessionsClient.rename(id, newName);
this.deps.notifications.toasts.addSuccess({
title: i18n.translate('xpack.data.mgmt.searchSessions.api.rename', {
title: i18n.translate('data.mgmt.searchSessions.api.rename', {
defaultMessage: 'The search session was renamed',
}),
});
} catch (err) {
this.deps.notifications.toasts.addError(err, {
title: i18n.translate('xpack.data.mgmt.searchSessions.api.renameError', {
title: i18n.translate('data.mgmt.searchSessions.api.renameError', {
defaultMessage: 'Failed to rename the search session',
}),
});

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import moment from 'moment';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { DocLinksStart } from '@kbn/core/public';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiTableFieldDataColumnType } from '@elastic/eui';
@ -12,23 +13,22 @@ import { CoreSetup, CoreStart } from '@kbn/core/public';
import moment from 'moment';
import { ReactElement } from 'react';
import { coreMock } from '@kbn/core/public/mocks';
import { SessionsClient } from '@kbn/data-plugin/public/search';
import { IManagementSectionsPluginsSetup, SessionsConfigSchema } from '..';
import { SearchSessionStatus } from '@kbn/data-plugin/common';
import { SearchUsageCollector, SessionsClient } from '../../..';
import { SearchSessionStatus } from '../../../../../common';
import { OnActionComplete } from '../components';
import { UISession } from '../types';
import { SearchSessionsMgmtAPI } from './api';
import { getColumns } from './get_columns';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { managementPluginMock } from '@kbn/management-plugin/public/mocks';
import { SharePluginStart } from '@kbn/share-plugin/public';
import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
import { SearchSessionsConfigSchema } from '../../../../../config';
import { createSearchUsageCollectorMock } from '../../../collectors/mocks';
let mockCoreSetup: MockedKeys<CoreSetup>;
let mockCoreStart: CoreStart;
let mockShareStart: jest.Mocked<SharePluginStart>;
let mockPluginsSetup: IManagementSectionsPluginsSetup;
let mockConfig: SessionsConfigSchema;
let mockSearchUsageCollector: SearchUsageCollector;
let mockConfig: SearchSessionsConfigSchema;
let api: SearchSessionsMgmtAPI;
let sessionsClient: SessionsClient;
let handleAction: OnActionComplete;
@ -41,10 +41,6 @@ describe('Search Sessions Management table column factory', () => {
mockCoreSetup = coreMock.createSetup();
mockCoreStart = coreMock.createStart();
mockShareStart = sharePluginMock.createStartContract();
mockPluginsSetup = {
data: dataPluginMock.createSetupContract(),
management: managementPluginMock.createSetupContract(),
};
mockConfig = {
defaultExpiration: moment.duration('7d'),
management: {
@ -55,6 +51,7 @@ describe('Search Sessions Management table column factory', () => {
},
} as any;
sessionsClient = new SessionsClient({ http: mockCoreSetup.http });
mockSearchUsageCollector = createSearchUsageCollectorMock();
api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, {
locators: mockShareStart.url.locators,
@ -86,12 +83,12 @@ describe('Search Sessions Management table column factory', () => {
test('returns columns', () => {
const columns = getColumns(
mockCoreStart,
mockPluginsSetup,
api,
mockConfig,
tz,
handleAction,
'7.14.0'
'7.14.0',
mockSearchUsageCollector
);
expect(columns).toMatchInlineSnapshot(`
Array [
@ -152,12 +149,12 @@ describe('Search Sessions Management table column factory', () => {
test('rendering', () => {
const [, nameColumn] = getColumns(
mockCoreStart,
mockPluginsSetup,
api,
mockConfig,
tz,
handleAction,
'7.14.0'
'7.14.0',
mockSearchUsageCollector
) as Array<EuiTableFieldDataColumnType<UISession>>;
const name = mount(nameColumn.render!(mockSession.name, mockSession) as ReactElement);
@ -172,12 +169,12 @@ describe('Search Sessions Management table column factory', () => {
beforeEach(() => {
const [, nameColumn] = getColumns(
mockCoreStart,
mockPluginsSetup,
api,
mockConfig,
tz,
handleAction,
currentKibanaVersion
currentKibanaVersion,
mockSearchUsageCollector
) as Array<EuiTableFieldDataColumnType<UISession>>;
hasRenderedVersionWarning = (partialSession: Partial<UISession>): boolean => {
@ -235,12 +232,12 @@ describe('Search Sessions Management table column factory', () => {
test('renders', () => {
const [, , numOfSearches] = getColumns(
mockCoreStart,
mockPluginsSetup,
api,
mockConfig,
tz,
handleAction,
'7.14.0'
'7.14.0',
mockSearchUsageCollector
) as Array<EuiTableFieldDataColumnType<UISession>>;
const numOfSearchesLine = mount(
@ -255,12 +252,12 @@ describe('Search Sessions Management table column factory', () => {
test('render in_progress', () => {
const [, , , status] = getColumns(
mockCoreStart,
mockPluginsSetup,
api,
mockConfig,
tz,
handleAction,
'7.14.0'
'7.14.0',
mockSearchUsageCollector
) as Array<EuiTableFieldDataColumnType<UISession>>;
const statusLine = mount(status.render!(mockSession.status, mockSession) as ReactElement);
@ -272,12 +269,12 @@ describe('Search Sessions Management table column factory', () => {
test('error handling', () => {
const [, , , status] = getColumns(
mockCoreStart,
mockPluginsSetup,
api,
mockConfig,
tz,
handleAction,
'7.14.0'
'7.14.0',
mockSearchUsageCollector
) as Array<EuiTableFieldDataColumnType<UISession>>;
mockSession.status = 'INVALID' as SearchSessionStatus;
@ -296,12 +293,12 @@ describe('Search Sessions Management table column factory', () => {
const [, , , , createdDateCol] = getColumns(
mockCoreStart,
mockPluginsSetup,
api,
mockConfig,
tz,
handleAction,
'7.14.0'
'7.14.0',
mockSearchUsageCollector
) as Array<EuiTableFieldDataColumnType<UISession>>;
const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement);
@ -314,12 +311,12 @@ describe('Search Sessions Management table column factory', () => {
const [, , , , createdDateCol] = getColumns(
mockCoreStart,
mockPluginsSetup,
api,
mockConfig,
tz,
handleAction,
'7.14.0'
'7.14.0',
mockSearchUsageCollector
) as Array<EuiTableFieldDataColumnType<UISession>>;
const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement);
@ -330,12 +327,12 @@ describe('Search Sessions Management table column factory', () => {
test('error handling', () => {
const [, , , , createdDateCol] = getColumns(
mockCoreStart,
mockPluginsSetup,
api,
mockConfig,
tz,
handleAction,
'7.14.0'
'7.14.0',
mockSearchUsageCollector
) as Array<EuiTableFieldDataColumnType<UISession>>;
mockSession.created = 'INVALID';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import {
@ -21,15 +22,15 @@ import { CoreStart } from '@kbn/core/public';
import { capitalize } from 'lodash';
import React from 'react';
import { RedirectAppLinks } from '@kbn/kibana-react-plugin/public';
import { SearchSessionStatus } from '@kbn/data-plugin/common';
import { IManagementSectionsPluginsSetup, SessionsConfigSchema } from '..';
import { TableText } from '../components';
import { OnActionComplete, PopoverActionsMenu } from '../components';
import { SearchSessionStatus } from '../../../../../common';
import { OnActionComplete, PopoverActionsMenu, TableText } from '../components';
import { StatusIndicator } from '../components/status';
import { dateString } from './date_string';
import { SearchSessionsMgmtAPI } from './api';
import { getExpirationStatus } from './get_expiration_status';
import { UISession } from '../types';
import { SearchUsageCollector } from '../../../collectors';
import { SearchSessionsConfigSchema } from '../../../../../config';
// Helper function: translate an app string to EuiIcon-friendly string
const appToIcon = (app: string) => {
@ -46,7 +47,7 @@ const appToIcon = (app: string) => {
// Helper function: translate an app id to user friendly string
const appToTooltip = (appId: string | undefined) => {
if (appId === 'ml') {
return i18n.translate('xpack.data.mgmt.searchSessions.table.mlAppName', {
return i18n.translate('data.mgmt.searchSessions.table.mlAppName', {
defaultMessage: 'Machine Learning',
});
}
@ -58,19 +59,19 @@ function isSessionRestorable(status: SearchSessionStatus) {
export const getColumns = (
core: CoreStart,
plugins: IManagementSectionsPluginsSetup,
api: SearchSessionsMgmtAPI,
config: SessionsConfigSchema,
config: SearchSessionsConfigSchema,
timezone: string,
onActionComplete: OnActionComplete,
kibanaVersion: string
kibanaVersion: string,
searchUsageCollector: SearchUsageCollector
): Array<EuiBasicTableColumn<UISession>> => {
// Use a literal array of table column definitions to detail a UISession object
return [
// App
{
field: 'appId',
name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerType', {
name: i18n.translate('data.mgmt.searchSessions.table.headerType', {
defaultMessage: 'App',
}),
sortable: true,
@ -91,7 +92,7 @@ export const getColumns = (
// Name, links to app and displays the search session data
{
field: 'name',
name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerName', {
name: i18n.translate('data.mgmt.searchSessions.table.headerName', {
defaultMessage: 'Name',
}),
sortable: true,
@ -100,8 +101,8 @@ export const getColumns = (
const isRestorable = isSessionRestorable(status);
const href = isRestorable ? restoreUrl : reloadUrl;
const trackAction = isRestorable
? plugins.data.search.usageCollector?.trackSessionViewRestored
: plugins.data.search.usageCollector?.trackSessionReloaded;
? searchUsageCollector.trackSessionViewRestored
: searchUsageCollector.trackSessionReloaded;
const notRestorableWarning = isRestorable ? null : (
<>
{' '}
@ -109,7 +110,7 @@ export const getColumns = (
type="alert"
content={
<FormattedMessage
id="xpack.data.mgmt.searchSessions.table.notRestorableWarning"
id="data.mgmt.searchSessions.table.notRestorableWarning"
defaultMessage="The search session will be executed again. You can then save it for future use."
/>
}
@ -130,7 +131,7 @@ export const getColumns = (
iconProps={{ 'data-test-subj': 'versionIncompatibleWarningTestSubj' }}
content={
<FormattedMessage
id="xpack.data.mgmt.searchSessions.table.versionIncompatibleWarning"
id="data.mgmt.searchSessions.table.versionIncompatibleWarning"
defaultMessage="This search session was created in a Kibana instance running a different version. It may not restore correctly."
/>
}
@ -160,7 +161,7 @@ export const getColumns = (
// # Searches
{
field: 'numSearches',
name: i18n.translate('xpack.data.mgmt.searchSessions.table.numSearches', {
name: i18n.translate('data.mgmt.searchSessions.table.numSearches', {
defaultMessage: '# Searches',
}),
sortable: true,
@ -174,7 +175,7 @@ export const getColumns = (
// Session status
{
field: 'status',
name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerStatus', {
name: i18n.translate('data.mgmt.searchSessions.table.headerStatus', {
defaultMessage: 'Status',
}),
sortable: true,
@ -186,7 +187,7 @@ export const getColumns = (
// Started date
{
field: 'created',
name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerStarted', {
name: i18n.translate('data.mgmt.searchSessions.table.headerStarted', {
defaultMessage: 'Created',
}),
sortable: true,
@ -209,7 +210,7 @@ export const getColumns = (
// Expiration date
{
field: 'expires',
name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerExpiration', {
name: i18n.translate('data.mgmt.searchSessions.table.headerExpiration', {
defaultMessage: 'Expiration',
}),
sortable: true,

View file

@ -1,15 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import moment from 'moment';
import { SessionsConfigSchema } from '..';
import { SearchSessionsConfigSchema } from '../../../../../config';
export const getExpirationStatus = (config: SessionsConfigSchema, expires: string | null) => {
export const getExpirationStatus = (config: SearchSessionsConfigSchema, expires: string | null) => {
const tNow = moment.utc().valueOf();
const tFuture = moment.utc(expires).valueOf();
@ -19,27 +20,27 @@ export const getExpirationStatus = (config: SessionsConfigSchema, expires: strin
const expiresInDays = Math.floor(durationToExpire.asDays());
const sufficientDays = Math.ceil(moment.duration(config.management.expiresSoonWarning).asDays());
let toolTipContent = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresSoonInDays', {
let toolTipContent = i18n.translate('data.mgmt.searchSessions.status.expiresSoonInDays', {
defaultMessage: 'Expires in {numDays} days',
values: { numDays: expiresInDays },
});
let statusContent = i18n.translate(
'xpack.data.mgmt.searchSessions.status.expiresSoonInDaysTooltip',
{ defaultMessage: '{numDays} days', values: { numDays: expiresInDays } }
);
let statusContent = i18n.translate('data.mgmt.searchSessions.status.expiresSoonInDaysTooltip', {
defaultMessage: '{numDays} days',
values: { numDays: expiresInDays },
});
if (expiresInDays === 0) {
// switch to show expires in hours
const expiresInHours = Math.floor(durationToExpire.asHours());
toolTipContent = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresSoonInHours', {
toolTipContent = i18n.translate('data.mgmt.searchSessions.status.expiresSoonInHours', {
defaultMessage: 'This session expires in {numHours} hours',
values: { numHours: expiresInHours },
});
statusContent = i18n.translate(
'xpack.data.mgmt.searchSessions.status.expiresSoonInHoursTooltip',
{ defaultMessage: '{numHours} hours', values: { numHours: expiresInHours } }
);
statusContent = i18n.translate('data.mgmt.searchSessions.status.expiresSoonInHoursTooltip', {
defaultMessage: '{numHours} hours',
values: { numHours: expiresInHours },
});
}
if (durationToExpire.valueOf() > 0 && expiresInDays <= sufficientDays) {

View file

@ -1,11 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { SearchSessionSavedObjectAttributes, SearchSessionStatus } from '@kbn/data-plugin/common';
import { SearchSessionSavedObjectAttributes, SearchSessionStatus } from '../../../../common';
import { ACTION } from './components/actions';
export const DATE_STRING_FORMAT = 'D MMM, YYYY, HH:mm:ss';

View file

@ -15,6 +15,9 @@ import { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { FieldFormatsSetup, FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { UsageCollectionSetup, UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import { Setup as InspectorSetup } from '@kbn/inspector-plugin/public';
import { ScreenshotModePluginStart } from '@kbn/screenshot-mode-plugin/public';
import { SharePluginStart } from '@kbn/share-plugin/public';
import { ManagementSetup } from '@kbn/management-plugin/public';
import { DatatableUtilitiesService } from '../common';
import { createFiltersFromRangeSelectAction, createFiltersFromValueClickAction } from './actions';
import type { ISearchSetup, ISearchStart } from './search';
@ -29,12 +32,15 @@ export interface DataSetupDependencies {
inspector: InspectorSetup;
usageCollection?: UsageCollectionSetup;
fieldFormats: FieldFormatsSetup;
management: ManagementSetup;
}
export interface DataStartDependencies {
uiActions: UiActionsStart;
fieldFormats: FieldFormatsStart;
dataViews: DataViewsPublicPluginStart;
screenshotMode: ScreenshotModePluginStart;
share: SharePluginStart;
}
/**

View file

@ -11,12 +11,12 @@ import { cloneDeep } from 'lodash';
import { applyDeprecations, configDeprecationFactory } from '@kbn/config';
import { configDeprecationsMock } from '@kbn/core/server/mocks';
import { autocompleteConfigDeprecationProvider } from './config_deprecations';
import { configDeprecationProvider } from './config_deprecations';
const deprecationContext = configDeprecationsMock.createContext();
const applyConfigDeprecations = (settings: Record<string, any> = {}) => {
const deprecations = autocompleteConfigDeprecationProvider(configDeprecationFactory);
const deprecations = configDeprecationProvider(configDeprecationFactory);
const deprecationMessages: string[] = [];
const migrated = applyDeprecations(
settings,
@ -37,40 +37,30 @@ const applyConfigDeprecations = (settings: Record<string, any> = {}) => {
describe('Config Deprecations', () => {
it('does not report deprecations for default configuration', () => {
const defaultConfig = { data: { autocomplete: { valueSuggestions: {} } } };
const defaultConfig = { data: { search: { sessions: {} } } };
const { messages, migrated } = applyConfigDeprecations(cloneDeep(defaultConfig));
expect(migrated).toEqual(defaultConfig);
expect(messages).toHaveLength(0);
});
it('renames kibana.autocompleteTerminateAfter to data.autocomplete.valueSuggestions.terminateAfter', () => {
it('renames xpack.data_enhanced.search.sessions.* to data.search.sessions.*', () => {
const config = {
kibana: {
autocompleteTerminateAfter: 123,
xpack: {
data_enhanced: {
search: {
sessions: {
enabled: false,
},
},
},
},
};
const { messages, migrated } = applyConfigDeprecations(cloneDeep(config));
expect(migrated.kibana?.autocompleteTerminateAfter).not.toBeDefined();
expect(migrated.data.autocomplete.valueSuggestions.terminateAfter).toEqual(123);
expect(migrated.xpack?.data_enhanced).not.toBeDefined();
expect(migrated.data.search.sessions.enabled).toEqual(false);
expect(messages).toMatchInlineSnapshot(`
Array [
"Setting \\"kibana.autocompleteTerminateAfter\\" has been replaced by \\"data.autocomplete.valueSuggestions.terminateAfter\\"",
]
`);
});
it('renames kibana.autocompleteTimeout to data.autocomplete.valueSuggestions.timeout', () => {
const config = {
kibana: {
autocompleteTimeout: 123,
},
};
const { messages, migrated } = applyConfigDeprecations(cloneDeep(config));
expect(migrated.kibana?.autocompleteTimeout).not.toBeDefined();
expect(migrated.data.autocomplete.valueSuggestions.timeout).toEqual(123);
expect(messages).toMatchInlineSnapshot(`
Array [
"Setting \\"kibana.autocompleteTimeout\\" has been replaced by \\"data.autocomplete.valueSuggestions.timeout\\"",
"Setting \\"xpack.data_enhanced.search.sessions\\" has been replaced by \\"data.search.sessions\\"",
]
`);
});

View file

@ -8,15 +8,8 @@
import type { ConfigDeprecationProvider } from '@kbn/core/server';
export const autocompleteConfigDeprecationProvider: ConfigDeprecationProvider = ({
renameFromRoot,
}) => [
renameFromRoot(
'kibana.autocompleteTerminateAfter',
'data.autocomplete.valueSuggestions.terminateAfter',
{ level: 'warning' }
),
renameFromRoot('kibana.autocompleteTimeout', 'data.autocomplete.valueSuggestions.timeout', {
export const configDeprecationProvider: ConfigDeprecationProvider = ({ renameFromRoot }) => [
renameFromRoot('xpack.data_enhanced.search.sessions', 'data.search.sessions', {
level: 'warning',
}),
];

View file

@ -70,6 +70,7 @@ import {
// tabify
calcAutoIntervalLessThan,
} from '../common';
import { configDeprecationProvider } from './config_deprecations';
export type {
ParsedInterval,
@ -122,6 +123,7 @@ export type { DataPluginSetup as PluginSetup, DataPluginStart as PluginStart };
export { DataServerPlugin as Plugin };
export const config: PluginConfigDescriptor<ConfigSchema> = {
deprecations: configDeprecationProvider,
exposeToBrowser: {
search: true,
},

View file

@ -12,9 +12,14 @@ import { BfetchServerSetup } from '@kbn/bfetch-plugin/server';
import { PluginStart as DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { FieldFormatsSetup, FieldFormatsStart } from '@kbn/field-formats-plugin/server';
import type {
TaskManagerSetupContract,
TaskManagerStartContract,
} from '@kbn/task-manager-plugin/server';
import type { SecurityPluginSetup } from '@kbn/security-plugin/server';
import { ConfigSchema } from '../config';
import type { ISearchSetup, ISearchStart } from './search';
import { DatatableUtilitiesService } from './datatable_utilities';
import type { ISearchSetup, ISearchStart, SearchEnhancements } from './search';
import { SearchService } from './search/search_service';
import { QueryService } from './query/query_service';
import { ScriptsService } from './scripts';
@ -22,10 +27,6 @@ import { KqlTelemetryService } from './kql_telemetry';
import { getUiSettings } from './ui_settings';
import { QuerySetup } from './query';
interface DataEnhancements {
search: SearchEnhancements;
}
export interface DataPluginSetup {
search: ISearchSetup;
query: QuerySetup;
@ -33,10 +34,6 @@ export interface DataPluginSetup {
* @deprecated - use "fieldFormats" plugin directly instead
*/
fieldFormats: FieldFormatsSetup;
/**
* @internal
*/
__enhance: (enhancements: DataEnhancements) => void;
}
export interface DataPluginStart {
@ -58,12 +55,15 @@ export interface DataPluginSetupDependencies {
expressions: ExpressionsServerSetup;
usageCollection?: UsageCollectionSetup;
fieldFormats: FieldFormatsSetup;
taskManager?: TaskManagerSetupContract;
security?: SecurityPluginSetup;
}
export interface DataPluginStartDependencies {
fieldFormats: FieldFormatsStart;
logger: Logger;
dataViews: DataViewsServerPluginStart;
taskManager?: TaskManagerStartContract;
}
export class DataServerPlugin
@ -90,7 +90,14 @@ export class DataServerPlugin
public setup(
core: CoreSetup<DataPluginStartDependencies, DataPluginStart>,
{ bfetch, expressions, usageCollection, fieldFormats }: DataPluginSetupDependencies
{
bfetch,
expressions,
usageCollection,
fieldFormats,
taskManager,
security,
}: DataPluginSetupDependencies
) {
this.scriptsService.setup(core);
const querySetup = this.queryService.setup(core);
@ -102,20 +109,26 @@ export class DataServerPlugin
bfetch,
expressions,
usageCollection,
security,
taskManager,
});
return {
__enhance: (enhancements: DataEnhancements) => {
searchSetup.__enhance(enhancements.search);
},
search: searchSetup,
query: querySetup,
fieldFormats,
};
}
public start(core: CoreStart, { fieldFormats, dataViews }: DataPluginStartDependencies) {
const search = this.searchService.start(core, { fieldFormats, indexPatterns: dataViews });
public start(
core: CoreStart,
{ fieldFormats, dataViews, taskManager }: DataPluginStartDependencies
) {
const search = this.searchService.start(core, {
fieldFormats,
indexPatterns: dataViews,
taskManager,
});
const datatableUtilities = new DatatableUtilitiesService(
search.aggs,
dataViews,

View file

@ -8,8 +8,8 @@
import { once, debounce } from 'lodash';
import type { CoreSetup, Logger } from '@kbn/core/server';
import type { IEsSearchResponse, ISearchOptions } from '../../../common';
import { isCompleteResponse } from '../../../common';
import type { IEsSearchResponse, ISearchOptions } from '../../../../common';
import { isCompleteResponse } from '../../../../common';
import { CollectedUsage } from './register';
const SAVED_OBJECT_ID = 'search-telemetry';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { SavedObjectsErrorHelpers, Logger } from '@kbn/core/server';

View file

@ -1,14 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { Logger } from '@kbn/core/server';
import { CollectorFetchContext } from '@kbn/usage-collection-plugin/server';
import { SEARCH_SESSION_TYPE } from '@kbn/data-plugin/common';
import { ReportedUsage } from './register';
import { SEARCH_SESSION_TYPE } from '../../../../common';
interface SessionPersistedTermsBucket {
key_as_string: 'false' | 'true';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { registerUsageCollector } from './register';

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { Logger } from '@kbn/core/server';

View file

@ -10,8 +10,8 @@ export * from './types';
export * from './strategies/es_search';
export * from './strategies/ese_search';
export * from './strategies/eql_search';
export type { SearchUsage } from './collectors';
export { usageProvider, searchUsageObserver } from './collectors';
export type { SearchUsage } from './collectors/search';
export { usageProvider, searchUsageObserver } from './collectors/search';
export * from './aggs';
export * from './session';
export * from './errors/no_search_id_in_session';

View file

@ -16,7 +16,6 @@ export function createSearchSetupMock(): jest.Mocked<ISearchSetup> {
return {
aggs: searchAggsSetupMock(),
registerSearchStrategy: jest.fn(),
__enhance: jest.fn(),
searchSource: searchSourceMock.createSetupContract(),
};
}

View file

@ -7,3 +7,4 @@
*/
export * from './search';
export * from './session';

View file

@ -1,16 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { MockedKeys } from '@kbn/utility-types/jest';
import type { CoreSetup, Logger } from '@kbn/core/server';
import { coreMock, httpServerMock } from '@kbn/core/server/mocks';
import type { PluginStart as DataPluginStart } from '@kbn/data-plugin/server';
import { dataPluginMock } from '@kbn/data-plugin/server/mocks';
import type { PluginStart as DataPluginStart } from '../..';
import { dataPluginMock } from '../../mocks';
import { registerSessionRoutes } from './session';
enum PostHandlerIndex {

View file

@ -1,18 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { schema } from '@kbn/config-schema';
import { Logger } from '@kbn/core/server';
import { reportServerError } from '@kbn/kibana-utils-plugin/server';
import { DataEnhancedPluginRouter } from '../type';
import { DataPluginRouter } from '../types';
const STORE_SEARCH_SESSIONS_ROLE_TAG = `access:store_search_session`;
export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: Logger): void {
export function registerSessionRoutes(router: DataPluginRouter, logger: Logger): void {
router.post(
{
path: '/internal/session',

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './search_session';

View file

@ -1,12 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { SavedObjectsType } from '@kbn/core/server';
import { SEARCH_SESSION_TYPE } from '@kbn/data-plugin/common';
import { SEARCH_SESSION_TYPE } from '../../../common';
import { searchSessionSavedObjectMigrations } from './search_session_migration';
export const searchSessionSavedObjectType: SavedObjectsType = {

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import {
@ -12,7 +13,7 @@ import {
SearchSessionSavedObjectAttributesPre$8$0$0,
} from './search_session_migration';
import { SavedObject } from '@kbn/core/types';
import { SEARCH_SESSION_TYPE, SearchSessionStatus } from '@kbn/data-plugin/common';
import { SEARCH_SESSION_TYPE, SearchSessionStatus } from '../../../common';
import { SavedObjectMigrationContext } from '@kbn/core/server';
describe('7.12.0 -> 7.13.0', () => {

View file

@ -1,15 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { SavedObjectMigrationMap, SavedObjectUnsanitizedDoc } from '@kbn/core/server';
import {
SearchSessionSavedObjectAttributes as SearchSessionSavedObjectAttributesLatest,
SearchSessionStatus,
} from '@kbn/data-plugin/common';
} from '../../../common';
/**
* Search sessions were released in 7.12.0

View file

@ -22,7 +22,6 @@ import type {
IEsSearchResponse,
IScopedSearchClient,
IScopedSearchSessionsClient,
ISearchSessionService,
ISearchStart,
ISearchStrategy,
} from '.';
@ -32,6 +31,16 @@ import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks';
import { createSearchSessionsClientMock } from './mocks';
import { ENHANCED_ES_SEARCH_STRATEGY } from '../../common';
let mockSessionClient: jest.Mocked<IScopedSearchSessionsClient>;
jest.mock('./session', () => {
class SearchSessionService {
asScopedProvider = () => (request: any) => mockSessionClient;
}
return {
SearchSessionService,
};
});
describe('Search service', () => {
let plugin: SearchService;
let mockCoreSetup: MockedKeys<CoreSetup<DataPluginStartDependencies, DataPluginStart>>;
@ -88,8 +97,7 @@ describe('Search service', () => {
let searchPluginStart: ISearchStart<IEsSearchRequest, IEsSearchResponse<any>>;
let mockStrategy: any;
let mockStrategyNoCancel: jest.Mocked<ISearchStrategy>;
let mockSessionService: ISearchSessionService<any>;
let mockSessionClient: jest.Mocked<IScopedSearchSessionsClient>;
const sessionId = '1234';
beforeEach(() => {
@ -104,9 +112,6 @@ describe('Search service', () => {
};
mockSessionClient = createSearchSessionsClientMock();
mockSessionService = {
asScopedProvider: () => (request: any) => mockSessionClient,
};
const pluginSetup = plugin.setup(mockCoreSetup, {
bfetch: bfetchPluginMock.createSetupContract(),
@ -114,9 +119,6 @@ describe('Search service', () => {
});
pluginSetup.registerSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY, mockStrategy);
pluginSetup.registerSearchStrategy('nocancel', mockStrategyNoCancel);
pluginSetup.__enhance({
sessionService: mockSessionService,
});
searchPluginStart = plugin.start(mockCoreStart, {
fieldFormats: createFieldFormatsStartMock(),

View file

@ -26,58 +26,63 @@ import { FieldFormatsStart } from '@kbn/field-formats-plugin/server';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { KbnServerError } from '@kbn/kibana-utils-plugin/server';
import type {
TaskManagerSetupContract,
TaskManagerStartContract,
} from '@kbn/task-manager-plugin/server';
import type { SecurityPluginSetup } from '@kbn/security-plugin/server';
import type {
DataRequestHandlerContext,
IScopedSearchClient,
ISearchSetup,
ISearchStart,
ISearchStrategy,
SearchEnhancements,
SearchStrategyDependencies,
DataRequestHandlerContext,
} from './types';
import { AggsService } from './aggs';
import { IndexPatternsServiceStart } from '../data_views';
import { registerSearchRoute } from './routes';
import { registerSearchRoute, registerSessionRoutes } from './routes';
import { ES_SEARCH_STRATEGY, esSearchStrategyProvider } from './strategies/es_search';
import { DataPluginStart, DataPluginStartDependencies } from '../plugin';
import { registerUsageCollector } from './collectors/register';
import { usageProvider } from './collectors/usage';
import { usageProvider } from './collectors/search/usage';
import { registerUsageCollector as registerSearchUsageCollector } from './collectors/search/register';
import { registerUsageCollector as registerSearchSessionUsageCollector } from './collectors/search_session/register';
import { searchTelemetry } from '../saved_objects';
import {
cidrFunction,
dateRangeFunction,
ENHANCED_ES_SEARCH_STRATEGY,
EQL_SEARCH_STRATEGY,
esRawResponse,
existsFilterFunction,
extendedBoundsFunction,
fieldFunction,
geoBoundingBoxFunction,
geoPointFunction,
IEsSearchRequest,
IEsSearchResponse,
IKibanaSearchRequest,
IKibanaSearchResponse,
ISearchOptions,
cidrFunction,
dateRangeFunction,
extendedBoundsFunction,
geoBoundingBoxFunction,
geoPointFunction,
ipRangeFunction,
ISearchOptions,
kibana,
kibanaContext,
kibanaTimerangeFunction,
kibanaFilterFunction,
kibanaTimerangeFunction,
kqlFunction,
luceneFunction,
numericalRangeFunction,
phraseFilterFunction,
queryFilterFunction,
rangeFilterFunction,
removeFilterFunction,
selectFilterFunction,
rangeFunction,
removeFilterFunction,
SearchSourceDependencies,
searchSourceRequiredUiSettings,
SearchSourceService,
phraseFilterFunction,
esRawResponse,
eqlRawResponse,
ENHANCED_ES_SEARCH_STRATEGY,
EQL_SEARCH_STRATEGY,
SQL_SEARCH_STRATEGY,
} from '../../common/search';
import { getEsaggs, getEsdsl, getEql } from './expressions';
@ -87,7 +92,7 @@ import {
} from '../../common/search/aggs/buckets/shard_delay';
import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn';
import { ConfigSchema } from '../../config';
import { ISearchSessionService, SearchSessionService } from './session';
import { SearchSessionService } from './session';
import { registerBsearchRoute } from './routes/bsearch';
import { getKibanaContext } from './expressions/kibana_context';
import { enhancedEsSearchStrategyProvider } from './strategies/ese_search';
@ -95,6 +100,7 @@ import { eqlSearchStrategyProvider } from './strategies/eql_search';
import { NoSearchIdInSessionError } from './errors/no_search_id_in_session';
import { CachedUiSettingsClient } from './services';
import { sqlSearchStrategyProvider } from './strategies/sql_search';
import { searchSessionSavedObjectType } from './saved_objects';
type StrategyMap = Record<string, ISearchStrategy<any, any>>;
@ -103,12 +109,15 @@ export interface SearchServiceSetupDependencies {
bfetch: BfetchServerSetup;
expressions: ExpressionsServerSetup;
usageCollection?: UsageCollectionSetup;
taskManager?: TaskManagerSetupContract;
security?: SecurityPluginSetup;
}
/** @internal */
export interface SearchServiceStartDependencies {
fieldFormats: FieldFormatsStart;
indexPatterns: IndexPatternsServiceStart;
taskManager?: TaskManagerStartContract;
}
/** @internal */
@ -121,7 +130,7 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
private readonly aggsService = new AggsService();
private readonly searchSourceService = new SearchSourceService();
private searchStrategies: StrategyMap = {};
private sessionService: ISearchSessionService;
private sessionService: SearchSessionService;
private asScoped!: ISearchStart['asScoped'];
private searchAsInternalUser!: ISearchStrategy;
@ -129,17 +138,31 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
private initializerContext: PluginInitializerContext<ConfigSchema>,
private readonly logger: Logger
) {
this.sessionService = new SearchSessionService();
this.sessionService = new SearchSessionService(
logger,
initializerContext.config.get(),
initializerContext.env.packageInfo.version
);
}
public setup(
core: CoreSetup<DataPluginStartDependencies, DataPluginStart>,
{ bfetch, expressions, usageCollection }: SearchServiceSetupDependencies
{ bfetch, expressions, usageCollection, taskManager, security }: SearchServiceSetupDependencies
): ISearchSetup {
core.savedObjects.registerType(searchSessionSavedObjectType);
const usage = usageCollection ? usageProvider(core) : undefined;
const router = core.http.createRouter<DataRequestHandlerContext>();
registerSearchRoute(router);
registerSessionRoutes(router, this.logger);
if (taskManager) {
this.sessionService.setup(core, { taskManager, security });
} else {
// this should never happen in real world, but
// taskManager and security are optional deps because they are in x-pack
this.logger.debug('Skipping sessionService setup because taskManager is not available');
}
core.http.registerRouteHandlerContext<DataRequestHandlerContext, 'search'>(
'search',
@ -188,7 +211,12 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
core.savedObjects.registerType(searchTelemetry);
if (usageCollection) {
registerUsageCollector(usageCollection, core.savedObjects.getKibanaIndex());
registerSearchUsageCollector(usageCollection, core.savedObjects.getKibanaIndex());
registerSearchSessionUsageCollector(
usageCollection,
core.savedObjects.getKibanaIndex(),
this.logger
);
}
expressions.registerFunction(getEsaggs({ getStartServices: core.getStartServices }));
@ -229,9 +257,6 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
});
return {
__enhance: (enhancements: SearchEnhancements) => {
this.sessionService = enhancements.sessionService;
},
aggs,
registerSearchStrategy: this.registerSearchStrategy,
usage,
@ -241,9 +266,14 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
public start(
core: CoreStart,
{ fieldFormats, indexPatterns }: SearchServiceStartDependencies
{ fieldFormats, indexPatterns, taskManager }: SearchServiceStartDependencies
): ISearchStart {
const { elasticsearch, savedObjects, uiSettings } = core;
if (taskManager) {
this.sessionService.start(core, { taskManager });
}
this.asScoped = this.asScopedProvider(core);
return {
aggs: this.aggsService.start({

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { checkNonPersistedSessions as checkNonPersistedSessions$ } from './check_non_persisted_sessions';
@ -11,25 +12,28 @@ import {
SearchSessionSavedObjectAttributes,
ENHANCED_ES_SEARCH_STRATEGY,
EQL_SEARCH_STRATEGY,
} from '@kbn/data-plugin/common';
} from '../../../common';
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
import { SearchSessionsConfig, CheckSearchSessionsDeps, SearchStatus } from './types';
import { CheckSearchSessionsDeps, SearchStatus } from './types';
import moment from 'moment';
import {
SavedObjectsBulkUpdateObject,
SavedObjectsDeleteOptions,
SavedObjectsClientContract,
} from '@kbn/core/server';
import { SearchSessionsConfigSchema } from '../../../config';
jest.useFakeTimers();
const checkNonPersistedSessions = (deps: CheckSearchSessionsDeps, config: SearchSessionsConfig) =>
checkNonPersistedSessions$(deps, config).toPromise();
const checkNonPersistedSessions = (
deps: CheckSearchSessionsDeps,
config: SearchSessionsConfigSchema
) => checkNonPersistedSessions$(deps, config).toPromise();
describe('checkNonPersistedSessions', () => {
let mockClient: any;
let savedObjectsClient: jest.Mocked<SavedObjectsClientContract>;
const config: SearchSessionsConfig = {
const config: SearchSessionsConfigSchema = {
enabled: true,
pageSize: 5,
notTouchedInProgressTimeout: moment.duration(1, 'm'),

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { SavedObjectsFindResult } from '@kbn/core/server';
@ -16,17 +17,18 @@ import {
SearchSessionSavedObjectAttributes,
SearchSessionStatus,
KueryNode,
} from '@kbn/data-plugin/common';
} from '../../../common';
import { checkSearchSessionsByPage, getSearchSessionsPage$ } from './get_search_session_page';
import { SearchSessionsConfig, CheckSearchSessionsDeps, SearchStatus } from './types';
import { CheckSearchSessionsDeps, SearchStatus } from './types';
import { bulkUpdateSessions, getAllSessionsStatusUpdates } from './update_session_status';
import { SearchSessionsConfigSchema } from '../../../config';
export const SEARCH_SESSIONS_CLEANUP_TASK_TYPE = 'search_sessions_cleanup';
export const SEARCH_SESSIONS_CLEANUP_TASK_ID = `data_enhanced_${SEARCH_SESSIONS_CLEANUP_TASK_TYPE}`;
function isSessionStale(
session: SavedObjectsFindResult<SearchSessionSavedObjectAttributes>,
config: SearchSessionsConfig
config: SearchSessionsConfigSchema
) {
const curTime = moment();
// Delete cancelled sessions immediately
@ -45,7 +47,7 @@ function isSessionStale(
function checkNonPersistedSessionsPage(
deps: CheckSearchSessionsDeps,
config: SearchSessionsConfig,
config: SearchSessionsConfigSchema,
filter: KueryNode,
page: number
) {
@ -118,7 +120,7 @@ function checkNonPersistedSessionsPage(
export function checkNonPersistedSessions(
deps: CheckSearchSessionsDeps,
config: SearchSessionsConfig
config: SearchSessionsConfigSchema
) {
const { logger } = deps;

View file

@ -1,20 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { checkPersistedSessionsProgress } from './check_persisted_sessions';
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
import { SearchSessionsConfig } from './types';
import moment from 'moment';
import { SavedObjectsClientContract } from '@kbn/core/server';
import { SearchSessionsConfigSchema } from '../../../config';
describe('checkPersistedSessionsProgress', () => {
let mockClient: any;
let savedObjectsClient: jest.Mocked<SavedObjectsClientContract>;
const config: SearchSessionsConfig = {
const config: SearchSessionsConfigSchema = {
enabled: true,
pageSize: 5,
notTouchedInProgressTimeout: moment.duration(1, 'm'),

View file

@ -1,28 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EMPTY, Observable } from 'rxjs';
import { catchError, concatMap } from 'rxjs/operators';
import {
nodeBuilder,
SEARCH_SESSION_TYPE,
SearchSessionStatus,
KueryNode,
} from '@kbn/data-plugin/common';
import { nodeBuilder, SEARCH_SESSION_TYPE, SearchSessionStatus, KueryNode } from '../../../common';
import { checkSearchSessionsByPage, getSearchSessionsPage$ } from './get_search_session_page';
import { SearchSessionsConfig, CheckSearchSessionsDeps, SearchSessionsResponse } from './types';
import { CheckSearchSessionsDeps, SearchSessionsResponse } from './types';
import { bulkUpdateSessions, getAllSessionsStatusUpdates } from './update_session_status';
import { SearchSessionsConfigSchema } from '../../../config';
export const SEARCH_SESSIONS_TASK_TYPE = 'search_sessions_monitor';
export const SEARCH_SESSIONS_TASK_ID = `data_enhanced_${SEARCH_SESSIONS_TASK_TYPE}`;
function checkPersistedSessionsPage(
deps: CheckSearchSessionsDeps,
config: SearchSessionsConfig,
config: SearchSessionsConfigSchema,
filter: KueryNode,
page: number
): Observable<SearchSessionsResponse> {
@ -50,7 +47,7 @@ function checkPersistedSessionsPage(
export function checkPersistedSessionsProgress(
deps: CheckSearchSessionsDeps,
config: SearchSessionsConfig
config: SearchSessionsConfigSchema
) {
const { logger } = deps;

View file

@ -1,28 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EMPTY, Observable } from 'rxjs';
import { catchError, concatMap } from 'rxjs/operators';
import {
nodeBuilder,
SEARCH_SESSION_TYPE,
SearchSessionStatus,
KueryNode,
} from '@kbn/data-plugin/common';
import { nodeBuilder, SEARCH_SESSION_TYPE, SearchSessionStatus, KueryNode } from '../../../common';
import { checkSearchSessionsByPage, getSearchSessionsPage$ } from './get_search_session_page';
import { SearchSessionsConfig, CheckSearchSessionsDeps, SearchSessionsResponse } from './types';
import { CheckSearchSessionsDeps, SearchSessionsResponse } from './types';
import { bulkUpdateSessions, getAllSessionsStatusUpdates } from './update_session_status';
import { SearchSessionsConfigSchema } from '../../../config';
export const SEARCH_SESSIONS_EXPIRE_TASK_TYPE = 'search_sessions_expire';
export const SEARCH_SESSIONS_EXPIRE_TASK_ID = `data_enhanced_${SEARCH_SESSIONS_EXPIRE_TASK_TYPE}`;
function checkSessionExpirationPage(
deps: CheckSearchSessionsDeps,
config: SearchSessionsConfig,
config: SearchSessionsConfigSchema,
filter: KueryNode,
page: number
): Observable<SearchSessionsResponse> {
@ -46,7 +43,7 @@ function checkSessionExpirationPage(
export function checkPersistedCompletedSessionExpiration(
deps: CheckSearchSessionsDeps,
config: SearchSessionsConfig
config: SearchSessionsConfigSchema
) {
const { logger } = deps;

View file

@ -1,24 +1,27 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { checkSearchSessionsByPage, getSearchSessionsPage$ } from './get_search_session_page';
import { SearchSessionStatus, ENHANCED_ES_SEARCH_STRATEGY } from '@kbn/data-plugin/common';
import { ENHANCED_ES_SEARCH_STRATEGY, SearchSessionStatus } from '../../../common';
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
import { SearchSessionsConfig, SearchStatus } from './types';
import { SearchStatus } from './types';
import moment from 'moment';
import { SavedObjectsClientContract } from '@kbn/core/server';
import { of, Subject, throwError } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { SearchSessionsConfigSchema } from '../../../config';
jest.useFakeTimers();
describe('checkSearchSessionsByPage', () => {
const mockClient = {} as any;
let savedObjectsClient: jest.Mocked<SavedObjectsClientContract>;
const config: SearchSessionsConfig = {
const config: SearchSessionsConfigSchema = {
enabled: true,
pageSize: 5,
management: {} as any,

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { SavedObjectsClientContract, Logger } from '@kbn/core/server';
@ -12,8 +13,9 @@ import {
SearchSessionSavedObjectAttributes,
SEARCH_SESSION_TYPE,
KueryNode,
} from '@kbn/data-plugin/common';
import { CheckSearchSessionsDeps, CheckSearchSessionsFn, SearchSessionsConfig } from './types';
} from '../../../common';
import { CheckSearchSessionsDeps, CheckSearchSessionsFn } from './types';
import { SearchSessionsConfigSchema } from '../../../config';
export interface GetSessionsDeps {
savedObjectsClient: SavedObjectsClientContract;
@ -43,7 +45,7 @@ export function getSearchSessionsPage$(
export const checkSearchSessionsByPage = (
checkFn: CheckSearchSessionsFn,
deps: CheckSearchSessionsDeps,
config: SearchSessionsConfig,
config: SearchSessionsConfigSchema,
filters: any,
nextPage = 1
): Observable<void> =>

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { SearchStatus } from './types';

View file

@ -1,15 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import type { TransportResult } from '@elastic/elasticsearch';
import { ElasticsearchClient } from '@kbn/core/server';
import { SearchSessionRequestInfo } from '@kbn/data-plugin/common';
import { AsyncSearchStatusResponse } from '@kbn/data-plugin/server';
import { SearchSessionRequestInfo } from '../../../common';
import { AsyncSearchStatusResponse } from '../..';
import { SearchStatus } from './types';
export async function getSearchStatus(
@ -30,7 +31,7 @@ export async function getSearchStatus(
if ((response.is_partial && !response.is_running) || response.completion_status >= 400) {
return {
status: SearchStatus.ERROR,
error: i18n.translate('xpack.data.search.statusError', {
error: i18n.translate('data.search.statusError', {
defaultMessage: `Search completed with a {errorCode} status`,
values: { errorCode: response.completion_status },
}),
@ -49,7 +50,7 @@ export async function getSearchStatus(
} catch (e) {
return {
status: SearchStatus.ERROR,
error: i18n.translate('xpack.data.search.statusThrow', {
error: i18n.translate('data.search.statusThrow', {
defaultMessage: `Search status threw an error {message} ({errorCode}) status`,
values: {
message: e.message,

View file

@ -1,19 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { SearchSessionsConfig, SearchStatus } from './types';
import { SearchStatus } from './types';
import { getSessionStatus } from './get_session_status';
import { SearchSessionStatus } from '@kbn/data-plugin/common';
import { SearchSessionStatus } from '../../../common';
import moment from 'moment';
import { SearchSessionsConfigSchema } from '../../../config';
describe('getSessionStatus', () => {
const mockConfig = {
notTouchedInProgressTimeout: moment.duration(1, 'm'),
} as unknown as SearchSessionsConfig;
} as unknown as SearchSessionsConfigSchema;
test("returns an in_progress status if there's nothing inside the session", () => {
const session: any = {
idMapping: {},

View file

@ -1,17 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import moment from 'moment';
import { SearchSessionSavedObjectAttributes, SearchSessionStatus } from '@kbn/data-plugin/common';
import { SearchSessionsConfig, SearchStatus } from './types';
import { SearchSessionSavedObjectAttributes, SearchSessionStatus } from '../../../common';
import { SearchStatus } from './types';
import { SearchSessionsConfigSchema } from '../../../config';
export function getSessionStatus(
session: SearchSessionSavedObjectAttributes,
config: SearchSessionsConfig
config: SearchSessionsConfigSchema
): SearchSessionStatus {
const searchStatuses = Object.values(session.idMapping);
const curTime = moment();

View file

@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import {
@ -17,9 +18,9 @@ import moment from 'moment';
import { coreMock } from '@kbn/core/server/mocks';
import { ConfigSchema } from '../../../config';
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { AuthenticatedUser } from '@kbn/security-plugin/common/model';
import { nodeBuilder, SEARCH_SESSION_TYPE, SearchSessionStatus } from '@kbn/data-plugin/common';
import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server';
import type { AuthenticatedUser } from '@kbn/security-plugin/common/model';
import { nodeBuilder, SEARCH_SESSION_TYPE, SearchSessionStatus } from '../../../common';
import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server';
const MAX_UPDATE_RETRIES = 3;
@ -81,13 +82,14 @@ describe('SearchSessionService', () => {
management: {} as any,
},
},
};
} as unknown as ConfigSchema;
const mockLogger: any = {
debug: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};
service = new SearchSessionService(mockLogger, config, '8.0.0');
service.setup(coreMock.createSetup(), { taskManager: taskManagerMock.createSetup() });
const coreStart = coreMock.createStart();
mockTaskManager = taskManagerMock.createStart();
await flushPromises();
@ -161,13 +163,14 @@ describe('SearchSessionService', () => {
management: {} as any,
},
},
};
} as unknown as ConfigSchema;
const mockLogger: any = {
debug: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};
service = new SearchSessionService(mockLogger, config, '8.0.0');
service.setup(coreMock.createSetup(), { taskManager: taskManagerMock.createSetup() });
const coreStart = coreMock.createStart();
mockTaskManager = taskManagerMock.createStart();
await flushPromises();

View file

@ -6,47 +6,549 @@
* Side Public License, v 1.
*/
import { ISearchSessionService } from './types';
import { notFound } from '@hapi/boom';
import { debounce } from 'lodash';
import { nodeBuilder, fromKueryExpression } from '@kbn/es-query';
import {
CoreSetup,
CoreStart,
KibanaRequest,
SavedObjectsClientContract,
Logger,
SavedObject,
SavedObjectsFindOptions,
SavedObjectsErrorHelpers,
} from '@kbn/core/server';
import type { AuthenticatedUser, SecurityPluginSetup } from '@kbn/security-plugin/server';
import type {
TaskManagerSetupContract,
TaskManagerStartContract,
} from '@kbn/task-manager-plugin/server';
import {
IKibanaSearchRequest,
ISearchOptions,
ENHANCED_ES_SEARCH_STRATEGY,
SEARCH_SESSION_TYPE,
SearchSessionRequestInfo,
SearchSessionSavedObjectAttributes,
SearchSessionStatus,
} from '../../../common';
import { ISearchSessionService, NoSearchIdInSessionError } from '../..';
import { createRequestHash } from './utils';
import { ConfigSchema, SearchSessionsConfigSchema } from '../../../config';
import {
registerSearchSessionsTask,
scheduleSearchSessionsTask,
unscheduleSearchSessionsTask,
} from './setup_task';
import { SearchStatus } from './types';
import {
checkPersistedSessionsProgress,
SEARCH_SESSIONS_TASK_ID,
SEARCH_SESSIONS_TASK_TYPE,
} from './check_persisted_sessions';
import {
SEARCH_SESSIONS_CLEANUP_TASK_TYPE,
checkNonPersistedSessions,
SEARCH_SESSIONS_CLEANUP_TASK_ID,
} from './check_non_persisted_sessions';
import {
SEARCH_SESSIONS_EXPIRE_TASK_TYPE,
SEARCH_SESSIONS_EXPIRE_TASK_ID,
checkPersistedCompletedSessionExpiration,
} from './expire_persisted_sessions';
/**
* The OSS session service, which leaves most search session-related logic unimplemented.
* @see x-pack/plugins/data_enhanced/server/search/session/session_service.ts
* @internal
*/
export class SearchSessionService implements ISearchSessionService {
constructor() {}
public asScopedProvider() {
return () => ({
getId: () => {
throw new Error('getId not implemented in OSS search session service');
},
trackId: async () => {},
getSearchIdMapping: async () => new Map<string, string>(),
save: async () => {
throw new Error('save not implemented in OSS search session service');
},
get: async () => {
throw new Error('get not implemented in OSS search session service');
},
find: async () => {
throw new Error('find not implemented in OSS search session service');
},
update: async () => {
throw new Error('update not implemented in OSS search session service');
},
extend: async () => {
throw new Error('extend not implemented in OSS search session service');
},
cancel: async () => {
throw new Error('cancel not implemented in OSS search session service');
},
delete: async () => {
throw new Error('delete not implemented in OSS search session service');
},
getConfig: () => {
return null;
},
});
}
export interface SearchSessionDependencies {
savedObjectsClient: SavedObjectsClientContract;
}
interface SetupDependencies {
taskManager: TaskManagerSetupContract;
security?: SecurityPluginSetup;
}
interface StartDependencies {
taskManager: TaskManagerStartContract;
}
const DEBOUNCE_UPDATE_OR_CREATE_WAIT = 1000;
const DEBOUNCE_UPDATE_OR_CREATE_MAX_WAIT = 5000;
interface UpdateOrCreateQueueEntry {
deps: SearchSessionDependencies;
user: AuthenticatedUser | null;
sessionId: string;
attributes: Partial<SearchSessionSavedObjectAttributes>;
resolve: () => void;
reject: (reason?: unknown) => void;
}
function sleep(ms: number) {
return new Promise((r) => setTimeout(r, ms));
}
export class SearchSessionService
implements ISearchSessionService<SearchSessionSavedObjectAttributes>
{
private sessionConfig: SearchSessionsConfigSchema;
private readonly updateOrCreateBatchQueue: UpdateOrCreateQueueEntry[] = [];
private security?: SecurityPluginSetup;
private setupCompleted = false;
constructor(
private readonly logger: Logger,
private readonly config: ConfigSchema,
private readonly version: string
) {
this.sessionConfig = this.config.search.sessions;
}
public setup(core: CoreSetup, deps: SetupDependencies) {
this.security = deps.security;
const taskDeps = {
config: this.config,
taskManager: deps.taskManager,
logger: this.logger,
};
registerSearchSessionsTask(
core,
taskDeps,
SEARCH_SESSIONS_TASK_TYPE,
'persisted session progress',
checkPersistedSessionsProgress
);
registerSearchSessionsTask(
core,
taskDeps,
SEARCH_SESSIONS_CLEANUP_TASK_TYPE,
'non persisted session cleanup',
checkNonPersistedSessions
);
registerSearchSessionsTask(
core,
taskDeps,
SEARCH_SESSIONS_EXPIRE_TASK_TYPE,
'complete session expiration',
checkPersistedCompletedSessionExpiration
);
this.setupCompleted = true;
}
public async start(core: CoreStart, deps: StartDependencies) {
if (!this.setupCompleted)
throw new Error('SearchSessionService setup() must be called before start()');
return this.setupMonitoring(core, deps);
}
public stop() {}
private setupMonitoring = async (core: CoreStart, deps: StartDependencies) => {
const taskDeps = {
config: this.config,
taskManager: deps.taskManager,
logger: this.logger,
};
if (this.sessionConfig.enabled) {
scheduleSearchSessionsTask(
taskDeps,
SEARCH_SESSIONS_TASK_ID,
SEARCH_SESSIONS_TASK_TYPE,
this.sessionConfig.trackingInterval
);
scheduleSearchSessionsTask(
taskDeps,
SEARCH_SESSIONS_CLEANUP_TASK_ID,
SEARCH_SESSIONS_CLEANUP_TASK_TYPE,
this.sessionConfig.cleanupInterval
);
scheduleSearchSessionsTask(
taskDeps,
SEARCH_SESSIONS_EXPIRE_TASK_ID,
SEARCH_SESSIONS_EXPIRE_TASK_TYPE,
this.sessionConfig.expireInterval
);
} else {
unscheduleSearchSessionsTask(taskDeps, SEARCH_SESSIONS_TASK_ID);
unscheduleSearchSessionsTask(taskDeps, SEARCH_SESSIONS_CLEANUP_TASK_ID);
unscheduleSearchSessionsTask(taskDeps, SEARCH_SESSIONS_EXPIRE_TASK_ID);
}
};
private processUpdateOrCreateBatchQueue = debounce(
() => {
const queue = [...this.updateOrCreateBatchQueue];
if (queue.length === 0) return;
this.updateOrCreateBatchQueue.length = 0;
const batchedSessionAttributes = queue.reduce((res, next) => {
if (!res[next.sessionId]) {
res[next.sessionId] = next.attributes;
} else {
res[next.sessionId] = {
...res[next.sessionId],
...next.attributes,
idMapping: {
...res[next.sessionId].idMapping,
...next.attributes.idMapping,
},
};
}
return res;
}, {} as { [sessionId: string]: Partial<SearchSessionSavedObjectAttributes> });
Object.keys(batchedSessionAttributes).forEach((sessionId) => {
const thisSession = queue.filter((s) => s.sessionId === sessionId);
this.updateOrCreate(
thisSession[0].deps,
thisSession[0].user,
sessionId,
batchedSessionAttributes[sessionId]
)
.then(() => {
thisSession.forEach((s) => s.resolve());
})
.catch((e) => {
thisSession.forEach((s) => s.reject(e));
});
});
},
DEBOUNCE_UPDATE_OR_CREATE_WAIT,
{ maxWait: DEBOUNCE_UPDATE_OR_CREATE_MAX_WAIT }
);
private scheduleUpdateOrCreate = (
deps: SearchSessionDependencies,
user: AuthenticatedUser | null,
sessionId: string,
attributes: Partial<SearchSessionSavedObjectAttributes>
): Promise<void> => {
return new Promise((resolve, reject) => {
this.updateOrCreateBatchQueue.push({ deps, user, sessionId, attributes, resolve, reject });
// TODO: this would be better if we'd debounce per sessionId
this.processUpdateOrCreateBatchQueue();
});
};
private updateOrCreate = async (
deps: SearchSessionDependencies,
user: AuthenticatedUser | null,
sessionId: string,
attributes: Partial<SearchSessionSavedObjectAttributes>,
retry: number = 1
): Promise<SavedObject<SearchSessionSavedObjectAttributes> | undefined> => {
const retryOnConflict = async (e: any) => {
this.logger.debug(`Conflict error | ${sessionId}`);
// Randomize sleep to spread updates out in case of conflicts
await sleep(100 + Math.random() * 50);
return await this.updateOrCreate(deps, user, sessionId, attributes, retry + 1);
};
this.logger.debug(`updateOrCreate | ${sessionId} | ${retry}`);
try {
return (await this.update(
deps,
user,
sessionId,
attributes
)) as SavedObject<SearchSessionSavedObjectAttributes>;
} catch (e) {
if (SavedObjectsErrorHelpers.isNotFoundError(e)) {
try {
this.logger.debug(`Object not found | ${sessionId}`);
return await this.create(deps, user, sessionId, attributes);
} catch (createError) {
if (
SavedObjectsErrorHelpers.isConflictError(createError) &&
retry < this.sessionConfig.maxUpdateRetries
) {
return await retryOnConflict(createError);
} else {
this.logger.error(createError);
}
}
} else if (
SavedObjectsErrorHelpers.isConflictError(e) &&
retry < this.sessionConfig.maxUpdateRetries
) {
return await retryOnConflict(e);
} else {
this.logger.error(e);
}
}
return undefined;
};
public save = async (
deps: SearchSessionDependencies,
user: AuthenticatedUser | null,
sessionId: string,
{
name,
appId,
locatorId,
initialState = {},
restoreState = {},
}: Partial<SearchSessionSavedObjectAttributes>
) => {
if (!this.sessionConfig.enabled) throw new Error('Search sessions are disabled');
if (!name) throw new Error('Name is required');
if (!appId) throw new Error('AppId is required');
if (!locatorId) throw new Error('locatorId is required');
return this.updateOrCreate(deps, user, sessionId, {
name,
appId,
locatorId,
initialState,
restoreState,
persisted: true,
});
};
private create = (
{ savedObjectsClient }: SearchSessionDependencies,
user: AuthenticatedUser | null,
sessionId: string,
attributes: Partial<SearchSessionSavedObjectAttributes>
) => {
this.logger.debug(`create | ${sessionId}`);
const realmType = user?.authentication_realm.type;
const realmName = user?.authentication_realm.name;
const username = user?.username;
return savedObjectsClient.create<SearchSessionSavedObjectAttributes>(
SEARCH_SESSION_TYPE,
{
sessionId,
status: SearchSessionStatus.IN_PROGRESS,
expires: new Date(
Date.now() + this.sessionConfig.defaultExpiration.asMilliseconds()
).toISOString(),
created: new Date().toISOString(),
touched: new Date().toISOString(),
idMapping: {},
persisted: false,
version: this.version,
realmType,
realmName,
username,
...attributes,
},
{ id: sessionId }
);
};
public get = async (
{ savedObjectsClient }: SearchSessionDependencies,
user: AuthenticatedUser | null,
sessionId: string
) => {
this.logger.debug(`get | ${sessionId}`);
const session = await savedObjectsClient.get<SearchSessionSavedObjectAttributes>(
SEARCH_SESSION_TYPE,
sessionId
);
this.throwOnUserConflict(user, session);
return session;
};
public find = (
{ savedObjectsClient }: SearchSessionDependencies,
user: AuthenticatedUser | null,
options: Omit<SavedObjectsFindOptions, 'type'>
) => {
const userFilters =
user === null
? []
: [
nodeBuilder.is(
`${SEARCH_SESSION_TYPE}.attributes.realmType`,
`${user.authentication_realm.type}`
),
nodeBuilder.is(
`${SEARCH_SESSION_TYPE}.attributes.realmName`,
`${user.authentication_realm.name}`
),
nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.username`, `${user.username}`),
];
const filterKueryNode =
typeof options.filter === 'string' ? fromKueryExpression(options.filter) : options.filter;
const filter = nodeBuilder.and(userFilters.concat(filterKueryNode ?? []));
return savedObjectsClient.find<SearchSessionSavedObjectAttributes>({
...options,
filter,
type: SEARCH_SESSION_TYPE,
});
};
public update = async (
deps: SearchSessionDependencies,
user: AuthenticatedUser | null,
sessionId: string,
attributes: Partial<SearchSessionSavedObjectAttributes>
) => {
this.logger.debug(`update | ${sessionId}`);
if (!this.sessionConfig.enabled) throw new Error('Search sessions are disabled');
await this.get(deps, user, sessionId); // Verify correct user
return deps.savedObjectsClient.update<SearchSessionSavedObjectAttributes>(
SEARCH_SESSION_TYPE,
sessionId,
{
...attributes,
touched: new Date().toISOString(),
}
);
};
public async extend(
deps: SearchSessionDependencies,
user: AuthenticatedUser | null,
sessionId: string,
expires: Date
) {
this.logger.debug(`extend | ${sessionId}`);
return this.update(deps, user, sessionId, { expires: expires.toISOString() });
}
public cancel = async (
deps: SearchSessionDependencies,
user: AuthenticatedUser | null,
sessionId: string
) => {
this.logger.debug(`delete | ${sessionId}`);
return this.update(deps, user, sessionId, {
status: SearchSessionStatus.CANCELLED,
});
};
public delete = async (
deps: SearchSessionDependencies,
user: AuthenticatedUser | null,
sessionId: string
) => {
if (!this.sessionConfig.enabled) throw new Error('Search sessions are disabled');
this.logger.debug(`delete | ${sessionId}`);
await this.get(deps, user, sessionId); // Verify correct user
return deps.savedObjectsClient.delete(SEARCH_SESSION_TYPE, sessionId);
};
/**
* Tracks the given search request/search ID in the saved session.
* @internal
*/
public trackId = async (
deps: SearchSessionDependencies,
user: AuthenticatedUser | null,
searchRequest: IKibanaSearchRequest,
searchId: string,
{ sessionId, strategy = ENHANCED_ES_SEARCH_STRATEGY }: ISearchOptions
) => {
if (!this.sessionConfig.enabled || !sessionId || !searchId) return;
this.logger.debug(`trackId | ${sessionId} | ${searchId}`);
let idMapping: Record<string, SearchSessionRequestInfo> = {};
if (searchRequest.params) {
const requestHash = createRequestHash(searchRequest.params);
const searchInfo = {
id: searchId,
strategy,
status: SearchStatus.IN_PROGRESS,
};
idMapping = { [requestHash]: searchInfo };
}
await this.scheduleUpdateOrCreate(deps, user, sessionId, { idMapping });
};
public async getSearchIdMapping(
deps: SearchSessionDependencies,
user: AuthenticatedUser | null,
sessionId: string
) {
const searchSession = await this.get(deps, user, sessionId);
const searchIdMapping = new Map<string, string>();
Object.values(searchSession.attributes.idMapping).forEach((requestInfo) => {
searchIdMapping.set(requestInfo.id, requestInfo.strategy);
});
return searchIdMapping;
}
/**
* Look up an existing search ID that matches the given request in the given session so that the
* request can continue rather than restart.
* @internal
*/
public getId = async (
deps: SearchSessionDependencies,
user: AuthenticatedUser | null,
searchRequest: IKibanaSearchRequest,
{ sessionId, isStored, isRestore }: ISearchOptions
) => {
if (!this.sessionConfig.enabled) {
throw new Error('Search sessions are disabled');
} else if (!sessionId) {
throw new Error('Session ID is required');
} else if (!isStored) {
throw new Error('Cannot get search ID from a session that is not stored');
} else if (!isRestore) {
throw new Error('Get search ID is only supported when restoring a session');
}
const session = await this.get(deps, user, sessionId);
const requestHash = createRequestHash(searchRequest.params);
if (!session.attributes.idMapping.hasOwnProperty(requestHash)) {
this.logger.error(`getId | ${sessionId} | ${requestHash} not found`);
throw new NoSearchIdInSessionError();
}
this.logger.debug(`getId | ${sessionId} | ${requestHash}`);
return session.attributes.idMapping[requestHash].id;
};
public asScopedProvider = ({ savedObjects }: CoreStart) => {
return (request: KibanaRequest) => {
const user = this.security?.authc.getCurrentUser(request) ?? null;
const savedObjectsClient = savedObjects.getScopedClient(request, {
includedHiddenTypes: [SEARCH_SESSION_TYPE],
});
const deps = { savedObjectsClient };
return {
getId: this.getId.bind(this, deps, user),
trackId: this.trackId.bind(this, deps, user),
getSearchIdMapping: this.getSearchIdMapping.bind(this, deps, user),
save: this.save.bind(this, deps, user),
get: this.get.bind(this, deps, user),
find: this.find.bind(this, deps, user),
update: this.update.bind(this, deps, user),
extend: this.extend.bind(this, deps, user),
cancel: this.cancel.bind(this, deps, user),
delete: this.delete.bind(this, deps, user),
getConfig: () => this.config.search.sessions,
};
};
};
private throwOnUserConflict = (
user: AuthenticatedUser | null,
session?: SavedObject<SearchSessionSavedObjectAttributes>
) => {
if (user === null || !session) return;
if (
user.authentication_realm.type !== session.attributes.realmType ||
user.authentication_realm.name !== session.attributes.realmName ||
user.username !== session.attributes.username
) {
this.logger.debug(
`User ${user.username} has no access to search session ${session.attributes.sessionId}`
);
throw notFound();
}
};
}

View file

@ -1,17 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { Duration } from 'moment';
import { filter, takeUntil } from 'rxjs/operators';
import { BehaviorSubject } from 'rxjs';
import { RunContext, TaskRunCreatorFunction } from '@kbn/task-manager-plugin/server';
import type { RunContext, TaskRunCreatorFunction } from '@kbn/task-manager-plugin/server';
import { CoreSetup, SavedObjectsClient } from '@kbn/core/server';
import { SEARCH_SESSION_TYPE } from '@kbn/data-plugin/common';
import { DataEnhancedStartDependencies } from '../../type';
import { SEARCH_SESSION_TYPE } from '../../../common';
import {
SearchSessionTaskSetupDeps,
SearchSessionTaskStartDeps,
@ -19,7 +19,7 @@ import {
} from './types';
export function searchSessionTaskRunner(
core: CoreSetup<DataEnhancedStartDependencies>,
core: CoreSetup,
deps: SearchSessionTaskSetupDeps,
title: string,
checkFn: SearchSessionTaskFn
@ -68,7 +68,7 @@ export function searchSessionTaskRunner(
}
export function registerSearchSessionsTask(
core: CoreSetup<DataEnhancedStartDependencies>,
core: CoreSetup,
deps: SearchSessionTaskSetupDeps,
taskType: string,
title: string,

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import { Observable } from 'rxjs';
import {
CoreStart,
KibanaRequest,
@ -13,9 +14,17 @@ import {
SavedObjectsFindOptions,
SavedObjectsFindResponse,
SavedObjectsUpdateResponse,
ElasticsearchClient,
Logger,
SavedObjectsClientContract,
} from '@kbn/core/server';
import type {
TaskManagerSetupContract,
TaskManagerStartContract,
} from '@kbn/task-manager-plugin/server';
import { KueryNode, SearchSessionSavedObjectAttributes } from '../../../common';
import { IKibanaSearchRequest, ISearchOptions } from '../../../common/search';
import { SearchSessionsConfigSchema } from '../../../config';
import { SearchSessionsConfigSchema, ConfigSchema } from '../../../config';
export interface IScopedSearchSessionsClient<T = unknown> {
getId: (request: IKibanaSearchRequest, options: ISearchOptions) => Promise<string>;
@ -38,3 +47,44 @@ export interface IScopedSearchSessionsClient<T = unknown> {
export interface ISearchSessionService<T = unknown> {
asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSearchSessionsClient<T>;
}
export enum SearchStatus {
IN_PROGRESS = 'in_progress',
ERROR = 'error',
COMPLETE = 'complete',
}
export interface CheckSearchSessionsDeps {
savedObjectsClient: SavedObjectsClientContract;
client: ElasticsearchClient;
logger: Logger;
}
export interface SearchSessionTaskSetupDeps {
taskManager: TaskManagerSetupContract;
logger: Logger;
config: ConfigSchema;
}
export interface SearchSessionTaskStartDeps {
taskManager: TaskManagerStartContract;
logger: Logger;
config: ConfigSchema;
}
export type SearchSessionTaskFn = (
deps: CheckSearchSessionsDeps,
config: SearchSessionsConfigSchema
) => Observable<void>;
export type SearchSessionsResponse = SavedObjectsFindResponse<
SearchSessionSavedObjectAttributes,
unknown
>;
export type CheckSearchSessionsFn = (
deps: CheckSearchSessionsDeps,
config: SearchSessionsConfigSchema,
filter: KueryNode,
page: number
) => Observable<SearchSessionsResponse>;

View file

@ -1,12 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { bulkUpdateSessions, updateSessionStatus } from './update_session_status';
import { SearchSessionStatus, SearchSessionSavedObjectAttributes } from '@kbn/data-plugin/common';
import { SearchSessionStatus, SearchSessionSavedObjectAttributes } from '../../../common';
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
import { SearchStatus } from './types';
import moment from 'moment';

Some files were not shown because too many files have changed in this diff Show more