From 58117ea47632c48f22d2a402d480889e51c37b1d Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 17 Dec 2020 16:47:11 -0700 Subject: [PATCH 01/52] logging and error handling in session client routes --- x-pack/plugins/data_enhanced/server/plugin.ts | 2 +- .../plugins/data_enhanced/server/routes/session.test.ts | 6 ++++-- x-pack/plugins/data_enhanced/server/routes/session.ts | 9 +++++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/plugin.ts b/x-pack/plugins/data_enhanced/server/plugin.ts index d0757ca5111b6..39185e0e58391 100644 --- a/x-pack/plugins/data_enhanced/server/plugin.ts +++ b/x-pack/plugins/data_enhanced/server/plugin.ts @@ -64,7 +64,7 @@ export class EnhancedDataServerPlugin implements Plugin { let mockCoreSetup: MockedKeys>; let mockContext: jest.Mocked; + let mockLogger: Logger; beforeEach(() => { mockCoreSetup = coreMock.createSetup(); + mockLogger = coreMock.createPluginInitializerContext().logger.get(); mockContext = createSearchRequestHandlerContext(); - registerSessionRoutes(mockCoreSetup.http.createRouter()); + registerSessionRoutes(mockCoreSetup.http.createRouter(), mockLogger); }); it('save calls session.save with sessionId and attributes', async () => { diff --git a/x-pack/plugins/data_enhanced/server/routes/session.ts b/x-pack/plugins/data_enhanced/server/routes/session.ts index b056513f1d2f5..9e61dd39c83b8 100644 --- a/x-pack/plugins/data_enhanced/server/routes/session.ts +++ b/x-pack/plugins/data_enhanced/server/routes/session.ts @@ -5,10 +5,10 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from 'src/core/server'; +import { IRouter, Logger } from 'src/core/server'; import { reportServerError } from '../../../../../src/plugins/kibana_utils/server'; -export function registerSessionRoutes(router: IRouter): void { +export function registerSessionRoutes(router: IRouter, logger: Logger): void { router.post( { path: '/internal/session', @@ -49,6 +49,7 @@ export function registerSessionRoutes(router: IRouter): void { body: response, }); } catch (err) { + logger.error(err); return reportServerError(res, err); } } @@ -73,6 +74,7 @@ export function registerSessionRoutes(router: IRouter): void { }); } catch (e) { const err = e.output?.payload || e; + logger.error(err); return reportServerError(res, err); } } @@ -106,6 +108,7 @@ export function registerSessionRoutes(router: IRouter): void { body: response, }); } catch (err) { + logger.error(err); return reportServerError(res, err); } } @@ -128,6 +131,7 @@ export function registerSessionRoutes(router: IRouter): void { return res.ok(); } catch (e) { const err = e.output?.payload || e; + logger.error(err); return reportServerError(res, err); } } @@ -156,6 +160,7 @@ export function registerSessionRoutes(router: IRouter): void { body: response, }); } catch (err) { + logger.error(err); return reportServerError(res, err); } } From eee4641cc177718f5aa15758057580f897cf6eac Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 8 Dec 2020 23:09:34 -0700 Subject: [PATCH 02/52] [Data] Background Search Session Management UI --- .../public/search/session/sessions_client.ts | 4 +- .../common/search/session/types.ts | 4 +- .../common/search/sessions_mgmt/constants.ts | 21 ++ .../common/search/sessions_mgmt/index.ts | 34 +++ x-pack/plugins/data_enhanced/kibana.json | 10 +- x-pack/plugins/data_enhanced/public/plugin.ts | 11 +- .../search/sessions_mgmt/__mocks__/index.tsx | 18 ++ .../sessions_mgmt/application/index.tsx | 81 +++++++ .../sessions_mgmt/application/render.tsx | 41 ++++ .../sessions_mgmt/components/actions.test.tsx | 77 +++++++ .../components/actions/get_action.tsx | 59 +++++ .../components/actions/index.tsx | 8 + .../components/actions/inline_actions.tsx | 42 ++++ .../components/actions/popover_actions.tsx | 134 ++++++++++++ .../components/delete_button.tsx | 86 ++++++++ .../sessions_mgmt/components/home.test.tsx | 78 +++++++ .../search/sessions_mgmt/components/home.tsx | 78 +++++++ .../search/sessions_mgmt/components/index.tsx | 43 ++++ .../sessions_mgmt/components/status.test.tsx | 150 +++++++++++++ .../sessions_mgmt/components/status.tsx | 193 +++++++++++++++++ .../components/table/app_filter.tsx | 27 +++ .../sessions_mgmt/components/table/index.ts | 7 + .../components/table/status_filter.tsx | 36 ++++ .../components/table/table.test.tsx | 90 ++++++++ .../sessions_mgmt/components/table/table.tsx | 107 ++++++++++ .../sessions_mgmt/icons/extend_session.svg | 3 + .../public/search/sessions_mgmt/index.ts | 57 +++++ .../search/sessions_mgmt/lib/api.test.ts | 101 +++++++++ .../public/search/sessions_mgmt/lib/api.ts | 173 +++++++++++++++ .../search/sessions_mgmt/lib/date_string.ts | 27 +++ .../search/sessions_mgmt/lib/documentation.ts | 21 ++ .../sessions_mgmt/lib/get_columns.test.tsx | 201 ++++++++++++++++++ .../search/sessions_mgmt/lib/get_columns.tsx | 198 +++++++++++++++++ .../background_session_indicator.tsx | 2 +- 34 files changed, 2209 insertions(+), 13 deletions(-) create mode 100644 x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts create mode 100644 x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/__mocks__/index.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions.test.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/delete_button.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/index.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/icons/extend_session.svg create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx diff --git a/src/plugins/data/public/search/session/sessions_client.ts b/src/plugins/data/public/search/session/sessions_client.ts index 38be647a37c7a..87dbe6f2fceea 100644 --- a/src/plugins/data/public/search/session/sessions_client.ts +++ b/src/plugins/data/public/search/session/sessions_client.ts @@ -67,8 +67,8 @@ export class SessionsClient { }); } - public find(options: SavedObjectsFindOptions): Promise { - return this.http!.post(`/internal/session`, { + public find(options: Omit): Promise { + return this.http!.post(`/internal/session/_find`, { body: JSON.stringify(options), }); } diff --git a/x-pack/plugins/data_enhanced/common/search/session/types.ts b/x-pack/plugins/data_enhanced/common/search/session/types.ts index 0b82c9160ea1a..c6cd1e78fc587 100644 --- a/x-pack/plugins/data_enhanced/common/search/session/types.ts +++ b/x-pack/plugins/data_enhanced/common/search/session/types.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { BackgroundSessionStatus } from './'; + export interface BackgroundSessionSavedObjectAttributes { /** * User-facing session name to be displayed in session management @@ -15,7 +17,7 @@ export interface BackgroundSessionSavedObjectAttributes { appId: string; created: string; expires: string; - status: string; + status: BackgroundSessionStatus; urlGeneratorId: string; initialState: Record; restoreState: Record; diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts new file mode 100644 index 0000000000000..fd81a49f9d30b --- /dev/null +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts @@ -0,0 +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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BackgroundSessionStatus } from '../'; + +export const REFRESH_INTERVAL_MS = 3000; + +export const EXPIRES_SOON_IN_DAYS = 2; + +export const MAX_SEARCH_HITS = 10000; + +export enum ACTION { + EXTEND = 'extend', + CANCEL = 'cancel', + DELETE = 'delete', +} + +export { BackgroundSessionStatus as STATUS }; diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts new file mode 100644 index 0000000000000..73cd5c899fee6 --- /dev/null +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BackgroundSessionSavedObjectAttributes } from '../'; +import { ACTION, STATUS } from './constants'; + +export interface Session { + id: string; + name: string; + appId: string; + created: string; + expires: string | null; + status: STATUS; + actions?: ACTION[]; + isViewable: boolean; + expiresSoon: boolean; + urlGeneratorId: BackgroundSessionSavedObjectAttributes['urlGeneratorId']; + restoreState: BackgroundSessionSavedObjectAttributes['restoreState']; +} + +export interface UISession extends Omit { + url: string; +} + +export interface ISession { + session: string; +} + +export type ActionComplete = (result: UISession[] | null) => void; + +export * from './constants'; diff --git a/x-pack/plugins/data_enhanced/kibana.json b/x-pack/plugins/data_enhanced/kibana.json index eea0101ec4ed7..90f0893ca5c34 100644 --- a/x-pack/plugins/data_enhanced/kibana.json +++ b/x-pack/plugins/data_enhanced/kibana.json @@ -2,14 +2,8 @@ "id": "dataEnhanced", "version": "8.0.0", "kibanaVersion": "kibana", - "configPath": [ - "xpack", "data_enhanced" - ], - "requiredPlugins": [ - "bfetch", - "data", - "features" - ], + "configPath": ["xpack", "data_enhanced"], + "requiredPlugins": ["bfetch", "data", "features", "management", "share"], "optionalPlugins": ["kibanaUtils", "usageCollection"], "server": true, "ui": true, diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index a3b37e47287e5..b71d2038983c0 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -8,10 +8,13 @@ import React from 'react'; import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { BfetchPublicSetup } from '../../../../src/plugins/bfetch/public'; +import { ManagementSetup } from '../../../../src/plugins/management/public'; +import { SharePluginStart } from '../../../../src/plugins/share/public'; import { setAutocompleteService } from './services'; import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete'; import { EnhancedSearchInterceptor } from './search/search_interceptor'; +import { registerBackgroundSessionsMgmt } from './search/sessions_mgmt'; import { toMountPoint } from '../../../../src/plugins/kibana_react/public'; import { createConnectedBackgroundSessionIndicator } from './search'; import { ConfigSchema } from '../config'; @@ -19,9 +22,11 @@ import { ConfigSchema } from '../config'; export interface DataEnhancedSetupDependencies { bfetch: BfetchPublicSetup; data: DataPublicPluginSetup; + management: ManagementSetup; } export interface DataEnhancedStartDependencies { data: DataPublicPluginStart; + share: SharePluginStart; } export type DataEnhancedSetup = ReturnType; @@ -35,7 +40,7 @@ export class DataEnhancedPlugin public setup( core: CoreSetup, - { bfetch, data }: DataEnhancedSetupDependencies + { bfetch, data, management }: DataEnhancedSetupDependencies ) { data.autocomplete.addQuerySuggestionProvider( KUERY_LANGUAGE_NAME, @@ -57,6 +62,10 @@ export class DataEnhancedPlugin searchInterceptor: this.enhancedSearchInterceptor, }, }); + + if (this.initializerContext.config.get().search.sendToBackground.enabled) { + registerBackgroundSessionsMgmt(core, { management }); + } } public start(core: CoreStart, plugins: DataEnhancedStartDependencies) { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/__mocks__/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/__mocks__/index.tsx new file mode 100644 index 0000000000000..e9fc8e6ac6bf9 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/__mocks__/index.tsx @@ -0,0 +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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { ReactNode } from 'react'; +import { IntlProvider } from 'react-intl'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { UrlGeneratorsStart } from '../../../../../../../src/plugins/share/public/url_generators'; + +export function LocaleWrapper({ children }: { children?: ReactNode }) { + return {children}; +} + +export const mockUrls = ({ + getUrlGenerator: (id: string) => ({ createUrl: () => `hello-cool-${id}-url` }), +} as unknown) as UrlGeneratorsStart; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx new file mode 100644 index 0000000000000..a8a7905fb609f --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup } from 'kibana/public'; +import * as Rx from 'rxjs'; +import { first } from 'rxjs/operators'; +import { ManagementAppMountParams } from 'src/plugins/management/public'; +import { SharePluginStart } from 'src/plugins/share/public'; +import { + APP, + AppDependencies, + IManagementSectionsPluginsSetup, + IManagementSectionsPluginsStart, +} from '../'; +import { SearchSessionsMgmtAPI } from '../lib/api'; +import { AsyncSearchIntroDocumentation } from '../lib/documentation'; +import { renderApp } from './render'; + +type UrlGeneratorsStart = SharePluginStart['urlGenerators']; + +// +export class SearchSessionsMgmtApp { + private urls$ = new Rx.Subject(); + + constructor( + private coreSetup: CoreSetup, + private params: ManagementAppMountParams, + private pluginsSetup: IManagementSectionsPluginsSetup + ) { + this.urls$.pipe(first()).subscribe((urls) => {}); + } + + public async mountManagementSection() { + const { coreSetup, params, pluginsSetup } = this; + const [coreStart, pluginsStart] = await coreSetup.getStartServices(); + + const { + chrome: { docTitle }, + http, + docLinks, + i18n, + notifications, + uiSettings, + } = coreStart; + const { data, share } = pluginsStart; + + const pluginName = APP.getI18nName(); + docTitle.change(pluginName); + params.setBreadcrumbs([{ text: pluginName }]); + + const { sessionsClient } = data.search; + const api = new SearchSessionsMgmtAPI(sessionsClient, share.urlGenerators, notifications); + + const documentation = new AsyncSearchIntroDocumentation(); + documentation.setup(docLinks); + + const dependencies: AppDependencies = { + plugins: pluginsSetup, + documentation, + api, + http, + i18n, + uiSettings, + share, + }; + const initialTable = await api.fetchTableData(); + + const { element } = params; + const unmountAppCb = renderApp(element, dependencies, initialTable); + + return () => { + docTitle.reset(); + unmountAppCb(); + }; + } +} + +export { renderApp }; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx new file mode 100644 index 0000000000000..0b25365cde248 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { AppDependencies } from '../'; +import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; +import { UISession } from '../../../../common/search/sessions_mgmt'; +import { SearchSessionsMgmtHome } from '../components/home'; + +export const renderApp = ( + elem: HTMLElement | null, + { i18n, ...homeDeps }: AppDependencies, + initialTable: UISession[] | null +) => { + if (!elem) { + return () => undefined; + } + + const { Context: I18nContext } = i18n; + // uiSettings is required by the listing table to format dates in the timezone from Settings + const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ + uiSettings: homeDeps.uiSettings, + }); + + render( + + + + + , + elem + ); + + return () => { + unmountComponentAtNode(elem); + }; +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions.test.tsx new file mode 100644 index 0000000000000..1f75d2abe1457 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions.test.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount } from 'enzyme'; +import React from 'react'; +import { STATUS, UISession } from '../../../../common/search/sessions_mgmt'; +import { LocaleWrapper } from '../__mocks__'; +import { InlineActions } from './actions'; + +let session: UISession; + +describe('Background Search Session management actions', () => { + beforeEach(() => { + session = { + name: 'cool search', + id: 'wtywp9u2802hahgp-gluk', + url: '/app/great-app-url/#43', + appId: 'canvas', + status: STATUS.IN_PROGRESS, + created: '2020-12-02T00:19:32Z', + expires: '2020-12-07T00:19:32Z', + isViewable: true, + expiresSoon: false, + }; + }); + + describe('Inline actions', () => { + test('isViewable = true', () => { + const actions = mount( + + + + ); + + expect( + actions.find(`[data-test-subj="session-mgmt-view-action-wtywp9u2802hahgp-gluk"]`).exists() + ).toBe(true); + + expect(actions.find(`[data-test-subj="session-mgmt-view-href"]`).first().prop('href')).toBe( + '/app/kibana/coolapp' + ); + }); + + test('isViewable = false', () => { + session.isViewable = false; + const actions = mount( + + + + ); + + expect( + actions.find(`[data-test-subj="session-mgmt-view-action-wtywp9u2802hahgp-gluk"]`).exists() + ).toBe(false); + }); + + test('error handling', () => { + (session as any).created = null; + (session as any).expires = null; + (session as any).status = null; + + const actions = mount( + + + + ); + + // no unhandled errors + expect( + actions.find(`[data-test-subj="session-mgmt-view-action-wtywp9u2802hahgp-gluk"]`).exists() + ).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx new file mode 100644 index 0000000000000..e48993882ee32 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { IClickActionDescriptor } from '../'; +import { ACTION, ActionComplete, UISession } from '../../../../../common/search/sessions_mgmt'; +import extendSessionIcon from '../../icons/extend_session.svg'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { DeleteButton } from '../delete_button'; + +export const getAction = ( + api: SearchSessionsMgmtAPI, + actionType: string, + { id }: UISession, + actionComplete: ActionComplete +): IClickActionDescriptor | null => { + switch (actionType) { + // + case ACTION.CANCEL: + return { + iconType: 'crossInACircleFilled', + textColor: 'default', + label: i18n.translate('xpack.data.mgmt.searchSessions.actionCancel', { + defaultMessage: 'Cancel', + }), + }; + + // + case ACTION.DELETE: + return { + iconType: 'trash', + textColor: 'danger', + label: , + }; + + // + case ACTION.EXTEND: + return { + iconType: extendSessionIcon, + textColor: 'default', + label: i18n.translate('xpack.data.mgmt.searchSessions.actionExtend', { + defaultMessage: 'Extend', + }), + }; + + // + default: + // eslint-disable-next-line no-console + console.error(`Unknown action: ${actionType}`); + } + + // Unknown action: do not show + + return null; +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx new file mode 100644 index 0000000000000..48b26be369dc8 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { InlineActions } from './inline_actions'; +export { PopoverActionsMenu } from './popover_actions'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx new file mode 100644 index 0000000000000..346e45a742c76 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; +import { TableText } from '../'; +import { UISession } from '../../../../../common/search/sessions_mgmt'; + +interface InlineActionProps { + url: string; + session: UISession; +} + +export const InlineActions = ({ url, session }: InlineActionProps) => { + if (!session.isViewable) { + return null; + } + // only the View action is required + return ( + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx new file mode 100644 index 0000000000000..75a98cb677aaa --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButtonIcon, + EuiContextMenu, + EuiContextMenuPanelDescriptor, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPopover, + EuiTextProps, + EuiToolTip, +} from '@elastic/eui'; +import { + EuiContextMenuPanelItemDescriptorEntry, + EuiContextMenuPanelItemSeparator, +} from '@elastic/eui/src/components/context_menu/context_menu'; +import { i18n } from '@kbn/i18n'; +import React, { ReactElement, useState } from 'react'; +import { TableText } from '../'; +import { ACTION, ActionComplete, UISession } from '../../../../../common/search/sessions_mgmt'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { getAction } from './get_action'; + +// interfaces +interface PopoverActionProps { + textColor?: EuiTextProps['color']; + iconType: string; + children: string | ReactElement; +} + +interface PopoverActionItemsProps { + session: UISession; + api: SearchSessionsMgmtAPI; + handleAction: ActionComplete; +} + +// helper +const PopoverAction = ({ textColor, iconType, children }: PopoverActionProps) => ( + + + + + + {children} + + +); + +// main +export const PopoverActionsMenu = ({ api, handleAction, session }: PopoverActionItemsProps) => { + const [isPopoverOpen, setPopover] = useState(false); + + const onPopoverClick = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + const renderPopoverButton = () => ( + + ); + + const { actions } = session; + if (!actions || actions.length === 0) { + return null; + } + + // Generic set of actions - up to the API to return what is available + const items = actions.reduce((itemSet, actionType) => { + const actionDef = getAction(api, actionType, session, handleAction); + if (actionDef) { + const { label, textColor, iconType } = actionDef; + + // add a line above the delete action (when there are multiple) + if (actions.length > 1 && actionType === ACTION.DELETE) { + itemSet.push({ isSeparator: true, key: 'separadorable' }); + } + + return [ + ...itemSet, + { + key: `action-${actionType}`, + name: ( + + {label} + + ), + }, + ]; + } + return itemSet; + }, [] as Array); + + // + const panels: EuiContextMenuPanelDescriptor[] = [{ id: 0, items }]; + + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/delete_button.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/delete_button.tsx new file mode 100644 index 0000000000000..0317177ad9279 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/delete_button.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useState } from 'react'; +import { ActionComplete } from '../../../../common/search/sessions_mgmt'; +import { SearchSessionsMgmtAPI } from '../lib/api'; +import { TableText } from './'; + +interface DeleteButtonProps { + id: string; + api: SearchSessionsMgmtAPI; + actionComplete: ActionComplete; +} + +const DeleteConfirm = ({ + onConfirmDismiss, + ...props +}: DeleteButtonProps & { onConfirmDismiss: () => void }) => { + const { id, api, actionComplete } = props; + const [isLoading, setIsLoading] = useState(false); + + const title = i18n.translate('xpack.data.mgmt.searchSessions.deleteModal.title', { + defaultMessage: 'Are you sure you want to delete the session?', + }); + const confirm = i18n.translate('xpack.data.mgmt.searchSessions.deleteModal.deleteButton', { + defaultMessage: 'Delete', + }); + const cancel = i18n.translate('xpack.data.mgmt.searchSessions.deleteModal.cancelButton', { + defaultMessage: 'Cancel', + }); + const message = i18n.translate('xpack.data.mgmt.searchSessions.deleteModal.message', { + defaultMessage: `You can't recover deleted sessions`, + }); + + return ( + + { + setIsLoading(true); + actionComplete(await api.sendDelete(id)); + }} + confirmButtonText={confirm} + confirmButtonDisabled={isLoading} + cancelButtonText={cancel} + defaultFocusedButton="confirm" + buttonColor="danger" + > + {message} + + + ); +}; + +export const DeleteButton = (props: DeleteButtonProps) => { + const [toShowDeleteConfirm, setToShowDeleteConfirm] = useState(false); + + const onClick = () => { + setToShowDeleteConfirm(true); + }; + + const onConfirmDismiss = () => { + setToShowDeleteConfirm(false); + }; + + return ( + <> + + + + {toShowDeleteConfirm ? ( + + ) : null} + + ); +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx new file mode 100644 index 0000000000000..f46a702ab42b0 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MockedKeys } from '@kbn/utility-types/jest'; +import { mount, ReactWrapper } from 'enzyme'; +import { CoreSetup } from 'kibana/public'; +import React from 'react'; +import { coreMock } from 'src/core/public/mocks'; +import { UISession, STATUS } from '../../../../common/search/sessions_mgmt'; +import { SearchSessionsMgmtAPI } from '../lib/api'; +import { AsyncSearchIntroDocumentation } from '../lib/documentation'; +import { LocaleWrapper, mockUrls } from '../__mocks__'; +import { SearchSessionsMgmtHome } from './home'; + +let mockCoreSetup: MockedKeys; +let initialTable: UISession[]; +let api: SearchSessionsMgmtAPI; + +describe('Background Search Session Management Home', () => { + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + + api = new SearchSessionsMgmtAPI(mockCoreSetup.http, mockUrls, mockCoreSetup.notifications); + + initialTable = [ + { + name: 'very background search', + id: 'wtywp9u2802hahgp-flps', + url: '/app/great-app-url/#48', + appId: 'canvas', + status: STATUS.IN_PROGRESS, + created: '2020-12-02T00:19:32Z', + expires: '2020-12-07T00:19:32Z', + isViewable: true, + expiresSoon: false, + }, + ]; + }); + + describe('renders', () => { + let home: ReactWrapper; + + beforeEach(() => { + mockCoreSetup.uiSettings.get.mockImplementation((key: string) => { + return key === 'dateFormat:tz' ? 'UTC' : null; + }); + + home = mount( + + + + ); + }); + + test('page title', () => { + expect(home.find('h1').text()).toBe('Background Sessions'); + }); + + test('documentation link', () => { + const docLink = home.find('a[href]').first(); + expect(docLink.text()).toBe('Documentation'); + expect(docLink.prop('href')).toBe('/async-search-intro.html'); + }); + + test('table is present', () => { + expect(home.find(`[data-test-subj="search-sessions-mgmt-table"]`).exists()).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.tsx new file mode 100644 index 0000000000000..b2637259e15d2 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiPageBody, + EuiPageContent, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { HttpStart, IUiSettingsClient } from 'kibana/public'; +import React from 'react'; +import { UISession } from '../../../../common/search/sessions_mgmt'; +import { SearchSessionsMgmtAPI } from '../lib/api'; +import { AsyncSearchIntroDocumentation } from '../lib/documentation'; +import { TableText } from './'; +import { SearchSessionsMgmtTable } from './table'; + +interface Props { + documentation: AsyncSearchIntroDocumentation; + api: SearchSessionsMgmtAPI; + http: HttpStart; + initialTable: UISession[] | null; + uiSettings: IUiSettingsClient; +} + +export function SearchSessionsMgmtHome({ documentation, ...tableProps }: Props) { + return ( + + + + + +

+ +

+
+
+ + + + + +
+ + +

+ +

+
+ + + + +
+
+ ); +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx new file mode 100644 index 0000000000000..bb8d48aa85de2 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiLinkProps, EuiText, EuiTextProps } from '@elastic/eui'; +import { IUiSettingsClient } from 'kibana/public'; +import React from 'react'; +import { UISession } from '../../../../common/search/sessions_mgmt'; +import extendSessionIcon from '../icons/extend_session.svg'; + +export const TableText = ({ children, ...props }: EuiTextProps) => { + return ( + + {children} + + ); +}; + +export interface IClickActionDescriptor { + label: string | React.ReactElement; + iconType: 'trash' | 'cancel' | typeof extendSessionIcon; + textColor: EuiTextProps['color']; +} + +export interface IHrefActionDescriptor { + label: string; + props: EuiLinkProps; +} + +export interface StatusIndicatorProps { + now?: string; + session: UISession; + uiSettings: IUiSettingsClient; +} + +export interface StatusDef { + textColor?: EuiTextProps['color']; + icon?: React.ReactElement; + label: React.ReactElement; + toolTipContent: string; +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx new file mode 100644 index 0000000000000..92222c8298c7c --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx @@ -0,0 +1,150 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiTextProps, EuiToolTipProps } from '@elastic/eui'; +import { MockedKeys } from '@kbn/utility-types/jest'; +import { mount } from 'enzyme'; +import { CoreSetup } from 'kibana/public'; +import React from 'react'; +import { coreMock } from 'src/core/public/mocks'; +import { STATUS, UISession } from '../../../../common/search/sessions_mgmt'; +import { LocaleWrapper } from '../__mocks__'; +import { getStatusText, StatusIndicator } from './status'; + +let mockCoreSetup: MockedKeys; +let session: UISession; + +const mockNowTime = new Date(); +mockNowTime.setTime(1607026176061); + +describe('Background Search Session management status labels', () => { + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + mockCoreSetup.uiSettings.get.mockImplementation(() => 'Browser'); + + session = { + name: 'amazing test', + id: 'wtywp9u2802hahgp-gsla', + url: '/app/great-app-url/#45', + appId: 'security', + status: STATUS.IN_PROGRESS, + created: '2020-12-02T00:19:32Z', + expires: '2020-12-07T00:19:32Z', + isViewable: true, + expiresSoon: false, + }; + }); + + describe('getStatusText', () => { + test('in progress', () => { + expect(getStatusText(STATUS.IN_PROGRESS)).toBe('In progress'); + }); + test('expired', () => { + expect(getStatusText(STATUS.EXPIRED)).toBe('Expired'); + }); + test('cancelled', () => { + expect(getStatusText(STATUS.CANCELLED)).toBe('Cancelled'); + }); + test('complete', () => { + expect(getStatusText(STATUS.COMPLETE)).toBe('Complete'); + }); + test('error', () => { + expect(getStatusText('error')).toBe('Error'); + }); + }); + + describe('StatusIndicator', () => { + test('render in progress', () => { + const statusIndicator = mount( + + + + ); + + const label = statusIndicator.find( + `.euiText[data-test-subj="session-mgmt-view-status-label-in_progress"]` + ); + expect(label.text()).toMatchInlineSnapshot(`"In progress"`); + }); + + test('complete', () => { + session.status = STATUS.COMPLETE; + + const statusIndicator = mount( + + + + ); + + const label = statusIndicator + .find(`[data-test-subj="session-mgmt-view-status-label-complete"]`) + .first(); + expect((label.props() as EuiTextProps).color).toBe('secondary'); + expect(label.text()).toBe('Complete'); + }); + + test('complete - expires soon', () => { + session.status = STATUS.COMPLETE; + session.expiresSoon = true; + + const statusIndicator = mount( + + + + ); + + const tooltip = statusIndicator.find('EuiToolTip'); + expect((tooltip.first().props() as EuiToolTipProps).content).toMatchInlineSnapshot( + `"Expires on 6 Dec, 2020, 19:19:32"` + ); + }); + + test('expired', () => { + session.status = STATUS.EXPIRED; + + const statusIndicator = mount( + + + + ); + + const label = statusIndicator + .find(`[data-test-subj="session-mgmt-view-status-label-expired"]`) + .first(); + expect(label.text()).toBe('Expired'); + }); + + test('error handling', () => { + session.status = STATUS.COMPLETE; + (session as any).created = null; + (session as any).expires = null; + + const statusIndicator = mount( + + + + ); + + // no unhandled errors + const label = statusIndicator + .find(`[data-test-subj="session-mgmt-view-status-label-complete"]`) + .first(); + expect(label.exists()).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx new file mode 100644 index 0000000000000..0751ba699daa9 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { ReactElement } from 'react'; +import { STATUS } from '../../../../common/search/sessions_mgmt'; +import { dateString } from '../lib/date_string'; +import { StatusDef as StatusAttributes, StatusIndicatorProps, TableText } from './'; + +// Shared helper function +export const getStatusText = (statusType: string): string => { + switch (statusType) { + case STATUS.IN_PROGRESS: + return i18n.translate('xpack.data.mgmt.searchSessions.status.label.inProgress', { + defaultMessage: 'In progress', + }); + case STATUS.EXPIRED: + return i18n.translate('xpack.data.mgmt.searchSessions.status.label.expired', { + defaultMessage: 'Expired', + }); + case STATUS.CANCELLED: + return i18n.translate('xpack.data.mgmt.searchSessions.status.label.cancelled', { + defaultMessage: 'Cancelled', + }); + case STATUS.COMPLETE: + return i18n.translate('xpack.data.mgmt.searchSessions.status.label.complete', { + defaultMessage: 'Complete', + }); + case STATUS.ERROR: + return i18n.translate('xpack.data.mgmt.searchSessions.status.label.error', { + defaultMessage: 'Error', + }); + default: + // eslint-disable-next-line no-console + console.error(`Unknown status ${statusType}`); + return statusType; + } +}; + +// Get the fields needed to show each status type +// can throw errors around date conversions +const getStatusAttributes = ({ + now, + session, + uiSettings, +}: StatusIndicatorProps): StatusAttributes | null => { + switch (session.status) { + case STATUS.IN_PROGRESS: + try { + const createdDate = dateString(session.created, uiSettings); + return { + textColor: 'default', + icon: , + label: {getStatusText(session.status)}, + toolTipContent: i18n.translate( + 'xpack.data.mgmt.searchSessions.status.message.createdOn', + { + defaultMessage: 'Started on {createdDate}', + values: { createdDate }, + } + ), + }; + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + throw new Error(`Could not instantiate a createdDate object from: ${session.created}`); + } + + case STATUS.EXPIRED: + try { + const expiredOnDate = dateString(session.expires!, uiSettings); + const expiredOnMessage = i18n.translate( + 'xpack.data.mgmt.searchSessions.status.message.expiredOn', + { + defaultMessage: 'Expired on {expiredOnDate}', + values: { expiredOnDate }, + } + ); + + return { + icon: , + label: {getStatusText(session.status)}, + toolTipContent: expiredOnMessage, + }; + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + throw new Error(`Could not instantiate a expiration Date object from: ${session.expires}`); + } + + // + case STATUS.CANCELLED: + return { + icon: , + label: {getStatusText(session.status)}, + toolTipContent: i18n.translate('xpack.data.mgmt.searchSessions.status.message.cancelled', { + defaultMessage: 'Cancelled by user', + }), + }; + + // + case STATUS.ERROR: + return { + textColor: 'danger', + icon: , + label: {getStatusText(session.status)}, + toolTipContent: i18n.translate('xpack.data.mgmt.searchSessions.status.message.error', { + defaultMessage: 'Error: {error}', + values: { error: (session as any).error || 'unknown' }, + }), + }; + + // + case STATUS.COMPLETE: + try { + const expiresOnDate = dateString(session.expires!, uiSettings); + const expiresOnMessage = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresOn', { + defaultMessage: 'Expires on {expiresOnDate}', + values: { expiresOnDate }, + }); + + return { + textColor: 'secondary', + icon: , + label: {getStatusText(session.status)}, + toolTipContent: expiresOnMessage, + }; + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + throw new Error( + `Could not instantiate a expiration Date object for completed session from: ${session.expires}` + ); + } + + // Error was thrown + return null; + + // + default: + throw new Error(`Unknown status: ${session.status}`); + } +}; + +export const StatusIndicator = (props: StatusIndicatorProps) => { + try { + const statusDef = getStatusAttributes(props); + const { session } = props; + + if (statusDef) { + const { toolTipContent } = statusDef; + let icon: ReactElement | string | undefined = statusDef.icon; + let label: ReactElement | string = statusDef.label; + + if (icon && toolTipContent) { + icon = {icon}; + } + if (toolTipContent) { + label = ( + + + {statusDef.label} + + + ); + } + + return ( + + {icon} + + + {label} + + + + ); + } + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + } + + // Exception has been caught + return {props.session.status}; +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx new file mode 100644 index 0000000000000..91774cf92eff8 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx @@ -0,0 +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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { capitalize } from 'lodash'; +import { UISession } from '../../../../../common/search/sessions_mgmt'; + +export const getAppFilter: (tableData: UISession[]) => SearchFilterConfig = (tableData) => ({ + type: 'field_value_selection', + name: i18n.translate('xpack.data.mgmt.searchSessions.search.filterApp', { + defaultMessage: 'App', + }), + field: 'app', + multiSelect: 'or', + options: tableData.reduce((options: FieldValueOptionType[], { appId }) => { + const existingOption = options.find((o) => o.value === appId); + if (!existingOption) { + return [...options, { value: appId, view: capitalize(appId) }]; + } + + return options; + }, []), +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/index.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/index.ts new file mode 100644 index 0000000000000..83ca1c223dfc4 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { SearchSessionsMgmtTable } from './table'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx new file mode 100644 index 0000000000000..7e3e8c9a9b436 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { TableText } from '../'; +import { UISession } from '../../../../../common/search/sessions_mgmt'; +import { getStatusText } from '../status'; + +export const getStatusFilter: (tableData: UISession[]) => SearchFilterConfig = (tableData) => ({ + type: 'field_value_selection', + name: i18n.translate('xpack.data.mgmt.searchSessions.search.filterStatus', { + defaultMessage: 'Status', + }), + field: 'status', + multiSelect: 'or', + options: tableData.reduce((options: FieldValueOptionType[], session) => { + const { status: statusType } = session; + const existingOption = options.find((o) => o.value === statusType); + if (!existingOption) { + return [ + ...options, + { + value: statusType, + view: {getStatusText(session.status)}, + }, + ]; + } + + return options; + }, []), +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx new file mode 100644 index 0000000000000..05eb226333244 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MockedKeys } from '@kbn/utility-types/jest'; +import { mount, ReactWrapper } from 'enzyme'; +import { CoreSetup } from 'kibana/public'; +import React from 'react'; +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { SessionsClient } from '../../../../../../../../src/plugins/data/public/search'; +import { STATUS, UISession } from '../../../../../common/search/sessions_mgmt'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { LocaleWrapper, mockUrls } from '../../__mocks__'; +import { SearchSessionsMgmtTable } from './table'; + +let mockCoreSetup: MockedKeys; +let initialTable: UISession[]; +let api: SearchSessionsMgmtAPI; + +describe('Background Search Session Management Table', () => { + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + const sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); + + api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); + + initialTable = [ + { + name: 'very background search', + id: 'wtywp9u2802hahgp-flps', + url: '/app/great-app-url/#48', + appId: 'canvas', + status: STATUS.IN_PROGRESS, + created: '2020-12-02T00:19:32Z', + expires: '2020-12-07T00:19:32Z', + isViewable: true, + expiresSoon: false, + }, + ]; + }); + + describe('renders', () => { + let table: ReactWrapper; + + beforeEach(() => { + mockCoreSetup.uiSettings.get.mockImplementation((key: string) => { + return key === 'dateFormat:tz' ? 'UTC' : null; + }); + + table = mount( + + + + ); + }); + + test('table header cells', () => { + expect(table.find('thead th').map((node) => node.text())).toMatchInlineSnapshot(` + Array [ + "TypeClick to sort in ascending order", + "NameClick to sort in ascending order", + "StatusClick to sort in ascending order", + "CreatedClick to sort in ascending order", + "ExpirationClick to sort in ascending order", + ] + `); + }); + + test('table body cells', () => { + expect(table.find('tbody td').map((node) => node.text())).toMatchInlineSnapshot(` + Array [ + "Type", + "Namevery background search", + "StatusIn progress", + "Created2 Dec, 2020, 00:19:32", + "Expiration--", + "", + "View", + ] + `); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx new file mode 100644 index 0000000000000..c0add3c15967b --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButton, EuiInMemoryTable, EuiSearchBarProps } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { HttpStart, IUiSettingsClient } from 'kibana/public'; +import React, { useEffect, useState } from 'react'; +import * as Rx from 'rxjs'; +import { switchMap } from 'rxjs/operators'; +import { TableText } from '../'; +import { + ActionComplete, + REFRESH_INTERVAL_MS, + UISession, +} from '../../../../../common/search/sessions_mgmt'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { getColumns } from '../../lib/get_columns'; +import { getAppFilter } from './app_filter'; +import { getStatusFilter } from './status_filter'; + +const TABLE_ID = 'backgroundSessionsMgmtTable'; + +interface Props { + api: SearchSessionsMgmtAPI; + http: HttpStart; + initialTable: UISession[] | null; + uiSettings: IUiSettingsClient; +} + +export function SearchSessionsMgmtTable({ api, http, uiSettings, initialTable, ...props }: Props) { + const [tableData, setTableData] = useState(initialTable ? initialTable : []); + const [isLoading, setIsLoading] = useState(false); + const [pagination, setPagination] = useState({ pageIndex: 0 }); + + // refresh behavior + const doRefresh = async () => { + setIsLoading(true); + await api.fetchTableData().then((results) => { + if (results) { + setTableData(results); + } + }); + setIsLoading(false); + }; + + // 3s auto-refresh + useEffect(() => { + const refreshRx = Rx.interval(REFRESH_INTERVAL_MS).pipe(switchMap(doRefresh)).subscribe(); + + return () => { + refreshRx.unsubscribe(); + }; + }); + + // When action such as cancel, delete, extend occurs, use the async return + // value to refresh the table + const handleActionCompleted: ActionComplete = (results: UISession[] | null) => { + if (results) { + setTableData(results); + } + }; + + // table config: search / filters + const search: EuiSearchBarProps = { + box: { incremental: true }, + filters: [getStatusFilter(tableData), getAppFilter(tableData)], + toolsRight: ( + + + + + + ), + }; + + // table config: sorting + const sorting = { sort: { field: 'startedDate', direction: 'desc' as 'desc' } }; + + // + return ( + + {...props} + id={TABLE_ID} + columns={getColumns(api, http, uiSettings, handleActionCompleted)} + items={tableData} + pagination={pagination} + search={search} + sorting={sorting} + onTableChange={({ page: { index } }) => { + setPagination({ pageIndex: index }); + }} + tableLayout="auto" + /> + ); +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/icons/extend_session.svg b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/icons/extend_session.svg new file mode 100644 index 0000000000000..7cb9f7e6a24c2 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/icons/extend_session.svg @@ -0,0 +1,3 @@ + + + diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts new file mode 100644 index 0000000000000..978135cbf5232 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { CoreSetup, HttpStart, I18nStart, IUiSettingsClient } from 'kibana/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; +import { ManagementSetup } from 'src/plugins/management/public'; +import { SharePluginStart } from 'src/plugins/share/public'; +import { DataEnhancedStartDependencies } from '../../plugin'; +import { SearchSessionsMgmtAPI } from './lib/api'; +import { AsyncSearchIntroDocumentation } from './lib/documentation'; + +export interface IManagementSectionsPluginsSetup { + management: ManagementSetup; +} + +export interface IManagementSectionsPluginsStart { + data: DataPublicPluginStart; + share: SharePluginStart; +} + +export interface AppDependencies { + plugins: IManagementSectionsPluginsSetup; + share: SharePluginStart; + uiSettings: IUiSettingsClient; + documentation: AsyncSearchIntroDocumentation; + api: SearchSessionsMgmtAPI; + http: HttpStart; + i18n: I18nStart; +} + +export const APP = { + id: 'background_sessions', + getI18nName: (): string => + i18n.translate('xpack.data.mgmt.searchSessions.appTitle', { + defaultMessage: 'Background Sessions', + }), +}; + +export function registerBackgroundSessionsMgmt( + coreSetup: CoreSetup, + services: IManagementSectionsPluginsSetup +) { + services.management.sections.section.kibana.registerApp({ + id: APP.id, + title: APP.getI18nName(), + order: 2, + mount: async (params) => { + const { SearchSessionsMgmtApp: MgmtApp } = await import('./application'); + const mgmtApp = new MgmtApp(coreSetup, params, services); + return mgmtApp.mountManagementSection(); + }, + }); +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts new file mode 100644 index 0000000000000..fe6b6b26ac6b6 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from '@hapi/boom'; +import type { MockedKeys } from '@kbn/utility-types/jest'; +import { CoreSetup } from 'kibana/public'; +import sinon from 'sinon'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { SessionsClient } from '../../../../../../../src/plugins/data/public/search'; +import { mockUrls } from '../__mocks__'; +import { SearchSessionsMgmtAPI } from './api'; + +let mockCoreSetup: MockedKeys; +let sessionsClient: SessionsClient; +let findSessions: sinon.SinonStub; + +describe('Background Sessions Management API', () => { + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + + sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); + + findSessions = sinon.stub(sessionsClient, 'find').callsFake( + async () => + ({ + saved_objects: [ + { + id: 'hello-pizza-123', + attributes: { name: 'Veggie', appId: 'pizza', status: 'baked' }, + }, + ], + } as any) // can't reach the actual type from public + ); + }); + + describe('listing', () => { + test('fetchDataTable calls the listing endpoint', async () => { + const api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); + expect(await api.fetchTableData()).toMatchInlineSnapshot(` + Array [ + Object { + "actions": Array [ + "delete", + ], + "appId": "pizza", + "created": undefined, + "expires": undefined, + "expiresSoon": false, + "id": "hello-pizza-123", + "isViewable": true, + "name": "Veggie", + "status": "baked", + "url": "hello-cool-undefined-url", + }, + ] + `); + }); + + test('error handling', async () => { + findSessions.callsFake(() => { + throw Boom.badImplementation('implementation is so bad'); + }); + + const api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); + await api.fetchTableData(); + + expect(mockCoreSetup.notifications.toasts.addError).toHaveBeenCalledWith( + new Error('implementation is so bad'), + { title: 'Failed to refresh the page!' } + ); + }); + }); + + describe('delete', () => { + test('send delete calls the delete endpoint with a session ID', async () => { + const api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); + await api.sendDelete('abc-123-cool-session-ID'); + + expect(mockCoreSetup.notifications.toasts.addSuccess).toHaveBeenCalledWith({ + title: 'Deleted session', + }); + }); + + test('error if deleting shows a toast message', async () => { + sinon.stub(sessionsClient, 'delete').callsFake(() => { + throw Boom.badImplementation('implementation is so bad'); + }); + + const api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); + await api.sendDelete('abc-123-cool-session-ID'); + + expect(mockCoreSetup.notifications.toasts.addError).toHaveBeenCalledWith( + new Error('implementation is so bad'), + { title: 'Failed to delete the session!' } + ); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts new file mode 100644 index 0000000000000..b2066322f0000 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -0,0 +1,173 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { NotificationsStart } from 'kibana/public'; +import moment from 'moment'; +import { SharePluginStart } from 'src/plugins/share/public'; +import { ISessionsClient } from '../../../../../../../src/plugins/data/public'; +import { BackgroundSessionSavedObjectAttributes } from '../../../../common'; +import { + ACTION, + EXPIRES_SOON_IN_DAYS, + MAX_SEARCH_HITS, + STATUS, + UISession, +} from '../../../../common/search/sessions_mgmt'; + +type UrlGeneratorsStart = SharePluginStart['urlGenerators']; + +interface BackgroundSessionSavedObject { + id: string; + attributes: BackgroundSessionSavedObjectAttributes; +} + +// Helper: factory for a function to map server objects to UI objects +const mapToUISession = (urls: UrlGeneratorsStart) => async ( + savedObject: BackgroundSessionSavedObject +): Promise => { + // Actions: always allow delete + const actions = [ACTION.DELETE]; + + const { + name, + appId, + created, + expires, + status, + urlGeneratorId, + restoreState, + } = savedObject.attributes; + + // calculate expiresSoon flag + let expiresSoon = false; + if (status === STATUS.COMPLETE) { + try { + const currentDate = moment(); + const expiresDate = moment(created); + const duration = moment.duration(expiresDate.diff(currentDate)); + + if (duration.asDays() <= EXPIRES_SOON_IN_DAYS) { + // TODO: handle negatives by setting status to expired? + expiresSoon = true; + } + } catch (err) { + // eslint-disable-next-line no-console + console.error(`Could not calculate duration to expiration`); + // eslint-disable-next-line no-console + console.error(err); + } + } + + // derive the URL and add it in + let url = '/'; + try { + url = await urls.getUrlGenerator(urlGeneratorId).createUrl(restoreState); + } catch (err) { + // eslint-disable-next-line no-console + console.error('Could not create URL from restoreState'); + // eslint-disable-next-line no-console + console.error(err); + } + + // + return { + id: savedObject.id, + isViewable: true, // always viewable + name, + appId, + created, + expires, + status, + actions, + expiresSoon, + url, + }; +}; + +// Main +export class SearchSessionsMgmtAPI { + // + constructor( + private sessionsClient: ISessionsClient, + private urls: UrlGeneratorsStart, + private notifications: NotificationsStart + ) {} + + // + public async fetchTableData(): Promise { + try { + const result = await this.sessionsClient.find({ + page: 1, + perPage: MAX_SEARCH_HITS, // NOTE: using an easier approach to paging the table in-memory, not requesting single pages as they are viewed + sortField: 'created', + sortOrder: 'asc', + }); + if (result.saved_objects) { + const savedObjects = result.saved_objects as BackgroundSessionSavedObject[]; + return await Promise.all(savedObjects.map(mapToUISession(this.urls))); + } + return null; + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + + this.notifications.toasts.addError(err, { + title: i18n.translate('xpack.data.mgmt.searchSessions.api.fetchError', { + defaultMessage: 'Failed to refresh the page!', + }), + }); + + return null; + } + } + + // Delete + public async sendDelete(id: string): Promise { + try { + await this.sessionsClient.delete(id); + + this.notifications.toasts.addSuccess({ + title: i18n.translate('xpack.data.mgmt.searchSessions.api.deleted', { + defaultMessage: 'Deleted session', + }), + }); + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + + this.notifications.toasts.addError(err, { + title: i18n.translate('xpack.data.mgmt.searchSessions.api.deleteError', { + defaultMessage: 'Failed to delete the session!', + }), + }); + } + + return await this.fetchTableData(); + } + + // Cancel: not implemented + public async sendCancel(id: string): Promise { + this.notifications.toasts.addError(new Error('Not implemented'), { + title: i18n.translate('xpack.data.mgmt.searchSessions.api.cancelError', { + defaultMessage: 'Failed to cancel the session!', + }), + }); + + return await this.fetchTableData(); + } + + // Extend + public async sendExtend(id: string): Promise { + this.notifications.toasts.addError(new Error('Not implemented'), { + title: i18n.translate('xpack.data.mgmt.searchSessions.api.cancelError', { + defaultMessage: 'Failed to extend the session expiration!', + }), + }); + + return await this.fetchTableData(); + } +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts new file mode 100644 index 0000000000000..9799016d75578 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts @@ -0,0 +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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IUiSettingsClient } from 'kibana/public'; +import moment from 'moment'; + +// Helper function to format date string per UI Settings config +// Can throw exception for null date, or error from moment + +export const dateString = (inputString: string, uiSettings: IUiSettingsClient): string => { + if (inputString == null) { + throw new Error('Invalid date string!'); + } + const format = 'D MMM, YYYY, HH:mm:ss'; + const tz: string = uiSettings.get('dateFormat:tz'); + let returnString: string; + if (tz === 'Browser') { + returnString = moment.utc(inputString).tz(moment.tz.guess()).format(format); + } else { + returnString = moment(inputString).tz(tz).format(format); + } + + return returnString; +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts new file mode 100644 index 0000000000000..a07f4afe3a2e6 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts @@ -0,0 +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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DocLinksStart } from 'kibana/public'; + +export class AsyncSearchIntroDocumentation { + private docsBasePath: string = ''; + + public setup(docs: DocLinksStart) { + const { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } = docs; + const docsBase = `${ELASTIC_WEBSITE_URL}guide/en`; + this.docsBasePath = `${docsBase}/elasticsearch/reference/${DOC_LINK_VERSION}`; + } + + public getElasticsearchDocLink() { + return `${this.docsBasePath}/async-search-intro.html`; + } +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx new file mode 100644 index 0000000000000..2cc60a6cd548f --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -0,0 +1,201 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiTableFieldDataColumnType } from '@elastic/eui'; +import { MockedKeys } from '@kbn/utility-types/jest'; +import { mount } from 'enzyme'; +import { CoreSetup } from 'kibana/public'; +import { ReactElement } from 'react'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { SessionsClient } from '../../../../../../../src/plugins/data/public/search'; +import { ActionComplete, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; +import { mockUrls } from '../__mocks__'; +import { SearchSessionsMgmtAPI } from './api'; +import { getColumns } from './get_columns'; + +let mockCoreSetup: MockedKeys; +let api: SearchSessionsMgmtAPI; +let sessionsClient: SessionsClient; +let handleAction: ActionComplete; +let mockSession: UISession; + +describe('Background Sessions Management table column factory', () => { + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + + sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); + + api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); + mockCoreSetup.uiSettings.get.mockImplementation((key) => { + return key === 'dateFormat:tz' ? 'UTC' : null; + }); + + handleAction = () => { + throw new Error('not testing handle action'); + }; + + mockSession = { + name: 'Cool mock session', + id: 'wtywp9u2802hahgp-thao', + url: '/app/great-app-url/#42', + appId: 'discovery', + status: STATUS.IN_PROGRESS, + created: '2020-12-02T00:19:32Z', + expires: '2020-12-07T00:19:32Z', + isViewable: true, + expiresSoon: false, + }; + }); + + test('returns columns', () => { + const columns = getColumns(api, mockCoreSetup.http, mockCoreSetup.uiSettings, handleAction); + expect(columns).toMatchInlineSnapshot(` + Array [ + Object { + "field": "appId", + "name": "Type", + "render": [Function], + "sortable": true, + }, + Object { + "field": "name", + "name": "Name", + "render": [Function], + "sortable": true, + "width": "20%", + }, + Object { + "field": "status", + "name": "Status", + "render": [Function], + "sortable": true, + }, + Object { + "field": "created", + "name": "Created", + "render": [Function], + "sortable": true, + }, + Object { + "field": "expires", + "name": "Expiration", + "render": [Function], + "sortable": true, + }, + Object { + "field": "status", + "name": "", + "render": [Function], + "sortable": false, + }, + Object { + "field": "actions", + "name": "", + "render": [Function], + "sortable": false, + }, + ] + `); + }); + + describe('name', () => { + test('rendering', () => { + const [, nameColumn] = getColumns( + api, + mockCoreSetup.http, + mockCoreSetup.uiSettings, + handleAction + ) as Array>; + + const name = mount(nameColumn.render!(mockSession.name, mockSession) as ReactElement); + + expect(name.text()).toBe('Cool mock session'); + }); + }); + + // Status column + describe('status', () => { + test('render in_progress', () => { + const [, , status] = getColumns( + api, + mockCoreSetup.http, + mockCoreSetup.uiSettings, + handleAction + ) as Array>; + + const statusLine = mount(status.render!(mockSession.status, mockSession) as ReactElement); + expect( + statusLine + .find('.euiText[data-test-subj="session-mgmt-view-status-tooltip-in_progress"]') + .text() + ).toMatchInlineSnapshot(`"In progress"`); + }); + + test('error handling', () => { + const [, , status] = getColumns( + api, + mockCoreSetup.http, + mockCoreSetup.uiSettings, + handleAction + ) as Array>; + + mockSession.status = 'INVALID' as STATUS; + const statusLine = mount(status.render!(mockSession.status, mockSession) as ReactElement); + + // no unhandled error + + expect(statusLine.text()).toMatchInlineSnapshot(`"INVALID"`); + }); + }); + + // Start Date column + describe('startedDate', () => { + test('render using Browser timezone', () => { + mockCoreSetup.uiSettings.get.mockImplementation(() => 'Browser'); + + const [, , , createdDateCol] = getColumns( + api, + mockCoreSetup.http, + mockCoreSetup.uiSettings, + handleAction + ) as Array>; + + const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement); + + expect(date.text()).toBe('1 Dec, 2020, 19:19:32'); + }); + + test('render using AK timezone', () => { + mockCoreSetup.uiSettings.get.mockImplementation(() => 'US/Alaska'); + + const [, , , createdDateCol] = getColumns( + api, + mockCoreSetup.http, + mockCoreSetup.uiSettings, + handleAction + ) as Array>; + + const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement); + + expect(date.text()).toBe('1 Dec, 2020, 15:19:32'); + }); + + test('error handling', () => { + const [, , , createdDateCol] = getColumns( + api, + mockCoreSetup.http, + mockCoreSetup.uiSettings, + handleAction + ) as Array>; + + mockSession.created = 'INVALID'; + const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement); + + // no unhandled error + expect(date.text()).toBe('Invalid date'); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx new file mode 100644 index 0000000000000..6fd6f56906699 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -0,0 +1,198 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiBadge, + EuiBasicTableColumn, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { HttpStart, IUiSettingsClient } from 'kibana/public'; +import { capitalize } from 'lodash'; +import moment from 'moment'; +import React from 'react'; +import { ActionComplete, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; +import { TableText } from '../components'; +import { InlineActions, PopoverActionsMenu } from '../components/actions'; +import { StatusIndicator } from '../components/status'; +import { SearchSessionsMgmtAPI } from './api'; +import { dateString } from './date_string'; + +// Helper function: translate an app string to EuiIcon-friendly string +const appToIcon = (app: string) => { + if (app === 'dashboards') { + return 'dashboard'; + } + return app; +}; + +// Main +export const getColumns = ( + api: SearchSessionsMgmtAPI, + http: HttpStart, + uiSettings: IUiSettingsClient, + handleAction: ActionComplete +): Array> => { + // Use a literal array of table column definitions to detail a UISession object + return [ + // Type (appIcon) + { + field: 'appId', + name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerName', { + defaultMessage: 'Type', + }), + sortable: true, + render: (appId: UISession['appId']) => { + const app = `${appToIcon(appId)}`; + return ( + + + + ); + }, + }, + + // Name, links to app and displays the search session data + { + field: 'name', + name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerName', { + defaultMessage: 'Name', + }), + sortable: true, + width: '20%', + render: (name: UISession['name'], { appId, url, id }) => { + return ( + + {name} + + ); + }, + }, + + // Session status + { + field: 'status', + name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerStatus', { + defaultMessage: 'Status', + }), + sortable: true, + render: (statusType: UISession['status'], session) => { + return ; + }, + }, + + // Started date, formatted in TZ from UI Settings + { + field: 'created', + name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerStarted', { + defaultMessage: 'Created', + }), + sortable: true, + render: (created: UISession['created'], { id }) => { + try { + const startedOn = dateString(created, uiSettings); + return {startedOn}; + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + return {created}; + } + }, + }, + + // Expiration date, also formatted. Shows a warning badge if expiration is soon + { + field: 'expires', + name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerExpiration', { + defaultMessage: 'Expiration', + }), + sortable: true, + render: (expires: UISession['expires'], session) => { + const { status } = session; + if (expires && status !== STATUS.IN_PROGRESS && status !== STATUS.ERROR) { + try { + const expiresOn = dateString(expires, uiSettings); + + // return + return {expiresOn}; + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + return {expires}; + } + } + return --; + }, + }, + + // Highlight Badge: if completed session expires soon, show a highlight badge + { + field: 'status', + name: '', + sortable: false, + render: (status, session) => { + if (session.expiresSoon) { + const tNow = moment().valueOf(); + const tFuture = moment(session.expires).valueOf(); + + const numDays = Math.floor(moment.duration(tFuture - tNow).asDays()); + const toolTipContent = i18n.translate( + 'xpack.data.mgmt.searchSessions.status.expiresSoonIn', + { + defaultMessage: 'Expires in {numDays} days', + values: { numDays }, + } + ); + + return ( + + + + + + ); + } + + return ; + }, + }, + + // Action(s) in-line in the row, additional action(s) in the popover, no column header + { + field: 'actions', + name: '', + sortable: false, + render: (actions: UISession['actions'], session) => { + if (session.isViewable || (actions && actions.length)) { + return ( + + + + + + + + + ); + } + }, + }, + ]; +}; diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx index ce77686c4f3c1..4c53068f77f24 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx @@ -66,7 +66,7 @@ const ContinueInBackgroundButton = ({ ); const ViewBackgroundSessionsButton = ({ - viewBackgroundSessionsLink = 'management', + viewBackgroundSessionsLink = 'management/kibana/background_sessions', buttonProps = {}, }: ActionButtonProps) => ( Date: Fri, 18 Dec 2020 15:00:17 -0700 Subject: [PATCH 03/52] functional tests --- .../components/{ => actions}/actions.test.tsx | 23 +- .../components/actions/inline_actions.tsx | 5 +- .../components/actions/popover_actions.tsx | 11 +- .../sessions_mgmt/components/table/table.tsx | 1 + .../search/sessions_mgmt/lib/get_columns.tsx | 43 +- .../data/bckgnd_sessions/data.json.gz | Bin 0 -> 1879 bytes .../data/bckgnd_sessions/mappings.json | 2596 +++++++++++++++++ .../services/send_to_background.ts | 4 + .../dashboard/async_search/async_search.ts | 33 + 9 files changed, 2684 insertions(+), 32 deletions(-) rename x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/{ => actions}/actions.test.tsx (69%) create mode 100644 x-pack/test/functional/es_archives/data/bckgnd_sessions/data.json.gz create mode 100644 x-pack/test/functional/es_archives/data/bckgnd_sessions/mappings.json diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/actions.test.tsx similarity index 69% rename from x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions.test.tsx rename to x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/actions.test.tsx index 1f75d2abe1457..4ae7fcc9cb03d 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/actions.test.tsx @@ -6,9 +6,9 @@ import { mount } from 'enzyme'; import React from 'react'; -import { STATUS, UISession } from '../../../../common/search/sessions_mgmt'; -import { LocaleWrapper } from '../__mocks__'; -import { InlineActions } from './actions'; +import { STATUS, UISession } from '../../../../../common/search/sessions_mgmt'; +import { LocaleWrapper } from '../../__mocks__'; +import { InlineActions } from './inline_actions'; let session: UISession; @@ -35,13 +35,8 @@ describe('Background Search Session management actions', () => { ); - expect( - actions.find(`[data-test-subj="session-mgmt-view-action-wtywp9u2802hahgp-gluk"]`).exists() - ).toBe(true); - - expect(actions.find(`[data-test-subj="session-mgmt-view-href"]`).first().prop('href')).toBe( - '/app/kibana/coolapp' - ); + expect(actions.find(`[data-test-subj="session-mgmt-view-action"]`).exists()).toBe(true); + expect(actions.find('a').prop('href')).toBe('/app/kibana/coolapp'); }); test('isViewable = false', () => { @@ -52,9 +47,7 @@ describe('Background Search Session management actions', () => { ); - expect( - actions.find(`[data-test-subj="session-mgmt-view-action-wtywp9u2802hahgp-gluk"]`).exists() - ).toBe(false); + expect(actions.find(`[data-test-subj="session-mgmt-view-action"]`).exists()).toBe(false); }); test('error handling', () => { @@ -69,9 +62,7 @@ describe('Background Search Session management actions', () => { ); // no unhandled errors - expect( - actions.find(`[data-test-subj="session-mgmt-view-action-wtywp9u2802hahgp-gluk"]`).exists() - ).toBe(true); + expect(actions.find(`[data-test-subj="session-mgmt-view-action"]`).exists()).toBe(true); }); }); }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx index 346e45a742c76..1e16467014592 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx @@ -24,12 +24,13 @@ export const InlineActions = ({ url, session }: InlineActionProps) => { - + ( - +const PopoverAction = ({ textColor, iconType, children, ...props }: PopoverActionProps) => ( + @@ -101,7 +101,12 @@ export const PopoverActionsMenu = ({ api, handleAction, session }: PopoverAction { key: `action-${actionType}`, name: ( - + {label} ), diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index c0add3c15967b..f6df39087f182 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -93,6 +93,7 @@ export function SearchSessionsMgmtTable({ api, http, uiSettings, initialTable, . {...props} id={TABLE_ID} + data-test-subj={TABLE_ID} columns={getColumns(api, http, uiSettings, handleActionCompleted)} items={tableData} pagination={pagination} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index 6fd6f56906699..b51200659ecc9 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -50,11 +50,15 @@ export const getColumns = ( defaultMessage: 'Type', }), sortable: true, - render: (appId: UISession['appId']) => { + render: (appId: UISession['appId'], { id }) => { const app = `${appToIcon(appId)}`; return ( - + ); }, @@ -70,8 +74,8 @@ export const getColumns = ( width: '20%', render: (name: UISession['name'], { appId, url, id }) => { return ( - - {name} + + {name} ); }, @@ -99,7 +103,15 @@ export const getColumns = ( render: (created: UISession['created'], { id }) => { try { const startedOn = dateString(created, uiSettings); - return {startedOn}; + return ( + + {startedOn} + + ); } catch (err) { // eslint-disable-next-line no-console console.error(err); @@ -115,14 +127,21 @@ export const getColumns = ( defaultMessage: 'Expiration', }), sortable: true, - render: (expires: UISession['expires'], session) => { - const { status } = session; + render: (expires: UISession['expires'], { id, status }) => { if (expires && status !== STATUS.IN_PROGRESS && status !== STATUS.ERROR) { try { const expiresOn = dateString(expires, uiSettings); // return - return {expiresOn}; + return ( + + {expiresOn} + + ); } catch (err) { // eslint-disable-next-line no-console console.error(err); @@ -138,10 +157,10 @@ export const getColumns = ( field: 'status', name: '', sortable: false, - render: (status, session) => { - if (session.expiresSoon) { + render: (status, { id, expires, expiresSoon }) => { + if (expiresSoon) { const tNow = moment().valueOf(); - const tFuture = moment(session.expires).valueOf(); + const tFuture = moment(expires).valueOf(); const numDays = Math.floor(moment.duration(tFuture - tNow).asDays()); const toolTipContent = i18n.translate( @@ -158,6 +177,8 @@ export const getColumns = ( diff --git a/x-pack/test/functional/es_archives/data/bckgnd_sessions/data.json.gz b/x-pack/test/functional/es_archives/data/bckgnd_sessions/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..8776707064cd20f9388afc6d19232cf2c60b4316 GIT binary patch literal 1879 zcmYk(c{Cdc9>8%1gH{YuX~~2taR!=8;NwDr)izH0F(P|KPs~FzA-Om4?_kO?c@25x<6DzWMooO3}R_l$FtBR!~EBG zKC;Yjs~J*)v>l(-=+xT0@-T&IxDY=>w6ypX4R^>Oj?P;cc#j7*{-e10;i~jA2GNB) zrh8|`nixB%iL8K!iRwnEvTbdUMJ=+vpLnp35t>hs}24sgZS z-Wz<7tY{PT@py&F3=y@nsrYrO8Kb-`3p+J}m7S`^mobm_oXkYT_F|-@#(16TV$JtR zWkc*1N8NjHK4d!n(IyriQmBJ&;-KgG8jgV~Wkx{1o~#mOuN&7pLZwUMe5W~F1V6T! z_i;4+bKU1@vfHRfX||`q)b_>;LV1k}+JnYS(g?llgD<@OLuvBKu7~*!mW2y`eG;g6 ztf{t0TQ1dHZdh?YC^`BT9~ZM6>i24Ft~?5dh>aYBesd2&9&0uH9+ba+-}$w1T^LTG z;*tTV>FeCYLAq z1A!iz&Y?SC!cFxb6v0Znv9+n2EeVB`+{5{KfWq5%_b2pC#g8%={EXu?NQ!9wjNT9m zH~Wb@sk*e~Y55$Ewils^7WSYR96e`V$UwC|FMrve6CK$Tr+l&Gsd7Nr(`)}kj%~9{ zBWRDF)W`pRrJ%dwSMOPC-A%o=d@&=3mwstACxJ}q$}!K|E}0Pgvi6D z!we?+i$+kRb3j(BxFy3QrBO(Np05W8^9dGZ94qvBw-XGeX6)HP+LA`y#_;RlotlCA zy@>S4u{gf$;lV8I0cYby>G&Gf6BMtIpoJXl0QBH*p~jZl!JOR~Pn%5cPm$`2no&LB z#(fA$dsk8lnKF`+_DrR9A>S6Sb%6+En^egFDyjq|F=wiPf8oSb^;-6yij@X#_ zSpD7-BNqQSk+5QyC*qJ-s9+;AG3&hFztOeSerN@ns;xDt7 zALUzcP)lC+&#{Xv*->J12&tx(ilhtAPd}|~hWu;FY*k|<%iDLv=&>50?oQXOq~j`+ z(tQQ2dR^z;xH?>?O@!}&emv4pReMZ4KDvHIYqL>=vl=^IK$G)2z%?5q%5IMvGU0nQ z^Z)UYC)PD7b}e0fFXCg7drWv6&`&CjKXNLiS_Q22MLBp3myy%bbuPRVCh1-9QM0Bm zC3wF8GORI+oyqMR-tkNVst zpGlC@Dt_KnM&u^aQF!K@{sP(rI5%Gt`4Zoaz<-z6o?^^4HNK(Jzi5<6wI*NvHV09T?CObxTfimlq5& zFfQM4%(-C8(}`#{+M_6KBQ#;Nc<6-D%meqe{g%yB@CWy?gINy+Xi7n^qcaX9lxURf zE%U42{><@a$p}cw4H->!VRG0hH4AawZoOzKwmj5K(6aqG8+0SYAS)*s1zmz*W-AGPgI>#6V=$ucey zGA?32){47{aslE~IOW9x25{ZM3~q0xbO=y&666->MHzGh9L%T{3}89~c#VVVyEK1B zLA48C=+-%9pV9B=U#KPL42%QhhGql*Mn8~3w0+Xf0v1RyRx&OPYJA33eg+ruh$@_R z9=Wgq0QYgjC7 zSS+<~Yf!Av3)U~@*>)(Apc7LlnQ@fm7I+x-RqxrgLz2OhD4821s2~YyO{eBTo6jiB lw*3p`p40QkgyX-^40?4QwCaqW!rM*-iK*iXG35dRe*%|#nil{7 literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/data/bckgnd_sessions/mappings.json b/x-pack/test/functional/es_archives/data/bckgnd_sessions/mappings.json new file mode 100644 index 0000000000000..ce06b7be35071 --- /dev/null +++ b/x-pack/test/functional/es_archives/data/bckgnd_sessions/mappings.json @@ -0,0 +1,2596 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "49eb3350984bd2a162914d3776e70cfb", + "api_key_pending_invalidation": "16f515278a295f6245149ad7c5ddedb7", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "background-session": "dfd06597e582fdbbbc09f1a3615e6ce0", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "477f214ff61acc3af26a7b7818e380c1", + "cases-comments": "8a50736330e953bca91747723a319593", + "cases-configure": "387c5f3a3bda7e0ae0dd4e106f914a69", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "core-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", + "dashboard": "40554caf09725935e2c02e02563a2d07", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "enterprise_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "epm-packages": "0cbbb16506734d341a96aaed65ec6413", + "epm-packages-assets": "44621b2f6052ef966da47b7c3a00f33b", + "exception-list": "67f055ab8c10abd7b2ebfd969b836788", + "exception-list-agnostic": "67f055ab8c10abd7b2ebfd969b836788", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "9511b565b1cc6441a42033db3d5de8e9", + "fleet-agent-events": "e20a508b6e805189356be381dbfac8db", + "fleet-agents": "cb661e8ede2b640c42c8e5ef99db0683", + "fleet-enrollment-api-keys": "a69ef7ae661dab31561d6c6f052ef2a7", + "graph-workspace": "27a94b2edcb0610c6aea54a7c56d7752", + "index-pattern": "45915a1ad866812242df474eb0479052", + "infrastructure-ui-source": "3d1b76c39bfb2cc8296b024d73854724", + "ingest-agent-policies": "8b0733cce189659593659dad8db426f0", + "ingest-outputs": "8854f34453a47e26f86a29f8f3b80b4e", + "ingest-package-policies": "c91ca97b1ff700f0fc64dc6b13d65a85", + "ingest_manager_settings": "02a03095f0e05b7a538fa801b88a217f", + "inventory-view": "3d1b76c39bfb2cc8296b024d73854724", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "52346cfec69ff7b47d5f0c12361a2797", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "3d1b76c39bfb2cc8296b024d73854724", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-job": "3bb64c31915acf93fc724af137a0891b", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "monitoring-telemetry": "2669d5ec15e82391cf58df4294ee9c68", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "originId": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "43012c7ebc4cb57054e0a490e4b43023", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "d12c5474364d737d17252acf1dc4585c", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "spaces-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", + "tag": "83d55da58f6530f7055415717ec06474", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "3d1b76c39bfb2cc8296b024d73854724", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "f819cf6636b75c9e76ba733a0c6ef355", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "executionStatus": { + "properties": { + "error": { + "properties": { + "message": { + "type": "keyword" + }, + "reason": { + "type": "keyword" + } + } + }, + "lastExecutionDate": { + "type": "date" + }, + "status": { + "type": "keyword" + } + } + }, + "meta": { + "properties": { + "versionApiKeyLastmodified": { + "type": "keyword" + } + } + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "notifyWhen": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedAt": { + "type": "date" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "api_key_pending_invalidation": { + "properties": { + "apiKeyId": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "background-session": { + "properties": { + "appId": { + "type": "keyword" + }, + "created": { + "type": "date" + }, + "expires": { + "type": "date" + }, + "idMapping": { + "enabled": false, + "type": "object" + }, + "initialState": { + "enabled": false, + "type": "object" + }, + "name": { + "type": "keyword" + }, + "restoreState": { + "enabled": false, + "type": "object" + }, + "sessionId": { + "type": "keyword" + }, + "status": { + "type": "keyword" + }, + "urlGeneratorId": { + "type": "keyword" + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector": { + "properties": { + "fields": { + "properties": { + "key": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "id": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "alertId": { + "type": "keyword" + }, + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "index": { + "type": "keyword" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector": { + "properties": { + "fields": { + "properties": { + "key": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "id": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "core-usage-stats": { + "dynamic": "false", + "type": "object" + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "optionsJSON": { + "index": false, + "type": "text" + }, + "panelsJSON": { + "index": false, + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "pause": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "section": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "value": { + "doc_values": false, + "index": false, + "type": "integer" + } + } + }, + "timeFrom": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "timeRestore": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "timeTo": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "enterprise_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "install_source": { + "type": "keyword" + }, + "install_started_at": { + "type": "date" + }, + "install_status": { + "type": "keyword" + }, + "install_version": { + "type": "keyword" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "package_assets": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "epm-packages-assets": { + "properties": { + "asset_path": { + "type": "keyword" + }, + "data_base64": { + "type": "binary" + }, + "data_utf8": { + "index": false, + "type": "text" + }, + "install_source": { + "type": "keyword" + }, + "media_type": { + "type": "keyword" + }, + "package_name": { + "type": "keyword" + }, + "package_version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "os_types": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "os_types": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "ack_data": { + "type": "text" + }, + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "policy_id": { + "type": "keyword" + }, + "policy_revision": { + "type": "integer" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "policy_id": { + "type": "keyword" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "policy_id": { + "type": "keyword" + }, + "policy_revision": { + "type": "integer" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "upgrade_started_at": { + "type": "date" + }, + "upgraded_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "policy_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "legacyIndexPatternRef": { + "index": false, + "type": "text" + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "dynamic": "false", + "properties": { + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "dynamic": "false", + "type": "object" + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_policies": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "config_yaml": { + "type": "text" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "compiled_input": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "policy_id": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_urls": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "dynamic": "false", + "type": "object" + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "dynamic": "false", + "type": "object" + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-job": { + "properties": { + "datafeed_id": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "job_id": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "monitoring-telemetry": { + "properties": { + "reportedClusterUuids": { + "type": "keyword" + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "originId": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "indexNames": { + "type": "text" + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "spaces-usage-stats": { + "dynamic": "false", + "type": "object" + }, + "tag": { + "properties": { + "color": { + "type": "text" + }, + "description": { + "type": "text" + }, + "name": { + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-counter": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "dynamic": "false", + "type": "object" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "savedSearchRefName": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "index": false, + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "index": false, + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts index 7fce2267099b9..918a5d44eeb15 100644 --- a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts @@ -65,6 +65,10 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { await this.ensurePopoverClosed(); } + public async openPopover() { + await this.ensurePopoverOpened(); + } + private async ensurePopoverOpened() { const isAlreadyOpen = await testSubjects.exists(SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ); if (isAlreadyOpen) return; diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts index 4859b2474f860..04981985e1489 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts @@ -11,6 +11,7 @@ import { getSearchSessionIdByPanelProvider } from './get_search_session_id_by_pa export default function ({ getService, getPageObjects }: FtrProviderContext) { const es = getService('es'); const testSubjects = getService('testSubjects'); + const find = getService('find'); const log = getService('log'); const PageObjects = getPageObjects(['common', 'header', 'dashboard', 'visChart']); const getSearchSessionIdByPanel = getSearchSessionIdByPanelProvider(getService); @@ -132,5 +133,37 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await sendToBackground.expectState('restored'); }); }); + + describe('Management UI', () => { + before(async () => { + await PageObjects.common.navigateToApp('dashboard'); + }); + + it('Saves a session and verifies it in the Management app', async () => { + await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); + await PageObjects.dashboard.waitForRenderComplete(); + await sendToBackground.expectState('completed'); + await sendToBackground.save(); + await sendToBackground.expectState('backgroundCompleted'); + + await sendToBackground.openPopover(); + await sendToBackground.viewBackgroundSessions(); + + await testSubjects.existOrFail('session-mgmt-view-status-label-in_progress'); + await testSubjects.existOrFail('session-mgmt-view-status-tooltip-in_progress'); + await testSubjects.existOrFail('session-mgmt-table-col-created'); + await testSubjects.existOrFail('session-mgmt-view-action'); + + expect( + await find.existsByCssSelector( + '[data-test-subj="session-mgmt-table-col-app-icon"][data-test-app-id="dashboard"]' + ) + ).to.be(true); + + expect( + await testSubjects.find('session-mgmt-table-col-name').then((n) => n.getVisibleText()) + ).to.be('Not Delayed'); + }); + }); }); } From 5f7a3c872b38879668c6d7c88b82b006c8c8aa4a Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 18 Dec 2020 17:08:48 -0700 Subject: [PATCH 04/52] fix ci --- .../public/search/sessions_mgmt/components/home.test.tsx | 6 +++++- .../search/sessions_mgmt/components/table/table.test.tsx | 4 ++-- .../public/search/sessions_mgmt/lib/api.test.ts | 4 ++-- .../data_enhanced/public/search/sessions_mgmt/lib/api.ts | 2 +- .../public/search/sessions_mgmt/lib/get_columns.test.tsx | 4 ++-- .../public/search/sessions_mgmt/lib/get_columns.tsx | 2 +- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx index f46a702ab42b0..9eca069606744 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx @@ -9,6 +9,7 @@ import { mount, ReactWrapper } from 'enzyme'; import { CoreSetup } from 'kibana/public'; import React from 'react'; import { coreMock } from 'src/core/public/mocks'; +import { SessionsClient } from 'src/plugins/data/public/search'; import { UISession, STATUS } from '../../../../common/search/sessions_mgmt'; import { SearchSessionsMgmtAPI } from '../lib/api'; import { AsyncSearchIntroDocumentation } from '../lib/documentation'; @@ -16,6 +17,7 @@ import { LocaleWrapper, mockUrls } from '../__mocks__'; import { SearchSessionsMgmtHome } from './home'; let mockCoreSetup: MockedKeys; +let sessionsClient: SessionsClient; let initialTable: UISession[]; let api: SearchSessionsMgmtAPI; @@ -23,7 +25,9 @@ describe('Background Search Session Management Home', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); - api = new SearchSessionsMgmtAPI(mockCoreSetup.http, mockUrls, mockCoreSetup.notifications); + sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); + + api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); initialTable = [ { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index 05eb226333244..32368b5e63bf7 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -8,8 +8,8 @@ import { MockedKeys } from '@kbn/utility-types/jest'; import { mount, ReactWrapper } from 'enzyme'; import { CoreSetup } from 'kibana/public'; import React from 'react'; -import { coreMock } from '../../../../../../../../src/core/public/mocks'; -import { SessionsClient } from '../../../../../../../../src/plugins/data/public/search'; +import { coreMock } from 'src/core/public/mocks'; +import { SessionsClient } from 'src/plugins/data/public/search'; import { STATUS, UISession } from '../../../../../common/search/sessions_mgmt'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { LocaleWrapper, mockUrls } from '../../__mocks__'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts index fe6b6b26ac6b6..c71c3e205c93a 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts @@ -8,8 +8,8 @@ import Boom from '@hapi/boom'; import type { MockedKeys } from '@kbn/utility-types/jest'; import { CoreSetup } from 'kibana/public'; import sinon from 'sinon'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; -import { SessionsClient } from '../../../../../../../src/plugins/data/public/search'; +import { coreMock } from 'src/core/public/mocks'; +import { SessionsClient } from 'src/plugins/data/public/search'; import { mockUrls } from '../__mocks__'; import { SearchSessionsMgmtAPI } from './api'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index b2066322f0000..7b721c035bc01 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -163,7 +163,7 @@ export class SearchSessionsMgmtAPI { // Extend public async sendExtend(id: string): Promise { this.notifications.toasts.addError(new Error('Not implemented'), { - title: i18n.translate('xpack.data.mgmt.searchSessions.api.cancelError', { + title: i18n.translate('xpack.data.mgmt.searchSessions.api.extendError', { defaultMessage: 'Failed to extend the session expiration!', }), }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index 2cc60a6cd548f..3160e8c6722a2 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -9,8 +9,8 @@ import { MockedKeys } from '@kbn/utility-types/jest'; import { mount } from 'enzyme'; import { CoreSetup } from 'kibana/public'; import { ReactElement } from 'react'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; -import { SessionsClient } from '../../../../../../../src/plugins/data/public/search'; +import { coreMock } from 'src/core/public/mocks'; +import { SessionsClient } from 'src/plugins/data/public/search'; import { ActionComplete, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; import { mockUrls } from '../__mocks__'; import { SearchSessionsMgmtAPI } from './api'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index b51200659ecc9..863f15f6638ba 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -46,7 +46,7 @@ export const getColumns = ( // Type (appIcon) { field: 'appId', - name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerName', { + name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerType', { defaultMessage: 'Type', }), sortable: true, From 5d1cdb077a6a872c9b8f02098fe1c6b05955d121 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Fri, 18 Dec 2020 17:22:45 -0700 Subject: [PATCH 05/52] new functional tests --- .../search/sessions_mgmt/lib/get_columns.tsx | 10 +- .../config.ts | 5 +- .../dashboard/async_search/async_search.ts | 33 ----- .../management/background_sessions/index.ts | 14 ++ .../sessions_management.ts | 136 ++++++++++++++++++ 5 files changed, 163 insertions(+), 35 deletions(-) create mode 100644 x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/index.ts create mode 100644 x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index 863f15f6638ba..642ef4707c61b 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -148,7 +148,15 @@ export const getColumns = ( return {expires}; } } - return --; + return ( + + -- + + ); }, }, diff --git a/x-pack/test/send_search_to_background_integration/config.ts b/x-pack/test/send_search_to_background_integration/config.ts index 7dd0915de3c33..ca6365f1f260d 100644 --- a/x-pack/test/send_search_to_background_integration/config.ts +++ b/x-pack/test/send_search_to_background_integration/config.ts @@ -20,7 +20,10 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { reportName: 'X-Pack Background Search UI (Enabled WIP Feature)', }, - testFiles: [resolve(__dirname, './tests/apps/dashboard/async_search')], + testFiles: [ + resolve(__dirname, './tests/apps/dashboard/async_search'), + resolve(__dirname, './tests/apps/management/background_sessions'), + ], kbnTestServer: { ...xpackFunctionalConfig.get('kbnTestServer'), diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts index 04981985e1489..4859b2474f860 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts @@ -11,7 +11,6 @@ import { getSearchSessionIdByPanelProvider } from './get_search_session_id_by_pa export default function ({ getService, getPageObjects }: FtrProviderContext) { const es = getService('es'); const testSubjects = getService('testSubjects'); - const find = getService('find'); const log = getService('log'); const PageObjects = getPageObjects(['common', 'header', 'dashboard', 'visChart']); const getSearchSessionIdByPanel = getSearchSessionIdByPanelProvider(getService); @@ -133,37 +132,5 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await sendToBackground.expectState('restored'); }); }); - - describe('Management UI', () => { - before(async () => { - await PageObjects.common.navigateToApp('dashboard'); - }); - - it('Saves a session and verifies it in the Management app', async () => { - await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); - await PageObjects.dashboard.waitForRenderComplete(); - await sendToBackground.expectState('completed'); - await sendToBackground.save(); - await sendToBackground.expectState('backgroundCompleted'); - - await sendToBackground.openPopover(); - await sendToBackground.viewBackgroundSessions(); - - await testSubjects.existOrFail('session-mgmt-view-status-label-in_progress'); - await testSubjects.existOrFail('session-mgmt-view-status-tooltip-in_progress'); - await testSubjects.existOrFail('session-mgmt-table-col-created'); - await testSubjects.existOrFail('session-mgmt-view-action'); - - expect( - await find.existsByCssSelector( - '[data-test-subj="session-mgmt-table-col-app-icon"][data-test-app-id="dashboard"]' - ) - ).to.be(true); - - expect( - await testSubjects.find('session-mgmt-table-col-name').then((n) => n.getVisibleText()) - ).to.be('Not Delayed'); - }); - }); }); } diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/index.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/index.ts new file mode 100644 index 0000000000000..d7bb288dfdb5f --- /dev/null +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('background sessions management', function () { + this.tags('ciGroup3'); + + loadTestFile(require.resolve('./sessions_management')); + }); +} diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts new file mode 100644 index 0000000000000..21095bb2e9de9 --- /dev/null +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'header', 'dashboard', 'visChart']); + const sendToBackground = getService('sendToBackground'); + const esArchiver = getService('esArchiver'); + + describe('dashboard with async search', () => { + describe('Send to background', () => { + before(async () => { + await PageObjects.common.navigateToApp('dashboard'); + }); + + it('Saves a session and verifies it in the Management app', async () => { + await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); + await PageObjects.dashboard.waitForRenderComplete(); + await sendToBackground.expectState('completed'); + await sendToBackground.save(); + await sendToBackground.expectState('backgroundCompleted'); + + await sendToBackground.openPopover(); + await sendToBackground.viewBackgroundSessions(); + + await testSubjects.existOrFail('session-mgmt-view-status-label-in_progress'); + await testSubjects.existOrFail('session-mgmt-view-status-tooltip-in_progress'); + await testSubjects.existOrFail('session-mgmt-table-col-created'); + await testSubjects.existOrFail('session-mgmt-view-action'); + + expect( + await testSubjects.find('session-mgmt-table-col-name').then((n) => n.getVisibleText()) + ).to.be('Not Delayed'); + }); + }); + + describe('Management UI', () => { + before(async () => { + await PageObjects.common.navigateToApp('management/kibana/background_sessions'); + }); + + it('shows no items found', async () => { + expectSnapshot( + await testSubjects.find('backgroundSessionsMgmtTable').then((n) => n.getVisibleText()) + ).toMatchInline(` + "Type + Name + Status + Created + Expiration + No items found" + `); + }); + + it('autorefreshes and shows items on the server', async () => { + await esArchiver.load('data/bckgnd_sessions'); + + const nameColumnText = await testSubjects + .findAll('session-mgmt-table-col-name') + .then((nCol) => Promise.all(nCol.map((n) => n.getVisibleText()))); + + expect(nameColumnText.length).to.be(10); + + expectSnapshot(nameColumnText).toMatchInline(` + Array [ + "In-Progress Session 1", + "Completed Session 1", + "Expired Session 1", + "Cancelled Session 1", + "Error Session 1", + "In-Progress Session 2", + "Completed Session 2", + "Expired Session 2", + "Cancelled Session 2", + "A very very very very very very very very very very very very very very very very very very very very very very very long name Error Session 2", + ] + `); + + const createdColText = await testSubjects + .findAll('session-mgmt-table-col-created') + .then((nCol) => Promise.all(nCol.map((n) => n.getVisibleText()))); + + expect(createdColText.length).to.be(10); + + expectSnapshot(createdColText).toMatchInline(` + Array [ + "1 Dec, 2020, 00:00:00", + "2 Dec, 2020, 00:00:00", + "3 Dec, 2020, 00:00:00", + "4 Dec, 2020, 00:00:00", + "5 Dec, 2020, 00:00:00", + "6 Dec, 2020, 00:00:00", + "7 Dec, 2020, 00:00:00", + "8 Dec, 2020, 00:00:00", + "9 Dec, 2020, 00:00:00", + "10 Dec, 2020, 00:00:00", + ] + `); + + const expiresColText = await testSubjects + .findAll('session-mgmt-table-col-expires') + .then((nCol) => Promise.all(nCol.map((n) => n.getVisibleText()))); + + expect(expiresColText.length).to.be(10); + + expectSnapshot(expiresColText).toMatchInline(` + Array [ + "--", + "3 Dec, 2020, 00:00:00", + "4 Dec, 2020, 00:00:00", + "5 Dec, 2020, 00:00:00", + "--", + "--", + "8 Dec, 2020, 00:00:00", + "9 Dec, 2020, 00:00:00", + "10 Dec, 2020, 00:00:00", + "--", + ] + `); + + await esArchiver.unload('data/bckgnd_sessions'); + }); + + it('pagination', async () => { + await esArchiver.load('data/bckgnd_sessions'); + await esArchiver.unload('data/bckgnd_sessions'); + }); + }); + }); +} From 534ae5276a219df36dcfeedaf4f65aef8466bd1c Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Sat, 19 Dec 2020 23:02:45 -0700 Subject: [PATCH 06/52] fix fn tests --- .../services/send_to_background.ts | 11 +++++++++++ .../apps/dashboard/async_search/async_search.ts | 4 ++++ .../apps/management/background_sessions/index.ts | 12 +++++++++++- .../background_sessions/sessions_management.ts | 10 +++++++--- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts index 918a5d44eeb15..d2f8386d59bc8 100644 --- a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts @@ -23,6 +23,7 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); const browser = getService('browser'); + const esSupertest = getService('esSupertest'); return new (class SendToBackgroundService { public async find(): Promise { @@ -47,6 +48,16 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { await testSubjects.click('backgroundSessionIndicatorViewBackgroundSessionsLink'); } + public async deleteAllBackgroundSessions() { + // ignores 409 errs and keeps retrying + await retry.tryForTime(5000, async () => { + await esSupertest + .post('/.kibana*/_delete_by_query') + .send({ query: { match: { type: 'background-session' } } }) + .expect(200); + }); + } + public async save() { await this.ensurePopoverOpened(); await testSubjects.click('backgroundSessionIndicatorSaveBtn'); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts index 4859b2474f860..10b1160d27249 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts @@ -27,6 +27,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } }); + after(async function () { + await sendToBackground.deleteAllBackgroundSessions(); + }); + it('not delayed should load', async () => { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/index.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/index.ts index d7bb288dfdb5f..d58d5499826e8 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/index.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/index.ts @@ -5,10 +5,20 @@ */ import { FtrProviderContext } from '../../../../ftr_provider_context'; -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile, getService }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const esArchiver = getService('esArchiver'); + describe('background sessions management', function () { this.tags('ciGroup3'); + before(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.load('dashboard/async_search'); + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); + await kibanaServer.uiSettings.replace({ 'search:timeout': 10000 }); + }); + loadTestFile(require.resolve('./sessions_management')); }); } diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts index 21095bb2e9de9..0d4db254f9f25 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts @@ -13,12 +13,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const sendToBackground = getService('sendToBackground'); const esArchiver = getService('esArchiver'); - describe('dashboard with async search', () => { - describe('Send to background', () => { + describe('Background search sessions Management UI', () => { + describe('New sessions', () => { before(async () => { await PageObjects.common.navigateToApp('dashboard'); }); + after(async () => { + await sendToBackground.deleteAllBackgroundSessions(); + }); + it('Saves a session and verifies it in the Management app', async () => { await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); await PageObjects.dashboard.waitForRenderComplete(); @@ -40,7 +44,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('Management UI', () => { + describe('Archived sessions', () => { before(async () => { await PageObjects.common.navigateToApp('management/kibana/background_sessions'); }); From 5c6fd958560d2ad924b0bde5c6f3a1f11317ad75 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Sat, 19 Dec 2020 23:35:49 -0700 Subject: [PATCH 07/52] cleanups --- .../common/search/sessions_mgmt/constants.ts | 2 +- .../common/search/sessions_mgmt/index.ts | 8 +------- .../search/sessions_mgmt/application/index.tsx | 12 +----------- .../sessions_mgmt/components/actions/get_action.tsx | 4 ---- .../components/actions/popover_actions.tsx | 2 -- .../search/sessions_mgmt/components/status.tsx | 4 ---- .../sessions_mgmt/components/table/status_filter.tsx | 9 ++------- .../search/sessions_mgmt/components/table/table.tsx | 1 - .../public/search/sessions_mgmt/lib/api.ts | 8 ++------ .../public/search/sessions_mgmt/lib/date_string.ts | 3 --- .../public/search/sessions_mgmt/lib/get_columns.tsx | 1 - 11 files changed, 7 insertions(+), 47 deletions(-) diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts index fd81a49f9d30b..21a29d12f7a03 100644 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts @@ -10,7 +10,7 @@ export const REFRESH_INTERVAL_MS = 3000; export const EXPIRES_SOON_IN_DAYS = 2; -export const MAX_SEARCH_HITS = 10000; +export const SESSIONS_LISTING_SEARCH_SIZE = 10000; export enum ACTION { EXTEND = 'extend', diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts index 73cd5c899fee6..c00f11dfb5b4e 100644 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BackgroundSessionSavedObjectAttributes } from '../'; import { ACTION, STATUS } from './constants'; -export interface Session { +export interface UISession { id: string; name: string; appId: string; @@ -17,11 +16,6 @@ export interface Session { actions?: ACTION[]; isViewable: boolean; expiresSoon: boolean; - urlGeneratorId: BackgroundSessionSavedObjectAttributes['urlGeneratorId']; - restoreState: BackgroundSessionSavedObjectAttributes['restoreState']; -} - -export interface UISession extends Omit { url: string; } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx index a8a7905fb609f..b8bb3697055bb 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx @@ -5,10 +5,7 @@ */ import { CoreSetup } from 'kibana/public'; -import * as Rx from 'rxjs'; -import { first } from 'rxjs/operators'; import { ManagementAppMountParams } from 'src/plugins/management/public'; -import { SharePluginStart } from 'src/plugins/share/public'; import { APP, AppDependencies, @@ -19,19 +16,12 @@ import { SearchSessionsMgmtAPI } from '../lib/api'; import { AsyncSearchIntroDocumentation } from '../lib/documentation'; import { renderApp } from './render'; -type UrlGeneratorsStart = SharePluginStart['urlGenerators']; - -// export class SearchSessionsMgmtApp { - private urls$ = new Rx.Subject(); - constructor( private coreSetup: CoreSetup, private params: ManagementAppMountParams, private pluginsSetup: IManagementSectionsPluginsSetup - ) { - this.urls$.pipe(first()).subscribe((urls) => {}); - } + ) {} public async mountManagementSection() { const { coreSetup, params, pluginsSetup } = this; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx index e48993882ee32..73806c124be9d 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx @@ -19,7 +19,6 @@ export const getAction = ( actionComplete: ActionComplete ): IClickActionDescriptor | null => { switch (actionType) { - // case ACTION.CANCEL: return { iconType: 'crossInACircleFilled', @@ -29,7 +28,6 @@ export const getAction = ( }), }; - // case ACTION.DELETE: return { iconType: 'trash', @@ -37,7 +35,6 @@ export const getAction = ( label: , }; - // case ACTION.EXTEND: return { iconType: extendSessionIcon, @@ -47,7 +44,6 @@ export const getAction = ( }), }; - // default: // eslint-disable-next-line no-console console.error(`Unknown action: ${actionType}`); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx index 966adbf343d5c..a01ccae9989c9 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx @@ -57,7 +57,6 @@ const PopoverAction = ({ textColor, iconType, children, ...props }: PopoverActio ); -// main export const PopoverActionsMenu = ({ api, handleAction, session }: PopoverActionItemsProps) => { const [isPopoverOpen, setPopover] = useState(false); @@ -116,7 +115,6 @@ export const PopoverActionsMenu = ({ api, handleAction, session }: PopoverAction return itemSet; }, [] as Array); - // const panels: EuiContextMenuPanelDescriptor[] = [{ id: 0, items }]; return ( diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx index 0751ba699daa9..c73ade09cab3d 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -92,7 +92,6 @@ const getStatusAttributes = ({ throw new Error(`Could not instantiate a expiration Date object from: ${session.expires}`); } - // case STATUS.CANCELLED: return { icon: , @@ -102,7 +101,6 @@ const getStatusAttributes = ({ }), }; - // case STATUS.ERROR: return { textColor: 'danger', @@ -114,7 +112,6 @@ const getStatusAttributes = ({ }), }; - // case STATUS.COMPLETE: try { const expiresOnDate = dateString(session.expires!, uiSettings); @@ -140,7 +137,6 @@ const getStatusAttributes = ({ // Error was thrown return null; - // default: throw new Error(`Unknown status: ${session.status}`); } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx index 7e3e8c9a9b436..c905091f88195 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx @@ -22,13 +22,8 @@ export const getStatusFilter: (tableData: UISession[]) => SearchFilterConfig = ( const { status: statusType } = session; const existingOption = options.find((o) => o.value === statusType); if (!existingOption) { - return [ - ...options, - { - value: statusType, - view: {getStatusText(session.status)}, - }, - ]; + const view = {getStatusText(session.status)}; + return [...options, { value: statusType, view }]; } return options; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index f6df39087f182..48213ca16bddb 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -88,7 +88,6 @@ export function SearchSessionsMgmtTable({ api, http, uiSettings, initialTable, . // table config: sorting const sorting = { sort: { field: 'startedDate', direction: 'desc' as 'desc' } }; - // return ( {...props} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index 7b721c035bc01..0328312276aa6 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -13,7 +13,7 @@ import { BackgroundSessionSavedObjectAttributes } from '../../../../common'; import { ACTION, EXPIRES_SOON_IN_DAYS, - MAX_SEARCH_HITS, + SESSIONS_LISTING_SEARCH_SIZE, STATUS, UISession, } from '../../../../common/search/sessions_mgmt'; @@ -73,7 +73,6 @@ const mapToUISession = (urls: UrlGeneratorsStart) => async ( console.error(err); } - // return { id: savedObject.id, isViewable: true, // always viewable @@ -88,21 +87,18 @@ const mapToUISession = (urls: UrlGeneratorsStart) => async ( }; }; -// Main export class SearchSessionsMgmtAPI { - // constructor( private sessionsClient: ISessionsClient, private urls: UrlGeneratorsStart, private notifications: NotificationsStart ) {} - // public async fetchTableData(): Promise { try { const result = await this.sessionsClient.find({ page: 1, - perPage: MAX_SEARCH_HITS, // NOTE: using an easier approach to paging the table in-memory, not requesting single pages as they are viewed + perPage: SESSIONS_LISTING_SEARCH_SIZE, sortField: 'created', sortOrder: 'asc', }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts index 9799016d75578..444f6a1ee2640 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts @@ -7,9 +7,6 @@ import { IUiSettingsClient } from 'kibana/public'; import moment from 'moment'; -// Helper function to format date string per UI Settings config -// Can throw exception for null date, or error from moment - export const dateString = (inputString: string, uiSettings: IUiSettingsClient): string => { if (inputString == null) { throw new Error('Invalid date string!'); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index 642ef4707c61b..142fd9ad2141a 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -34,7 +34,6 @@ const appToIcon = (app: string) => { return app; }; -// Main export const getColumns = ( api: SearchSessionsMgmtAPI, http: HttpStart, From d374a6b9d930bdb555074aab56ff7f2081f08681 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Sun, 20 Dec 2020 22:19:15 -0700 Subject: [PATCH 08/52] cleanup --- .../components/actions/inline_actions.tsx | 1 - .../components/actions/popover_actions.tsx | 1 - .../search/sessions_mgmt/lib/get_columns.tsx | 21 ++++--------------- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx index 1e16467014592..eff47a07dbc13 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx @@ -25,7 +25,6 @@ export const InlineActions = ({ url, session }: InlineActionProps) => { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx index a01ccae9989c9..a91508f37c075 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx @@ -104,7 +104,6 @@ export const PopoverActionsMenu = ({ api, handleAction, session }: PopoverAction textColor={textColor} iconType={iconType} data-test-subj={`session-mgmt-popover-action-${actionType}`} - data-test-id={session.id} > {label} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index 142fd9ad2141a..579179c526469 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -73,7 +73,7 @@ export const getColumns = ( width: '20%', render: (name: UISession['name'], { appId, url, id }) => { return ( - + {name} ); @@ -103,11 +103,7 @@ export const getColumns = ( try { const startedOn = dateString(created, uiSettings); return ( - + {startedOn} ); @@ -133,11 +129,7 @@ export const getColumns = ( // return return ( - + {expiresOn} ); @@ -148,11 +140,7 @@ export const getColumns = ( } } return ( - + -- ); @@ -185,7 +173,6 @@ export const getColumns = ( id="xpack.data.mgmt.searchSessions.status.expiresSoonTooltip" defaultMessage="{numDays} days" data-test-subj="session-mgmt-table-col-expires" - data-test-id={id} values={{ numDays }} /> From 866bb06cc92b6dd091ee39e14eefc961015d0e10 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Sun, 20 Dec 2020 22:29:28 -0700 Subject: [PATCH 09/52] restore test --- .../sessions_management.ts | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts index 0d4db254f9f25..5ad75b68973ba 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts @@ -38,9 +38,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.existOrFail('session-mgmt-table-col-created'); await testSubjects.existOrFail('session-mgmt-view-action'); - expect( - await testSubjects.find('session-mgmt-table-col-name').then((n) => n.getVisibleText()) - ).to.be('Not Delayed'); + // find there is only one item in the table which is the newly saved session + const names = await testSubjects.findAll('session-mgmt-table-col-name'); + expect(names.length).to.be(1); + expect(await Promise.all(names.map((n) => n.getVisibleText()))).to.eql(['Not Delayed']); + + // navigate to dashboard + await testSubjects.click('session-mgmt-table-col-name'); + + // embeddable has loaded + await testSubjects.existOrFail('embeddablePanelHeading-SumofBytesbyExtension'); + await PageObjects.dashboard.waitForRenderComplete(); + + // background session was restored + await sendToBackground.expectState('restored'); }); }); @@ -130,11 +141,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await esArchiver.unload('data/bckgnd_sessions'); }); - - it('pagination', async () => { - await esArchiver.load('data/bckgnd_sessions'); - await esArchiver.unload('data/bckgnd_sessions'); - }); }); }); } From 11a5f7a307a19108ff954f6467e4b94a63f26dce Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Mon, 28 Dec 2020 15:23:56 -0700 Subject: [PATCH 10/52] configurable refresh and fetch timeout --- .../common/search/sessions_mgmt/constants.ts | 6 -- x-pack/plugins/data_enhanced/config.ts | 6 ++ x-pack/plugins/data_enhanced/public/plugin.ts | 6 +- .../sessions_mgmt/application/index.tsx | 16 +++-- .../sessions_mgmt/components/home.test.tsx | 19 ++++- .../search/sessions_mgmt/components/home.tsx | 2 + .../components/table/table.test.tsx | 17 ++++- .../sessions_mgmt/components/table/table.tsx | 34 ++++++--- .../public/search/sessions_mgmt/index.ts | 22 +++--- .../search/sessions_mgmt/lib/api.test.ts | 37 ++++++++-- .../public/search/sessions_mgmt/lib/api.ts | 69 ++++++++++++------- .../sessions_mgmt/lib/get_columns.test.tsx | 16 ++++- .../search/session/session_service.test.ts | 1 + 13 files changed, 190 insertions(+), 61 deletions(-) diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts index 21a29d12f7a03..badfb37224302 100644 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts @@ -6,12 +6,6 @@ import { BackgroundSessionStatus } from '../'; -export const REFRESH_INTERVAL_MS = 3000; - -export const EXPIRES_SOON_IN_DAYS = 2; - -export const SESSIONS_LISTING_SEARCH_SIZE = 10000; - export enum ACTION { EXTEND = 'extend', CANCEL = 'cancel', diff --git a/x-pack/plugins/data_enhanced/config.ts b/x-pack/plugins/data_enhanced/config.ts index 9838f0959ef19..3641238daf107 100644 --- a/x-pack/plugins/data_enhanced/config.ts +++ b/x-pack/plugins/data_enhanced/config.ts @@ -10,6 +10,12 @@ export const configSchema = schema.object({ search: schema.object({ sendToBackground: schema.object({ enabled: schema.boolean({ defaultValue: false }), + sessionsManagement: schema.object({ + maxSessions: schema.number({ defaultValue: 10000 }), + refreshInterval: schema.duration({ defaultValue: '3s' }), + refreshTimeout: schema.duration({ defaultValue: '1m' }), + expiresSoonWarning: schema.duration({ defaultValue: '1d' }), + }), }), }), }); diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index b71d2038983c0..f32cfab572eee 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -63,8 +63,10 @@ export class DataEnhancedPlugin }, }); - if (this.initializerContext.config.get().search.sendToBackground.enabled) { - registerBackgroundSessionsMgmt(core, { management }); + const { search: searchConfig } = this.initializerContext.config.get(); + if (searchConfig.sendToBackground.enabled) { + const { sessionsManagement: sessionsMgmtConfig } = searchConfig.sendToBackground; + registerBackgroundSessionsMgmt(core, sessionsMgmtConfig, { management }); } } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx index b8bb3697055bb..015348fed5119 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx @@ -5,13 +5,14 @@ */ import { CoreSetup } from 'kibana/public'; -import { ManagementAppMountParams } from 'src/plugins/management/public'; -import { - APP, +import type { ManagementAppMountParams } from 'src/plugins/management/public'; +import type { AppDependencies, IManagementSectionsPluginsSetup, IManagementSectionsPluginsStart, + SessionsMgmtConfigSchema, } from '../'; +import { APP } from '../'; import { SearchSessionsMgmtAPI } from '../lib/api'; import { AsyncSearchIntroDocumentation } from '../lib/documentation'; import { renderApp } from './render'; @@ -19,6 +20,7 @@ import { renderApp } from './render'; export class SearchSessionsMgmtApp { constructor( private coreSetup: CoreSetup, + private config: SessionsMgmtConfigSchema, private params: ManagementAppMountParams, private pluginsSetup: IManagementSectionsPluginsSetup ) {} @@ -42,13 +44,19 @@ export class SearchSessionsMgmtApp { params.setBreadcrumbs([{ text: pluginName }]); const { sessionsClient } = data.search; - const api = new SearchSessionsMgmtAPI(sessionsClient, share.urlGenerators, notifications); + const api = new SearchSessionsMgmtAPI( + sessionsClient, + share.urlGenerators, + notifications, + this.config + ); const documentation = new AsyncSearchIntroDocumentation(); documentation.setup(docLinks); const dependencies: AppDependencies = { plugins: pluginsSetup, + config: this.config, documentation, api, http, diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx index 9eca069606744..16802f4bbb18c 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx @@ -4,19 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ +import moment from 'moment'; import { MockedKeys } from '@kbn/utility-types/jest'; import { mount, ReactWrapper } from 'enzyme'; import { CoreSetup } from 'kibana/public'; import React from 'react'; import { coreMock } from 'src/core/public/mocks'; import { SessionsClient } from 'src/plugins/data/public/search'; -import { UISession, STATUS } from '../../../../common/search/sessions_mgmt'; +import { SessionsMgmtConfigSchema } from '../'; +import { STATUS, UISession } from '../../../../common/search/sessions_mgmt'; import { SearchSessionsMgmtAPI } from '../lib/api'; import { AsyncSearchIntroDocumentation } from '../lib/documentation'; import { LocaleWrapper, mockUrls } from '../__mocks__'; import { SearchSessionsMgmtHome } from './home'; let mockCoreSetup: MockedKeys; +let mockConfig: SessionsMgmtConfigSchema; let sessionsClient: SessionsClient; let initialTable: UISession[]; let api: SearchSessionsMgmtAPI; @@ -24,10 +27,21 @@ let api: SearchSessionsMgmtAPI; describe('Background Search Session Management Home', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); + mockConfig = { + expiresSoonWarning: moment.duration('1d'), + maxSessions: 2000, + refreshInterval: moment.duration('1s'), + refreshTimeout: moment.duration('10m'), + }; sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); - api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); + api = new SearchSessionsMgmtAPI( + sessionsClient, + mockUrls, + mockCoreSetup.notifications, + mockConfig + ); initialTable = [ { @@ -60,6 +74,7 @@ describe('Background Search Session Management Home', () => { uiSettings={mockCoreSetup.uiSettings} initialTable={initialTable} documentation={new AsyncSearchIntroDocumentation()} + config={mockConfig} /> ); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.tsx index b2637259e15d2..2a0d66ff65c91 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.tsx @@ -17,6 +17,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { HttpStart, IUiSettingsClient } from 'kibana/public'; import React from 'react'; +import { SessionsMgmtConfigSchema } from '../'; import { UISession } from '../../../../common/search/sessions_mgmt'; import { SearchSessionsMgmtAPI } from '../lib/api'; import { AsyncSearchIntroDocumentation } from '../lib/documentation'; @@ -29,6 +30,7 @@ interface Props { http: HttpStart; initialTable: UISession[] | null; uiSettings: IUiSettingsClient; + config: SessionsMgmtConfigSchema; } export function SearchSessionsMgmtHome({ documentation, ...tableProps }: Props) { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index 32368b5e63bf7..69c27d1fbad94 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -7,24 +7,38 @@ import { MockedKeys } from '@kbn/utility-types/jest'; import { mount, ReactWrapper } from 'enzyme'; import { CoreSetup } from 'kibana/public'; +import moment from 'moment'; import React from 'react'; import { coreMock } from 'src/core/public/mocks'; import { SessionsClient } from 'src/plugins/data/public/search'; +import { SessionsMgmtConfigSchema } from '../../'; import { STATUS, UISession } from '../../../../../common/search/sessions_mgmt'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { LocaleWrapper, mockUrls } from '../../__mocks__'; import { SearchSessionsMgmtTable } from './table'; let mockCoreSetup: MockedKeys; +let mockConfig: SessionsMgmtConfigSchema; let initialTable: UISession[]; let api: SearchSessionsMgmtAPI; describe('Background Search Session Management Table', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); + mockConfig = { + expiresSoonWarning: moment.duration('1d'), + maxSessions: 2000, + refreshInterval: moment.duration('1s'), + refreshTimeout: moment.duration('10m'), + }; const sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); - api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); + api = new SearchSessionsMgmtAPI( + sessionsClient, + mockUrls, + mockCoreSetup.notifications, + mockConfig + ); initialTable = [ { @@ -56,6 +70,7 @@ describe('Background Search Session Management Table', () => { http={mockCoreSetup.http} uiSettings={mockCoreSetup.uiSettings} initialTable={initialTable} + config={mockConfig} /> ); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index 48213ca16bddb..9a7a72af325b9 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -7,15 +7,13 @@ import { EuiButton, EuiInMemoryTable, EuiSearchBarProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { HttpStart, IUiSettingsClient } from 'kibana/public'; +import moment from 'moment'; import React, { useEffect, useState } from 'react'; import * as Rx from 'rxjs'; -import { switchMap } from 'rxjs/operators'; +import { catchError, switchMap } from 'rxjs/operators'; import { TableText } from '../'; -import { - ActionComplete, - REFRESH_INTERVAL_MS, - UISession, -} from '../../../../../common/search/sessions_mgmt'; +import { SessionsMgmtConfigSchema } from '../..'; +import { ActionComplete, UISession } from '../../../../../common/search/sessions_mgmt'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { getColumns } from '../../lib/get_columns'; import { getAppFilter } from './app_filter'; @@ -28,9 +26,17 @@ interface Props { http: HttpStart; initialTable: UISession[] | null; uiSettings: IUiSettingsClient; + config: SessionsMgmtConfigSchema; } -export function SearchSessionsMgmtTable({ api, http, uiSettings, initialTable, ...props }: Props) { +export function SearchSessionsMgmtTable({ + api, + http, + uiSettings, + initialTable, + config, + ...props +}: Props) { const [tableData, setTableData] = useState(initialTable ? initialTable : []); const [isLoading, setIsLoading] = useState(false); const [pagination, setPagination] = useState({ pageIndex: 0 }); @@ -46,9 +52,19 @@ export function SearchSessionsMgmtTable({ api, http, uiSettings, initialTable, . setIsLoading(false); }; - // 3s auto-refresh + // configurable auto-refresh useEffect(() => { - const refreshRx = Rx.interval(REFRESH_INTERVAL_MS).pipe(switchMap(doRefresh)).subscribe(); + const refreshInterval = moment.duration(config.refreshInterval); + const refreshRx = Rx.interval(refreshInterval.asMilliseconds()) + .pipe( + switchMap(doRefresh), + catchError((err) => { + // eslint-disable-next-line no-console + console.error(err); + return Rx.of(null); + }) + ) + .subscribe(); return () => { refreshRx.unsubscribe(); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts index 978135cbf5232..b954df478dba2 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts @@ -5,13 +5,15 @@ */ import { i18n } from '@kbn/i18n'; -import { CoreSetup, HttpStart, I18nStart, IUiSettingsClient } from 'kibana/public'; -import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { ManagementSetup } from 'src/plugins/management/public'; -import { SharePluginStart } from 'src/plugins/share/public'; -import { DataEnhancedStartDependencies } from '../../plugin'; -import { SearchSessionsMgmtAPI } from './lib/api'; -import { AsyncSearchIntroDocumentation } from './lib/documentation'; +import type { HttpStart, I18nStart, IUiSettingsClient } from 'kibana/public'; +import { CoreSetup } from 'kibana/public'; +import type { DataPublicPluginStart } from 'src/plugins/data/public'; +import type { ManagementSetup } from 'src/plugins/management/public'; +import type { SharePluginStart } from 'src/plugins/share/public'; +import type { ConfigSchema } from '../../../config'; +import type { DataEnhancedStartDependencies } from '../../plugin'; +import type { SearchSessionsMgmtAPI } from './lib/api'; +import type { AsyncSearchIntroDocumentation } from './lib/documentation'; export interface IManagementSectionsPluginsSetup { management: ManagementSetup; @@ -30,6 +32,7 @@ export interface AppDependencies { api: SearchSessionsMgmtAPI; http: HttpStart; i18n: I18nStart; + config: SessionsMgmtConfigSchema; } export const APP = { @@ -40,8 +43,11 @@ export const APP = { }), }; +export type SessionsMgmtConfigSchema = ConfigSchema['search']['sendToBackground']['sessionsManagement']; + export function registerBackgroundSessionsMgmt( coreSetup: CoreSetup, + config: SessionsMgmtConfigSchema, services: IManagementSectionsPluginsSetup ) { services.management.sections.section.kibana.registerApp({ @@ -50,7 +56,7 @@ export function registerBackgroundSessionsMgmt( order: 2, mount: async (params) => { const { SearchSessionsMgmtApp: MgmtApp } = await import('./application'); - const mgmtApp = new MgmtApp(coreSetup, params, services); + const mgmtApp = new MgmtApp(coreSetup, config, params, services); return mgmtApp.mountManagementSection(); }, }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts index c71c3e205c93a..9cdd8a9861e91 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts @@ -4,22 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ +import moment from 'moment'; import Boom from '@hapi/boom'; import type { MockedKeys } from '@kbn/utility-types/jest'; import { CoreSetup } from 'kibana/public'; import sinon from 'sinon'; import { coreMock } from 'src/core/public/mocks'; import { SessionsClient } from 'src/plugins/data/public/search'; +import { SessionsMgmtConfigSchema } from '../'; import { mockUrls } from '../__mocks__'; import { SearchSessionsMgmtAPI } from './api'; let mockCoreSetup: MockedKeys; +let mockConfig: SessionsMgmtConfigSchema; let sessionsClient: SessionsClient; let findSessions: sinon.SinonStub; describe('Background Sessions Management API', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); + mockConfig = { + expiresSoonWarning: moment.duration('1d'), + maxSessions: 2000, + refreshInterval: moment.duration('1s'), + refreshTimeout: moment.duration('10m'), + }; sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); @@ -38,7 +47,12 @@ describe('Background Sessions Management API', () => { describe('listing', () => { test('fetchDataTable calls the listing endpoint', async () => { - const api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); + const api = new SearchSessionsMgmtAPI( + sessionsClient, + mockUrls, + mockCoreSetup.notifications, + mockConfig + ); expect(await api.fetchTableData()).toMatchInlineSnapshot(` Array [ Object { @@ -64,7 +78,12 @@ describe('Background Sessions Management API', () => { throw Boom.badImplementation('implementation is so bad'); }); - const api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); + const api = new SearchSessionsMgmtAPI( + sessionsClient, + mockUrls, + mockCoreSetup.notifications, + mockConfig + ); await api.fetchTableData(); expect(mockCoreSetup.notifications.toasts.addError).toHaveBeenCalledWith( @@ -76,7 +95,12 @@ describe('Background Sessions Management API', () => { describe('delete', () => { test('send delete calls the delete endpoint with a session ID', async () => { - const api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); + const api = new SearchSessionsMgmtAPI( + sessionsClient, + mockUrls, + mockCoreSetup.notifications, + mockConfig + ); await api.sendDelete('abc-123-cool-session-ID'); expect(mockCoreSetup.notifications.toasts.addSuccess).toHaveBeenCalledWith({ @@ -89,7 +113,12 @@ describe('Background Sessions Management API', () => { throw Boom.badImplementation('implementation is so bad'); }); - const api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); + const api = new SearchSessionsMgmtAPI( + sessionsClient, + mockUrls, + mockCoreSetup.notifications, + mockConfig + ); await api.sendDelete('abc-123-cool-session-ID'); expect(mockCoreSetup.notifications.toasts.addError).toHaveBeenCalledWith( diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index 0328312276aa6..77b174d865b4b 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -5,18 +5,16 @@ */ import { i18n } from '@kbn/i18n'; -import { NotificationsStart } from 'kibana/public'; +import type { NotificationsStart } from 'kibana/public'; import moment from 'moment'; -import { SharePluginStart } from 'src/plugins/share/public'; -import { ISessionsClient } from '../../../../../../../src/plugins/data/public'; -import { BackgroundSessionSavedObjectAttributes } from '../../../../common'; -import { - ACTION, - EXPIRES_SOON_IN_DAYS, - SESSIONS_LISTING_SEARCH_SIZE, - STATUS, - UISession, -} from '../../../../common/search/sessions_mgmt'; +import * as Rx from 'rxjs'; +import { first, mapTo, tap } from 'rxjs/operators'; +import type { SharePluginStart } from 'src/plugins/share/public'; +import { SessionsMgmtConfigSchema } from '../'; +import type { ISessionsClient } from '../../../../../../../src/plugins/data/public'; +import type { BackgroundSessionSavedObjectAttributes } from '../../../../common'; +import type { UISession } from '../../../../common/search/sessions_mgmt'; +import { ACTION, STATUS } from '../../../../common/search/sessions_mgmt'; type UrlGeneratorsStart = SharePluginStart['urlGenerators']; @@ -26,9 +24,10 @@ interface BackgroundSessionSavedObject { } // Helper: factory for a function to map server objects to UI objects -const mapToUISession = (urls: UrlGeneratorsStart) => async ( - savedObject: BackgroundSessionSavedObject -): Promise => { +const mapToUISession = ( + urls: UrlGeneratorsStart, + { expiresSoonWarning }: SessionsMgmtConfigSchema +) => async (savedObject: BackgroundSessionSavedObject): Promise => { // Actions: always allow delete const actions = [ACTION.DELETE]; @@ -50,7 +49,7 @@ const mapToUISession = (urls: UrlGeneratorsStart) => async ( const expiresDate = moment(created); const duration = moment.duration(expiresDate.diff(currentDate)); - if (duration.asDays() <= EXPIRES_SOON_IN_DAYS) { + if (duration.asDays() <= expiresSoonWarning.asDays()) { // TODO: handle negatives by setting status to expired? expiresSoon = true; } @@ -91,20 +90,42 @@ export class SearchSessionsMgmtAPI { constructor( private sessionsClient: ISessionsClient, private urls: UrlGeneratorsStart, - private notifications: NotificationsStart + private notifications: NotificationsStart, + private config: SessionsMgmtConfigSchema ) {} public async fetchTableData(): Promise { try { - const result = await this.sessionsClient.find({ - page: 1, - perPage: SESSIONS_LISTING_SEARCH_SIZE, - sortField: 'created', - sortOrder: 'asc', - }); - if (result.saved_objects) { + const refreshTimeout = moment.duration(this.config.refreshTimeout); + const result = await Rx.race<{ saved_objects: object[] } | null>([ + // fetch the background sessions before timeout triggers + this.sessionsClient.find({ + page: 1, + perPage: this.config.maxSessions, + sortField: 'created', + sortOrder: 'asc', + }), + + // this gets unsubscribed if the happy-path observable triggers first + Rx.timer(refreshTimeout.asMilliseconds()).pipe( + tap(() => { + this.notifications.toasts.addDanger( + i18n.translate('xpack.data.mgmt.searchSessions.api.fetchTimeout', { + defaultMessage: + 'Fetching the Background Session info timed out after {timeout} seconds', + values: { timeout: refreshTimeout.asSeconds() }, + }) + ); + }), + mapTo(null) + ), + ]) + .pipe(first()) + .toPromise(); + + if (result && result.saved_objects) { const savedObjects = result.saved_objects as BackgroundSessionSavedObject[]; - return await Promise.all(savedObjects.map(mapToUISession(this.urls))); + return await Promise.all(savedObjects.map(mapToUISession(this.urls, this.config))); } return null; } catch (err) { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index 3160e8c6722a2..dc6446b0fab3f 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -8,15 +8,18 @@ import { EuiTableFieldDataColumnType } from '@elastic/eui'; import { MockedKeys } from '@kbn/utility-types/jest'; import { mount } from 'enzyme'; import { CoreSetup } from 'kibana/public'; +import moment from 'moment'; import { ReactElement } from 'react'; import { coreMock } from 'src/core/public/mocks'; import { SessionsClient } from 'src/plugins/data/public/search'; +import { SessionsMgmtConfigSchema } from '../'; import { ActionComplete, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; import { mockUrls } from '../__mocks__'; import { SearchSessionsMgmtAPI } from './api'; import { getColumns } from './get_columns'; let mockCoreSetup: MockedKeys; +let mockConfig: SessionsMgmtConfigSchema; let api: SearchSessionsMgmtAPI; let sessionsClient: SessionsClient; let handleAction: ActionComplete; @@ -25,10 +28,21 @@ let mockSession: UISession; describe('Background Sessions Management table column factory', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); + mockConfig = { + expiresSoonWarning: moment.duration('1d'), + maxSessions: 2000, + refreshInterval: moment.duration('1s'), + refreshTimeout: moment.duration('10m'), + }; sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); - api = new SearchSessionsMgmtAPI(sessionsClient, mockUrls, mockCoreSetup.notifications); + api = new SearchSessionsMgmtAPI( + sessionsClient, + mockUrls, + mockCoreSetup.notifications, + mockConfig + ); mockCoreSetup.uiSettings.get.mockImplementation((key) => { return key === 'dateFormat:tz' ? 'UTC' : null; }); diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts index 766de908353f5..660b6003915e6 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts @@ -403,6 +403,7 @@ describe('BackgroundSessionService', () => { search: { sendToBackground: { enabled: true, + sessionsManagement: {} as any, }, }, }); From 1644f2db1d88eda99bd4e76ef40b279cbe60fb7f Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Mon, 28 Dec 2020 18:29:18 -0700 Subject: [PATCH 11/52] more tests --- .../sessions_mgmt/components/home.test.tsx | 6 +- .../components/table/table.test.tsx | 64 +++++++++++++++---- .../sessions_mgmt/components/table/table.tsx | 1 + .../search/sessions_mgmt/lib/api.test.ts | 34 ++++++++-- .../public/search/sessions_mgmt/lib/api.ts | 62 +++++++++--------- .../sessions_mgmt/lib/get_columns.test.tsx | 6 +- 6 files changed, 120 insertions(+), 53 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx index 16802f4bbb18c..1bf96ac9c4caf 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx @@ -28,10 +28,10 @@ describe('Background Search Session Management Home', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockConfig = { - expiresSoonWarning: moment.duration('1d'), + expiresSoonWarning: moment.duration(1, 'days'), maxSessions: 2000, - refreshInterval: moment.duration('1s'), - refreshTimeout: moment.duration('10m'), + refreshInterval: moment.duration(1, 'seconds'), + refreshTimeout: moment.duration(10, 'minutes'), }; sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index 69c27d1fbad94..cd6f5e05b5789 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -5,11 +5,15 @@ */ import { MockedKeys } from '@kbn/utility-types/jest'; +import { waitFor } from '@testing-library/react'; import { mount, ReactWrapper } from 'enzyme'; import { CoreSetup } from 'kibana/public'; import moment from 'moment'; import React from 'react'; +import sinon from 'sinon'; import { coreMock } from 'src/core/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import type { SavedObjectsFindResponse } from 'src/core/server'; import { SessionsClient } from 'src/plugins/data/public/search'; import { SessionsMgmtConfigSchema } from '../../'; import { STATUS, UISession } from '../../../../../common/search/sessions_mgmt'; @@ -19,6 +23,7 @@ import { SearchSessionsMgmtTable } from './table'; let mockCoreSetup: MockedKeys; let mockConfig: SessionsMgmtConfigSchema; +let sessionsClient: SessionsClient; let initialTable: UISession[]; let api: SearchSessionsMgmtAPI; @@ -26,19 +31,11 @@ describe('Background Search Session Management Table', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockConfig = { - expiresSoonWarning: moment.duration('1d'), + expiresSoonWarning: moment.duration(1, 'days'), maxSessions: 2000, - refreshInterval: moment.duration('1s'), - refreshTimeout: moment.duration('10m'), + refreshInterval: moment.duration(1, 'seconds'), + refreshTimeout: moment.duration(10, 'minutes'), }; - const sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); - - api = new SearchSessionsMgmtAPI( - sessionsClient, - mockUrls, - mockCoreSetup.notifications, - mockConfig - ); initialTable = [ { @@ -53,6 +50,14 @@ describe('Background Search Session Management Table', () => { expiresSoon: false, }, ]; + + sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); + api = new SearchSessionsMgmtAPI( + sessionsClient, + mockUrls, + mockCoreSetup.notifications, + mockConfig + ); }); describe('renders', () => { @@ -102,4 +107,41 @@ describe('Background Search Session Management Table', () => { `); }); }); + + describe('fetching sessions data', () => { + test('refresh button uses the session client', async () => { + const findSessions = sinon + .stub(sessionsClient, 'find') + .callsFake(async () => ({} as SavedObjectsFindResponse)); + + mockConfig = { + ...mockConfig, + refreshInterval: moment.duration(1, 'day'), + refreshTimeout: moment.duration(2, 'days'), + }; + + const table = mount( + + + + ); + + expect(findSessions.called).toBe(false); + + const buttonSelector = `[data-test-subj="session-mgmt-table-btn-refresh"] button`; + + await waitFor(() => { + table.find(buttonSelector).first().simulate('click'); + table.update(); + }); + + expect(findSessions.called).toBe(true); + }); + }); }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index 9a7a72af325b9..ad21081f9fa2b 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -91,6 +91,7 @@ export function SearchSessionsMgmtTable({ onClick={doRefresh} disabled={isLoading} isLoading={isLoading} + data-test-subj="session-mgmt-table-btn-refresh" > { }; sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); - findSessions = sinon.stub(sessionsClient, 'find').callsFake( async () => ({ @@ -73,8 +73,8 @@ describe('Background Sessions Management API', () => { `); }); - test('error handling', async () => { - findSessions.callsFake(() => { + test('handle error from sessionsClient response', async () => { + findSessions.callsFake(async () => { throw Boom.badImplementation('implementation is so bad'); }); @@ -91,6 +91,30 @@ describe('Background Sessions Management API', () => { { title: 'Failed to refresh the page!' } ); }); + + test('handle timeout error', async () => { + mockConfig = { + ...mockConfig, + refreshInterval: moment.duration(1, 'hours'), + refreshTimeout: moment.duration(1, 'seconds'), + }; + + findSessions.callsFake(async () => { + return await Rx.timer(2000).toPromise(); + }); + + const api = new SearchSessionsMgmtAPI( + sessionsClient, + mockUrls, + mockCoreSetup.notifications, + mockConfig + ); + await api.fetchTableData(); + + expect(mockCoreSetup.notifications.toasts.addDanger).toHaveBeenCalledWith( + 'Fetching the Background Session info timed out after 1 seconds' + ); + }); }); describe('delete', () => { @@ -109,7 +133,7 @@ describe('Background Sessions Management API', () => { }); test('error if deleting shows a toast message', async () => { - sinon.stub(sessionsClient, 'delete').callsFake(() => { + sinon.stub(sessionsClient, 'delete').callsFake(async () => { throw Boom.badImplementation('implementation is so bad'); }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index 77b174d865b4b..92c0713dcabce 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { NotificationsStart } from 'kibana/public'; import moment from 'moment'; import * as Rx from 'rxjs'; -import { first, mapTo, tap } from 'rxjs/operators'; +import { mapTo, tap } from 'rxjs/operators'; import type { SharePluginStart } from 'src/plugins/share/public'; import { SessionsMgmtConfigSchema } from '../'; import type { ISessionsClient } from '../../../../../../../src/plugins/data/public'; @@ -95,51 +95,51 @@ export class SearchSessionsMgmtAPI { ) {} public async fetchTableData(): Promise { - try { - const refreshTimeout = moment.duration(this.config.refreshTimeout); - const result = await Rx.race<{ saved_objects: object[] } | null>([ - // fetch the background sessions before timeout triggers - this.sessionsClient.find({ - page: 1, - perPage: this.config.maxSessions, - sortField: 'created', - sortOrder: 'asc', - }), + interface FetchResult { + saved_objects: object[]; + } - // this gets unsubscribed if the happy-path observable triggers first - Rx.timer(refreshTimeout.asMilliseconds()).pipe( - tap(() => { - this.notifications.toasts.addDanger( - i18n.translate('xpack.data.mgmt.searchSessions.api.fetchTimeout', { - defaultMessage: - 'Fetching the Background Session info timed out after {timeout} seconds', - values: { timeout: refreshTimeout.asSeconds() }, - }) - ); - }), - mapTo(null) - ), - ]) - .pipe(first()) - .toPromise(); + const refreshTimeout = moment.duration(this.config.refreshTimeout); + + const fetch$ = Rx.from( + this.sessionsClient.find({ + page: 1, + perPage: this.config.maxSessions, + sortField: 'created', + sortOrder: 'asc', + }) + ); + const timeout$ = Rx.timer(refreshTimeout.asMilliseconds()).pipe( + tap(() => { + this.notifications.toasts.addDanger( + i18n.translate('xpack.data.mgmt.searchSessions.api.fetchTimeout', { + defaultMessage: + 'Fetching the Background Session info timed out after {timeout} seconds', + values: { timeout: refreshTimeout.asSeconds() }, + }) + ); + }), + mapTo(null) + ); + // fetch the background sessions before timeout triggers + try { + const result = await Rx.race(fetch$, timeout$).toPromise(); if (result && result.saved_objects) { const savedObjects = result.saved_objects as BackgroundSessionSavedObject[]; return await Promise.all(savedObjects.map(mapToUISession(this.urls, this.config))); } - return null; } catch (err) { // eslint-disable-next-line no-console console.error(err); - this.notifications.toasts.addError(err, { title: i18n.translate('xpack.data.mgmt.searchSessions.api.fetchError', { defaultMessage: 'Failed to refresh the page!', }), }); - - return null; } + + return null; } // Delete diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index dc6446b0fab3f..339e495740a05 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -29,10 +29,10 @@ describe('Background Sessions Management table column factory', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockConfig = { - expiresSoonWarning: moment.duration('1d'), + expiresSoonWarning: moment.duration(1, 'days'), maxSessions: 2000, - refreshInterval: moment.duration('1s'), - refreshTimeout: moment.duration('10m'), + refreshInterval: moment.duration(1, 'seconds'), + refreshTimeout: moment.duration(10, 'minutes'), }; sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); From 858c232c13046a9995f503b9f692a69fb943ee9e Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 29 Dec 2020 11:06:08 -0700 Subject: [PATCH 12/52] feedback items --- .../common/search/sessions_mgmt/index.ts | 4 --- .../sessions_mgmt/application/index.tsx | 3 +- .../sessions_mgmt/application/render.tsx | 4 +-- .../{home.test.tsx => main.test.tsx} | 34 ++++++++++++------- .../components/{home.tsx => main.tsx} | 18 +++++----- .../public/search/sessions_mgmt/lib/api.ts | 6 ++-- .../search/sessions_mgmt/lib/documentation.ts | 2 +- 7 files changed, 37 insertions(+), 34 deletions(-) rename x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/{home.test.tsx => main.test.tsx} (75%) rename x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/{home.tsx => main.tsx} (77%) diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts index c00f11dfb5b4e..5feaf19772a68 100644 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts @@ -19,10 +19,6 @@ export interface UISession { url: string; } -export interface ISession { - session: string; -} - export type ActionComplete = (result: UISession[] | null) => void; export * from './constants'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx index 015348fed5119..3208c449b2070 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx @@ -51,8 +51,7 @@ export class SearchSessionsMgmtApp { this.config ); - const documentation = new AsyncSearchIntroDocumentation(); - documentation.setup(docLinks); + const documentation = new AsyncSearchIntroDocumentation(docLinks); const dependencies: AppDependencies = { plugins: pluginsSetup, diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx index 0b25365cde248..fc63d4a92285b 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx @@ -9,7 +9,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { AppDependencies } from '../'; import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; import { UISession } from '../../../../common/search/sessions_mgmt'; -import { SearchSessionsMgmtHome } from '../components/home'; +import { SearchSessionsMgmtMain } from '../components/main'; export const renderApp = ( elem: HTMLElement | null, @@ -29,7 +29,7 @@ export const renderApp = ( render( - + , elem diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx similarity index 75% rename from x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx rename to x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx index 1bf96ac9c4caf..8973ca1548954 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx @@ -4,19 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import moment from 'moment'; import { MockedKeys } from '@kbn/utility-types/jest'; import { mount, ReactWrapper } from 'enzyme'; -import { CoreSetup } from 'kibana/public'; +import { CoreSetup, DocLinksStart } from 'kibana/public'; +import moment from 'moment'; import React from 'react'; import { coreMock } from 'src/core/public/mocks'; import { SessionsClient } from 'src/plugins/data/public/search'; -import { SessionsMgmtConfigSchema } from '../'; +import { SessionsMgmtConfigSchema } from '..'; import { STATUS, UISession } from '../../../../common/search/sessions_mgmt'; import { SearchSessionsMgmtAPI } from '../lib/api'; import { AsyncSearchIntroDocumentation } from '../lib/documentation'; import { LocaleWrapper, mockUrls } from '../__mocks__'; -import { SearchSessionsMgmtHome } from './home'; +import { SearchSessionsMgmtMain } from './main'; let mockCoreSetup: MockedKeys; let mockConfig: SessionsMgmtConfigSchema; @@ -24,7 +24,7 @@ let sessionsClient: SessionsClient; let initialTable: UISession[]; let api: SearchSessionsMgmtAPI; -describe('Background Search Session Management Home', () => { +describe('Background Search Session Management Main', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockConfig = { @@ -59,21 +59,27 @@ describe('Background Search Session Management Home', () => { }); describe('renders', () => { - let home: ReactWrapper; + const docLinks: DocLinksStart = { + ELASTIC_WEBSITE_URL: 'boo/', + DOC_LINK_VERSION: '#foo', + links: {} as any, + }; + + let main: ReactWrapper; beforeEach(() => { mockCoreSetup.uiSettings.get.mockImplementation((key: string) => { return key === 'dateFormat:tz' ? 'UTC' : null; }); - home = mount( + main = mount( - @@ -81,17 +87,19 @@ describe('Background Search Session Management Home', () => { }); test('page title', () => { - expect(home.find('h1').text()).toBe('Background Sessions'); + expect(main.find('h1').text()).toBe('Background Sessions'); }); test('documentation link', () => { - const docLink = home.find('a[href]').first(); + const docLink = main.find('a[href]').first(); expect(docLink.text()).toBe('Documentation'); - expect(docLink.prop('href')).toBe('/async-search-intro.html'); + expect(docLink.prop('href')).toBe( + 'boo/guide/en/elasticsearch/reference/#foo/async-search-intro.html' + ); }); test('table is present', () => { - expect(home.find(`[data-test-subj="search-sessions-mgmt-table"]`).exists()).toBe(true); + expect(main.find(`[data-test-subj="search-sessions-mgmt-table"]`).exists()).toBe(true); }); }); }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx similarity index 77% rename from x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.tsx rename to x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx index 2a0d66ff65c91..ec2ca61386a91 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/home.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx @@ -15,12 +15,12 @@ import { EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { HttpStart, IUiSettingsClient } from 'kibana/public'; +import type { HttpStart, IUiSettingsClient } from 'kibana/public'; import React from 'react'; -import { SessionsMgmtConfigSchema } from '../'; -import { UISession } from '../../../../common/search/sessions_mgmt'; -import { SearchSessionsMgmtAPI } from '../lib/api'; -import { AsyncSearchIntroDocumentation } from '../lib/documentation'; +import type { SessionsMgmtConfigSchema } from '../'; +import type { UISession } from '../../../../common/search/sessions_mgmt'; +import type { SearchSessionsMgmtAPI } from '../lib/api'; +import type { AsyncSearchIntroDocumentation } from '../lib/documentation'; import { TableText } from './'; import { SearchSessionsMgmtTable } from './table'; @@ -33,7 +33,7 @@ interface Props { config: SessionsMgmtConfigSchema; } -export function SearchSessionsMgmtHome({ documentation, ...tableProps }: Props) { +export function SearchSessionsMgmtMain({ documentation, ...tableProps }: Props) { return ( @@ -42,7 +42,7 @@ export function SearchSessionsMgmtHome({ documentation, ...tableProps }: Props)

@@ -55,7 +55,7 @@ export function SearchSessionsMgmtHome({ documentation, ...tableProps }: Props) iconType="help" >
@@ -65,7 +65,7 @@ export function SearchSessionsMgmtHome({ documentation, ...tableProps }: Props)

diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index 92c0713dcabce..a5ac3e79cfcc9 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -46,10 +46,10 @@ const mapToUISession = ( if (status === STATUS.COMPLETE) { try { const currentDate = moment(); - const expiresDate = moment(created); - const duration = moment.duration(expiresDate.diff(currentDate)); + const expiresDate = moment(expires); + const durationToExpiration = moment.duration(expiresDate.diff(currentDate)); - if (duration.asDays() <= expiresSoonWarning.asDays()) { + if (durationToExpiration.asDays() <= expiresSoonWarning.asDays()) { // TODO: handle negatives by setting status to expired? expiresSoon = true; } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts index a07f4afe3a2e6..6c71fea60383d 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts @@ -9,7 +9,7 @@ import { DocLinksStart } from 'kibana/public'; export class AsyncSearchIntroDocumentation { private docsBasePath: string = ''; - public setup(docs: DocLinksStart) { + constructor(docs: DocLinksStart) { const { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } = docs; const docsBase = `${ELASTIC_WEBSITE_URL}guide/en`; this.docsBasePath = `${docsBase}/elasticsearch/reference/${DOC_LINK_VERSION}`; From d586d647bd0f754b3d4159df8d91b4697959407d Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 29 Dec 2020 12:08:15 -0700 Subject: [PATCH 13/52] take expiresSoon field out of the interface --- .../common/search/sessions_mgmt/index.ts | 1 - .../components/actions/actions.test.tsx | 1 - .../sessions_mgmt/components/main.test.tsx | 1 - .../sessions_mgmt/components/status.test.tsx | 2 - .../components/table/table.test.tsx | 1 - .../sessions_mgmt/components/table/table.tsx | 2 +- .../search/sessions_mgmt/lib/api.test.ts | 1 - .../public/search/sessions_mgmt/lib/api.ts | 23 +----------- .../sessions_mgmt/lib/get_columns.test.tsx | 15 ++++---- .../search/sessions_mgmt/lib/get_columns.tsx | 37 ++++++++----------- 10 files changed, 25 insertions(+), 59 deletions(-) diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts index 5feaf19772a68..f5b6efb31b1ca 100644 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts @@ -15,7 +15,6 @@ export interface UISession { status: STATUS; actions?: ACTION[]; isViewable: boolean; - expiresSoon: boolean; url: string; } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/actions.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/actions.test.tsx index 4ae7fcc9cb03d..15515cec50432 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/actions.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/actions.test.tsx @@ -23,7 +23,6 @@ describe('Background Search Session management actions', () => { created: '2020-12-02T00:19:32Z', expires: '2020-12-07T00:19:32Z', isViewable: true, - expiresSoon: false, }; }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx index 8973ca1548954..577a5d69a6e0f 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx @@ -53,7 +53,6 @@ describe('Background Search Session Management Main', () => { created: '2020-12-02T00:19:32Z', expires: '2020-12-07T00:19:32Z', isViewable: true, - expiresSoon: false, }, ]; }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx index 92222c8298c7c..5dc9cf99a28c5 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx @@ -34,7 +34,6 @@ describe('Background Search Session management status labels', () => { created: '2020-12-02T00:19:32Z', expires: '2020-12-07T00:19:32Z', isViewable: true, - expiresSoon: false, }; }); @@ -88,7 +87,6 @@ describe('Background Search Session management status labels', () => { test('complete - expires soon', () => { session.status = STATUS.COMPLETE; - session.expiresSoon = true; const statusIndicator = mount( diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index cd6f5e05b5789..3ea4761ac0b43 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -47,7 +47,6 @@ describe('Background Search Session Management Table', () => { created: '2020-12-02T00:19:32Z', expires: '2020-12-07T00:19:32Z', isViewable: true, - expiresSoon: false, }, ]; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index ad21081f9fa2b..fa067f24e02bc 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -110,7 +110,7 @@ export function SearchSessionsMgmtTable({ {...props} id={TABLE_ID} data-test-subj={TABLE_ID} - columns={getColumns(api, http, uiSettings, handleActionCompleted)} + columns={getColumns(api, config, uiSettings, handleActionCompleted)} items={tableData} pagination={pagination} search={search} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts index 655ffb928eb08..4bd310fa03047 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts @@ -62,7 +62,6 @@ describe('Background Sessions Management API', () => { "appId": "pizza", "created": undefined, "expires": undefined, - "expiresSoon": false, "id": "hello-pizza-123", "isViewable": true, "name": "Veggie", diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index a5ac3e79cfcc9..f25d898d523b3 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -14,7 +14,7 @@ import { SessionsMgmtConfigSchema } from '../'; import type { ISessionsClient } from '../../../../../../../src/plugins/data/public'; import type { BackgroundSessionSavedObjectAttributes } from '../../../../common'; import type { UISession } from '../../../../common/search/sessions_mgmt'; -import { ACTION, STATUS } from '../../../../common/search/sessions_mgmt'; +import { ACTION } from '../../../../common/search/sessions_mgmt'; type UrlGeneratorsStart = SharePluginStart['urlGenerators']; @@ -41,26 +41,6 @@ const mapToUISession = ( restoreState, } = savedObject.attributes; - // calculate expiresSoon flag - let expiresSoon = false; - if (status === STATUS.COMPLETE) { - try { - const currentDate = moment(); - const expiresDate = moment(expires); - const durationToExpiration = moment.duration(expiresDate.diff(currentDate)); - - if (durationToExpiration.asDays() <= expiresSoonWarning.asDays()) { - // TODO: handle negatives by setting status to expired? - expiresSoon = true; - } - } catch (err) { - // eslint-disable-next-line no-console - console.error(`Could not calculate duration to expiration`); - // eslint-disable-next-line no-console - console.error(err); - } - } - // derive the URL and add it in let url = '/'; try { @@ -81,7 +61,6 @@ const mapToUISession = ( expires, status, actions, - expiresSoon, url, }; }; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index 339e495740a05..1bf8132dc376f 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -60,12 +60,11 @@ describe('Background Sessions Management table column factory', () => { created: '2020-12-02T00:19:32Z', expires: '2020-12-07T00:19:32Z', isViewable: true, - expiresSoon: false, }; }); test('returns columns', () => { - const columns = getColumns(api, mockCoreSetup.http, mockCoreSetup.uiSettings, handleAction); + const columns = getColumns(api, mockConfig, mockCoreSetup.uiSettings, handleAction); expect(columns).toMatchInlineSnapshot(` Array [ Object { @@ -119,7 +118,7 @@ describe('Background Sessions Management table column factory', () => { test('rendering', () => { const [, nameColumn] = getColumns( api, - mockCoreSetup.http, + mockConfig, mockCoreSetup.uiSettings, handleAction ) as Array>; @@ -135,7 +134,7 @@ describe('Background Sessions Management table column factory', () => { test('render in_progress', () => { const [, , status] = getColumns( api, - mockCoreSetup.http, + mockConfig, mockCoreSetup.uiSettings, handleAction ) as Array>; @@ -151,7 +150,7 @@ describe('Background Sessions Management table column factory', () => { test('error handling', () => { const [, , status] = getColumns( api, - mockCoreSetup.http, + mockConfig, mockCoreSetup.uiSettings, handleAction ) as Array>; @@ -172,7 +171,7 @@ describe('Background Sessions Management table column factory', () => { const [, , , createdDateCol] = getColumns( api, - mockCoreSetup.http, + mockConfig, mockCoreSetup.uiSettings, handleAction ) as Array>; @@ -187,7 +186,7 @@ describe('Background Sessions Management table column factory', () => { const [, , , createdDateCol] = getColumns( api, - mockCoreSetup.http, + mockConfig, mockCoreSetup.uiSettings, handleAction ) as Array>; @@ -200,7 +199,7 @@ describe('Background Sessions Management table column factory', () => { test('error handling', () => { const [, , , createdDateCol] = getColumns( api, - mockCoreSetup.http, + mockConfig, mockCoreSetup.uiSettings, handleAction ) as Array>; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index 579179c526469..2056aab64eb50 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -14,17 +14,18 @@ import { EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { HttpStart, IUiSettingsClient } from 'kibana/public'; +import { IUiSettingsClient } from 'kibana/public'; import { capitalize } from 'lodash'; import moment from 'moment'; import React from 'react'; +import { SessionsMgmtConfigSchema } from '../'; import { ActionComplete, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; import { TableText } from '../components'; import { InlineActions, PopoverActionsMenu } from '../components/actions'; import { StatusIndicator } from '../components/status'; import { SearchSessionsMgmtAPI } from './api'; import { dateString } from './date_string'; +import { getExpirationStatus } from './get_expiration_status'; // Helper function: translate an app string to EuiIcon-friendly string const appToIcon = (app: string) => { @@ -36,7 +37,7 @@ const appToIcon = (app: string) => { export const getColumns = ( api: SearchSessionsMgmtAPI, - http: HttpStart, + config: SessionsMgmtConfigSchema, uiSettings: IUiSettingsClient, handleAction: ActionComplete ): Array> => { @@ -152,29 +153,23 @@ export const getColumns = ( field: 'status', name: '', sortable: false, - render: (status, { id, expires, expiresSoon }) => { - if (expiresSoon) { - const tNow = moment().valueOf(); - const tFuture = moment(expires).valueOf(); + render: (status, { id, expires }) => { + const tNow = moment.utc().valueOf(); + const tFuture = moment.utc(expires).valueOf(); + const durationToExpire = moment.duration(tFuture - tNow); + const expiresInDays = Math.floor(durationToExpire.asDays()); + const sufficientDays = Math.ceil(moment.duration(config.expiresSoonWarning).asDays()); - const numDays = Math.floor(moment.duration(tFuture - tNow).asDays()); - const toolTipContent = i18n.translate( - 'xpack.data.mgmt.searchSessions.status.expiresSoonIn', - { - defaultMessage: 'Expires in {numDays} days', - values: { numDays }, - } + if (durationToExpire.valueOf() > 0 && expiresInDays <= sufficientDays) { + const { toolTipContent, statusContent } = getExpirationStatus( + durationToExpire, + expiresInDays ); return ( - - + + {statusContent} ); From 8c91842d9688e08da230cf53b8cde05284c81820 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 29 Dec 2020 12:12:07 -0700 Subject: [PATCH 14/52] move helper to common --- .../common/search/sessions_mgmt/constants.ts | 2 ++ .../search/sessions_mgmt}/date_string.ts | 6 ++-- .../sessions_mgmt/components/status.tsx | 2 +- .../search/sessions_mgmt/lib/get_columns.tsx | 2 +- .../lib/get_expiration_status.ts | 35 +++++++++++++++++++ 5 files changed, 42 insertions(+), 5 deletions(-) rename x-pack/plugins/data_enhanced/{public/search/sessions_mgmt/lib => common/search/sessions_mgmt}/date_string.ts (82%) create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts index badfb37224302..3bebb984e5c65 100644 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts @@ -12,4 +12,6 @@ export enum ACTION { DELETE = 'delete', } +export const DATE_STRING_FORMAT = 'D MMM, YYYY, HH:mm:ss'; + export { BackgroundSessionStatus as STATUS }; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/date_string.ts similarity index 82% rename from x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts rename to x-pack/plugins/data_enhanced/common/search/sessions_mgmt/date_string.ts index 444f6a1ee2640..039d187c2e347 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/date_string.ts @@ -6,18 +6,18 @@ import { IUiSettingsClient } from 'kibana/public'; import moment from 'moment'; +import { DATE_STRING_FORMAT } from './constants'; export const dateString = (inputString: string, uiSettings: IUiSettingsClient): string => { if (inputString == null) { throw new Error('Invalid date string!'); } - const format = 'D MMM, YYYY, HH:mm:ss'; const tz: string = uiSettings.get('dateFormat:tz'); let returnString: string; if (tz === 'Browser') { - returnString = moment.utc(inputString).tz(moment.tz.guess()).format(format); + returnString = moment.utc(inputString).tz(moment.tz.guess()).format(DATE_STRING_FORMAT); } else { - returnString = moment(inputString).tz(tz).format(format); + returnString = moment(inputString).tz(tz).format(DATE_STRING_FORMAT); } return returnString; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx index c73ade09cab3d..7ca70ff827105 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -8,7 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLoadingSpinner, EuiToolTip } fro import { i18n } from '@kbn/i18n'; import React, { ReactElement } from 'react'; import { STATUS } from '../../../../common/search/sessions_mgmt'; -import { dateString } from '../lib/date_string'; +import { dateString } from '../../../../common/search/sessions_mgmt/date_string'; import { StatusDef as StatusAttributes, StatusIndicatorProps, TableText } from './'; // Shared helper function diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index 2056aab64eb50..df415e030bd00 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -24,7 +24,7 @@ import { TableText } from '../components'; import { InlineActions, PopoverActionsMenu } from '../components/actions'; import { StatusIndicator } from '../components/status'; import { SearchSessionsMgmtAPI } from './api'; -import { dateString } from './date_string'; +import { dateString } from '../../../../common/search/sessions_mgmt/date_string'; import { getExpirationStatus } from './get_expiration_status'; // Helper function: translate an app string to EuiIcon-friendly string diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts new file mode 100644 index 0000000000000..50e2c2b7c2fd8 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; + +export const getExpirationStatus = (durationToExpire: moment.Duration, expiresInDays: number) => { + let toolTipContent = i18n.translate('xpack.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 } } + ); + + if (expiresInDays === 0) { + // switch to show expires in hours + const expiresInHours = Math.floor(durationToExpire.asHours()); + + toolTipContent = i18n.translate('xpack.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 } } + ); + } + + return { toolTipContent, statusContent }; +}; From f632a4c37062f01ff35c9638e8ac475f25632806 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 29 Dec 2020 13:54:54 -0700 Subject: [PATCH 15/52] remove bg sessions w/find and delete --- .../services/send_to_background.ts | 46 ++++++++++++++----- .../sessions_management.ts | 4 ++ 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts index d2f8386d59bc8..6c9218110d7a3 100644 --- a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { SavedObjectsFindResponse } from 'src/core/server'; import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; +import { FtrProviderContext } from '../ftr_provider_context'; const SEND_TO_BACKGROUND_TEST_SUBJ = 'backgroundSessionIndicator'; const SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ = 'backgroundSessionIndicatorPopoverContainer'; @@ -21,9 +22,10 @@ type SessionStateType = export function SendToBackgroundProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); + const log = getService('log'); const retry = getService('retry'); const browser = getService('browser'); - const esSupertest = getService('esSupertest'); + const supertest = getService('supertest'); return new (class SendToBackgroundService { public async find(): Promise { @@ -48,16 +50,6 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { await testSubjects.click('backgroundSessionIndicatorViewBackgroundSessionsLink'); } - public async deleteAllBackgroundSessions() { - // ignores 409 errs and keeps retrying - await retry.tryForTime(5000, async () => { - await esSupertest - .post('/.kibana*/_delete_by_query') - .send({ query: { match: { type: 'background-session' } } }) - .expect(200); - }); - } - public async save() { await this.ensurePopoverOpened(); await testSubjects.click('backgroundSessionIndicatorSaveBtn'); @@ -99,5 +91,35 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { return !(await testSubjects.exists(SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ)); }); } + + /* + * This cleanup function should be used by tests that create new background sesions. + * Tests should not end with new background sessions remaining in storage since that interferes with functional tests that check the _find API. + * Alternatively, a test can navigate to `Managment > Background Sessions` and use the UI to delete any created tests. + */ + public async deleteAllBackgroundSessions() { + log.debug('Deleting created background sessions'); + // ignores 409 errs and keeps retrying + await retry.tryForTime(5000, async () => { + const { body } = await supertest + .post('/internal/session/_find') + .set('kbn-xsrf', 'anything') + .set('kbn-system-request', 'true') + .send({ page: 1, perPage: 10000, sortField: 'created', sortOrder: 'asc' }) + .expect(200); + + const { saved_objects: savedObjects } = body as SavedObjectsFindResponse; + log.debug(`Found created background sessions: ${savedObjects.map(({ id }) => id)}`); + await Promise.all( + savedObjects.map(async (so) => { + log.debug(`Deleting background session: ${so.id}`); + await supertest + .delete(`/internal/session/${so.id}`) + .set(`kbn-xsrf`, `anything`) + .expect(200); + }) + ); + }); + } })(); } diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts index 5ad75b68973ba..95eb2f85b8cc7 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts @@ -60,6 +60,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.navigateToApp('management/kibana/background_sessions'); }); + after(async () => { + await sendToBackground.deleteAllBackgroundSessions(); + }); + it('shows no items found', async () => { expectSnapshot( await testSubjects.find('backgroundSessionsMgmtTable').then((n) => n.getVisibleText()) From 5dc31dd9ba107e0e144aadfce3cd4aa5309532ca Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 29 Dec 2020 17:11:45 -0700 Subject: [PATCH 16/52] add storybook --- .../search/sessions_mgmt/date_string.ts | 4 +- .../search/sessions_mgmt/components/index.tsx | 8 - .../sessions_mgmt/components/status.tsx | 18 +- .../components/table/table.stories.tsx | 235 ++++++++++++++++++ .../sessions_mgmt/components/table/table.tsx | 15 +- .../search/sessions_mgmt/lib/get_columns.tsx | 11 +- 6 files changed, 256 insertions(+), 35 deletions(-) create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/date_string.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/date_string.ts index 039d187c2e347..b3bbd077faf65 100644 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/date_string.ts +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/date_string.ts @@ -4,15 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IUiSettingsClient } from 'kibana/public'; import moment from 'moment'; import { DATE_STRING_FORMAT } from './constants'; -export const dateString = (inputString: string, uiSettings: IUiSettingsClient): string => { +export const dateString = (inputString: string, tz: string): string => { if (inputString == null) { throw new Error('Invalid date string!'); } - const tz: string = uiSettings.get('dateFormat:tz'); let returnString: string; if (tz === 'Browser') { returnString = moment.utc(inputString).tz(moment.tz.guess()).format(DATE_STRING_FORMAT); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx index bb8d48aa85de2..0aed3e68b25cc 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx @@ -5,9 +5,7 @@ */ import { EuiLinkProps, EuiText, EuiTextProps } from '@elastic/eui'; -import { IUiSettingsClient } from 'kibana/public'; import React from 'react'; -import { UISession } from '../../../../common/search/sessions_mgmt'; import extendSessionIcon from '../icons/extend_session.svg'; export const TableText = ({ children, ...props }: EuiTextProps) => { @@ -29,12 +27,6 @@ export interface IHrefActionDescriptor { props: EuiLinkProps; } -export interface StatusIndicatorProps { - now?: string; - session: UISession; - uiSettings: IUiSettingsClient; -} - export interface StatusDef { textColor?: EuiTextProps['color']; icon?: React.ReactElement; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx index 7ca70ff827105..1ebf8756f76c2 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -7,9 +7,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { ReactElement } from 'react'; -import { STATUS } from '../../../../common/search/sessions_mgmt'; +import { STATUS, UISession } from '../../../../common/search/sessions_mgmt'; import { dateString } from '../../../../common/search/sessions_mgmt/date_string'; -import { StatusDef as StatusAttributes, StatusIndicatorProps, TableText } from './'; +import { StatusDef as StatusAttributes, TableText } from './'; // Shared helper function export const getStatusText = (statusType: string): string => { @@ -41,17 +41,23 @@ export const getStatusText = (statusType: string): string => { } }; +interface StatusIndicatorProps { + now?: string; + session: UISession; + timezone: string; +} + // Get the fields needed to show each status type // can throw errors around date conversions const getStatusAttributes = ({ now, session, - uiSettings, + timezone, }: StatusIndicatorProps): StatusAttributes | null => { switch (session.status) { case STATUS.IN_PROGRESS: try { - const createdDate = dateString(session.created, uiSettings); + const createdDate = dateString(session.created, timezone); return { textColor: 'default', icon: , @@ -72,7 +78,7 @@ const getStatusAttributes = ({ case STATUS.EXPIRED: try { - const expiredOnDate = dateString(session.expires!, uiSettings); + const expiredOnDate = dateString(session.expires!, timezone); const expiredOnMessage = i18n.translate( 'xpack.data.mgmt.searchSessions.status.message.expiredOn', { @@ -114,7 +120,7 @@ const getStatusAttributes = ({ case STATUS.COMPLETE: try { - const expiresOnDate = dateString(session.expires!, uiSettings); + const expiresOnDate = dateString(session.expires!, timezone); const expiresOnMessage = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresOn', { defaultMessage: 'Expires on {expiresOnDate}', values: { expiresOnDate }, diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx new file mode 100644 index 0000000000000..59f77197348aa --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx @@ -0,0 +1,235 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { storiesOf } from '@storybook/react'; +import type { HttpSetup, NotificationsStart } from 'kibana/public'; +import moment from 'moment'; +import * as React from 'react'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import type { UrlGeneratorsStart } from 'src/plugins/share/public/url_generators'; +import { SessionsMgmtConfigSchema } from '../..'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { SessionsClient } from '../../../../../../../../src/plugins/data/public/search'; +import { ACTION, STATUS } from '../../../../../common/search/sessions_mgmt'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { SearchSessionsMgmtTable } from './table'; + +export default { + title: 'BackgroundSessionsMgmt/Table', + component: SearchSessionsMgmtTable, +}; + +storiesOf('components/BackgroundSessionsMgmt/Table', module) + .add('no items', () => { + const sessionsClient = new SessionsClient({ http: ({} as unknown) as HttpSetup }); + const urls = ({ + urlGenerator: {}, + getUrlGenerator: () => ({ + createUrl: () => ({}), + }), + } as unknown) as UrlGeneratorsStart; + const notifications = ({ + toasts: { + addInfo: () => ({}), + }, + } as unknown) as NotificationsStart; + const config: SessionsMgmtConfigSchema = { + maxSessions: 100, + refreshInterval: moment.duration(1, 'minutes'), + refreshTimeout: moment.duration(20, 'minutes'), + expiresSoonWarning: moment.duration(2, 'days'), + }; + + const props = { + initialTable: [], + api: new SearchSessionsMgmtAPI(sessionsClient, urls, notifications, config), + timezone: 'UTC', + config, + }; + + return ; + }) + .add('completed session', () => { + const sessionsClient = new SessionsClient({ http: ({} as unknown) as HttpSetup }); + const urls = ({ + urlGenerator: {}, + getUrlGenerator: () => ({ + createUrl: () => ({}), + }), + } as unknown) as UrlGeneratorsStart; + const notifications = ({ + toasts: { + addInfo: () => ({}), + }, + } as unknown) as NotificationsStart; + const config: SessionsMgmtConfigSchema = { + maxSessions: 100, + refreshInterval: moment.duration(1, 'minutes'), + refreshTimeout: moment.duration(20, 'minutes'), + expiresSoonWarning: moment.duration(2, 'days'), + }; + + const initialTable = [ + { + id: 'foo-123', + name: 'Single Session', + appId: 'dashboards', + created: '2020-12-29T00:00:00Z', + expires: '2021-01-15T00:00:00Z', + status: STATUS.COMPLETE, + url: '/pepperoni', + isViewable: true, + }, + ]; + + const props = { + api: new SearchSessionsMgmtAPI(sessionsClient, urls, notifications, config), + timezone: 'UTC', + initialTable, + config, + }; + + return ; + }) + .add('completed session, expires soon', () => { + const sessionsClient = new SessionsClient({ http: ({} as unknown) as HttpSetup }); + const urls = ({ + urlGenerator: {}, + getUrlGenerator: () => ({ + createUrl: () => ({}), + }), + } as unknown) as UrlGeneratorsStart; + const notifications = ({ + toasts: { + addInfo: () => ({}), + }, + } as unknown) as NotificationsStart; + const config: SessionsMgmtConfigSchema = { + maxSessions: 100, + refreshInterval: moment.duration(1, 'minutes'), + refreshTimeout: moment.duration(20, 'minutes'), + expiresSoonWarning: moment.duration(2, 'days'), + }; + + const created = moment().subtract(86000000); + const expires = moment().add(86000000); + const initialTable = [ + { + id: 'foo-123', + name: 'Single Session', + appId: 'dashboards', + created: created.format(), + expires: expires.format(), + status: STATUS.COMPLETE, + url: '/pepperoni', + isViewable: true, + }, + { + id: 'foo-123', + name: 'Single Session', + appId: 'dashboards', + created: created.format(), + expires: expires.add(86000000).format(), + status: STATUS.COMPLETE, + url: '/pepperoni', + isViewable: true, + }, + ]; + + const props = { + api: new SearchSessionsMgmtAPI(sessionsClient, urls, notifications, config), + timezone: 'UTC', + initialTable, + config, + }; + + return ; + }) + .add('pages of random data', () => { + const sessionsClient = new SessionsClient({ http: ({} as unknown) as HttpSetup }); + const urls = ({ + urlGenerator: {}, + getUrlGenerator: () => ({ + createUrl: () => ({}), + }), + } as unknown) as UrlGeneratorsStart; + const notifications = ({ + toasts: { + addInfo: () => ({}), + }, + } as unknown) as NotificationsStart; + const config: SessionsMgmtConfigSchema = { + maxSessions: 100, + refreshInterval: moment.duration(1, 'minutes'), + refreshTimeout: moment.duration(20, 'minutes'), + expiresSoonWarning: moment.duration(2, 'days'), + }; + + const names = [ + 'Session 1', + 'Session 2', + 'Session 3', + 'Session 3', + 'Session 4', + 'Session 5', + 'very very very very very very very very very very very very very very very very very very very very very very very long name', + 'Session 10', + 'Session 11', + 'Session 12', + 'Session 13', + 'Session 14', + 'Session 15', + 'Session 20', + 'Session 21', + 'Session 22', + 'Session 23', + 'Session 24', + 'Session 25', + 'Session 30', + 'Session 31', + 'Session 32', + 'Session 33', + 'Session 34', + 'Session 35', + ]; + + const appIds = ['canvas', 'discover', 'dashboards', 'visualize', 'security']; + const statuses = [ + STATUS.CANCELLED, + STATUS.COMPLETE, + STATUS.ERROR, + STATUS.EXPIRED, + STATUS.IN_PROGRESS, + ]; + const viewability = [true, true, false]; + const actions = [[ACTION.DELETE]]; + + const mockTable = names.map((name, ndx) => { + const created = moment().subtract(86000000 * ndx); + const expires = moment().add(86000000 * ndx); + + return { + name, + id: `cool-session-${ndx}`, + created: created.format(), + expires: expires.format(), + url: `/cool-app-${ndx}`, + appId: appIds[ndx % 5], + status: statuses[ndx % 5], + isViewable: viewability[ndx % 3], + actions: actions[ndx % 2], + }; + }); + + const props = { + api: new SearchSessionsMgmtAPI(sessionsClient, urls, notifications, config), + timezone: 'UTC', + initialTable: mockTable, + config, + }; + + return ; + }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index fa067f24e02bc..7ab767b5ed067 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -6,7 +6,6 @@ import { EuiButton, EuiInMemoryTable, EuiSearchBarProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { HttpStart, IUiSettingsClient } from 'kibana/public'; import moment from 'moment'; import React, { useEffect, useState } from 'react'; import * as Rx from 'rxjs'; @@ -23,20 +22,12 @@ const TABLE_ID = 'backgroundSessionsMgmtTable'; interface Props { api: SearchSessionsMgmtAPI; - http: HttpStart; initialTable: UISession[] | null; - uiSettings: IUiSettingsClient; + timezone: string; config: SessionsMgmtConfigSchema; } -export function SearchSessionsMgmtTable({ - api, - http, - uiSettings, - initialTable, - config, - ...props -}: Props) { +export function SearchSessionsMgmtTable({ api, timezone, initialTable, config, ...props }: Props) { const [tableData, setTableData] = useState(initialTable ? initialTable : []); const [isLoading, setIsLoading] = useState(false); const [pagination, setPagination] = useState({ pageIndex: 0 }); @@ -110,7 +101,7 @@ export function SearchSessionsMgmtTable({ {...props} id={TABLE_ID} data-test-subj={TABLE_ID} - columns={getColumns(api, config, uiSettings, handleActionCompleted)} + columns={getColumns(api, config, timezone, handleActionCompleted)} items={tableData} pagination={pagination} search={search} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index df415e030bd00..943e08ecc5399 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -14,17 +14,16 @@ import { EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { IUiSettingsClient } from 'kibana/public'; import { capitalize } from 'lodash'; import moment from 'moment'; import React from 'react'; import { SessionsMgmtConfigSchema } from '../'; import { ActionComplete, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; +import { dateString } from '../../../../common/search/sessions_mgmt/date_string'; import { TableText } from '../components'; import { InlineActions, PopoverActionsMenu } from '../components/actions'; import { StatusIndicator } from '../components/status'; import { SearchSessionsMgmtAPI } from './api'; -import { dateString } from '../../../../common/search/sessions_mgmt/date_string'; import { getExpirationStatus } from './get_expiration_status'; // Helper function: translate an app string to EuiIcon-friendly string @@ -38,7 +37,7 @@ const appToIcon = (app: string) => { export const getColumns = ( api: SearchSessionsMgmtAPI, config: SessionsMgmtConfigSchema, - uiSettings: IUiSettingsClient, + timezone: string, handleAction: ActionComplete ): Array> => { // Use a literal array of table column definitions to detail a UISession object @@ -89,7 +88,7 @@ export const getColumns = ( }), sortable: true, render: (statusType: UISession['status'], session) => { - return ; + return ; }, }, @@ -102,7 +101,7 @@ export const getColumns = ( sortable: true, render: (created: UISession['created'], { id }) => { try { - const startedOn = dateString(created, uiSettings); + const startedOn = dateString(created, timezone); return ( {startedOn} @@ -126,7 +125,7 @@ export const getColumns = ( render: (expires: UISession['expires'], { id, status }) => { if (expires && status !== STATUS.IN_PROGRESS && status !== STATUS.ERROR) { try { - const expiresOn = dateString(expires, uiSettings); + const expiresOn = dateString(expires, timezone); // return return ( From 5d0fe85c0cd6ef9f7e2a9882127eefdf445019dc Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 29 Dec 2020 23:09:37 -0700 Subject: [PATCH 17/52] fix tests --- .../sessions_mgmt/application/render.tsx | 10 ++- .../sessions_mgmt/components/main.test.tsx | 2 +- .../search/sessions_mgmt/components/main.tsx | 4 +- .../sessions_mgmt/components/status.test.tsx | 31 ++------- .../components/table/table.test.tsx | 15 +---- .../sessions_mgmt/lib/get_columns.test.tsx | 66 +++++++------------ 6 files changed, 43 insertions(+), 85 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx index fc63d4a92285b..c6c730a3583b6 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx @@ -13,7 +13,7 @@ import { SearchSessionsMgmtMain } from '../components/main'; export const renderApp = ( elem: HTMLElement | null, - { i18n, ...homeDeps }: AppDependencies, + { i18n, uiSettings, ...homeDeps }: AppDependencies, initialTable: UISession[] | null ) => { if (!elem) { @@ -23,13 +23,17 @@ export const renderApp = ( const { Context: I18nContext } = i18n; // uiSettings is required by the listing table to format dates in the timezone from Settings const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ - uiSettings: homeDeps.uiSettings, + uiSettings, }); render( - + , elem diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx index 577a5d69a6e0f..4cdeae2356cf1 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx @@ -76,7 +76,7 @@ describe('Background Search Session Management Main', () => { ; +let tz: string; let session: UISession; const mockNowTime = new Date(); @@ -22,9 +19,7 @@ mockNowTime.setTime(1607026176061); describe('Background Search Session management status labels', () => { beforeEach(() => { - mockCoreSetup = coreMock.createSetup(); - mockCoreSetup.uiSettings.get.mockImplementation(() => 'Browser'); - + tz = 'Browser'; session = { name: 'amazing test', id: 'wtywp9u2802hahgp-gsla', @@ -59,7 +54,7 @@ describe('Background Search Session management status labels', () => { test('render in progress', () => { const statusIndicator = mount( - + ); @@ -74,7 +69,7 @@ describe('Background Search Session management status labels', () => { const statusIndicator = mount( - + ); @@ -90,11 +85,7 @@ describe('Background Search Session management status labels', () => { const statusIndicator = mount( - + ); @@ -109,11 +100,7 @@ describe('Background Search Session management status labels', () => { const statusIndicator = mount( - + ); @@ -130,11 +117,7 @@ describe('Background Search Session management status labels', () => { const statusIndicator = mount( - + ); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index 3ea4761ac0b43..88e49c16443eb 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -63,16 +63,11 @@ describe('Background Search Session Management Table', () => { let table: ReactWrapper; beforeEach(() => { - mockCoreSetup.uiSettings.get.mockImplementation((key: string) => { - return key === 'dateFormat:tz' ? 'UTC' : null; - }); - table = mount( @@ -121,13 +116,7 @@ describe('Background Search Session Management Table', () => { const table = mount( - + ); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index 1bf8132dc376f..be4a79c3850d8 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -25,6 +25,8 @@ let sessionsClient: SessionsClient; let handleAction: ActionComplete; let mockSession: UISession; +let tz = 'UTC'; + describe('Background Sessions Management table column factory', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); @@ -43,9 +45,7 @@ describe('Background Sessions Management table column factory', () => { mockCoreSetup.notifications, mockConfig ); - mockCoreSetup.uiSettings.get.mockImplementation((key) => { - return key === 'dateFormat:tz' ? 'UTC' : null; - }); + tz = 'UTC'; handleAction = () => { throw new Error('not testing handle action'); @@ -64,7 +64,7 @@ describe('Background Sessions Management table column factory', () => { }); test('returns columns', () => { - const columns = getColumns(api, mockConfig, mockCoreSetup.uiSettings, handleAction); + const columns = getColumns(api, mockConfig, tz, handleAction); expect(columns).toMatchInlineSnapshot(` Array [ Object { @@ -116,12 +116,9 @@ describe('Background Sessions Management table column factory', () => { describe('name', () => { test('rendering', () => { - const [, nameColumn] = getColumns( - api, - mockConfig, - mockCoreSetup.uiSettings, - handleAction - ) as Array>; + const [, nameColumn] = getColumns(api, mockConfig, tz, handleAction) as Array< + EuiTableFieldDataColumnType + >; const name = mount(nameColumn.render!(mockSession.name, mockSession) as ReactElement); @@ -132,12 +129,9 @@ describe('Background Sessions Management table column factory', () => { // Status column describe('status', () => { test('render in_progress', () => { - const [, , status] = getColumns( - api, - mockConfig, - mockCoreSetup.uiSettings, - handleAction - ) as Array>; + const [, , status] = getColumns(api, mockConfig, tz, handleAction) as Array< + EuiTableFieldDataColumnType + >; const statusLine = mount(status.render!(mockSession.status, mockSession) as ReactElement); expect( @@ -148,12 +142,9 @@ describe('Background Sessions Management table column factory', () => { }); test('error handling', () => { - const [, , status] = getColumns( - api, - mockConfig, - mockCoreSetup.uiSettings, - handleAction - ) as Array>; + const [, , status] = getColumns(api, mockConfig, tz, handleAction) as Array< + EuiTableFieldDataColumnType + >; mockSession.status = 'INVALID' as STATUS; const statusLine = mount(status.render!(mockSession.status, mockSession) as ReactElement); @@ -167,14 +158,11 @@ describe('Background Sessions Management table column factory', () => { // Start Date column describe('startedDate', () => { test('render using Browser timezone', () => { - mockCoreSetup.uiSettings.get.mockImplementation(() => 'Browser'); + tz = 'Browser'; - const [, , , createdDateCol] = getColumns( - api, - mockConfig, - mockCoreSetup.uiSettings, - handleAction - ) as Array>; + const [, , , createdDateCol] = getColumns(api, mockConfig, tz, handleAction) as Array< + EuiTableFieldDataColumnType + >; const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement); @@ -182,14 +170,11 @@ describe('Background Sessions Management table column factory', () => { }); test('render using AK timezone', () => { - mockCoreSetup.uiSettings.get.mockImplementation(() => 'US/Alaska'); + tz = 'US/Alaska'; - const [, , , createdDateCol] = getColumns( - api, - mockConfig, - mockCoreSetup.uiSettings, - handleAction - ) as Array>; + const [, , , createdDateCol] = getColumns(api, mockConfig, tz, handleAction) as Array< + EuiTableFieldDataColumnType + >; const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement); @@ -197,12 +182,9 @@ describe('Background Sessions Management table column factory', () => { }); test('error handling', () => { - const [, , , createdDateCol] = getColumns( - api, - mockConfig, - mockCoreSetup.uiSettings, - handleAction - ) as Array>; + const [, , , createdDateCol] = getColumns(api, mockConfig, tz, handleAction) as Array< + EuiTableFieldDataColumnType + >; mockSession.created = 'INVALID'; const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement); From a38f1d6ac7cc0b08fd597a70e8a1e71ce92d072c Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Tue, 29 Dec 2020 23:30:43 -0700 Subject: [PATCH 18/52] storybook actions --- .../components/actions/popover_actions.tsx | 6 +- .../components/table/table.stories.tsx | 107 ++---------------- 2 files changed, 9 insertions(+), 104 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx index a91508f37c075..7b4ed099ec63e 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx @@ -79,11 +79,7 @@ export const PopoverActionsMenu = ({ api, handleAction, session }: PopoverAction /> ); - const { actions } = session; - if (!actions || actions.length === 0) { - return null; - } - + const actions = session.actions || []; // Generic set of actions - up to the API to return what is available const items = actions.reduce((itemSet, actionType) => { const actionDef = getAction(api, actionType, session, handleAction); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx index 59f77197348aa..e87f66aa27a88 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx @@ -52,103 +52,7 @@ storiesOf('components/BackgroundSessionsMgmt/Table', module) return ; }) - .add('completed session', () => { - const sessionsClient = new SessionsClient({ http: ({} as unknown) as HttpSetup }); - const urls = ({ - urlGenerator: {}, - getUrlGenerator: () => ({ - createUrl: () => ({}), - }), - } as unknown) as UrlGeneratorsStart; - const notifications = ({ - toasts: { - addInfo: () => ({}), - }, - } as unknown) as NotificationsStart; - const config: SessionsMgmtConfigSchema = { - maxSessions: 100, - refreshInterval: moment.duration(1, 'minutes'), - refreshTimeout: moment.duration(20, 'minutes'), - expiresSoonWarning: moment.duration(2, 'days'), - }; - - const initialTable = [ - { - id: 'foo-123', - name: 'Single Session', - appId: 'dashboards', - created: '2020-12-29T00:00:00Z', - expires: '2021-01-15T00:00:00Z', - status: STATUS.COMPLETE, - url: '/pepperoni', - isViewable: true, - }, - ]; - - const props = { - api: new SearchSessionsMgmtAPI(sessionsClient, urls, notifications, config), - timezone: 'UTC', - initialTable, - config, - }; - - return ; - }) - .add('completed session, expires soon', () => { - const sessionsClient = new SessionsClient({ http: ({} as unknown) as HttpSetup }); - const urls = ({ - urlGenerator: {}, - getUrlGenerator: () => ({ - createUrl: () => ({}), - }), - } as unknown) as UrlGeneratorsStart; - const notifications = ({ - toasts: { - addInfo: () => ({}), - }, - } as unknown) as NotificationsStart; - const config: SessionsMgmtConfigSchema = { - maxSessions: 100, - refreshInterval: moment.duration(1, 'minutes'), - refreshTimeout: moment.duration(20, 'minutes'), - expiresSoonWarning: moment.duration(2, 'days'), - }; - - const created = moment().subtract(86000000); - const expires = moment().add(86000000); - const initialTable = [ - { - id: 'foo-123', - name: 'Single Session', - appId: 'dashboards', - created: created.format(), - expires: expires.format(), - status: STATUS.COMPLETE, - url: '/pepperoni', - isViewable: true, - }, - { - id: 'foo-123', - name: 'Single Session', - appId: 'dashboards', - created: created.format(), - expires: expires.add(86000000).format(), - status: STATUS.COMPLETE, - url: '/pepperoni', - isViewable: true, - }, - ]; - - const props = { - api: new SearchSessionsMgmtAPI(sessionsClient, urls, notifications, config), - timezone: 'UTC', - initialTable, - config, - }; - - return ; - }) - .add('pages of random data', () => { + .add('multiple pages', () => { const sessionsClient = new SessionsClient({ http: ({} as unknown) as HttpSetup }); const urls = ({ urlGenerator: {}, @@ -205,7 +109,12 @@ storiesOf('components/BackgroundSessionsMgmt/Table', module) STATUS.IN_PROGRESS, ]; const viewability = [true, true, false]; - const actions = [[ACTION.DELETE]]; + const actions = [ + [ACTION.DELETE], + [ACTION.CANCEL, ACTION.DELETE], + [ACTION.EXTEND, ACTION.DELETE], + [ACTION.CANCEL, ACTION.EXTEND, ACTION.DELETE], + ]; const mockTable = names.map((name, ndx) => { const created = moment().subtract(86000000 * ndx); @@ -220,7 +129,7 @@ storiesOf('components/BackgroundSessionsMgmt/Table', module) appId: appIds[ndx % 5], status: statuses[ndx % 5], isViewable: viewability[ndx % 3], - actions: actions[ndx % 2], + actions: actions[ndx % 4], }; }); From f3e1a9bc131b721e0237cd7cb849268c9c4d76a1 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 30 Dec 2020 13:00:34 -0700 Subject: [PATCH 19/52] refactor expiration status calculation --- .../sessions_mgmt/components/status.tsx | 8 ++--- .../search/sessions_mgmt/lib/get_columns.tsx | 29 +++++++------------ .../lib/get_expiration_status.ts | 16 ++++++++-- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx index 1ebf8756f76c2..3f4a2b5640152 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -79,7 +79,7 @@ const getStatusAttributes = ({ case STATUS.EXPIRED: try { const expiredOnDate = dateString(session.expires!, timezone); - const expiredOnMessage = i18n.translate( + const toolTipContent = i18n.translate( 'xpack.data.mgmt.searchSessions.status.message.expiredOn', { defaultMessage: 'Expired on {expiredOnDate}', @@ -90,7 +90,7 @@ const getStatusAttributes = ({ return { icon: , label: {getStatusText(session.status)}, - toolTipContent: expiredOnMessage, + toolTipContent, }; } catch (err) { // eslint-disable-next-line no-console @@ -121,7 +121,7 @@ const getStatusAttributes = ({ case STATUS.COMPLETE: try { const expiresOnDate = dateString(session.expires!, timezone); - const expiresOnMessage = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresOn', { + const toolTipContent = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresOn', { defaultMessage: 'Expires on {expiresOnDate}', values: { expiresOnDate }, }); @@ -130,7 +130,7 @@ const getStatusAttributes = ({ textColor: 'secondary', icon: , label: {getStatusText(session.status)}, - toolTipContent: expiresOnMessage, + toolTipContent, }; } catch (err) { // eslint-disable-next-line no-console diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index 943e08ecc5399..f14158fed44e9 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -15,7 +15,6 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { capitalize } from 'lodash'; -import moment from 'moment'; import React from 'react'; import { SessionsMgmtConfigSchema } from '../'; import { ActionComplete, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; @@ -87,12 +86,12 @@ export const getColumns = ( defaultMessage: 'Status', }), sortable: true, - render: (statusType: UISession['status'], session) => { - return ; - }, + render: (statusType: UISession['status'], session) => ( + + ), }, - // Started date, formatted in TZ from UI Settings + // Started date { field: 'created', name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerStarted', { @@ -115,7 +114,7 @@ export const getColumns = ( }, }, - // Expiration date, also formatted. Shows a warning badge if expiration is soon + // Expiration date { field: 'expires', name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerExpiration', { @@ -147,23 +146,15 @@ export const getColumns = ( }, }, - // Highlight Badge: if completed session expires soon, show a highlight badge + // Highlight Badge, if completed session expires soon { field: 'status', name: '', sortable: false, - render: (status, { id, expires }) => { - const tNow = moment.utc().valueOf(); - const tFuture = moment.utc(expires).valueOf(); - const durationToExpire = moment.duration(tFuture - tNow); - const expiresInDays = Math.floor(durationToExpire.asDays()); - const sufficientDays = Math.ceil(moment.duration(config.expiresSoonWarning).asDays()); - - if (durationToExpire.valueOf() > 0 && expiresInDays <= sufficientDays) { - const { toolTipContent, statusContent } = getExpirationStatus( - durationToExpire, - expiresInDays - ); + render: (status, { expires }) => { + const expirationStatus = getExpirationStatus(config, expires); + if (expirationStatus) { + const { toolTipContent, statusContent } = expirationStatus; return ( diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts index 50e2c2b7c2fd8..3c167d6dbe41a 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_expiration_status.ts @@ -6,8 +6,18 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment'; +import { SessionsMgmtConfigSchema } from '../'; + +export const getExpirationStatus = (config: SessionsMgmtConfigSchema, expires: string | null) => { + const tNow = moment.utc().valueOf(); + const tFuture = moment.utc(expires).valueOf(); + + // NOTE this could end up negative. If server time is off from the browser's clock + // and the session was early expired when the browser refreshed the listing + const durationToExpire = moment.duration(tFuture - tNow); + const expiresInDays = Math.floor(durationToExpire.asDays()); + const sufficientDays = Math.ceil(moment.duration(config.expiresSoonWarning).asDays()); -export const getExpirationStatus = (durationToExpire: moment.Duration, expiresInDays: number) => { let toolTipContent = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresSoonInDays', { defaultMessage: 'Expires in {numDays} days', values: { numDays: expiresInDays }, @@ -31,5 +41,7 @@ export const getExpirationStatus = (durationToExpire: moment.Duration, expiresIn ); } - return { toolTipContent, statusContent }; + if (durationToExpire.valueOf() > 0 && expiresInDays <= sufficientDays) { + return { toolTipContent, statusContent }; + } }; From d9676577715048d293dbd2a54c773b4b34906ddd Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 30 Dec 2020 13:11:41 -0700 Subject: [PATCH 20/52] isViewable as calculated field --- .../components/actions/popover_actions.tsx | 1 + .../search/sessions_mgmt/lib/api.test.ts | 99 ++++++++++++++++--- .../public/search/sessions_mgmt/lib/api.ts | 20 ++-- .../search/sessions_mgmt/lib/get_columns.tsx | 16 +-- 4 files changed, 104 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx index 7b4ed099ec63e..540fe6c82ec5f 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx @@ -87,6 +87,7 @@ export const PopoverActionsMenu = ({ api, handleAction, session }: PopoverAction const { label, textColor, iconType } = actionDef; // add a line above the delete action (when there are multiple) + // NOTE: Delete action MUST be the final action[] item if (actions.length > 1 && actionType === ACTION.DELETE) { itemSet.push({ isSeparator: true, key: 'separadorable' }); } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts index 4bd310fa03047..af5139c16d46f 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts @@ -11,8 +11,11 @@ import moment from 'moment'; import * as Rx from 'rxjs'; import sinon from 'sinon'; import { coreMock } from 'src/core/public/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import type { SavedObjectsFindResponse } from 'src/core/server'; import { SessionsClient } from 'src/plugins/data/public/search'; -import { SessionsMgmtConfigSchema } from '../'; +import type { SessionsMgmtConfigSchema } from '../'; +import { STATUS } from '../../../../common/search/sessions_mgmt'; import { mockUrls } from '../__mocks__'; import { SearchSessionsMgmtAPI } from './api'; @@ -32,17 +35,16 @@ describe('Background Sessions Management API', () => { }; sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); - findSessions = sinon.stub(sessionsClient, 'find').callsFake( - async () => - ({ - saved_objects: [ - { - id: 'hello-pizza-123', - attributes: { name: 'Veggie', appId: 'pizza', status: 'baked' }, - }, - ], - } as any) // can't reach the actual type from public - ); + findSessions = sinon.stub(sessionsClient, 'find').callsFake(async () => { + return { + saved_objects: [ + { + id: 'hello-pizza-123', + attributes: { name: 'Veggie', appId: 'pizza', status: 'baked' }, + }, + ], + } as SavedObjectsFindResponse; + }); }); describe('listing', () => { @@ -72,6 +74,79 @@ describe('Background Sessions Management API', () => { `); }); + test('isViewable is calculated based on status', async () => { + findSessions.callsFake(async () => { + return { + saved_objects: [ + { + id: 'hello-pizza-000', + attributes: { name: 'Veggie 1', appId: 'pizza', status: STATUS.IN_PROGRESS }, + }, + { + id: 'hello-pizza-123', + attributes: { name: 'Veggie 2', appId: 'pizza', status: STATUS.COMPLETE }, + }, + { + id: 'hello-pizza-456', + attributes: { name: 'Veggie B', appId: 'pizza', status: STATUS.EXPIRED }, + }, + { + id: 'hello-pizza-789', + attributes: { name: 'Veggie C', appId: 'pizza', status: STATUS.CANCELLED }, + }, + { + id: 'hello-pizza-999', + attributes: { name: 'Veggie X', appId: 'pizza', status: STATUS.ERROR }, + }, + ], + } as SavedObjectsFindResponse; + }); + + const api = new SearchSessionsMgmtAPI( + sessionsClient, + mockUrls, + mockCoreSetup.notifications, + mockConfig + ); + const data = await api.fetchTableData(); + expect(data).not.toBe(null); + + expect( + data && + data.map((session) => { + return [session.name, session.status, session.isViewable]; + }) + ).toMatchInlineSnapshot(` + Array [ + Array [ + "Veggie 1", + "in_progress", + true, + ], + Array [ + "Veggie 2", + "complete", + true, + ], + Array [ + "Veggie B", + "expired", + false, + ], + Array [ + "Veggie C", + "cancelled", + false, + ], + Array [ + "Veggie X", + "error", + false, + ], + ] + `); + }); + test('handle error from sessionsClient response', async () => { findSessions.callsFake(async () => { throw Boom.badImplementation('implementation is so bad'); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index f25d898d523b3..d61e6460b9d58 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -13,8 +13,7 @@ import type { SharePluginStart } from 'src/plugins/share/public'; import { SessionsMgmtConfigSchema } from '../'; import type { ISessionsClient } from '../../../../../../../src/plugins/data/public'; import type { BackgroundSessionSavedObjectAttributes } from '../../../../common'; -import type { UISession } from '../../../../common/search/sessions_mgmt'; -import { ACTION } from '../../../../common/search/sessions_mgmt'; +import { ACTION, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; type UrlGeneratorsStart = SharePluginStart['urlGenerators']; @@ -52,9 +51,13 @@ const mapToUISession = ( console.error(err); } + // viewable if available + const isViewable = + status !== STATUS.CANCELLED && status !== STATUS.EXPIRED && status !== STATUS.ERROR; + return { id: savedObject.id, - isViewable: true, // always viewable + isViewable, name, appId, created, @@ -145,17 +148,6 @@ export class SearchSessionsMgmtAPI { return await this.fetchTableData(); } - // Cancel: not implemented - public async sendCancel(id: string): Promise { - this.notifications.toasts.addError(new Error('Not implemented'), { - title: i18n.translate('xpack.data.mgmt.searchSessions.api.cancelError', { - defaultMessage: 'Failed to cancel the session!', - }), - }); - - return await this.fetchTableData(); - } - // Extend public async sendExtend(id: string): Promise { this.notifications.toasts.addError(new Error('Not implemented'), { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index f14158fed44e9..32285b92ec06d 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -70,12 +70,16 @@ export const getColumns = ( }), sortable: true, width: '20%', - render: (name: UISession['name'], { appId, url, id }) => { - return ( - - {name} - - ); + render: (name: UISession['name'], { isViewable, url }) => { + if (isViewable) { + return ( + + {name} + + ); + } + + return {name}; }, }, From e3f91138f54f4e98844bea52d1979d77cdb89a7b Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Mon, 4 Jan 2021 11:01:08 -0700 Subject: [PATCH 21/52] refreshInterval 10s default --- x-pack/plugins/data_enhanced/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/data_enhanced/config.ts b/x-pack/plugins/data_enhanced/config.ts index 3641238daf107..6eb326c4d735c 100644 --- a/x-pack/plugins/data_enhanced/config.ts +++ b/x-pack/plugins/data_enhanced/config.ts @@ -12,7 +12,7 @@ export const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: false }), sessionsManagement: schema.object({ maxSessions: schema.number({ defaultValue: 10000 }), - refreshInterval: schema.duration({ defaultValue: '3s' }), + refreshInterval: schema.duration({ defaultValue: '10s' }), refreshTimeout: schema.duration({ defaultValue: '1m' }), expiresSoonWarning: schema.duration({ defaultValue: '1d' }), }), From d2ad3f3dd6f190d8516efe99b8124e9e6fd476f5 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 6 Jan 2021 23:15:21 -0700 Subject: [PATCH 22/52] list newest first --- .../public/search/sessions_mgmt/components/table/table.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index 7ab767b5ed067..53aea99936b7a 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -93,9 +93,6 @@ export function SearchSessionsMgmtTable({ api, timezone, initialTable, config, . ), }; - // table config: sorting - const sorting = { sort: { field: 'startedDate', direction: 'desc' as 'desc' } }; - return ( {...props} @@ -105,7 +102,7 @@ export function SearchSessionsMgmtTable({ api, timezone, initialTable, config, . items={tableData} pagination={pagination} search={search} - sorting={sorting} + sorting={{ sort: { field: 'created', direction: 'desc' } }} onTableChange={({ page: { index } }) => { setPagination({ pageIndex: index }); }} From 30419608f164026ed795ffbced4eee978eca1607 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 6 Jan 2021 23:17:08 -0700 Subject: [PATCH 23/52] "Type" => "App" --- .../search/sessions_mgmt/components/table/table.test.tsx | 2 +- .../public/search/sessions_mgmt/lib/get_columns.test.tsx | 2 +- .../public/search/sessions_mgmt/lib/get_columns.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index 88e49c16443eb..42a67f14ea441 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -90,7 +90,7 @@ describe('Background Search Session Management Table', () => { test('table body cells', () => { expect(table.find('tbody td').map((node) => node.text())).toMatchInlineSnapshot(` Array [ - "Type", + "App", "Namevery background search", "StatusIn progress", "Created2 Dec, 2020, 00:19:32", diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index be4a79c3850d8..ba97d010cf720 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -69,7 +69,7 @@ describe('Background Sessions Management table column factory', () => { Array [ Object { "field": "appId", - "name": "Type", + "name": "App", "render": [Function], "sortable": true, }, diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index 32285b92ec06d..5be2059d65f2d 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -41,11 +41,11 @@ export const getColumns = ( ): Array> => { // Use a literal array of table column definitions to detail a UISession object return [ - // Type (appIcon) + // App { field: 'appId', name: i18n.translate('xpack.data.mgmt.searchSessions.table.headerType', { - defaultMessage: 'Type', + defaultMessage: 'App', }), sortable: true, render: (appId: UISession['appId'], { id }) => { From 6d3b66a4a5674292aaa67149c4428c7828bcec46 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 6 Jan 2021 23:20:59 -0700 Subject: [PATCH 24/52] remove inline view action --- .../components/actions/actions.test.tsx | 67 ------------------- .../components/actions/index.tsx | 1 - .../components/actions/inline_actions.tsx | 42 ------------ .../search/sessions_mgmt/lib/get_columns.tsx | 7 +- 4 files changed, 2 insertions(+), 115 deletions(-) delete mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/actions.test.tsx delete mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/actions.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/actions.test.tsx deleted file mode 100644 index 15515cec50432..0000000000000 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/actions.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { mount } from 'enzyme'; -import React from 'react'; -import { STATUS, UISession } from '../../../../../common/search/sessions_mgmt'; -import { LocaleWrapper } from '../../__mocks__'; -import { InlineActions } from './inline_actions'; - -let session: UISession; - -describe('Background Search Session management actions', () => { - beforeEach(() => { - session = { - name: 'cool search', - id: 'wtywp9u2802hahgp-gluk', - url: '/app/great-app-url/#43', - appId: 'canvas', - status: STATUS.IN_PROGRESS, - created: '2020-12-02T00:19:32Z', - expires: '2020-12-07T00:19:32Z', - isViewable: true, - }; - }); - - describe('Inline actions', () => { - test('isViewable = true', () => { - const actions = mount( - - - - ); - - expect(actions.find(`[data-test-subj="session-mgmt-view-action"]`).exists()).toBe(true); - expect(actions.find('a').prop('href')).toBe('/app/kibana/coolapp'); - }); - - test('isViewable = false', () => { - session.isViewable = false; - const actions = mount( - - - - ); - - expect(actions.find(`[data-test-subj="session-mgmt-view-action"]`).exists()).toBe(false); - }); - - test('error handling', () => { - (session as any).created = null; - (session as any).expires = null; - (session as any).status = null; - - const actions = mount( - - - - ); - - // no unhandled errors - expect(actions.find(`[data-test-subj="session-mgmt-view-action"]`).exists()).toBe(true); - }); - }); -}); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx index 48b26be369dc8..662d36c2536d2 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { InlineActions } from './inline_actions'; export { PopoverActionsMenu } from './popover_actions'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx deleted file mode 100644 index eff47a07dbc13..0000000000000 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/inline_actions.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; -import { TableText } from '../'; -import { UISession } from '../../../../../common/search/sessions_mgmt'; - -interface InlineActionProps { - url: string; - session: UISession; -} - -export const InlineActions = ({ url, session }: InlineActionProps) => { - if (!session.isViewable) { - return null; - } - // only the View action is required - return ( - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index 5be2059d65f2d..aa7c3b27d770e 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -20,7 +20,7 @@ import { SessionsMgmtConfigSchema } from '../'; import { ActionComplete, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; import { dateString } from '../../../../common/search/sessions_mgmt/date_string'; import { TableText } from '../components'; -import { InlineActions, PopoverActionsMenu } from '../components/actions'; +import { PopoverActionsMenu } from '../components/actions'; import { StatusIndicator } from '../components/status'; import { SearchSessionsMgmtAPI } from './api'; import { getExpirationStatus } from './get_expiration_status'; @@ -181,10 +181,7 @@ export const getColumns = ( render: (actions: UISession['actions'], session) => { if (session.isViewable || (actions && actions.length)) { return ( - - - - + Date: Wed, 6 Jan 2021 23:31:00 -0700 Subject: [PATCH 25/52] in-progress status tooltip shows expire date --- .../sessions_mgmt/components/status.tsx | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx index 3f4a2b5640152..268c231f6d7be 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -54,10 +54,18 @@ const getStatusAttributes = ({ session, timezone, }: StatusIndicatorProps): StatusAttributes | null => { + let expireDate: string; + if (session.expires) { + expireDate = dateString(session.expires!, timezone); + } else { + expireDate = i18n.translate('xpack.data.mgmt.searchSessions.status.expireDateUnknown', { + defaultMessage: 'unknown', + }); + } + switch (session.status) { case STATUS.IN_PROGRESS: try { - const createdDate = dateString(session.created, timezone); return { textColor: 'default', icon: , @@ -65,8 +73,8 @@ const getStatusAttributes = ({ toolTipContent: i18n.translate( 'xpack.data.mgmt.searchSessions.status.message.createdOn', { - defaultMessage: 'Started on {createdDate}', - values: { createdDate }, + defaultMessage: 'Expires on {expireDate}', + values: { expireDate }, } ), }; @@ -78,12 +86,11 @@ const getStatusAttributes = ({ case STATUS.EXPIRED: try { - const expiredOnDate = dateString(session.expires!, timezone); const toolTipContent = i18n.translate( 'xpack.data.mgmt.searchSessions.status.message.expiredOn', { - defaultMessage: 'Expired on {expiredOnDate}', - values: { expiredOnDate }, + defaultMessage: 'Expired on {expireDate}', + values: { expireDate }, } ); @@ -120,10 +127,9 @@ const getStatusAttributes = ({ case STATUS.COMPLETE: try { - const expiresOnDate = dateString(session.expires!, timezone); const toolTipContent = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresOn', { - defaultMessage: 'Expires on {expiresOnDate}', - values: { expiresOnDate }, + defaultMessage: 'Expires on {expireDate}', + values: { expireDate }, }); return { From 88ee0341af02fc8104234dac714ed0bd710fc2ef Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 6 Jan 2021 23:40:54 -0700 Subject: [PATCH 26/52] move date_string to public --- .../public/search/sessions_mgmt/components/status.tsx | 2 +- .../search/sessions_mgmt/lib}/date_string.ts | 2 +- .../public/search/sessions_mgmt/lib/get_columns.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename x-pack/plugins/data_enhanced/{common/search/sessions_mgmt => public/search/sessions_mgmt/lib}/date_string.ts (89%) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx index 268c231f6d7be..ab42fd03d0b90 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -8,7 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLoadingSpinner, EuiToolTip } fro import { i18n } from '@kbn/i18n'; import React, { ReactElement } from 'react'; import { STATUS, UISession } from '../../../../common/search/sessions_mgmt'; -import { dateString } from '../../../../common/search/sessions_mgmt/date_string'; +import { dateString } from '../lib/date_string'; import { StatusDef as StatusAttributes, TableText } from './'; // Shared helper function diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/date_string.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts similarity index 89% rename from x-pack/plugins/data_enhanced/common/search/sessions_mgmt/date_string.ts rename to x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts index b3bbd077faf65..8339c1d116d7e 100644 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/date_string.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts @@ -5,7 +5,7 @@ */ import moment from 'moment'; -import { DATE_STRING_FORMAT } from './constants'; +import { DATE_STRING_FORMAT } from '../../../../common/search/sessions_mgmt'; export const dateString = (inputString: string, tz: string): string => { if (inputString == null) { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index aa7c3b27d770e..130308ddaed9b 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -18,10 +18,10 @@ import { capitalize } from 'lodash'; import React from 'react'; import { SessionsMgmtConfigSchema } from '../'; import { ActionComplete, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; -import { dateString } from '../../../../common/search/sessions_mgmt/date_string'; import { TableText } from '../components'; import { PopoverActionsMenu } from '../components/actions'; import { StatusIndicator } from '../components/status'; +import { dateString } from '../lib/date_string'; import { SearchSessionsMgmtAPI } from './api'; import { getExpirationStatus } from './get_expiration_status'; From 660b46719b6beae2c4084be692f0fe9cdb8f9c61 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Wed, 6 Jan 2021 23:57:24 -0700 Subject: [PATCH 27/52] fix tests --- .../search/sessions_mgmt/components/status.test.tsx | 8 ++++---- .../search/sessions_mgmt/components/table/table.test.tsx | 6 +++--- .../management/background_sessions/sessions_management.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx index cccbaecf666b1..de518ae8df321 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx @@ -122,10 +122,10 @@ describe('Background Search Session management status labels', () => { ); // no unhandled errors - const label = statusIndicator - .find(`[data-test-subj="session-mgmt-view-status-label-complete"]`) - .first(); - expect(label.exists()).toBe(false); + const tooltip = statusIndicator.find('EuiToolTip'); + expect((tooltip.first().props() as EuiToolTipProps).content).toMatchInlineSnapshot( + `"Expires on unknown"` + ); }); }); }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index 42a67f14ea441..7bebbdcaf4549 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -78,10 +78,10 @@ describe('Background Search Session Management Table', () => { test('table header cells', () => { expect(table.find('thead th').map((node) => node.text())).toMatchInlineSnapshot(` Array [ - "TypeClick to sort in ascending order", + "AppClick to sort in ascending order", "NameClick to sort in ascending order", "StatusClick to sort in ascending order", - "CreatedClick to sort in ascending order", + "CreatedClick to unsort", "ExpirationClick to sort in ascending order", ] `); @@ -96,7 +96,7 @@ describe('Background Search Session Management Table', () => { "Created2 Dec, 2020, 00:19:32", "Expiration--", "", - "View", + "", ] `); }); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts index 95eb2f85b8cc7..b467fd1697172 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts @@ -68,7 +68,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expectSnapshot( await testSubjects.find('backgroundSessionsMgmtTable').then((n) => n.getVisibleText()) ).toMatchInline(` - "Type + "App Name Status Created From 25e13fc66aafb7c3d67c658c5f33717445eb407c Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Thu, 7 Jan 2021 08:53:19 -0700 Subject: [PATCH 28/52] Adds management to tsconfig refs --- src/dev/run_find_plugins_without_ts_refs.ts | 3 ++- src/plugins/management/tsconfig.json | 22 +++++++++++++++++++++ test/tsconfig.json | 1 + tsconfig.json | 2 ++ tsconfig.refs.json | 1 + x-pack/test/tsconfig.json | 1 + x-pack/tsconfig.json | 1 + 7 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/plugins/management/tsconfig.json diff --git a/src/dev/run_find_plugins_without_ts_refs.ts b/src/dev/run_find_plugins_without_ts_refs.ts index ad63884671e24..be8778d78c56c 100644 --- a/src/dev/run_find_plugins_without_ts_refs.ts +++ b/src/dev/run_find_plugins_without_ts_refs.ts @@ -20,6 +20,7 @@ import Path from 'path'; import Fs from 'fs'; import { get } from 'lodash'; +import JSON5 from 'json5'; import { run } from '@kbn/dev-utils'; import { getPluginDeps, findPlugins } from './plugin_discovery'; @@ -88,7 +89,7 @@ function isMigratedToTsProjectRefs(dir: string): boolean { try { const path = Path.join(dir, 'tsconfig.json'); const content = Fs.readFileSync(path, { encoding: 'utf8' }); - return get(JSON.parse(content), 'compilerOptions.composite', false); + return get(JSON5.parse(content), 'compilerOptions.composite', false); } catch (e) { return false; } diff --git a/src/plugins/management/tsconfig.json b/src/plugins/management/tsconfig.json new file mode 100644 index 0000000000000..ba3661666631a --- /dev/null +++ b/src/plugins/management/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + "../../../typings/**/*" + ], + "references": [ + { "path": "../../core/tsconfig.json" }, + { "path": "../home/tsconfig.json"}, + { "path": "../kibana_react/tsconfig.json"}, + { "path": "../kibana_utils/tsconfig.json"} + ] +} diff --git a/test/tsconfig.json b/test/tsconfig.json index 5a0d2670a843c..f9008505ed66e 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -8,6 +8,7 @@ "exclude": ["plugin_functional/plugins/**/*", "interpreter_functional/plugins/**/*"], "references": [ { "path": "../src/core/tsconfig.json" }, + { "path": "../src/plugins/management/tsconfig.json" }, { "path": "../src/plugins/bfetch/tsconfig.json" }, { "path": "../src/plugins/embeddable/tsconfig.json" }, { "path": "../src/plugins/expressions/tsconfig.json" }, diff --git a/tsconfig.json b/tsconfig.json index 75e1b097c734f..e309d69cc610b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "exclude": [ "src/**/__fixtures__/**/*", "src/core/**/*", + "src/plugins/management/**/*", "src/plugins/bfetch/**/*", "src/plugins/data/**/*", "src/plugins/dev_tools/**/*", @@ -37,6 +38,7 @@ ], "references": [ { "path": "./src/core/tsconfig.json" }, + { "path": "./src/plugins/management/tsconfig.json"}, { "path": "./src/plugins/bfetch/tsconfig.json" }, { "path": "./src/plugins/data/tsconfig.json" }, { "path": "./src/plugins/dev_tools/tsconfig.json" }, diff --git a/tsconfig.refs.json b/tsconfig.refs.json index 282bf7fb1f011..2295043455434 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -24,5 +24,6 @@ { "path": "./src/plugins/ui_actions/tsconfig.json" }, { "path": "./src/plugins/url_forwarding/tsconfig.json" }, { "path": "./src/plugins/usage_collection/tsconfig.json" }, + { "path": "./src/plugins/management/tsconfig.json" }, ] } diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 5b05628d618a7..2b83ba29eaec0 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -9,6 +9,7 @@ "exclude": ["../typings/jest.d.ts"], "references": [ { "path": "../../src/core/tsconfig.json" }, + { "path": "../../src/plugins/management/tsconfig.json" }, { "path": "../../src/plugins/bfetch/tsconfig.json" }, { "path": "../../src/plugins/data/tsconfig.json" }, { "path": "../../src/plugins/embeddable/tsconfig.json" }, diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index f6911d1203104..1aa5fe9d11433 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -20,6 +20,7 @@ }, "references": [ { "path": "../src/core/tsconfig.json" }, + { "path": "../src/plugins/management/tsconfig.json" }, { "path": "../src/plugins/bfetch/tsconfig.json" }, { "path": "../src/plugins/data/tsconfig.json" }, { "path": "../src/plugins/dev_tools/tsconfig.json" }, From 56044ba9b2a3e10d7715d6080c26bc995515626b Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Thu, 7 Jan 2021 09:03:14 -0700 Subject: [PATCH 29/52] removes preemptive script fix --- src/dev/run_find_plugins_without_ts_refs.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dev/run_find_plugins_without_ts_refs.ts b/src/dev/run_find_plugins_without_ts_refs.ts index be8778d78c56c..ad63884671e24 100644 --- a/src/dev/run_find_plugins_without_ts_refs.ts +++ b/src/dev/run_find_plugins_without_ts_refs.ts @@ -20,7 +20,6 @@ import Path from 'path'; import Fs from 'fs'; import { get } from 'lodash'; -import JSON5 from 'json5'; import { run } from '@kbn/dev-utils'; import { getPluginDeps, findPlugins } from './plugin_discovery'; @@ -89,7 +88,7 @@ function isMigratedToTsProjectRefs(dir: string): boolean { try { const path = Path.join(dir, 'tsconfig.json'); const content = Fs.readFileSync(path, { encoding: 'utf8' }); - return get(JSON5.parse(content), 'compilerOptions.composite', false); + return get(JSON.parse(content), 'compilerOptions.composite', false); } catch (e) { return false; } From 4adb390bc5120a7eb9f13a3c41d2ee2020fd7e78 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 7 Jan 2021 10:01:29 -0700 Subject: [PATCH 30/52] view action was removed --- .../apps/management/background_sessions/sessions_management.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts index b467fd1697172..3ca5fbbb100ab 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts @@ -36,7 +36,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.existOrFail('session-mgmt-view-status-label-in_progress'); await testSubjects.existOrFail('session-mgmt-view-status-tooltip-in_progress'); await testSubjects.existOrFail('session-mgmt-table-col-created'); - await testSubjects.existOrFail('session-mgmt-view-action'); // find there is only one item in the table which is the newly saved session const names = await testSubjects.findAll('session-mgmt-table-col-name'); From 1089170d2a9c276c7f11253a5809ba0760a5751b Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 7 Jan 2021 13:27:14 -0700 Subject: [PATCH 31/52] rename the feature to Search Sessions --- .../common/search/sessions_mgmt/constants.ts | 4 ++-- x-pack/plugins/data_enhanced/public/plugin.ts | 4 ++-- .../search/sessions_mgmt/components/main.test.tsx | 2 +- .../search/sessions_mgmt/components/main.tsx | 2 +- .../components/table/table.stories.tsx | 4 ++-- .../public/search/sessions_mgmt/index.ts | 6 +++--- .../public/search/sessions_mgmt/lib/api.test.ts | 4 ++-- .../public/search/sessions_mgmt/lib/api.ts | 13 ++++++------- .../search/sessions_mgmt/lib/get_columns.test.tsx | 2 +- .../search_session_indicator.tsx | 2 +- .../data.json.gz | Bin .../mappings.json | 0 .../config.ts | 2 +- .../services/send_to_background.ts | 4 ++-- .../apps/dashboard/async_search/async_search.ts | 2 +- .../index.ts | 0 .../sessions_management.ts | 12 ++++++------ 17 files changed, 31 insertions(+), 32 deletions(-) rename x-pack/test/functional/es_archives/data/{bckgnd_sessions => search_sessions}/data.json.gz (100%) rename x-pack/test/functional/es_archives/data/{bckgnd_sessions => search_sessions}/mappings.json (100%) rename x-pack/test/send_search_to_background_integration/tests/apps/management/{background_sessions => search_sessions}/index.ts (100%) rename x-pack/test/send_search_to_background_integration/tests/apps/management/{background_sessions => search_sessions}/sessions_management.ts (94%) diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts index 3bebb984e5c65..53d915ed8c70b 100644 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BackgroundSessionStatus } from '../'; +import { SearchSessionStatus } from '../'; export enum ACTION { EXTEND = 'extend', @@ -14,4 +14,4 @@ export enum ACTION { export const DATE_STRING_FORMAT = 'D MMM, YYYY, HH:mm:ss'; -export { BackgroundSessionStatus as STATUS }; +export { SearchSessionStatus as STATUS }; diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index c10db4ef6f25b..9dde6ba521e62 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -14,7 +14,7 @@ import { SharePluginStart } from '../../../../src/plugins/share/public'; import { setAutocompleteService } from './services'; import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete'; import { EnhancedSearchInterceptor } from './search/search_interceptor'; -import { registerBackgroundSessionsMgmt } from './search/sessions_mgmt'; +import { registerSearchSessionsMgmt } from './search/sessions_mgmt'; import { toMountPoint } from '../../../../src/plugins/kibana_react/public'; import { createConnectedSearchSessionIndicator } from './search'; import { ConfigSchema } from '../config'; @@ -66,7 +66,7 @@ export class DataEnhancedPlugin const { search: searchConfig } = this.initializerContext.config.get(); if (searchConfig.sendToBackground.enabled) { const { sessionsManagement: sessionsMgmtConfig } = searchConfig.sendToBackground; - registerBackgroundSessionsMgmt(core, sessionsMgmtConfig, { management }); + registerSearchSessionsMgmt(core, sessionsMgmtConfig, { management }); } } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx index 4cdeae2356cf1..de80b849631b2 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx @@ -86,7 +86,7 @@ describe('Background Search Session Management Main', () => { }); test('page title', () => { - expect(main.find('h1').text()).toBe('Background Sessions'); + expect(main.find('h1').text()).toBe('Search Sessions'); }); test('documentation link', () => { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx index c29ea3879c7fe..bfcc5be035269 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx @@ -43,7 +43,7 @@ export function SearchSessionsMgmtMain({ documentation, ...tableProps }: Props)

diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx index e87f66aa27a88..c185ff335bb38 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx @@ -18,11 +18,11 @@ import { SearchSessionsMgmtAPI } from '../../lib/api'; import { SearchSessionsMgmtTable } from './table'; export default { - title: 'BackgroundSessionsMgmt/Table', + title: 'SearchSessionsMgmt/Table', component: SearchSessionsMgmtTable, }; -storiesOf('components/BackgroundSessionsMgmt/Table', module) +storiesOf('components/SearchSessionsMgmt/Table', module) .add('no items', () => { const sessionsClient = new SessionsClient({ http: ({} as unknown) as HttpSetup }); const urls = ({ diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts index b954df478dba2..e8dbe05f02bee 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts @@ -36,16 +36,16 @@ export interface AppDependencies { } export const APP = { - id: 'background_sessions', + id: 'search_sessions', getI18nName: (): string => i18n.translate('xpack.data.mgmt.searchSessions.appTitle', { - defaultMessage: 'Background Sessions', + defaultMessage: 'Search Sessions', }), }; export type SessionsMgmtConfigSchema = ConfigSchema['search']['sendToBackground']['sessionsManagement']; -export function registerBackgroundSessionsMgmt( +export function registerSearchSessionsMgmt( coreSetup: CoreSetup, config: SessionsMgmtConfigSchema, services: IManagementSectionsPluginsSetup diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts index af5139c16d46f..e5f4c73dc3886 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts @@ -24,7 +24,7 @@ let mockConfig: SessionsMgmtConfigSchema; let sessionsClient: SessionsClient; let findSessions: sinon.SinonStub; -describe('Background Sessions Management API', () => { +describe('Search Sessions Management API', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockConfig = { @@ -186,7 +186,7 @@ describe('Background Sessions Management API', () => { await api.fetchTableData(); expect(mockCoreSetup.notifications.toasts.addDanger).toHaveBeenCalledWith( - 'Fetching the Background Session info timed out after 1 seconds' + 'Fetching the Search Session info timed out after 1 seconds' ); }); }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index d61e6460b9d58..02b951c806f71 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -12,21 +12,21 @@ import { mapTo, tap } from 'rxjs/operators'; import type { SharePluginStart } from 'src/plugins/share/public'; import { SessionsMgmtConfigSchema } from '../'; import type { ISessionsClient } from '../../../../../../../src/plugins/data/public'; -import type { BackgroundSessionSavedObjectAttributes } from '../../../../common'; +import type { SearchSessionSavedObjectAttributes } from '../../../../common'; import { ACTION, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; type UrlGeneratorsStart = SharePluginStart['urlGenerators']; -interface BackgroundSessionSavedObject { +interface SearchSessionSavedObject { id: string; - attributes: BackgroundSessionSavedObjectAttributes; + attributes: SearchSessionSavedObjectAttributes; } // Helper: factory for a function to map server objects to UI objects const mapToUISession = ( urls: UrlGeneratorsStart, { expiresSoonWarning }: SessionsMgmtConfigSchema -) => async (savedObject: BackgroundSessionSavedObject): Promise => { +) => async (savedObject: SearchSessionSavedObject): Promise => { // Actions: always allow delete const actions = [ACTION.DELETE]; @@ -95,8 +95,7 @@ export class SearchSessionsMgmtAPI { tap(() => { this.notifications.toasts.addDanger( i18n.translate('xpack.data.mgmt.searchSessions.api.fetchTimeout', { - defaultMessage: - 'Fetching the Background Session info timed out after {timeout} seconds', + defaultMessage: 'Fetching the Search Session info timed out after {timeout} seconds', values: { timeout: refreshTimeout.asSeconds() }, }) ); @@ -108,7 +107,7 @@ export class SearchSessionsMgmtAPI { try { const result = await Rx.race(fetch$, timeout$).toPromise(); if (result && result.saved_objects) { - const savedObjects = result.saved_objects as BackgroundSessionSavedObject[]; + const savedObjects = result.saved_objects as SearchSessionSavedObject[]; return await Promise.all(savedObjects.map(mapToUISession(this.urls, this.config))); } } catch (err) { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index ba97d010cf720..639c9edf1c572 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -27,7 +27,7 @@ let mockSession: UISession; let tz = 'UTC'; -describe('Background Sessions Management table column factory', () => { +describe('Search Sessions Management table column factory', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockConfig = { diff --git a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx index 458f2e951d875..361688581b4f1 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx @@ -66,7 +66,7 @@ const ContinueInBackgroundButton = ({ ); const ViewAllSearchSessionsButton = ({ - viewSearchSessionsLink = 'management/kibana/background_sessions', + viewSearchSessionsLink = 'management/kibana/search_sessions', buttonProps = {}, }: ActionButtonProps) => ( Background Sessions` and use the UI to delete any created tests. + * Alternatively, a test can navigate to `Managment > Search Sessions` and use the UI to delete any created tests. */ - public async deleteAllBackgroundSessions() { + public async deleteAllSearchSessions() { log.debug('Deleting created background sessions'); // ignores 409 errs and keeps retrying await retry.tryForTime(5000, async () => { diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts index 10b1160d27249..0ad11e578c2a8 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async function () { - await sendToBackground.deleteAllBackgroundSessions(); + await sendToBackground.deleteAllSearchSessions(); }); it('not delayed should load', async () => { diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/index.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/index.ts similarity index 100% rename from x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/index.ts rename to x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/index.ts diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts similarity index 94% rename from x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts rename to x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts index 3ca5fbbb100ab..3862cd40158b8 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/background_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts @@ -20,7 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { - await sendToBackground.deleteAllBackgroundSessions(); + await sendToBackground.deleteAllSearchSessions(); }); it('Saves a session and verifies it in the Management app', async () => { @@ -31,7 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await sendToBackground.expectState('backgroundCompleted'); await sendToBackground.openPopover(); - await sendToBackground.viewBackgroundSessions(); + await sendToBackground.viewSearchSessions(); await testSubjects.existOrFail('session-mgmt-view-status-label-in_progress'); await testSubjects.existOrFail('session-mgmt-view-status-tooltip-in_progress'); @@ -56,11 +56,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Archived sessions', () => { before(async () => { - await PageObjects.common.navigateToApp('management/kibana/background_sessions'); + await PageObjects.common.navigateToApp('management/kibana/search_sessions'); }); after(async () => { - await sendToBackground.deleteAllBackgroundSessions(); + await sendToBackground.deleteAllSearchSessions(); }); it('shows no items found', async () => { @@ -77,7 +77,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('autorefreshes and shows items on the server', async () => { - await esArchiver.load('data/bckgnd_sessions'); + await esArchiver.load('data/search_sessions'); const nameColumnText = await testSubjects .findAll('session-mgmt-table-col-name') @@ -142,7 +142,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ] `); - await esArchiver.unload('data/bckgnd_sessions'); + await esArchiver.unload('data/search_sessions'); }); }); }); From e4cd13edb8dd84c0e88e2310aeb83a66e6514786 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Thu, 7 Jan 2021 15:16:25 -0700 Subject: [PATCH 32/52] Update x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx Co-authored-by: Liza Katz --- .../public/search/sessions_mgmt/components/status.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx index ab42fd03d0b90..4a1623ff9e0a2 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -102,7 +102,7 @@ const getStatusAttributes = ({ } catch (err) { // eslint-disable-next-line no-console console.error(err); - throw new Error(`Could not instantiate a expiration Date object from: ${session.expires}`); + throw new Error(`Could not instantiate an expiration Date object from: ${session.expires}`); } case STATUS.CANCELLED: From f6bd874c160f82f724fe054235517f06a3362936 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Thu, 7 Jan 2021 15:18:23 -0700 Subject: [PATCH 33/52] Update x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx Co-authored-by: Liza Katz --- .../public/search/sessions_mgmt/components/status.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx index 4a1623ff9e0a2..4ee3ff189b266 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -142,7 +142,7 @@ const getStatusAttributes = ({ // eslint-disable-next-line no-console console.error(err); throw new Error( - `Could not instantiate a expiration Date object for completed session from: ${session.expires}` + `Could not instantiate an expiration Date object for completed session from: ${session.expires}` ); } From c46548eceb3c9e5397d6e7c6f90081616c7eef9c Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Thu, 7 Jan 2021 15:22:32 -0700 Subject: [PATCH 34/52] add TODO --- .../public/search/sessions_mgmt/lib/documentation.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts index 6c71fea60383d..eac3245dfe2bc 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/documentation.ts @@ -12,6 +12,7 @@ export class AsyncSearchIntroDocumentation { constructor(docs: DocLinksStart) { const { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } = docs; const docsBase = `${ELASTIC_WEBSITE_URL}guide/en`; + // TODO: There should be Kibana documentation link about Search Sessions in Kibana this.docsBasePath = `${docsBase}/elasticsearch/reference/${DOC_LINK_VERSION}`; } From 7c2ac8be448d674407f1044f89f382e368dcdb30 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 7 Jan 2021 15:58:29 -0700 Subject: [PATCH 35/52] use RedirectAppLinks --- .../sessions_mgmt/application/index.tsx | 1 + .../search/sessions_mgmt/components/main.tsx | 3 +- .../components/table/table.stories.tsx | 28 ++++++++++-- .../components/table/table.test.tsx | 16 +++++-- .../sessions_mgmt/components/table/table.tsx | 13 +++++- .../public/search/sessions_mgmt/index.ts | 3 +- .../sessions_mgmt/lib/get_columns.test.tsx | 45 ++++++++++++------- .../search/sessions_mgmt/lib/get_columns.tsx | 11 +++-- 8 files changed, 92 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx index 3208c449b2070..ff62f3663166a 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx @@ -57,6 +57,7 @@ export class SearchSessionsMgmtApp { plugins: pluginsSetup, config: this.config, documentation, + core: coreStart, api, http, i18n, diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx index bfcc5be035269..892bea81c5b57 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx @@ -15,7 +15,7 @@ import { EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import type { HttpStart } from 'kibana/public'; +import type { CoreStart, HttpStart } from 'kibana/public'; import React from 'react'; import type { SessionsMgmtConfigSchema } from '../'; import type { UISession } from '../../../../common/search/sessions_mgmt'; @@ -26,6 +26,7 @@ import { SearchSessionsMgmtTable } from './table'; interface Props { documentation: AsyncSearchIntroDocumentation; + core: CoreStart; api: SearchSessionsMgmtAPI; http: HttpStart; initialTable: UISession[] | null; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx index c185ff335bb38..e23887f2d3f54 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx @@ -4,10 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { IntlProvider } from 'react-intl'; import { storiesOf } from '@storybook/react'; -import type { HttpSetup, NotificationsStart } from 'kibana/public'; +import type { CoreStart, HttpSetup, NotificationsStart } from 'kibana/public'; import moment from 'moment'; import * as React from 'react'; +import * as Rx from 'rxjs'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import type { UrlGeneratorsStart } from 'src/plugins/share/public/url_generators'; import { SessionsMgmtConfigSchema } from '../..'; @@ -22,6 +24,16 @@ export default { component: SearchSessionsMgmtTable, }; +const mockCoreStart = ({ + application: { + currentAppId$: Rx.of('foo'), + navigateToUrl: async (url: string) => { + // eslint-disable-next-line no-console + console.log(`navigate to URL: ${url}`); + }, + }, +} as unknown) as CoreStart; + storiesOf('components/SearchSessionsMgmt/Table', module) .add('no items', () => { const sessionsClient = new SessionsClient({ http: ({} as unknown) as HttpSetup }); @@ -44,13 +56,18 @@ storiesOf('components/SearchSessionsMgmt/Table', module) }; const props = { + core: mockCoreStart, initialTable: [], api: new SearchSessionsMgmtAPI(sessionsClient, urls, notifications, config), timezone: 'UTC', config, }; - return ; + return ( + + + + ); }) .add('multiple pages', () => { const sessionsClient = new SessionsClient({ http: ({} as unknown) as HttpSetup }); @@ -134,11 +151,16 @@ storiesOf('components/SearchSessionsMgmt/Table', module) }); const props = { + core: mockCoreStart, api: new SearchSessionsMgmtAPI(sessionsClient, urls, notifications, config), timezone: 'UTC', initialTable: mockTable, config, }; - return ; + return ( + + + + ); }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index 7bebbdcaf4549..457fa675260f2 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -7,7 +7,7 @@ import { MockedKeys } from '@kbn/utility-types/jest'; import { waitFor } from '@testing-library/react'; import { mount, ReactWrapper } from 'enzyme'; -import { CoreSetup } from 'kibana/public'; +import { CoreSetup, CoreStart } from 'kibana/public'; import moment from 'moment'; import React from 'react'; import sinon from 'sinon'; @@ -22,13 +22,14 @@ import { LocaleWrapper, mockUrls } from '../../__mocks__'; import { SearchSessionsMgmtTable } from './table'; let mockCoreSetup: MockedKeys; +let mockCoreStart: CoreStart; let mockConfig: SessionsMgmtConfigSchema; let sessionsClient: SessionsClient; let initialTable: UISession[]; let api: SearchSessionsMgmtAPI; describe('Background Search Session Management Table', () => { - beforeEach(() => { + beforeEach(async () => { mockCoreSetup = coreMock.createSetup(); mockConfig = { expiresSoonWarning: moment.duration(1, 'days'), @@ -57,6 +58,8 @@ describe('Background Search Session Management Table', () => { mockCoreSetup.notifications, mockConfig ); + + [mockCoreStart] = await mockCoreSetup.getStartServices(); }); describe('renders', () => { @@ -66,6 +69,7 @@ describe('Background Search Session Management Table', () => { table = mount( { const table = mount( - + ); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index 53aea99936b7a..2da8d0b2680f5 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -6,6 +6,7 @@ import { EuiButton, EuiInMemoryTable, EuiSearchBarProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { CoreStart } from 'kibana/public'; import moment from 'moment'; import React, { useEffect, useState } from 'react'; import * as Rx from 'rxjs'; @@ -21,13 +22,21 @@ import { getStatusFilter } from './status_filter'; const TABLE_ID = 'backgroundSessionsMgmtTable'; interface Props { + core: CoreStart; api: SearchSessionsMgmtAPI; initialTable: UISession[] | null; timezone: string; config: SessionsMgmtConfigSchema; } -export function SearchSessionsMgmtTable({ api, timezone, initialTable, config, ...props }: Props) { +export function SearchSessionsMgmtTable({ + core, + api, + timezone, + initialTable, + config, + ...props +}: Props) { const [tableData, setTableData] = useState(initialTable ? initialTable : []); const [isLoading, setIsLoading] = useState(false); const [pagination, setPagination] = useState({ pageIndex: 0 }); @@ -98,7 +107,7 @@ export function SearchSessionsMgmtTable({ api, timezone, initialTable, config, . {...props} id={TABLE_ID} data-test-subj={TABLE_ID} - columns={getColumns(api, config, timezone, handleActionCompleted)} + columns={getColumns(core, api, config, timezone, handleActionCompleted)} items={tableData} pagination={pagination} search={search} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts index e8dbe05f02bee..645a18ccdf23b 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import type { HttpStart, I18nStart, IUiSettingsClient } from 'kibana/public'; +import type { CoreStart, HttpStart, I18nStart, IUiSettingsClient } from 'kibana/public'; import { CoreSetup } from 'kibana/public'; import type { DataPublicPluginStart } from 'src/plugins/data/public'; import type { ManagementSetup } from 'src/plugins/management/public'; @@ -29,6 +29,7 @@ export interface AppDependencies { share: SharePluginStart; uiSettings: IUiSettingsClient; documentation: AsyncSearchIntroDocumentation; + core: CoreStart; // for RedirectAppLinks api: SearchSessionsMgmtAPI; http: HttpStart; i18n: I18nStart; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index 639c9edf1c572..19b40c1d3c1aa 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -7,7 +7,7 @@ import { EuiTableFieldDataColumnType } from '@elastic/eui'; import { MockedKeys } from '@kbn/utility-types/jest'; import { mount } from 'enzyme'; -import { CoreSetup } from 'kibana/public'; +import { CoreSetup, CoreStart } from 'kibana/public'; import moment from 'moment'; import { ReactElement } from 'react'; import { coreMock } from 'src/core/public/mocks'; @@ -19,6 +19,7 @@ import { SearchSessionsMgmtAPI } from './api'; import { getColumns } from './get_columns'; let mockCoreSetup: MockedKeys; +let mockCoreStart: CoreStart; let mockConfig: SessionsMgmtConfigSchema; let api: SearchSessionsMgmtAPI; let sessionsClient: SessionsClient; @@ -28,7 +29,7 @@ let mockSession: UISession; let tz = 'UTC'; describe('Search Sessions Management table column factory', () => { - beforeEach(() => { + beforeEach(async () => { mockCoreSetup = coreMock.createSetup(); mockConfig = { expiresSoonWarning: moment.duration(1, 'days'), @@ -61,10 +62,12 @@ describe('Search Sessions Management table column factory', () => { expires: '2020-12-07T00:19:32Z', isViewable: true, }; + + [mockCoreStart] = await mockCoreSetup.getStartServices(); }); test('returns columns', () => { - const columns = getColumns(api, mockConfig, tz, handleAction); + const columns = getColumns(mockCoreStart, api, mockConfig, tz, handleAction); expect(columns).toMatchInlineSnapshot(` Array [ Object { @@ -116,7 +119,7 @@ describe('Search Sessions Management table column factory', () => { describe('name', () => { test('rendering', () => { - const [, nameColumn] = getColumns(api, mockConfig, tz, handleAction) as Array< + const [, nameColumn] = getColumns(mockCoreStart, api, mockConfig, tz, handleAction) as Array< EuiTableFieldDataColumnType >; @@ -129,7 +132,7 @@ describe('Search Sessions Management table column factory', () => { // Status column describe('status', () => { test('render in_progress', () => { - const [, , status] = getColumns(api, mockConfig, tz, handleAction) as Array< + const [, , status] = getColumns(mockCoreStart, api, mockConfig, tz, handleAction) as Array< EuiTableFieldDataColumnType >; @@ -142,7 +145,7 @@ describe('Search Sessions Management table column factory', () => { }); test('error handling', () => { - const [, , status] = getColumns(api, mockConfig, tz, handleAction) as Array< + const [, , status] = getColumns(mockCoreStart, api, mockConfig, tz, handleAction) as Array< EuiTableFieldDataColumnType >; @@ -160,9 +163,13 @@ describe('Search Sessions Management table column factory', () => { test('render using Browser timezone', () => { tz = 'Browser'; - const [, , , createdDateCol] = getColumns(api, mockConfig, tz, handleAction) as Array< - EuiTableFieldDataColumnType - >; + const [, , , createdDateCol] = getColumns( + mockCoreStart, + api, + mockConfig, + tz, + handleAction + ) as Array>; const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement); @@ -172,9 +179,13 @@ describe('Search Sessions Management table column factory', () => { test('render using AK timezone', () => { tz = 'US/Alaska'; - const [, , , createdDateCol] = getColumns(api, mockConfig, tz, handleAction) as Array< - EuiTableFieldDataColumnType - >; + const [, , , createdDateCol] = getColumns( + mockCoreStart, + api, + mockConfig, + tz, + handleAction + ) as Array>; const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement); @@ -182,9 +193,13 @@ describe('Search Sessions Management table column factory', () => { }); test('error handling', () => { - const [, , , createdDateCol] = getColumns(api, mockConfig, tz, handleAction) as Array< - EuiTableFieldDataColumnType - >; + const [, , , createdDateCol] = getColumns( + mockCoreStart, + api, + mockConfig, + tz, + handleAction + ) as Array>; mockSession.created = 'INVALID'; const date = mount(createdDateCol.render!(mockSession.created, mockSession) as ReactElement); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index 130308ddaed9b..023d5856fc7cd 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -14,8 +14,10 @@ import { EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { CoreStart } from 'kibana/public'; import { capitalize } from 'lodash'; import React from 'react'; +import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; import { SessionsMgmtConfigSchema } from '../'; import { ActionComplete, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; import { TableText } from '../components'; @@ -34,6 +36,7 @@ const appToIcon = (app: string) => { }; export const getColumns = ( + core: CoreStart, api: SearchSessionsMgmtAPI, config: SessionsMgmtConfigSchema, timezone: string, @@ -73,9 +76,11 @@ export const getColumns = ( render: (name: UISession['name'], { isViewable, url }) => { if (isViewable) { return ( - - {name} - + + + {name} + + ); } From f59dc041029c0d6d3279be7ecbd32714986fdab8 Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 11 Jan 2021 19:57:47 +0200 Subject: [PATCH 36/52] code review and react improvements --- x-pack/plugins/data_enhanced/common/index.ts | 1 + .../common/search/session/types.ts | 4 + .../components/delete_button.tsx | 2 +- .../sessions_mgmt/components/main.test.tsx | 5 +- .../sessions_mgmt/components/status.tsx | 4 +- .../components/table/table.test.tsx | 42 +++++++--- .../sessions_mgmt/components/table/table.tsx | 72 ++++++++++-------- .../public/search/sessions_mgmt/lib/api.ts | 7 +- x-pack/plugins/data_enhanced/tsconfig.json | 2 + .../data/search_sessions/data.json.gz | Bin 1879 -> 1956 bytes .../data/search_sessions/mappings.json | 2 +- .../apps/management/search_sessions/index.ts | 2 +- .../search_sessions/sessions_management.ts | 8 +- 13 files changed, 95 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/data_enhanced/common/index.ts b/x-pack/plugins/data_enhanced/common/index.ts index e3e91ccf967c1..860b938f5bbdc 100644 --- a/x-pack/plugins/data_enhanced/common/index.ts +++ b/x-pack/plugins/data_enhanced/common/index.ts @@ -12,6 +12,7 @@ export { EqlSearchStrategyResponse, IAsyncSearchOptions, pollSearch, + SearchSessionSavedObject, SearchSessionSavedObjectAttributes, SearchSessionFindOptions, SearchSessionStatus, diff --git a/x-pack/plugins/data_enhanced/common/search/session/types.ts b/x-pack/plugins/data_enhanced/common/search/session/types.ts index b41867212d2c0..e49d8d64e4c33 100644 --- a/x-pack/plugins/data_enhanced/common/search/session/types.ts +++ b/x-pack/plugins/data_enhanced/common/search/session/types.ts @@ -24,6 +24,10 @@ export interface SearchSessionSavedObjectAttributes { idMapping: Record; } +export interface SearchSessionSavedObject { + id: string; + attributes: SearchSessionSavedObjectAttributes; +} export interface SearchSessionRequestInfo { id: string; // ID of the async search request strategy: string; // Search strategy used to submit the search request diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/delete_button.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/delete_button.tsx index 0317177ad9279..c42b124c3a396 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/delete_button.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/delete_button.tsx @@ -35,7 +35,7 @@ const DeleteConfirm = ({ defaultMessage: 'Cancel', }); const message = i18n.translate('xpack.data.mgmt.searchSessions.deleteModal.message', { - defaultMessage: `You can't recover deleted sessions`, + defaultMessage: `A deleted session can be recovered by re-running the search.`, }); return ( diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx index de80b849631b2..b8c8d6eaa7b79 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx @@ -6,7 +6,7 @@ import { MockedKeys } from '@kbn/utility-types/jest'; import { mount, ReactWrapper } from 'enzyme'; -import { CoreSetup, DocLinksStart } from 'kibana/public'; +import { CoreSetup, CoreStart, DocLinksStart } from 'kibana/public'; import moment from 'moment'; import React from 'react'; import { coreMock } from 'src/core/public/mocks'; @@ -19,6 +19,7 @@ import { LocaleWrapper, mockUrls } from '../__mocks__'; import { SearchSessionsMgmtMain } from './main'; let mockCoreSetup: MockedKeys; +let mockCoreStart: MockedKeys; let mockConfig: SessionsMgmtConfigSchema; let sessionsClient: SessionsClient; let initialTable: UISession[]; @@ -27,6 +28,7 @@ let api: SearchSessionsMgmtAPI; describe('Background Search Session Management Main', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); + mockCoreStart = coreMock.createStart(); mockConfig = { expiresSoonWarning: moment.duration(1, 'days'), maxSessions: 2000, @@ -74,6 +76,7 @@ describe('Background Search Session Management Main', () => { main = mount( { if (statusDef) { const { toolTipContent } = statusDef; - let icon: ReactElement | string | undefined = statusDef.icon; - let label: ReactElement | string = statusDef.label; + let icon: ReactElement | undefined = statusDef.icon; + let label: ReactElement = statusDef.label; if (icon && toolTipContent) { icon = {icon}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index 457fa675260f2..b6bdb083d58b6 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -5,15 +5,12 @@ */ import { MockedKeys } from '@kbn/utility-types/jest'; -import { waitFor } from '@testing-library/react'; +import { act, waitFor } from '@testing-library/react'; import { mount, ReactWrapper } from 'enzyme'; import { CoreSetup, CoreStart } from 'kibana/public'; import moment from 'moment'; import React from 'react'; -import sinon from 'sinon'; import { coreMock } from 'src/core/public/mocks'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { SavedObjectsFindResponse } from 'src/core/server'; import { SessionsClient } from 'src/plugins/data/public/search'; import { SessionsMgmtConfigSchema } from '../../'; import { STATUS, UISession } from '../../../../../common/search/sessions_mgmt'; @@ -107,10 +104,37 @@ describe('Background Search Session Management Table', () => { }); describe('fetching sessions data', () => { + test('re-fetches data', async () => { + jest.useFakeTimers(); + sessionsClient.find = jest.fn(); + mockConfig = { + ...mockConfig, + refreshInterval: moment.duration(10, 'seconds'), + }; + + mount( + + + + ); + + act(() => { + jest.advanceTimersByTime(20000); + }); + + expect(sessionsClient.find).toBeCalledTimes(2); + + jest.useRealTimers(); + }); + test('refresh button uses the session client', async () => { - const findSessions = sinon - .stub(sessionsClient, 'find') - .callsFake(async () => ({} as SavedObjectsFindResponse)); + sessionsClient.find = jest.fn(); mockConfig = { ...mockConfig, @@ -130,7 +154,7 @@ describe('Background Search Session Management Table', () => { ); - expect(findSessions.called).toBe(false); + expect(sessionsClient.find).not.toBeCalled(); const buttonSelector = `[data-test-subj="session-mgmt-table-btn-refresh"] button`; @@ -139,7 +163,7 @@ describe('Background Search Session Management Table', () => { table.update(); }); - expect(findSessions.called).toBe(true); + expect(sessionsClient.find).toBeCalledTimes(1); }); }); }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index 2da8d0b2680f5..df7b2c3a72f48 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -8,9 +8,9 @@ import { EuiButton, EuiInMemoryTable, EuiSearchBarProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { CoreStart } from 'kibana/public'; import moment from 'moment'; -import React, { useEffect, useState } from 'react'; -import * as Rx from 'rxjs'; -import { catchError, switchMap } from 'rxjs/operators'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import useDebounce from 'react-use/lib/useDebounce'; +import useInterval from 'react-use/lib/useInterval'; import { TableText } from '../'; import { SessionsMgmtConfigSchema } from '../..'; import { ActionComplete, UISession } from '../../../../../common/search/sessions_mgmt'; @@ -19,7 +19,7 @@ import { getColumns } from '../../lib/get_columns'; import { getAppFilter } from './app_filter'; import { getStatusFilter } from './status_filter'; -const TABLE_ID = 'backgroundSessionsMgmtTable'; +const TABLE_ID = 'searchSessionsMgmtTable'; interface Props { core: CoreStart; @@ -39,45 +39,55 @@ export function SearchSessionsMgmtTable({ }: Props) { const [tableData, setTableData] = useState(initialTable ? initialTable : []); const [isLoading, setIsLoading] = useState(false); + const [debouncedIsLoading, setDebouncedIsLoading] = useState(false); const [pagination, setPagination] = useState({ pageIndex: 0 }); + const [refreshInterval, setRefreshInterval] = useState(undefined); + const showLatestResultsHandler = useRef(); + + // Debounce rendering the state of the Refresh button + useDebounce( + () => { + setDebouncedIsLoading(isLoading); + }, + 250, + [isLoading] + ); + + // Convert the config interval to ms + useEffect(() => { + setRefreshInterval(moment.duration(config.refreshInterval).asMilliseconds()); + }, [config.refreshInterval]); // refresh behavior - const doRefresh = async () => { + const doRefresh = useCallback(async () => { setIsLoading(true); - await api.fetchTableData().then((results) => { - if (results) { + try { + const renderResults = (results: UISession[]) => { setTableData(results); + }; + showLatestResultsHandler.current = renderResults; + const results = await api.fetchTableData(); + if (results) { + // Ignore results from any but the latest api call + if (showLatestResultsHandler.current === renderResults) renderResults(results); + } else { + renderResults([]); } - }); + } catch (e) { + setTableData([]); + } setIsLoading(false); - }; + }, [api]); - // configurable auto-refresh - useEffect(() => { - const refreshInterval = moment.duration(config.refreshInterval); - const refreshRx = Rx.interval(refreshInterval.asMilliseconds()) - .pipe( - switchMap(doRefresh), - catchError((err) => { - // eslint-disable-next-line no-console - console.error(err); - return Rx.of(null); - }) - ) - .subscribe(); - - return () => { - refreshRx.unsubscribe(); - }; - }); + useInterval(doRefresh, refreshInterval); // When action such as cancel, delete, extend occurs, use the async return // value to refresh the table - const handleActionCompleted: ActionComplete = (results: UISession[] | null) => { + const handleActionCompleted: ActionComplete = useCallback((results: UISession[] | null) => { if (results) { setTableData(results); } - }; + }, []); // table config: search / filters const search: EuiSearchBarProps = { @@ -89,8 +99,8 @@ export function SearchSessionsMgmtTable({ fill iconType="refresh" onClick={doRefresh} - disabled={isLoading} - isLoading={isLoading} + disabled={debouncedIsLoading} + isLoading={debouncedIsLoading} data-test-subj="session-mgmt-table-btn-refresh" > )XoBaqK zZlb5cW0S}AEXNoT5lf!+Jg?`U=dbr4-`D5+{(Rrx-{^93mpp!)0Z9@3BK(X(!Y^I` z&E|N!3|c71j)?ZMhHD8@t*9J_!wEVeYp7D*Bil)Yt)84uT*GW^y?+%tW0T~j_N zzst5vQ8k>o9AWg>JlB#6&98gttUIN4dY6VVvJGq(`ZJnql9pJL>7vx5_0)vfo3wJ0 zm3VFZV)qQe-qf_Ifzv&slMBw(%amrO9g0HJ)7DeU{dKH=H$&}Ee|VATdgPqf2g3sW zs0HTY>NpDmF@Xo!(IIcgOOmo#HGYpKq#u8Fr?oBcCa~!0ZFGWH=l$d{oHKOA$)?of#1DMyBX+L6o41yzk9C_(?owkc!ja#8) zSFKe`6{9S(9R&&Uz##~kG(F&(EU3kn@X31slr3MbD(^Bs1Y+cNAhKv9R&g@w(YKK& z2bZg=1d+@6OR%NBhiRoxkE;?n?DFb*)91vb9oZ+MaJYG>26g1S!bjMn!!Z-@ai)2) z4YOA!(61N4T?cV+FSwW6`IVYm$_=k9dKZs2nsPr*SH7DDh`r=8G&9G(I9O@RZ8X`= zNU&SSE)wE1zC*)sC+}4Iial`Us$Rxt7lMV$83j(koLub`S*?4`GFSqf-x=t>NSmv?hGnI{*?t4V;vxS0q#YCy8rrK1v z&w*%Wa9G!_FBz}#YCC13($j>jfLA(R731|6%TTAB0c8K0z${7$X`OGTTb9o%Jj4^Y6p*AwLY%5 zwi*A*uW&zL&(q9|uw`Q$)VS3OEa7~$(GT+Z+{q0LALi+-uLpJgfdR%eQ?KA7Z+!U9 zJe11VMOK5UL&9|%2DQld7Y~G;IH@=>dDFu9csT#13E%3lj9%mTr@`SZ0h93&l9tNo z7O*aaYS)rrh5sC}UO7p9Y4LR}k4`dp+O`tdNqb~+Brt?dkJdp+hS%3#d}r=Br=ycfv^N6<3a8F1s>9t#`9 z)~~l{=V9g8s}hSB-@MLWXTKcl z95}&vo*K3o(_ZBuL?ZnMdtyn_cX2#GVo+h;F@Dy4m5}W`kp-IVAGm}Lx@FdTc!&R> zMq;^5RT6LJdWod}(drFz|1Vo_N#SeW$05IZrNJU$wZmT>oseT*jeaT>zHjz#5~cfA zZr)mFE`7G(l|#M%t~e(JXpeq750&^ts?sJvb6Vy?wE)RhxWCAT zm`L&UM30ia4dp!BWh!F8wQiR@ZiFU-xC+8G76T*%cnU0r1A&j|dPY40B(=f`d#8z@ z0fo84vI9}ji6=GL8`wo5t66`l42Bx34x)=}dcwn;gLZRVp%SI(k{E<`9|wjafhgEA zIlK4!iJ{_?1&?^rZVoM$`(SBPP)%z>$`Vn@`zW(;i+HD_O#Nf^K!L%09YU2>5Gcjf zz#wp%ZewC-^Y+6~5P?seM(KfE=>K=I>hqLGQQ*=zXrrTehu_- z0-(@;)Sm7CSCju$lmD+S)BQ(XcJ4p*=%+YmtQ_TzCmKZYCW<@3SO{=o23+-7iWmxW z*(^rv48YsYVr+E{Eg`#_c*KX4kZDfbY;uIee}7S)*MGY8oC=WFuPBK>*RS|wNEKq+ zUu8!Ga~N>tXNh7c%zk1q-pl|V;^JVRUpIw$#EU^Y%Y8uJboN&#vd7gFt@+=FP|z literal 1879 zcmYk(c{Cdc9>8%1gH{YuX~~2taR!=8;NwDr)izH0F(P|KPs~FzA-Om4?_kO?c@25x<6DzWMooO3}R_l$FtBR!~EBG zKC;Yjs~J*)v>l(-=+xT0@-T&IxDY=>w6ypX4R^>Oj?P;cc#j7*{-e10;i~jA2GNB) zrh8|`nixB%iL8K!iRwnEvTbdUMJ=+vpLnp35t>hs}24sgZS z-Wz<7tY{PT@py&F3=y@nsrYrO8Kb-`3p+J}m7S`^mobm_oXkYT_F|-@#(16TV$JtR zWkc*1N8NjHK4d!n(IyriQmBJ&;-KgG8jgV~Wkx{1o~#mOuN&7pLZwUMe5W~F1V6T! z_i;4+bKU1@vfHRfX||`q)b_>;LV1k}+JnYS(g?llgD<@OLuvBKu7~*!mW2y`eG;g6 ztf{t0TQ1dHZdh?YC^`BT9~ZM6>i24Ft~?5dh>aYBesd2&9&0uH9+ba+-}$w1T^LTG z;*tTV>FeCYLAq z1A!iz&Y?SC!cFxb6v0Znv9+n2EeVB`+{5{KfWq5%_b2pC#g8%={EXu?NQ!9wjNT9m zH~Wb@sk*e~Y55$Ewils^7WSYR96e`V$UwC|FMrve6CK$Tr+l&Gsd7Nr(`)}kj%~9{ zBWRDF)W`pRrJ%dwSMOPC-A%o=d@&=3mwstACxJ}q$}!K|E}0Pgvi6D z!we?+i$+kRb3j(BxFy3QrBO(Np05W8^9dGZ94qvBw-XGeX6)HP+LA`y#_;RlotlCA zy@>S4u{gf$;lV8I0cYby>G&Gf6BMtIpoJXl0QBH*p~jZl!JOR~Pn%5cPm$`2no&LB z#(fA$dsk8lnKF`+_DrR9A>S6Sb%6+En^egFDyjq|F=wiPf8oSb^;-6yij@X#_ zSpD7-BNqQSk+5QyC*qJ-s9+;AG3&hFztOeSerN@ns;xDt7 zALUzcP)lC+&#{Xv*->J12&tx(ilhtAPd}|~hWu;FY*k|<%iDLv=&>50?oQXOq~j`+ z(tQQ2dR^z;xH?>?O@!}&emv4pReMZ4KDvHIYqL>=vl=^IK$G)2z%?5q%5IMvGU0nQ z^Z)UYC)PD7b}e0fFXCg7drWv6&`&CjKXNLiS_Q22MLBp3myy%bbuPRVCh1-9QM0Bm zC3wF8GORI+oyqMR-tkNVst zpGlC@Dt_KnM&u^aQF!K@{sP(rI5%Gt`4Zoaz<-z6o?^^4HNK(Jzi5<6wI*NvHV09T?CObxTfimlq5& zFfQM4%(-C8(}`#{+M_6KBQ#;Nc<6-D%meqe{g%yB@CWy?gINy+Xi7n^qcaX9lxURf zE%U42{><@a$p}cw4H->!VRG0hH4AawZoOzKwmj5K(6aqG8+0SYAS)*s1zmz*W-AGPgI>#6V=$ucey zGA?32){47{aslE~IOW9x25{ZM3~q0xbO=y&666->MHzGh9L%T{3}89~c#VVVyEK1B zLA48C=+-%9pV9B=U#KPL42%QhhGql*Mn8~3w0+Xf0v1RyRx&OPYJA33eg+ruh$@_R z9=Wgq0QYgjC7 zSS+<~Yf!Av3)U~@*>)(Apc7LlnQ@fm7I+x-RqxrgLz2OhD4821s2~YyO{eBTo6jiB lw*3p`p40QkgyX-^40?4QwCaqW!rM*-iK*iXG35dRe*%|#nil{7 diff --git a/x-pack/test/functional/es_archives/data/search_sessions/mappings.json b/x-pack/test/functional/es_archives/data/search_sessions/mappings.json index ce06b7be35071..24bbcbea23385 100644 --- a/x-pack/test/functional/es_archives/data/search_sessions/mappings.json +++ b/x-pack/test/functional/es_archives/data/search_sessions/mappings.json @@ -302,7 +302,7 @@ "dynamic": "false", "type": "object" }, - "background-session": { + "search-session": { "properties": { "appId": { "type": "keyword" diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/index.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/index.ts index d58d5499826e8..6a11a15f31567 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/index.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/index.ts @@ -9,7 +9,7 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); - describe('background sessions management', function () { + describe('search sessions management', function () { this.tags('ciGroup3'); before(async () => { diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts index 3862cd40158b8..538c81f9ff699 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts @@ -13,8 +13,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const sendToBackground = getService('sendToBackground'); const esArchiver = getService('esArchiver'); - describe('Background search sessions Management UI', () => { - describe('New sessions', () => { + describe('Search search sessions Management UI', () => { + describe('New search sessions', () => { before(async () => { await PageObjects.common.navigateToApp('dashboard'); }); @@ -54,7 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('Archived sessions', () => { + describe('Archived search sessions', () => { before(async () => { await PageObjects.common.navigateToApp('management/kibana/search_sessions'); }); @@ -65,7 +65,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('shows no items found', async () => { expectSnapshot( - await testSubjects.find('backgroundSessionsMgmtTable').then((n) => n.getVisibleText()) + await testSubjects.find('searchSessionsMgmtTable').then((n) => n.getVisibleText()) ).toMatchInline(` "App Name From ce1062dc5005cc053a6694a8f9e5947f42afe2f5 Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 11 Jan 2021 20:15:54 +0200 Subject: [PATCH 37/52] config --- x-pack/plugins/data_enhanced/public/plugin.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index 9dde6ba521e62..e0a304d0b4074 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -35,6 +35,7 @@ export type DataEnhancedStart = ReturnType; export class DataEnhancedPlugin implements Plugin { private enhancedSearchInterceptor!: EnhancedSearchInterceptor; + private config!: ConfigSchema; constructor(private initializerContext: PluginInitializerContext) {} @@ -63,9 +64,9 @@ export class DataEnhancedPlugin }, }); - const { search: searchConfig } = this.initializerContext.config.get(); - if (searchConfig.sendToBackground.enabled) { - const { sessionsManagement: sessionsMgmtConfig } = searchConfig.sendToBackground; + this.config = this.initializerContext.config.get(); + if (this.config.search.sendToBackground.enabled) { + const { sessionsManagement: sessionsMgmtConfig } = this.config.search.sendToBackground; registerSearchSessionsMgmt(core, sessionsMgmtConfig, { management }); } } @@ -73,7 +74,7 @@ export class DataEnhancedPlugin public start(core: CoreStart, plugins: DataEnhancedStartDependencies) { setAutocompleteService(plugins.data.autocomplete); - if (this.initializerContext.config.get().search.sendToBackground.enabled) { + if (this.config.search.sendToBackground.enabled) { core.chrome.setBreadcrumbsAppendExtension({ content: toMountPoint( React.createElement( From c89eb43f3c86a60a23ea6cc412d390d8c7c6cfea Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 12 Jan 2021 14:02:03 +0200 Subject: [PATCH 38/52] fix test --- .../management/search_sessions/sessions_management.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts index 538c81f9ff699..8a4375e19397c 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts @@ -33,17 +33,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await sendToBackground.openPopover(); await sendToBackground.viewSearchSessions(); - await testSubjects.existOrFail('session-mgmt-view-status-label-in_progress'); - await testSubjects.existOrFail('session-mgmt-view-status-tooltip-in_progress'); + // wait until completed + await testSubjects.existOrFail('session-mgmt-view-status-label-complete'); + await testSubjects.existOrFail('session-mgmt-view-status-tooltip-complete'); await testSubjects.existOrFail('session-mgmt-table-col-created'); // find there is only one item in the table which is the newly saved session - const names = await testSubjects.findAll('session-mgmt-table-col-name'); + const names = await testSubjects.findAll('session-mgmt-table-col-name-viewable'); expect(names.length).to.be(1); expect(await Promise.all(names.map((n) => n.getVisibleText()))).to.eql(['Not Delayed']); // navigate to dashboard - await testSubjects.click('session-mgmt-table-col-name'); + await testSubjects.click('session-mgmt-table-col-name-viewable'); // embeddable has loaded await testSubjects.existOrFail('embeddablePanelHeading-SumofBytesbyExtension'); From ca5919714f4abdf101c17d8e4c566f644448504c Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 12 Jan 2021 17:47:53 +0200 Subject: [PATCH 39/52] Fix merge --- .../tests/apps/dashboard/async_search/async_search.ts | 4 ---- .../tests/apps/dashboard/async_search/send_to_background.ts | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts index 89630df338881..a1de22318ade4 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/async_search.ts @@ -24,10 +24,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } }); - after(async function () { - await sendToBackground.deleteAllSearchSessions(); - }); - it('not delayed should load', async () => { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts index 2edaeb1918b25..aa9185adfcf60 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts @@ -26,6 +26,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.navigateToApp('dashboard'); }); + after(async function () { + await sendToBackground.deleteAllSearchSessions(); + }); + it('Restore using non-existing sessionId errors out. Refresh starts a new session and completes.', async () => { await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); const url = await browser.getCurrentUrl(); From ae5bcc0b410607dc19d8b10af8e255b492e6928d Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 13 Jan 2021 13:18:34 +0200 Subject: [PATCH 40/52] Fix management test --- .../search_sessions/sessions_management.ts | 53 ++++++------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts index 8a4375e19397c..0e96df402cdcf 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts @@ -80,27 +80,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('autorefreshes and shows items on the server', async () => { await esArchiver.load('data/search_sessions'); - const nameColumnText = await testSubjects - .findAll('session-mgmt-table-col-name') - .then((nCol) => Promise.all(nCol.map((n) => n.getVisibleText()))); - - expect(nameColumnText.length).to.be(10); - - expectSnapshot(nameColumnText).toMatchInline(` - Array [ - "In-Progress Session 1", - "Completed Session 1", - "Expired Session 1", - "Cancelled Session 1", - "Error Session 1", - "In-Progress Session 2", - "Completed Session 2", - "Expired Session 2", - "Cancelled Session 2", - "A very very very very very very very very very very very very very very very very very very very very very very very long name Error Session 2", - ] - `); - const createdColText = await testSubjects .findAll('session-mgmt-table-col-created') .then((nCol) => Promise.all(nCol.map((n) => n.getVisibleText()))); @@ -109,16 +88,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expectSnapshot(createdColText).toMatchInline(` Array [ - "1 Dec, 2020, 00:00:00", - "2 Dec, 2020, 00:00:00", - "3 Dec, 2020, 00:00:00", - "4 Dec, 2020, 00:00:00", - "5 Dec, 2020, 00:00:00", - "6 Dec, 2020, 00:00:00", - "7 Dec, 2020, 00:00:00", - "8 Dec, 2020, 00:00:00", - "9 Dec, 2020, 00:00:00", - "10 Dec, 2020, 00:00:00", + "25 Dec, 2020, 00:00:00", + "24 Dec, 2020, 00:00:00", + "23 Dec, 2020, 00:00:00", + "22 Dec, 2020, 00:00:00", + "21 Dec, 2020, 00:00:00", + "20 Dec, 2020, 00:00:00", + "19 Dec, 2020, 00:00:00", + "18 Dec, 2020, 00:00:00", + "17 Dec, 2020, 00:00:00", + "16 Dec, 2020, 00:00:00", ] `); @@ -131,14 +110,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expectSnapshot(expiresColText).toMatchInline(` Array [ "--", - "3 Dec, 2020, 00:00:00", - "4 Dec, 2020, 00:00:00", - "5 Dec, 2020, 00:00:00", + "25 Dec, 2020, 00:00:00", + "24 Dec, 2020, 00:00:00", + "23 Dec, 2020, 00:00:00", "--", "--", - "8 Dec, 2020, 00:00:00", - "9 Dec, 2020, 00:00:00", - "10 Dec, 2020, 00:00:00", + "20 Dec, 2020, 00:00:00", + "19 Dec, 2020, 00:00:00", + "18 Dec, 2020, 00:00:00", "--", ] `); From 3a38aadf192e686536412236722cc6ec17c32400 Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 13 Jan 2021 16:51:44 +0200 Subject: [PATCH 41/52] @Dosant code review --- .../common/search/sessions_mgmt/index.ts | 2 +- .../sessions_mgmt/application/index.tsx | 3 +- .../sessions_mgmt/application/render.tsx | 10 +- .../components/actions/popover_actions.tsx | 41 ++--- .../sessions_mgmt/components/main.test.tsx | 45 ++--- .../search/sessions_mgmt/components/main.tsx | 2 - .../sessions_mgmt/components/status.test.tsx | 2 +- .../components/table/app_filter.tsx | 2 +- .../components/table/table.stories.tsx | 4 +- .../components/table/table.test.tsx | 154 ++++++++++-------- .../sessions_mgmt/components/table/table.tsx | 40 ++--- .../search/sessions_mgmt/lib/api.test.ts | 62 ++++--- .../public/search/sessions_mgmt/lib/api.ts | 25 +-- .../sessions_mgmt/lib/get_columns.test.tsx | 2 +- .../search/sessions_mgmt/lib/get_columns.tsx | 41 +++-- .../search_sessions/sessions_management.ts | 10 +- 16 files changed, 234 insertions(+), 211 deletions(-) diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts index f5b6efb31b1ca..3f7752ef84df8 100644 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts @@ -14,7 +14,7 @@ export interface UISession { expires: string | null; status: STATUS; actions?: ACTION[]; - isViewable: boolean; + isRestorable: boolean; url: string; } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx index ff62f3663166a..7cb367957a7d7 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx @@ -64,10 +64,9 @@ export class SearchSessionsMgmtApp { uiSettings, share, }; - const initialTable = await api.fetchTableData(); const { element } = params; - const unmountAppCb = renderApp(element, dependencies, initialTable); + const unmountAppCb = renderApp(element, dependencies); return () => { docTitle.reset(); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx index c6c730a3583b6..f5ee35fcff9a9 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/render.tsx @@ -8,13 +8,11 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { AppDependencies } from '../'; import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; -import { UISession } from '../../../../common/search/sessions_mgmt'; import { SearchSessionsMgmtMain } from '../components/main'; export const renderApp = ( elem: HTMLElement | null, - { i18n, uiSettings, ...homeDeps }: AppDependencies, - initialTable: UISession[] | null + { i18n, uiSettings, ...homeDeps }: AppDependencies ) => { if (!elem) { return () => undefined; @@ -29,11 +27,7 @@ export const renderApp = ( render( - + , elem diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx index 540fe6c82ec5f..54c6614fd0ec1 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx @@ -69,14 +69,20 @@ export const PopoverActionsMenu = ({ api, handleAction, session }: PopoverAction }; const renderPopoverButton = () => ( - + > + +
); const actions = session.actions || []; @@ -114,20 +120,15 @@ export const PopoverActionsMenu = ({ api, handleAction, session }: PopoverAction const panels: EuiContextMenuPanelDescriptor[] = [{ id: 0, items }]; return ( - - - - - + + ); }; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx index b8c8d6eaa7b79..763c6fb69494a 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx @@ -9,10 +9,10 @@ import { mount, ReactWrapper } from 'enzyme'; import { CoreSetup, CoreStart, DocLinksStart } from 'kibana/public'; import moment from 'moment'; import React from 'react'; +import { act } from 'react-dom/test-utils'; import { coreMock } from 'src/core/public/mocks'; import { SessionsClient } from 'src/plugins/data/public/search'; import { SessionsMgmtConfigSchema } from '..'; -import { STATUS, UISession } from '../../../../common/search/sessions_mgmt'; import { SearchSessionsMgmtAPI } from '../lib/api'; import { AsyncSearchIntroDocumentation } from '../lib/documentation'; import { LocaleWrapper, mockUrls } from '../__mocks__'; @@ -22,7 +22,6 @@ let mockCoreSetup: MockedKeys; let mockCoreStart: MockedKeys; let mockConfig: SessionsMgmtConfigSchema; let sessionsClient: SessionsClient; -let initialTable: UISession[]; let api: SearchSessionsMgmtAPI; describe('Background Search Session Management Main', () => { @@ -44,19 +43,6 @@ describe('Background Search Session Management Main', () => { mockCoreSetup.notifications, mockConfig ); - - initialTable = [ - { - name: 'very background search', - id: 'wtywp9u2802hahgp-flps', - url: '/app/great-app-url/#48', - appId: 'canvas', - status: STATUS.IN_PROGRESS, - created: '2020-12-02T00:19:32Z', - expires: '2020-12-07T00:19:32Z', - isViewable: true, - }, - ]; }); describe('renders', () => { @@ -68,24 +54,25 @@ describe('Background Search Session Management Main', () => { let main: ReactWrapper; - beforeEach(() => { + beforeEach(async () => { mockCoreSetup.uiSettings.get.mockImplementation((key: string) => { return key === 'dateFormat:tz' ? 'UTC' : null; }); - main = mount( - - - - ); + await act(async () => { + main = mount( + + + + ); + }); }); test('page title', () => { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx index 892bea81c5b57..5d946ed6fee9a 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx @@ -18,7 +18,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; import type { CoreStart, HttpStart } from 'kibana/public'; import React from 'react'; import type { SessionsMgmtConfigSchema } from '../'; -import type { UISession } from '../../../../common/search/sessions_mgmt'; import type { SearchSessionsMgmtAPI } from '../lib/api'; import type { AsyncSearchIntroDocumentation } from '../lib/documentation'; import { TableText } from './'; @@ -29,7 +28,6 @@ interface Props { core: CoreStart; api: SearchSessionsMgmtAPI; http: HttpStart; - initialTable: UISession[] | null; timezone: string; config: SessionsMgmtConfigSchema; } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx index de518ae8df321..935b4c4a83162 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx @@ -28,7 +28,7 @@ describe('Background Search Session management status labels', () => { status: STATUS.IN_PROGRESS, created: '2020-12-02T00:19:32Z', expires: '2020-12-07T00:19:32Z', - isViewable: true, + isRestorable: true, }; }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx index 91774cf92eff8..a72dcee3f2ad9 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx @@ -14,7 +14,7 @@ export const getAppFilter: (tableData: UISession[]) => SearchFilterConfig = (tab name: i18n.translate('xpack.data.mgmt.searchSessions.search.filterApp', { defaultMessage: 'App', }), - field: 'app', + field: 'appId', multiSelect: 'or', options: tableData.reduce((options: FieldValueOptionType[], { appId }) => { const existingOption = options.find((o) => o.value === appId); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx index e23887f2d3f54..d501485cef521 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx @@ -57,7 +57,6 @@ storiesOf('components/SearchSessionsMgmt/Table', module) const props = { core: mockCoreStart, - initialTable: [], api: new SearchSessionsMgmtAPI(sessionsClient, urls, notifications, config), timezone: 'UTC', config, @@ -145,7 +144,7 @@ storiesOf('components/SearchSessionsMgmt/Table', module) url: `/cool-app-${ndx}`, appId: appIds[ndx % 5], status: statuses[ndx % 5], - isViewable: viewability[ndx % 3], + isRestorable: viewability[ndx % 3], actions: actions[ndx % 4], }; }); @@ -154,7 +153,6 @@ storiesOf('components/SearchSessionsMgmt/Table', module) core: mockCoreStart, api: new SearchSessionsMgmtAPI(sessionsClient, urls, notifications, config), timezone: 'UTC', - initialTable: mockTable, config, }; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index b6bdb083d58b6..9667bd69c2a16 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -13,7 +13,7 @@ import React from 'react'; import { coreMock } from 'src/core/public/mocks'; import { SessionsClient } from 'src/plugins/data/public/search'; import { SessionsMgmtConfigSchema } from '../../'; -import { STATUS, UISession } from '../../../../../common/search/sessions_mgmt'; +import { STATUS } from '../../../../../common/search/sessions_mgmt'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { LocaleWrapper, mockUrls } from '../../__mocks__'; import { SearchSessionsMgmtTable } from './table'; @@ -22,7 +22,6 @@ let mockCoreSetup: MockedKeys; let mockCoreStart: CoreStart; let mockConfig: SessionsMgmtConfigSchema; let sessionsClient: SessionsClient; -let initialTable: UISession[]; let api: SearchSessionsMgmtAPI; describe('Background Search Session Management Table', () => { @@ -35,19 +34,6 @@ describe('Background Search Session Management Table', () => { refreshTimeout: moment.duration(10, 'minutes'), }; - initialTable = [ - { - name: 'very background search', - id: 'wtywp9u2802hahgp-flps', - url: '/app/great-app-url/#48', - appId: 'canvas', - status: STATUS.IN_PROGRESS, - created: '2020-12-02T00:19:32Z', - expires: '2020-12-07T00:19:32Z', - isViewable: true, - }, - ]; - sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); api = new SearchSessionsMgmtAPI( sessionsClient, @@ -62,21 +48,44 @@ describe('Background Search Session Management Table', () => { describe('renders', () => { let table: ReactWrapper; - beforeEach(() => { - table = mount( - - - - ); - }); + const getInitialResponse = () => { + return { + saved_objects: [ + { + id: 'wtywp9u2802hahgp-flps', + attributes: { + name: 'very background search', + id: 'wtywp9u2802hahgp-flps', + url: '/app/great-app-url/#48', + appId: 'canvas', + status: STATUS.IN_PROGRESS, + created: '2020-12-02T00:19:32Z', + expires: '2020-12-07T00:19:32Z', + isViewable: true, + }, + }, + ], + }; + }; + + test('table header cells', async () => { + sessionsClient.find = jest.fn().mockImplementation(async () => { + return getInitialResponse(); + }); + + await act(async () => { + table = mount( + + + + ); + }); - test('table header cells', () => { expect(table.find('thead th').map((node) => node.text())).toMatchInlineSnapshot(` Array [ "AppClick to sort in ascending order", @@ -88,7 +97,25 @@ describe('Background Search Session Management Table', () => { `); }); - test('table body cells', () => { + test('table body cells', async () => { + sessionsClient.find = jest.fn().mockImplementation(async () => { + return getInitialResponse(); + }); + + await act(async () => { + table = mount( + + + + ); + }); + table.update(); + expect(table.find('tbody td').map((node) => node.text())).toMatchInlineSnapshot(` Array [ "App", @@ -112,23 +139,22 @@ describe('Background Search Session Management Table', () => { refreshInterval: moment.duration(10, 'seconds'), }; - mount( - - - - ); - - act(() => { + await act(async () => { + mount( + + + + ); jest.advanceTimersByTime(20000); }); - expect(sessionsClient.find).toBeCalledTimes(2); + // 1 for initial load + 2 refresh calls + expect(sessionsClient.find).toBeCalledTimes(3); jest.useRealTimers(); }); @@ -142,28 +168,28 @@ describe('Background Search Session Management Table', () => { refreshTimeout: moment.duration(2, 'days'), }; - const table = mount( - - - - ); - - expect(sessionsClient.find).not.toBeCalled(); - - const buttonSelector = `[data-test-subj="session-mgmt-table-btn-refresh"] button`; - - await waitFor(() => { - table.find(buttonSelector).first().simulate('click'); - table.update(); + await act(async () => { + const table = mount( + + + + ); + + const buttonSelector = `[data-test-subj="session-mgmt-table-btn-refresh"] button`; + + await waitFor(() => { + table.find(buttonSelector).first().simulate('click'); + table.update(); + }); }); - expect(sessionsClient.find).toBeCalledTimes(1); + // initial call + click + expect(sessionsClient.find).toBeCalledTimes(2); }); }); }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index df7b2c3a72f48..11a03db033f3e 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -8,7 +8,7 @@ import { EuiButton, EuiInMemoryTable, EuiSearchBarProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { CoreStart } from 'kibana/public'; import moment from 'moment'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useMemo, useRef, useEffect, useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; import useInterval from 'react-use/lib/useInterval'; import { TableText } from '../'; @@ -24,25 +24,19 @@ const TABLE_ID = 'searchSessionsMgmtTable'; interface Props { core: CoreStart; api: SearchSessionsMgmtAPI; - initialTable: UISession[] | null; timezone: string; config: SessionsMgmtConfigSchema; } -export function SearchSessionsMgmtTable({ - core, - api, - timezone, - initialTable, - config, - ...props -}: Props) { - const [tableData, setTableData] = useState(initialTable ? initialTable : []); +export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props }: Props) { + const [tableData, setTableData] = useState([]); const [isLoading, setIsLoading] = useState(false); const [debouncedIsLoading, setDebouncedIsLoading] = useState(false); const [pagination, setPagination] = useState({ pageIndex: 0 }); - const [refreshInterval, setRefreshInterval] = useState(undefined); const showLatestResultsHandler = useRef(); + const refreshInterval = useMemo(() => moment.duration(config.refreshInterval).asMilliseconds(), [ + config.refreshInterval, + ]); // Debounce rendering the state of the Refresh button useDebounce( @@ -53,11 +47,6 @@ export function SearchSessionsMgmtTable({ [isLoading] ); - // Convert the config interval to ms - useEffect(() => { - setRefreshInterval(moment.duration(config.refreshInterval).asMilliseconds()); - }, [config.refreshInterval]); - // refresh behavior const doRefresh = useCallback(async () => { setIsLoading(true); @@ -67,27 +56,28 @@ export function SearchSessionsMgmtTable({ }; showLatestResultsHandler.current = renderResults; const results = await api.fetchTableData(); - if (results) { - // Ignore results from any but the latest api call - if (showLatestResultsHandler.current === renderResults) renderResults(results); - } else { - renderResults([]); - } + + if (showLatestResultsHandler.current === renderResults) renderResults(results || []); } catch (e) { setTableData([]); } setIsLoading(false); }, [api]); + // initial data load + useEffect(() => { + doRefresh(); + }, [doRefresh]); + useInterval(doRefresh, refreshInterval); // When action such as cancel, delete, extend occurs, use the async return // value to refresh the table - const handleActionCompleted: ActionComplete = useCallback((results: UISession[] | null) => { + const handleActionCompleted: ActionComplete = (results: UISession[] | null) => { if (results) { setTableData(results); } - }, []); + }; // table config: search / filters const search: EuiSearchBarProps = { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts index e5f4c73dc3886..6adb1188e252e 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts @@ -4,12 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from '@hapi/boom'; import type { MockedKeys } from '@kbn/utility-types/jest'; import { CoreSetup } from 'kibana/public'; import moment from 'moment'; -import * as Rx from 'rxjs'; -import sinon from 'sinon'; import { coreMock } from 'src/core/public/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import type { SavedObjectsFindResponse } from 'src/core/server'; @@ -22,7 +19,6 @@ import { SearchSessionsMgmtAPI } from './api'; let mockCoreSetup: MockedKeys; let mockConfig: SessionsMgmtConfigSchema; let sessionsClient: SessionsClient; -let findSessions: sinon.SinonStub; describe('Search Sessions Management API', () => { beforeEach(() => { @@ -35,20 +31,21 @@ describe('Search Sessions Management API', () => { }; sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); - findSessions = sinon.stub(sessionsClient, 'find').callsFake(async () => { - return { - saved_objects: [ - { - id: 'hello-pizza-123', - attributes: { name: 'Veggie', appId: 'pizza', status: 'baked' }, - }, - ], - } as SavedObjectsFindResponse; - }); }); describe('listing', () => { test('fetchDataTable calls the listing endpoint', async () => { + sessionsClient.find = jest.fn().mockImplementation(async () => { + return { + saved_objects: [ + { + id: 'hello-pizza-123', + attributes: { name: 'Veggie', appId: 'pizza', status: 'complete' }, + }, + ], + } as SavedObjectsFindResponse; + }); + const api = new SearchSessionsMgmtAPI( sessionsClient, mockUrls, @@ -65,9 +62,9 @@ describe('Search Sessions Management API', () => { "created": undefined, "expires": undefined, "id": "hello-pizza-123", - "isViewable": true, + "isRestorable": true, "name": "Veggie", - "status": "baked", + "status": "complete", "url": "hello-cool-undefined-url", }, ] @@ -75,7 +72,7 @@ describe('Search Sessions Management API', () => { }); test('isViewable is calculated based on status', async () => { - findSessions.callsFake(async () => { + sessionsClient.find = jest.fn().mockImplementation(async () => { return { saved_objects: [ { @@ -114,7 +111,7 @@ describe('Search Sessions Management API', () => { expect( data && data.map((session) => { - return [session.name, session.status, session.isViewable]; + return [session.name, session.status, session.isRestorable]; }) ).toMatchInlineSnapshot(` Array [ @@ -148,9 +145,7 @@ describe('Search Sessions Management API', () => { }); test('handle error from sessionsClient response', async () => { - findSessions.callsFake(async () => { - throw Boom.badImplementation('implementation is so bad'); - }); + sessionsClient.find = jest.fn().mockRejectedValue(new Error('implementation is so bad')); const api = new SearchSessionsMgmtAPI( sessionsClient, @@ -173,8 +168,10 @@ describe('Search Sessions Management API', () => { refreshTimeout: moment.duration(1, 'seconds'), }; - findSessions.callsFake(async () => { - return await Rx.timer(2000).toPromise(); + sessionsClient.find = jest.fn().mockImplementation(async () => { + return new Promise((resolve) => { + setTimeout(resolve, 2000); + }); }); const api = new SearchSessionsMgmtAPI( @@ -192,6 +189,19 @@ describe('Search Sessions Management API', () => { }); describe('delete', () => { + beforeEach(() => { + sessionsClient.find = jest.fn().mockImplementation(async () => { + return { + saved_objects: [ + { + id: 'hello-pizza-123', + attributes: { name: 'Veggie', appId: 'pizza', status: 'baked' }, + }, + ], + } as SavedObjectsFindResponse; + }); + }); + test('send delete calls the delete endpoint with a session ID', async () => { const api = new SearchSessionsMgmtAPI( sessionsClient, @@ -202,14 +212,12 @@ describe('Search Sessions Management API', () => { await api.sendDelete('abc-123-cool-session-ID'); expect(mockCoreSetup.notifications.toasts.addSuccess).toHaveBeenCalledWith({ - title: 'Deleted session', + title: 'Deleted the session', }); }); test('error if deleting shows a toast message', async () => { - sinon.stub(sessionsClient, 'delete').callsFake(async () => { - throw Boom.badImplementation('implementation is so bad'); - }); + sessionsClient.delete = jest.fn().mockRejectedValue(new Error('implementation is so bad')); const api = new SearchSessionsMgmtAPI( sessionsClient, diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index 3049af8d5a04e..4ff618f24dedc 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import type { NotificationsStart } from 'kibana/public'; import moment from 'moment'; -import * as Rx from 'rxjs'; +import { from, race, timer } from 'rxjs'; import { mapTo, tap } from 'rxjs/operators'; import type { SharePluginStart } from 'src/plugins/share/public'; import { SessionsMgmtConfigSchema } from '../'; @@ -32,13 +32,18 @@ const mapToUISession = ( expires, status, urlGeneratorId, + initialState, restoreState, } = savedObject.attributes; + const isRestorable = status === STATUS.IN_PROGRESS || status === STATUS.COMPLETE; + // derive the URL and add it in let url = '/'; try { - url = await urls.getUrlGenerator(urlGeneratorId).createUrl(restoreState); + url = await urls + .getUrlGenerator(urlGeneratorId) + .createUrl(isRestorable ? restoreState : initialState); } catch (err) { // eslint-disable-next-line no-console console.error('Could not create URL from restoreState'); @@ -46,13 +51,9 @@ const mapToUISession = ( console.error(err); } - // viewable if available - const isViewable = - status !== STATUS.CANCELLED && status !== STATUS.EXPIRED && status !== STATUS.ERROR; - return { id: savedObject.id, - isViewable, + isRestorable, name, appId, created, @@ -78,7 +79,7 @@ export class SearchSessionsMgmtAPI { const refreshTimeout = moment.duration(this.config.refreshTimeout); - const fetch$ = Rx.from( + const fetch$ = from( this.sessionsClient.find({ page: 1, perPage: this.config.maxSessions, @@ -86,7 +87,7 @@ export class SearchSessionsMgmtAPI { sortOrder: 'asc', }) ); - const timeout$ = Rx.timer(refreshTimeout.asMilliseconds()).pipe( + const timeout$ = timer(refreshTimeout.asMilliseconds()).pipe( tap(() => { this.notifications.toasts.addDanger( i18n.translate('xpack.data.mgmt.searchSessions.api.fetchTimeout', { @@ -98,9 +99,9 @@ export class SearchSessionsMgmtAPI { mapTo(null) ); - // fetch the background sessions before timeout triggers + // fetch the search sessions before timeout triggers try { - const result = await Rx.race(fetch$, timeout$).toPromise(); + const result = await race(fetch$, timeout$).toPromise(); if (result && result.saved_objects) { const savedObjects = result.saved_objects as SearchSessionSavedObject[]; return await Promise.all(savedObjects.map(mapToUISession(this.urls, this.config))); @@ -125,7 +126,7 @@ export class SearchSessionsMgmtAPI { this.notifications.toasts.addSuccess({ title: i18n.translate('xpack.data.mgmt.searchSessions.api.deleted', { - defaultMessage: 'Deleted session', + defaultMessage: 'Deleted the session', }), }); } catch (err) { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index 19b40c1d3c1aa..7813a68061e1b 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -60,7 +60,7 @@ describe('Search Sessions Management table column factory', () => { status: STATUS.IN_PROGRESS, created: '2020-12-02T00:19:32Z', expires: '2020-12-07T00:19:32Z', - isViewable: true, + isRestorable: true, }; [mockCoreStart] = await mockCoreSetup.getStartServices(); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index 023d5856fc7cd..ab5e5214cdef5 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -10,6 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, + EuiIconTip, EuiLink, EuiToolTip, } from '@elastic/eui'; @@ -17,6 +18,7 @@ import { i18n } from '@kbn/i18n'; import { CoreStart } from 'kibana/public'; import { capitalize } from 'lodash'; import React from 'react'; +import { FormattedMessage } from 'react-intl'; import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; import { SessionsMgmtConfigSchema } from '../'; import { ActionComplete, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; @@ -73,18 +75,31 @@ export const getColumns = ( }), sortable: true, width: '20%', - render: (name: UISession['name'], { isViewable, url }) => { - if (isViewable) { - return ( - - - {name} - - - ); - } - - return {name}; + render: (name: UISession['name'], { isRestorable, url }) => { + const notRestorableWarning = isRestorable ? null : ( + <> + {' '} + + } + /> + + ); + return ( + + + + {name} + {notRestorableWarning} + + + + ); }, }, @@ -184,7 +199,7 @@ export const getColumns = ( name: '', sortable: false, render: (actions: UISession['actions'], session) => { - if (session.isViewable || (actions && actions.length)) { + if (actions && actions.length) { return ( diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts index 0e96df402cdcf..abc61b55a6c91 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts @@ -39,12 +39,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.existOrFail('session-mgmt-table-col-created'); // find there is only one item in the table which is the newly saved session - const names = await testSubjects.findAll('session-mgmt-table-col-name-viewable'); + const names = await testSubjects.findAll('session-mgmt-table-col-name'); expect(names.length).to.be(1); expect(await Promise.all(names.map((n) => n.getVisibleText()))).to.eql(['Not Delayed']); // navigate to dashboard - await testSubjects.click('session-mgmt-table-col-name-viewable'); + await testSubjects.click('session-mgmt-table-col-name'); // embeddable has loaded await testSubjects.existOrFail('embeddablePanelHeading-SumofBytesbyExtension'); @@ -80,6 +80,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('autorefreshes and shows items on the server', async () => { await esArchiver.load('data/search_sessions'); + const nameColumnText = await testSubjects + .findAll('session-mgmt-table-col-name') + .then((nCol) => Promise.all(nCol.map((n) => n.getVisibleText()))); + + expect(nameColumnText.length).to.be(10); + const createdColText = await testSubjects .findAll('session-mgmt-table-col-created') .then((nCol) => Promise.all(nCol.map((n) => n.getVisibleText()))); From c030ed1e322f71d04035af1e14b9c80079d3d68b Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 13 Jan 2021 16:54:10 +0200 Subject: [PATCH 42/52] code review --- .../sessions_mgmt/components/table/table.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index 11a03db033f3e..c26358bfc3580 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -50,18 +50,21 @@ export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props // refresh behavior const doRefresh = useCallback(async () => { setIsLoading(true); + const renderResults = (results: UISession[]) => { + setTableData(results); + }; + showLatestResultsHandler.current = renderResults; try { - const renderResults = (results: UISession[]) => { - setTableData(results); - }; - showLatestResultsHandler.current = renderResults; const results = await api.fetchTableData(); - if (showLatestResultsHandler.current === renderResults) renderResults(results || []); + if (showLatestResultsHandler.current === renderResults) { + renderResults(results || []); + setIsLoading(false); + } } catch (e) { setTableData([]); + setIsLoading(false); } - setIsLoading(false); }, [api]); // initial data load From 83d4a739341c00240e4c9adef8f69f3050d050bd Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 13 Jan 2021 17:45:46 +0200 Subject: [PATCH 43/52] Deleteed story --- .../components/table/table.stories.tsx | 164 ------------------ 1 file changed, 164 deletions(-) delete mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx deleted file mode 100644 index d501485cef521..0000000000000 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.stories.tsx +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { IntlProvider } from 'react-intl'; -import { storiesOf } from '@storybook/react'; -import type { CoreStart, HttpSetup, NotificationsStart } from 'kibana/public'; -import moment from 'moment'; -import * as React from 'react'; -import * as Rx from 'rxjs'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { UrlGeneratorsStart } from 'src/plugins/share/public/url_generators'; -import { SessionsMgmtConfigSchema } from '../..'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SessionsClient } from '../../../../../../../../src/plugins/data/public/search'; -import { ACTION, STATUS } from '../../../../../common/search/sessions_mgmt'; -import { SearchSessionsMgmtAPI } from '../../lib/api'; -import { SearchSessionsMgmtTable } from './table'; - -export default { - title: 'SearchSessionsMgmt/Table', - component: SearchSessionsMgmtTable, -}; - -const mockCoreStart = ({ - application: { - currentAppId$: Rx.of('foo'), - navigateToUrl: async (url: string) => { - // eslint-disable-next-line no-console - console.log(`navigate to URL: ${url}`); - }, - }, -} as unknown) as CoreStart; - -storiesOf('components/SearchSessionsMgmt/Table', module) - .add('no items', () => { - const sessionsClient = new SessionsClient({ http: ({} as unknown) as HttpSetup }); - const urls = ({ - urlGenerator: {}, - getUrlGenerator: () => ({ - createUrl: () => ({}), - }), - } as unknown) as UrlGeneratorsStart; - const notifications = ({ - toasts: { - addInfo: () => ({}), - }, - } as unknown) as NotificationsStart; - const config: SessionsMgmtConfigSchema = { - maxSessions: 100, - refreshInterval: moment.duration(1, 'minutes'), - refreshTimeout: moment.duration(20, 'minutes'), - expiresSoonWarning: moment.duration(2, 'days'), - }; - - const props = { - core: mockCoreStart, - api: new SearchSessionsMgmtAPI(sessionsClient, urls, notifications, config), - timezone: 'UTC', - config, - }; - - return ( - - - - ); - }) - .add('multiple pages', () => { - const sessionsClient = new SessionsClient({ http: ({} as unknown) as HttpSetup }); - const urls = ({ - urlGenerator: {}, - getUrlGenerator: () => ({ - createUrl: () => ({}), - }), - } as unknown) as UrlGeneratorsStart; - const notifications = ({ - toasts: { - addInfo: () => ({}), - }, - } as unknown) as NotificationsStart; - const config: SessionsMgmtConfigSchema = { - maxSessions: 100, - refreshInterval: moment.duration(1, 'minutes'), - refreshTimeout: moment.duration(20, 'minutes'), - expiresSoonWarning: moment.duration(2, 'days'), - }; - - const names = [ - 'Session 1', - 'Session 2', - 'Session 3', - 'Session 3', - 'Session 4', - 'Session 5', - 'very very very very very very very very very very very very very very very very very very very very very very very long name', - 'Session 10', - 'Session 11', - 'Session 12', - 'Session 13', - 'Session 14', - 'Session 15', - 'Session 20', - 'Session 21', - 'Session 22', - 'Session 23', - 'Session 24', - 'Session 25', - 'Session 30', - 'Session 31', - 'Session 32', - 'Session 33', - 'Session 34', - 'Session 35', - ]; - - const appIds = ['canvas', 'discover', 'dashboards', 'visualize', 'security']; - const statuses = [ - STATUS.CANCELLED, - STATUS.COMPLETE, - STATUS.ERROR, - STATUS.EXPIRED, - STATUS.IN_PROGRESS, - ]; - const viewability = [true, true, false]; - const actions = [ - [ACTION.DELETE], - [ACTION.CANCEL, ACTION.DELETE], - [ACTION.EXTEND, ACTION.DELETE], - [ACTION.CANCEL, ACTION.EXTEND, ACTION.DELETE], - ]; - - const mockTable = names.map((name, ndx) => { - const created = moment().subtract(86000000 * ndx); - const expires = moment().add(86000000 * ndx); - - return { - name, - id: `cool-session-${ndx}`, - created: created.format(), - expires: expires.format(), - url: `/cool-app-${ndx}`, - appId: appIds[ndx % 5], - status: statuses[ndx % 5], - isRestorable: viewability[ndx % 3], - actions: actions[ndx % 4], - }; - }); - - const props = { - core: mockCoreStart, - api: new SearchSessionsMgmtAPI(sessionsClient, urls, notifications, config), - timezone: 'UTC', - config, - }; - - return ( - - - - ); - }); From aad9f738a449fd417bda00b56611b16b9b0e8bed Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 14 Jan 2021 13:43:44 +0200 Subject: [PATCH 44/52] some more code review stuffs --- .../common/search/sessions_mgmt/index.ts | 2 +- .../sessions_mgmt/components/table/table.tsx | 14 ++++++-------- .../public/search/sessions_mgmt/lib/api.test.ts | 2 +- .../public/search/sessions_mgmt/lib/api.ts | 4 ++-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts index 3f7752ef84df8..a3767c592260e 100644 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts @@ -18,6 +18,6 @@ export interface UISession { url: string; } -export type ActionComplete = (result: UISession[] | null) => void; +export type ActionComplete = (result: UISession[]) => void; export * from './constants'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index c26358bfc3580..1f3f0e488ccc8 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -54,15 +54,13 @@ export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props setTableData(results); }; showLatestResultsHandler.current = renderResults; + let results: UISession[] = []; try { - const results = await api.fetchTableData(); + results = await api.fetchTableData(); + } catch (e) {} // eslint-disable-line no-empty - if (showLatestResultsHandler.current === renderResults) { - renderResults(results || []); - setIsLoading(false); - } - } catch (e) { - setTableData([]); + if (showLatestResultsHandler.current === renderResults) { + renderResults(results); setIsLoading(false); } }, [api]); @@ -76,7 +74,7 @@ export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props // When action such as cancel, delete, extend occurs, use the async return // value to refresh the table - const handleActionCompleted: ActionComplete = (results: UISession[] | null) => { + const handleActionCompleted: ActionComplete = (results: UISession[]) => { if (results) { setTableData(results); } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts index 6adb1188e252e..69365f5b15bfd 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts @@ -106,7 +106,7 @@ describe('Search Sessions Management API', () => { mockConfig ); const data = await api.fetchTableData(); - expect(data).not.toBe(null); + expect(data).not.toHaveLength(0); expect( data && diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index 4ff618f24dedc..05504fb5658f5 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -72,7 +72,7 @@ export class SearchSessionsMgmtAPI { private config: SessionsMgmtConfigSchema ) {} - public async fetchTableData(): Promise { + public async fetchTableData(): Promise { interface FetchResult { saved_objects: object[]; } @@ -116,7 +116,7 @@ export class SearchSessionsMgmtAPI { }); } - return null; + return []; } // Delete From 34cd7421b5d4b3ab219e27b91497cebbd33a69e0 Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 14 Jan 2021 14:00:20 +0200 Subject: [PATCH 45/52] fix ts --- .../data_enhanced/public/search/sessions_mgmt/lib/api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index 05504fb5658f5..2f9fcfbf73de1 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -120,7 +120,7 @@ export class SearchSessionsMgmtAPI { } // Delete - public async sendDelete(id: string): Promise { + public async sendDelete(id: string): Promise { try { await this.sessionsClient.delete(id); @@ -144,7 +144,7 @@ export class SearchSessionsMgmtAPI { } // Extend - public async sendExtend(id: string): Promise { + public async sendExtend(id: string): Promise { this.notifications.toasts.addError(new Error('Not implemented'), { title: i18n.translate('xpack.data.mgmt.searchSessions.api.extendError', { defaultMessage: 'Failed to extend the session expiration!', From 22eba85b180973ecbc6b204a3fde5d36adc158d9 Mon Sep 17 00:00:00 2001 From: Liza K Date: Sun, 17 Jan 2021 16:26:17 +0200 Subject: [PATCH 46/52] Code review and cleanup --- x-pack/plugins/data_enhanced/common/index.ts | 1 - .../data_enhanced/common/search/index.ts | 1 + .../common/search/session/types.ts | 5 - .../common/search/sessions_mgmt/constants.ts | 6 +- .../common/search/sessions_mgmt/index.ts | 11 +- .../sessions_mgmt/application/index.tsx | 10 +- .../cancel_button.tsx} | 55 ++--- .../components/actions/extend_button.tsx | 89 ++++++++ .../components/actions/get_action.tsx | 28 ++- .../components/actions/index.tsx | 1 + .../components/actions/popover_actions.tsx | 15 +- .../components/actions/reload_button.tsx | 32 +++ .../sessions_mgmt/components/actions/types.ts | 7 + .../search/sessions_mgmt/components/index.tsx | 2 + .../sessions_mgmt/components/main.test.tsx | 11 +- .../search/sessions_mgmt/components/main.tsx | 2 +- .../sessions_mgmt/components/status.test.tsx | 24 +- .../sessions_mgmt/components/status.tsx | 22 +- .../components/table/app_filter.tsx | 2 +- .../components/table/status_filter.tsx | 2 +- .../components/table/table.test.tsx | 19 +- .../sessions_mgmt/components/table/table.tsx | 11 +- .../search/sessions_mgmt/lib/api.test.ts | 214 ++++++++---------- .../public/search/sessions_mgmt/lib/api.ts | 108 +++++---- .../search/sessions_mgmt/lib/date_string.ts | 2 +- .../sessions_mgmt/lib/get_columns.test.tsx | 28 ++- .../search/sessions_mgmt/lib/get_columns.tsx | 26 ++- 27 files changed, 432 insertions(+), 302 deletions(-) rename x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/{delete_button.tsx => actions/cancel_button.tsx} (53%) create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/reload_button.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts diff --git a/x-pack/plugins/data_enhanced/common/index.ts b/x-pack/plugins/data_enhanced/common/index.ts index 1f0ff87ca46b4..669c33230a34c 100644 --- a/x-pack/plugins/data_enhanced/common/index.ts +++ b/x-pack/plugins/data_enhanced/common/index.ts @@ -12,7 +12,6 @@ export { EqlSearchStrategyResponse, IAsyncSearchOptions, pollSearch, - SearchSessionSavedObject, SearchSessionSavedObjectAttributes, SearchSessionStatus, SearchSessionRequestInfo, diff --git a/x-pack/plugins/data_enhanced/common/search/index.ts b/x-pack/plugins/data_enhanced/common/search/index.ts index 5617a1780c269..cf4c3e08ce3ef 100644 --- a/x-pack/plugins/data_enhanced/common/search/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/index.ts @@ -7,3 +7,4 @@ export * from './types'; export * from './poll_search'; export * from './session'; +export * from './sessions_mgmt'; diff --git a/x-pack/plugins/data_enhanced/common/search/session/types.ts b/x-pack/plugins/data_enhanced/common/search/session/types.ts index c89f763bb4117..9eefdf43aa245 100644 --- a/x-pack/plugins/data_enhanced/common/search/session/types.ts +++ b/x-pack/plugins/data_enhanced/common/search/session/types.ts @@ -46,11 +46,6 @@ export interface SearchSessionSavedObjectAttributes { */ idMapping: Record; } - -export interface SearchSessionSavedObject { - id: string; - attributes: SearchSessionSavedObjectAttributes; -} export interface SearchSessionRequestInfo { /** * ID of the async search request diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts index 53d915ed8c70b..2ed8cbed59329 100644 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts @@ -4,14 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchSessionStatus } from '../'; - export enum ACTION { EXTEND = 'extend', CANCEL = 'cancel', - DELETE = 'delete', + RELOAD = 'reload', } export const DATE_STRING_FORMAT = 'D MMM, YYYY, HH:mm:ss'; - -export { SearchSessionStatus as STATUS }; diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts index a3767c592260e..7172e2cfaa294 100644 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ACTION, STATUS } from './constants'; +import { ACTION } from './constants'; +import { SearchSessionStatus } from '..'; export interface UISession { id: string; @@ -12,12 +13,10 @@ export interface UISession { appId: string; created: string; expires: string | null; - status: STATUS; + status: SearchSessionStatus; actions?: ACTION[]; - isRestorable: boolean; - url: string; + reloadUrl: string; + restoreUrl: string; } -export type ActionComplete = (result: UISession[]) => void; - export * from './constants'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx index 7cb367957a7d7..27f1482a4d20d 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx @@ -36,6 +36,7 @@ export class SearchSessionsMgmtApp { i18n, notifications, uiSettings, + application, } = coreStart; const { data, share } = pluginsStart; @@ -44,12 +45,11 @@ export class SearchSessionsMgmtApp { params.setBreadcrumbs([{ text: pluginName }]); const { sessionsClient } = data.search; - const api = new SearchSessionsMgmtAPI( - sessionsClient, - share.urlGenerators, + const api = new SearchSessionsMgmtAPI(sessionsClient, this.config, { notifications, - this.config - ); + urls: share.urlGenerators, + application, + }); const documentation = new AsyncSearchIntroDocumentation(docLinks); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/delete_button.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/cancel_button.tsx similarity index 53% rename from x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/delete_button.tsx rename to x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/cancel_button.tsx index c42b124c3a396..8f4c8845de235 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/delete_button.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/cancel_button.tsx @@ -8,34 +8,38 @@ import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState } from 'react'; -import { ActionComplete } from '../../../../common/search/sessions_mgmt'; -import { SearchSessionsMgmtAPI } from '../lib/api'; -import { TableText } from './'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { TableText } from '../'; +import { OnActionComplete } from './types'; -interface DeleteButtonProps { +interface CancelButtonProps { id: string; + name: string; api: SearchSessionsMgmtAPI; - actionComplete: ActionComplete; + onActionComplete: OnActionComplete; } -const DeleteConfirm = ({ +const CancelConfirm = ({ onConfirmDismiss, ...props -}: DeleteButtonProps & { onConfirmDismiss: () => void }) => { - const { id, api, actionComplete } = props; +}: CancelButtonProps & { onConfirmDismiss: () => void }) => { + const { id, name, api, onActionComplete } = props; const [isLoading, setIsLoading] = useState(false); - const title = i18n.translate('xpack.data.mgmt.searchSessions.deleteModal.title', { - defaultMessage: 'Are you sure you want to delete the session?', + const title = i18n.translate('xpack.data.mgmt.searchSessions.cancelModal.title', { + defaultMessage: 'Cancel search session', }); - const confirm = i18n.translate('xpack.data.mgmt.searchSessions.deleteModal.deleteButton', { - defaultMessage: 'Delete', - }); - const cancel = i18n.translate('xpack.data.mgmt.searchSessions.deleteModal.cancelButton', { + const confirm = i18n.translate('xpack.data.mgmt.searchSessions.cancelModal.cancelButton', { defaultMessage: 'Cancel', }); - const message = i18n.translate('xpack.data.mgmt.searchSessions.deleteModal.message', { - defaultMessage: `A deleted session can be recovered by re-running the search.`, + const cancel = i18n.translate('xpack.data.mgmt.searchSessions.cancelModal.dontCancelButton', { + defaultMessage: 'Dismiss', + }); + const message = i18n.translate('xpack.data.mgmt.searchSessions.cancelModal.message', { + defaultMessage: `Canceling the search session \'{name}\' will expire any cached results, so that quick restore will no longer be available. You will still be able to re-run it, using the reload action.`, + values: { + name, + }, }); return ( @@ -45,7 +49,8 @@ const DeleteConfirm = ({ onCancel={onConfirmDismiss} onConfirm={async () => { setIsLoading(true); - actionComplete(await api.sendDelete(id)); + await api.sendCancel(id); + onActionComplete(); }} confirmButtonText={confirm} confirmButtonDisabled={isLoading} @@ -59,28 +64,26 @@ const DeleteConfirm = ({ ); }; -export const DeleteButton = (props: DeleteButtonProps) => { - const [toShowDeleteConfirm, setToShowDeleteConfirm] = useState(false); +export const CancelButton = (props: CancelButtonProps) => { + const [showConfirm, setShowConfirm] = useState(false); const onClick = () => { - setToShowDeleteConfirm(true); + setShowConfirm(true); }; const onConfirmDismiss = () => { - setToShowDeleteConfirm(false); + setShowConfirm(false); }; return ( <> - {toShowDeleteConfirm ? ( - - ) : null} + {showConfirm ? : null} ); }; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx new file mode 100644 index 0000000000000..4c8a7b0217688 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useState } from 'react'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { TableText } from '../'; +import { OnActionComplete } from './types'; + +interface ExtendButtonProps { + id: string; + name: string; + api: SearchSessionsMgmtAPI; + onActionComplete: OnActionComplete; +} + +const ExtendConfirm = ({ + onConfirmDismiss, + ...props +}: ExtendButtonProps & { onConfirmDismiss: () => void }) => { + const { id, name, api, onActionComplete } = props; + const [isLoading, setIsLoading] = useState(false); + + const title = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.title', { + defaultMessage: 'Extend search session expiration', + }); + const confirm = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.extendButton', { + defaultMessage: 'Extend', + }); + const extend = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.dontExtendButton', { + defaultMessage: 'Cancel', + }); + const message = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.extendMessage', { + defaultMessage: "When would you like the search session '{name}' to expire?", + values: { + name, + }, + }); + + return ( + + { + setIsLoading(true); + await api.sendExtend(id, '1'); + onActionComplete(); + }} + confirmButtonText={confirm} + confirmButtonDisabled={isLoading} + cancelButtonText={extend} + defaultFocusedButton="confirm" + buttonColor="primary" + > + {message} + + + ); +}; + +export const ExtendButton = (props: ExtendButtonProps) => { + const [showConfirm, setShowConfirm] = useState(false); + + const onClick = () => { + setShowConfirm(true); + }; + + const onConfirmDismiss = () => { + setShowConfirm(false); + }; + + return ( + <> + + + + {showConfirm ? : null} + + ); +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx index 73806c124be9d..c67c46190088e 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx @@ -4,44 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; import React from 'react'; import { IClickActionDescriptor } from '../'; -import { ACTION, ActionComplete, UISession } from '../../../../../common/search/sessions_mgmt'; +import { ACTION, UISession } from '../../../../../common/search'; import extendSessionIcon from '../../icons/extend_session.svg'; import { SearchSessionsMgmtAPI } from '../../lib/api'; -import { DeleteButton } from '../delete_button'; +import { CancelButton } from './cancel_button'; +import { ExtendButton } from './extend_button'; +import { ReloadButton } from './reload_button'; +import { OnActionComplete } from './types'; export const getAction = ( api: SearchSessionsMgmtAPI, actionType: string, - { id }: UISession, - actionComplete: ActionComplete + { id, name, reloadUrl }: UISession, + onActionComplete: OnActionComplete ): IClickActionDescriptor | null => { switch (actionType) { case ACTION.CANCEL: return { iconType: 'crossInACircleFilled', textColor: 'default', - label: i18n.translate('xpack.data.mgmt.searchSessions.actionCancel', { - defaultMessage: 'Cancel', - }), + label: , }; - case ACTION.DELETE: + case ACTION.RELOAD: return { - iconType: 'trash', - textColor: 'danger', - label: , + iconType: 'refresh', + textColor: 'default', + label: , }; case ACTION.EXTEND: return { iconType: extendSessionIcon, textColor: 'default', - label: i18n.translate('xpack.data.mgmt.searchSessions.actionExtend', { - defaultMessage: 'Extend', - }), + label: , }; default: diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx index 662d36c2536d2..a18d905131567 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx @@ -5,3 +5,4 @@ */ export { PopoverActionsMenu } from './popover_actions'; +export { OnActionComplete } from './types'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx index 54c6614fd0ec1..45ac11d20ba7e 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx @@ -28,9 +28,10 @@ import { import { i18n } from '@kbn/i18n'; import React, { ReactElement, useState } from 'react'; import { TableText } from '../'; -import { ACTION, ActionComplete, UISession } from '../../../../../common/search/sessions_mgmt'; +import { ACTION, UISession } from '../../../../../common/search'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { getAction } from './get_action'; +import { OnActionComplete } from './types'; // interfaces interface PopoverActionProps { @@ -42,7 +43,7 @@ interface PopoverActionProps { interface PopoverActionItemsProps { session: UISession; api: SearchSessionsMgmtAPI; - handleAction: ActionComplete; + onActionComplete: OnActionComplete; } // helper @@ -57,7 +58,7 @@ const PopoverAction = ({ textColor, iconType, children, ...props }: PopoverActio ); -export const PopoverActionsMenu = ({ api, handleAction, session }: PopoverActionItemsProps) => { +export const PopoverActionsMenu = ({ api, onActionComplete, session }: PopoverActionItemsProps) => { const [isPopoverOpen, setPopover] = useState(false); const onPopoverClick = () => { @@ -88,13 +89,13 @@ export const PopoverActionsMenu = ({ api, handleAction, session }: PopoverAction const actions = session.actions || []; // Generic set of actions - up to the API to return what is available const items = actions.reduce((itemSet, actionType) => { - const actionDef = getAction(api, actionType, session, handleAction); + const actionDef = getAction(api, actionType, session, onActionComplete); if (actionDef) { const { label, textColor, iconType } = actionDef; // add a line above the delete action (when there are multiple) // NOTE: Delete action MUST be the final action[] item - if (actions.length > 1 && actionType === ACTION.DELETE) { + if (actions.length > 1 && actionType === ACTION.CANCEL) { itemSet.push({ isSeparator: true, key: 'separadorable' }); } @@ -119,7 +120,7 @@ export const PopoverActionsMenu = ({ api, handleAction, session }: PopoverAction const panels: EuiContextMenuPanelDescriptor[] = [{ id: 0, items }]; - return ( + return actions.length ? ( - ); + ) : null; }; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/reload_button.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/reload_button.tsx new file mode 100644 index 0000000000000..9a98ab2044770 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/reload_button.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; +import { TableText } from '../'; +import { SearchSessionsMgmtAPI } from '../../lib/api'; + +interface ReloadButtonProps { + api: SearchSessionsMgmtAPI; + reloadUrl: string; +} + +export const ReloadButton = (props: ReloadButtonProps) => { + function onClick() { + props.api.reloadSearchSession(props.reloadUrl); + } + + return ( + <> + + + + + ); +}; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts new file mode 100644 index 0000000000000..7d761cec46df0 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type OnActionComplete = () => void; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx index 0aed3e68b25cc..ffb0992469a8a 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/index.tsx @@ -8,6 +8,8 @@ import { EuiLinkProps, EuiText, EuiTextProps } from '@elastic/eui'; import React from 'react'; import extendSessionIcon from '../icons/extend_session.svg'; +export { OnActionComplete, PopoverActionsMenu } from './actions'; + export const TableText = ({ children, ...props }: EuiTextProps) => { return ( diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx index 763c6fb69494a..e01d1a28c5e54 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx @@ -37,12 +37,11 @@ describe('Background Search Session Management Main', () => { sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); - api = new SearchSessionsMgmtAPI( - sessionsClient, - mockUrls, - mockCoreSetup.notifications, - mockConfig - ); + api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); }); describe('renders', () => { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx index 5d946ed6fee9a..80c6a580dd183 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.tsx @@ -65,7 +65,7 @@ export function SearchSessionsMgmtMain({ documentation, ...tableProps }: Props)

diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx index 935b4c4a83162..3be14ce2d89e0 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx @@ -7,7 +7,7 @@ import { EuiTextProps, EuiToolTipProps } from '@elastic/eui'; import { mount } from 'enzyme'; import React from 'react'; -import { STATUS, UISession } from '../../../../common/search/sessions_mgmt'; +import { SearchSessionStatus, UISession } from '../../../../common/search'; import { LocaleWrapper } from '../__mocks__'; import { getStatusText, StatusIndicator } from './status'; @@ -23,27 +23,27 @@ describe('Background Search Session management status labels', () => { session = { name: 'amazing test', id: 'wtywp9u2802hahgp-gsla', - url: '/app/great-app-url/#45', + restoreUrl: '/app/great-app-url/#45', + reloadUrl: '/app/great-app-url/#45', appId: 'security', - status: STATUS.IN_PROGRESS, + status: SearchSessionStatus.IN_PROGRESS, created: '2020-12-02T00:19:32Z', expires: '2020-12-07T00:19:32Z', - isRestorable: true, }; }); describe('getStatusText', () => { test('in progress', () => { - expect(getStatusText(STATUS.IN_PROGRESS)).toBe('In progress'); + expect(getStatusText(SearchSessionStatus.IN_PROGRESS)).toBe('In progress'); }); test('expired', () => { - expect(getStatusText(STATUS.EXPIRED)).toBe('Expired'); + expect(getStatusText(SearchSessionStatus.EXPIRED)).toBe('Expired'); }); test('cancelled', () => { - expect(getStatusText(STATUS.CANCELLED)).toBe('Cancelled'); + expect(getStatusText(SearchSessionStatus.CANCELLED)).toBe('Cancelled'); }); test('complete', () => { - expect(getStatusText(STATUS.COMPLETE)).toBe('Complete'); + expect(getStatusText(SearchSessionStatus.COMPLETE)).toBe('Complete'); }); test('error', () => { expect(getStatusText('error')).toBe('Error'); @@ -65,7 +65,7 @@ describe('Background Search Session management status labels', () => { }); test('complete', () => { - session.status = STATUS.COMPLETE; + session.status = SearchSessionStatus.COMPLETE; const statusIndicator = mount( @@ -81,7 +81,7 @@ describe('Background Search Session management status labels', () => { }); test('complete - expires soon', () => { - session.status = STATUS.COMPLETE; + session.status = SearchSessionStatus.COMPLETE; const statusIndicator = mount( @@ -96,7 +96,7 @@ describe('Background Search Session management status labels', () => { }); test('expired', () => { - session.status = STATUS.EXPIRED; + session.status = SearchSessionStatus.EXPIRED; const statusIndicator = mount( @@ -111,7 +111,7 @@ describe('Background Search Session management status labels', () => { }); test('error handling', () => { - session.status = STATUS.COMPLETE; + session.status = SearchSessionStatus.COMPLETE; (session as any).created = null; (session as any).expires = null; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx index fec45387b831f..5785989d11ae2 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -7,30 +7,30 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { ReactElement } from 'react'; -import { STATUS, UISession } from '../../../../common/search/sessions_mgmt'; +import { SearchSessionStatus, UISession } from '../../../../common/search'; import { dateString } from '../lib/date_string'; import { StatusDef as StatusAttributes, TableText } from './'; // Shared helper function export const getStatusText = (statusType: string): string => { switch (statusType) { - case STATUS.IN_PROGRESS: + case SearchSessionStatus.IN_PROGRESS: return i18n.translate('xpack.data.mgmt.searchSessions.status.label.inProgress', { defaultMessage: 'In progress', }); - case STATUS.EXPIRED: + case SearchSessionStatus.EXPIRED: return i18n.translate('xpack.data.mgmt.searchSessions.status.label.expired', { defaultMessage: 'Expired', }); - case STATUS.CANCELLED: + case SearchSessionStatus.CANCELLED: return i18n.translate('xpack.data.mgmt.searchSessions.status.label.cancelled', { defaultMessage: 'Cancelled', }); - case STATUS.COMPLETE: + case SearchSessionStatus.COMPLETE: return i18n.translate('xpack.data.mgmt.searchSessions.status.label.complete', { defaultMessage: 'Complete', }); - case STATUS.ERROR: + case SearchSessionStatus.ERROR: return i18n.translate('xpack.data.mgmt.searchSessions.status.label.error', { defaultMessage: 'Error', }); @@ -64,7 +64,7 @@ const getStatusAttributes = ({ } switch (session.status) { - case STATUS.IN_PROGRESS: + case SearchSessionStatus.IN_PROGRESS: try { return { textColor: 'default', @@ -84,7 +84,7 @@ const getStatusAttributes = ({ throw new Error(`Could not instantiate a createdDate object from: ${session.created}`); } - case STATUS.EXPIRED: + case SearchSessionStatus.EXPIRED: try { const toolTipContent = i18n.translate( 'xpack.data.mgmt.searchSessions.status.message.expiredOn', @@ -105,7 +105,7 @@ const getStatusAttributes = ({ throw new Error(`Could not instantiate an expiration Date object from: ${session.expires}`); } - case STATUS.CANCELLED: + case SearchSessionStatus.CANCELLED: return { icon: , label: {getStatusText(session.status)}, @@ -114,7 +114,7 @@ const getStatusAttributes = ({ }), }; - case STATUS.ERROR: + case SearchSessionStatus.ERROR: return { textColor: 'danger', icon: , @@ -125,7 +125,7 @@ const getStatusAttributes = ({ }), }; - case STATUS.COMPLETE: + case SearchSessionStatus.COMPLETE: try { const toolTipContent = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresOn', { defaultMessage: 'Expires on {expireDate}', diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx index a72dcee3f2ad9..232241569a5c3 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx @@ -7,7 +7,7 @@ import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { capitalize } from 'lodash'; -import { UISession } from '../../../../../common/search/sessions_mgmt'; +import { UISession } from '../../../../../common/search'; export const getAppFilter: (tableData: UISession[]) => SearchFilterConfig = (tableData) => ({ type: 'field_value_selection', diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx index c905091f88195..c1bb2ea5782ba 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx @@ -8,7 +8,7 @@ import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { TableText } from '../'; -import { UISession } from '../../../../../common/search/sessions_mgmt'; +import { UISession } from '../../../../../common/search'; import { getStatusText } from '../status'; export const getStatusFilter: (tableData: UISession[]) => SearchFilterConfig = (tableData) => ({ diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index 9667bd69c2a16..b87251897148f 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -12,8 +12,8 @@ import moment from 'moment'; import React from 'react'; import { coreMock } from 'src/core/public/mocks'; import { SessionsClient } from 'src/plugins/data/public/search'; +import { SearchSessionStatus } from '../../../../../common/search'; import { SessionsMgmtConfigSchema } from '../../'; -import { STATUS } from '../../../../../common/search/sessions_mgmt'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { LocaleWrapper, mockUrls } from '../../__mocks__'; import { SearchSessionsMgmtTable } from './table'; @@ -27,6 +27,7 @@ let api: SearchSessionsMgmtAPI; describe('Background Search Session Management Table', () => { beforeEach(async () => { mockCoreSetup = coreMock.createSetup(); + mockCoreStart = coreMock.createStart(); mockConfig = { expiresSoonWarning: moment.duration(1, 'days'), maxSessions: 2000, @@ -35,14 +36,11 @@ describe('Background Search Session Management Table', () => { }; sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); - api = new SearchSessionsMgmtAPI( - sessionsClient, - mockUrls, - mockCoreSetup.notifications, - mockConfig - ); - - [mockCoreStart] = await mockCoreSetup.getStartServices(); + api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); }); describe('renders', () => { @@ -58,10 +56,9 @@ describe('Background Search Session Management Table', () => { id: 'wtywp9u2802hahgp-flps', url: '/app/great-app-url/#48', appId: 'canvas', - status: STATUS.IN_PROGRESS, + status: SearchSessionStatus.IN_PROGRESS, created: '2020-12-02T00:19:32Z', expires: '2020-12-07T00:19:32Z', - isViewable: true, }, }, ], diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index 1f3f0e488ccc8..cb99efeec7053 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -13,9 +13,10 @@ import useDebounce from 'react-use/lib/useDebounce'; import useInterval from 'react-use/lib/useInterval'; import { TableText } from '../'; import { SessionsMgmtConfigSchema } from '../..'; -import { ActionComplete, UISession } from '../../../../../common/search/sessions_mgmt'; +import { UISession } from '../../../../../common/search'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { getColumns } from '../../lib/get_columns'; +import { OnActionComplete } from '../actions'; import { getAppFilter } from './app_filter'; import { getStatusFilter } from './status_filter'; @@ -74,10 +75,8 @@ export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props // When action such as cancel, delete, extend occurs, use the async return // value to refresh the table - const handleActionCompleted: ActionComplete = (results: UISession[]) => { - if (results) { - setTableData(results); - } + const onActionComplete: OnActionComplete = () => { + doRefresh(); }; // table config: search / filters @@ -108,7 +107,7 @@ export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props {...props} id={TABLE_ID} data-test-subj={TABLE_ID} - columns={getColumns(core, api, config, timezone, handleActionCompleted)} + columns={getColumns(core, api, config, timezone, onActionComplete)} items={tableData} pagination={pagination} search={search} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts index 69365f5b15bfd..5b337dfd03eb1 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.test.ts @@ -5,24 +5,26 @@ */ import type { MockedKeys } from '@kbn/utility-types/jest'; -import { CoreSetup } from 'kibana/public'; +import { CoreSetup, CoreStart } from 'kibana/public'; import moment from 'moment'; import { coreMock } from 'src/core/public/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import type { SavedObjectsFindResponse } from 'src/core/server'; import { SessionsClient } from 'src/plugins/data/public/search'; import type { SessionsMgmtConfigSchema } from '../'; -import { STATUS } from '../../../../common/search/sessions_mgmt'; +import { SearchSessionStatus } from '../../../../common/search'; import { mockUrls } from '../__mocks__'; import { SearchSessionsMgmtAPI } from './api'; let mockCoreSetup: MockedKeys; +let mockCoreStart: MockedKeys; let mockConfig: SessionsMgmtConfigSchema; let sessionsClient: SessionsClient; describe('Search Sessions Management API', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); + mockCoreStart = coreMock.createStart(); mockConfig = { expiresSoonWarning: moment.duration('1d'), maxSessions: 2000, @@ -46,116 +48,43 @@ describe('Search Sessions Management API', () => { } as SavedObjectsFindResponse; }); - const api = new SearchSessionsMgmtAPI( - sessionsClient, - mockUrls, - mockCoreSetup.notifications, - mockConfig - ); + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); expect(await api.fetchTableData()).toMatchInlineSnapshot(` Array [ Object { "actions": Array [ - "delete", + "reload", + "extend", + "cancel", ], "appId": "pizza", "created": undefined, "expires": undefined, "id": "hello-pizza-123", - "isRestorable": true, "name": "Veggie", + "reloadUrl": "hello-cool-undefined-url", + "restoreUrl": "hello-cool-undefined-url", "status": "complete", - "url": "hello-cool-undefined-url", }, ] `); }); - test('isViewable is calculated based on status', async () => { - sessionsClient.find = jest.fn().mockImplementation(async () => { - return { - saved_objects: [ - { - id: 'hello-pizza-000', - attributes: { name: 'Veggie 1', appId: 'pizza', status: STATUS.IN_PROGRESS }, - }, - { - id: 'hello-pizza-123', - attributes: { name: 'Veggie 2', appId: 'pizza', status: STATUS.COMPLETE }, - }, - { - id: 'hello-pizza-456', - attributes: { name: 'Veggie B', appId: 'pizza', status: STATUS.EXPIRED }, - }, - { - id: 'hello-pizza-789', - attributes: { name: 'Veggie C', appId: 'pizza', status: STATUS.CANCELLED }, - }, - { - id: 'hello-pizza-999', - attributes: { name: 'Veggie X', appId: 'pizza', status: STATUS.ERROR }, - }, - ], - } as SavedObjectsFindResponse; - }); - - const api = new SearchSessionsMgmtAPI( - sessionsClient, - mockUrls, - mockCoreSetup.notifications, - mockConfig - ); - const data = await api.fetchTableData(); - expect(data).not.toHaveLength(0); - - expect( - data && - data.map((session) => { - return [session.name, session.status, session.isRestorable]; - }) - ).toMatchInlineSnapshot(` - Array [ - Array [ - "Veggie 1", - "in_progress", - true, - ], - Array [ - "Veggie 2", - "complete", - true, - ], - Array [ - "Veggie B", - "expired", - false, - ], - Array [ - "Veggie C", - "cancelled", - false, - ], - Array [ - "Veggie X", - "error", - false, - ], - ] - `); - }); - test('handle error from sessionsClient response', async () => { sessionsClient.find = jest.fn().mockRejectedValue(new Error('implementation is so bad')); - const api = new SearchSessionsMgmtAPI( - sessionsClient, - mockUrls, - mockCoreSetup.notifications, - mockConfig - ); + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); await api.fetchTableData(); - expect(mockCoreSetup.notifications.toasts.addError).toHaveBeenCalledWith( + expect(mockCoreStart.notifications.toasts.addError).toHaveBeenCalledWith( new Error('implementation is so bad'), { title: 'Failed to refresh the page!' } ); @@ -174,21 +103,20 @@ describe('Search Sessions Management API', () => { }); }); - const api = new SearchSessionsMgmtAPI( - sessionsClient, - mockUrls, - mockCoreSetup.notifications, - mockConfig - ); + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); await api.fetchTableData(); - expect(mockCoreSetup.notifications.toasts.addDanger).toHaveBeenCalledWith( + expect(mockCoreStart.notifications.toasts.addDanger).toHaveBeenCalledWith( 'Fetching the Search Session info timed out after 1 seconds' ); }); }); - describe('delete', () => { + describe('cancel', () => { beforeEach(() => { sessionsClient.find = jest.fn().mockImplementation(async () => { return { @@ -202,35 +130,85 @@ describe('Search Sessions Management API', () => { }); }); - test('send delete calls the delete endpoint with a session ID', async () => { - const api = new SearchSessionsMgmtAPI( - sessionsClient, - mockUrls, - mockCoreSetup.notifications, - mockConfig - ); - await api.sendDelete('abc-123-cool-session-ID'); + test('send cancel calls the cancel endpoint with a session ID', async () => { + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + await api.sendCancel('abc-123-cool-session-ID'); - expect(mockCoreSetup.notifications.toasts.addSuccess).toHaveBeenCalledWith({ - title: 'Deleted the session', + expect(mockCoreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith({ + title: 'The search session was canceled and expired.', }); }); test('error if deleting shows a toast message', async () => { sessionsClient.delete = jest.fn().mockRejectedValue(new Error('implementation is so bad')); - const api = new SearchSessionsMgmtAPI( - sessionsClient, - mockUrls, - mockCoreSetup.notifications, - mockConfig - ); - await api.sendDelete('abc-123-cool-session-ID'); + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + await api.sendCancel('abc-123-cool-session-ID'); - expect(mockCoreSetup.notifications.toasts.addError).toHaveBeenCalledWith( + expect(mockCoreStart.notifications.toasts.addError).toHaveBeenCalledWith( new Error('implementation is so bad'), - { title: 'Failed to delete the session!' } + { title: 'Failed to cancel the search session!' } ); }); }); + + describe('reload', () => { + beforeEach(() => { + sessionsClient.find = jest.fn().mockImplementation(async () => { + return { + saved_objects: [ + { + id: 'hello-pizza-123', + attributes: { name: 'Veggie', appId: 'pizza', status: SearchSessionStatus.COMPLETE }, + }, + ], + } as SavedObjectsFindResponse; + }); + }); + + test('send cancel calls the cancel endpoint with a session ID', async () => { + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + await api.reloadSearchSession('www.myurl.com'); + + expect(mockCoreStart.application.navigateToUrl).toHaveBeenCalledWith('www.myurl.com'); + }); + }); + + describe('extend', () => { + beforeEach(() => { + sessionsClient.find = jest.fn().mockImplementation(async () => { + return { + saved_objects: [ + { + id: 'hello-pizza-123', + attributes: { name: 'Veggie', appId: 'pizza', status: SearchSessionStatus.COMPLETE }, + }, + ], + } as SavedObjectsFindResponse; + }); + }); + + test('send extend throws an error for now', async () => { + const api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); + await api.sendExtend('my-id', '5d'); + + expect(mockCoreStart.notifications.toasts.addError).toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index 2f9fcfbf73de1..a3702ad255b46 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -5,26 +5,50 @@ */ import { i18n } from '@kbn/i18n'; -import type { NotificationsStart } from 'kibana/public'; +import type { ApplicationStart, NotificationsStart, SavedObject } from 'kibana/public'; import moment from 'moment'; import { from, race, timer } from 'rxjs'; import { mapTo, tap } from 'rxjs/operators'; import type { SharePluginStart } from 'src/plugins/share/public'; import { SessionsMgmtConfigSchema } from '../'; import type { ISessionsClient } from '../../../../../../../src/plugins/data/public'; -import type { SearchSessionSavedObject } from '../../../../common'; -import { ACTION, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; +import type { SearchSessionSavedObjectAttributes } from '../../../../common'; +import { ACTION, SearchSessionStatus, UISession } from '../../../../common/search'; type UrlGeneratorsStart = SharePluginStart['urlGenerators']; +function getActions(status: SearchSessionStatus) { + const actions: ACTION[] = []; + actions.push(ACTION.RELOAD); + if (status === SearchSessionStatus.IN_PROGRESS || status === SearchSessionStatus.COMPLETE) { + actions.push(ACTION.EXTEND); + actions.push(ACTION.CANCEL); + } + return actions; +} + +async function getUrlFromState( + urls: UrlGeneratorsStart, + urlGeneratorId: string, + state: Record +) { + let url = '/'; + try { + url = await urls.getUrlGenerator(urlGeneratorId).createUrl(state); + } catch (err) { + // eslint-disable-next-line no-console + console.error('Could not create URL from restoreState'); + // eslint-disable-next-line no-console + console.error(err); + } + return url; +} + // Helper: factory for a function to map server objects to UI objects const mapToUISession = ( urls: UrlGeneratorsStart, { expiresSoonWarning }: SessionsMgmtConfigSchema -) => async (savedObject: SearchSessionSavedObject): Promise => { - // Actions: always allow delete - const actions = [ACTION.DELETE]; - +) => async (savedObject: SavedObject): Promise => { const { name, appId, @@ -36,40 +60,38 @@ const mapToUISession = ( restoreState, } = savedObject.attributes; - const isRestorable = status === STATUS.IN_PROGRESS || status === STATUS.COMPLETE; + const actions = getActions(status); + // TODO: initialState should be saved without the searchSessionID + if (initialState) delete initialState.searchSessionId; // derive the URL and add it in - let url = '/'; - try { - url = await urls - .getUrlGenerator(urlGeneratorId) - .createUrl(isRestorable ? restoreState : initialState); - } catch (err) { - // eslint-disable-next-line no-console - console.error('Could not create URL from restoreState'); - // eslint-disable-next-line no-console - console.error(err); - } + const reloadUrl = await getUrlFromState(urls, urlGeneratorId, initialState); + const restoreUrl = await getUrlFromState(urls, urlGeneratorId, restoreState); return { id: savedObject.id, - isRestorable, name, appId, created, expires, status, actions, - url, + restoreUrl, + reloadUrl, }; }; +interface SearcgSessuibManagementDeps { + urls: UrlGeneratorsStart; + notifications: NotificationsStart; + application: ApplicationStart; +} + export class SearchSessionsMgmtAPI { constructor( private sessionsClient: ISessionsClient, - private urls: UrlGeneratorsStart, - private notifications: NotificationsStart, - private config: SessionsMgmtConfigSchema + private config: SessionsMgmtConfigSchema, + private deps: SearcgSessuibManagementDeps ) {} public async fetchTableData(): Promise { @@ -89,7 +111,7 @@ export class SearchSessionsMgmtAPI { ); const timeout$ = timer(refreshTimeout.asMilliseconds()).pipe( tap(() => { - this.notifications.toasts.addDanger( + this.deps.notifications.toasts.addDanger( i18n.translate('xpack.data.mgmt.searchSessions.api.fetchTimeout', { defaultMessage: 'Fetching the Search Session info timed out after {timeout} seconds', values: { timeout: refreshTimeout.asSeconds() }, @@ -103,13 +125,15 @@ export class SearchSessionsMgmtAPI { try { const result = await race(fetch$, timeout$).toPromise(); if (result && result.saved_objects) { - const savedObjects = result.saved_objects as SearchSessionSavedObject[]; - return await Promise.all(savedObjects.map(mapToUISession(this.urls, this.config))); + const savedObjects = result.saved_objects as Array< + SavedObject + >; + return await Promise.all(savedObjects.map(mapToUISession(this.deps.urls, this.config))); } } catch (err) { // eslint-disable-next-line no-console console.error(err); - this.notifications.toasts.addError(err, { + this.deps.notifications.toasts.addError(err, { title: i18n.translate('xpack.data.mgmt.searchSessions.api.fetchError', { defaultMessage: 'Failed to refresh the page!', }), @@ -119,38 +143,38 @@ export class SearchSessionsMgmtAPI { return []; } - // Delete - public async sendDelete(id: string): Promise { + public reloadSearchSession(reloadUrl: string) { + this.deps.application.navigateToUrl(reloadUrl); + } + + // Cancel and expire + public async sendCancel(id: string): Promise { try { await this.sessionsClient.delete(id); - this.notifications.toasts.addSuccess({ - title: i18n.translate('xpack.data.mgmt.searchSessions.api.deleted', { - defaultMessage: 'Deleted the session', + this.deps.notifications.toasts.addSuccess({ + title: i18n.translate('xpack.data.mgmt.searchSessions.api.canceled', { + defaultMessage: 'The search session was canceled and expired.', }), }); } catch (err) { // eslint-disable-next-line no-console console.error(err); - this.notifications.toasts.addError(err, { - title: i18n.translate('xpack.data.mgmt.searchSessions.api.deleteError', { - defaultMessage: 'Failed to delete the session!', + this.deps.notifications.toasts.addError(err, { + title: i18n.translate('xpack.data.mgmt.searchSessions.api.cancelError', { + defaultMessage: 'Failed to cancel the search session!', }), }); } - - return await this.fetchTableData(); } // Extend - public async sendExtend(id: string): Promise { - this.notifications.toasts.addError(new Error('Not implemented'), { + public async sendExtend(id: string, ttl: string): Promise { + this.deps.notifications.toasts.addError(new Error('Not implemented'), { title: i18n.translate('xpack.data.mgmt.searchSessions.api.extendError', { defaultMessage: 'Failed to extend the session expiration!', }), }); - - return await this.fetchTableData(); } } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts index 8339c1d116d7e..b714297ed9eb1 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts @@ -5,7 +5,7 @@ */ import moment from 'moment'; -import { DATE_STRING_FORMAT } from '../../../../common/search/sessions_mgmt'; +import { DATE_STRING_FORMAT } from '../../../../common/search'; export const dateString = (inputString: string, tz: string): string => { if (inputString == null) { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index 7813a68061e1b..12cd4a205053c 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -13,7 +13,8 @@ import { ReactElement } from 'react'; import { coreMock } from 'src/core/public/mocks'; import { SessionsClient } from 'src/plugins/data/public/search'; import { SessionsMgmtConfigSchema } from '../'; -import { ActionComplete, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; +import { SearchSessionStatus, UISession } from '../../../../common/search'; +import { OnActionComplete } from '../components'; import { mockUrls } from '../__mocks__'; import { SearchSessionsMgmtAPI } from './api'; import { getColumns } from './get_columns'; @@ -23,7 +24,7 @@ let mockCoreStart: CoreStart; let mockConfig: SessionsMgmtConfigSchema; let api: SearchSessionsMgmtAPI; let sessionsClient: SessionsClient; -let handleAction: ActionComplete; +let handleAction: OnActionComplete; let mockSession: UISession; let tz = 'UTC'; @@ -31,21 +32,20 @@ let tz = 'UTC'; describe('Search Sessions Management table column factory', () => { beforeEach(async () => { mockCoreSetup = coreMock.createSetup(); + mockCoreStart = coreMock.createStart(); mockConfig = { expiresSoonWarning: moment.duration(1, 'days'), maxSessions: 2000, refreshInterval: moment.duration(1, 'seconds'), refreshTimeout: moment.duration(10, 'minutes'), }; - sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); - api = new SearchSessionsMgmtAPI( - sessionsClient, - mockUrls, - mockCoreSetup.notifications, - mockConfig - ); + api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { + urls: mockUrls, + notifications: mockCoreStart.notifications, + application: mockCoreStart.application, + }); tz = 'UTC'; handleAction = () => { @@ -55,15 +55,13 @@ describe('Search Sessions Management table column factory', () => { mockSession = { name: 'Cool mock session', id: 'wtywp9u2802hahgp-thao', - url: '/app/great-app-url/#42', + reloadUrl: '/app/great-app-url', + restoreUrl: '/app/great-app-url/#42', appId: 'discovery', - status: STATUS.IN_PROGRESS, + status: SearchSessionStatus.IN_PROGRESS, created: '2020-12-02T00:19:32Z', expires: '2020-12-07T00:19:32Z', - isRestorable: true, }; - - [mockCoreStart] = await mockCoreSetup.getStartServices(); }); test('returns columns', () => { @@ -149,7 +147,7 @@ describe('Search Sessions Management table column factory', () => { EuiTableFieldDataColumnType >; - mockSession.status = 'INVALID' as STATUS; + mockSession.status = 'INVALID' as SearchSessionStatus; const statusLine = mount(status.render!(mockSession.status, mockSession) as ReactElement); // no unhandled error diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index ab5e5214cdef5..4e773f19002c9 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -21,9 +21,9 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; import { SessionsMgmtConfigSchema } from '../'; -import { ActionComplete, STATUS, UISession } from '../../../../common/search/sessions_mgmt'; +import { SearchSessionStatus, UISession } from '../../../../common/search'; import { TableText } from '../components'; -import { PopoverActionsMenu } from '../components/actions'; +import { OnActionComplete, PopoverActionsMenu } from '../components'; import { StatusIndicator } from '../components/status'; import { dateString } from '../lib/date_string'; import { SearchSessionsMgmtAPI } from './api'; @@ -37,12 +37,16 @@ const appToIcon = (app: string) => { return app; }; +function isSessionRestorable(status: SearchSessionStatus) { + return status === SearchSessionStatus.IN_PROGRESS || status === SearchSessionStatus.COMPLETE; +} + export const getColumns = ( core: CoreStart, api: SearchSessionsMgmtAPI, config: SessionsMgmtConfigSchema, timezone: string, - handleAction: ActionComplete + onActionComplete: OnActionComplete ): Array> => { // Use a literal array of table column definitions to detail a UISession object return [ @@ -75,7 +79,8 @@ export const getColumns = ( }), sortable: true, width: '20%', - render: (name: UISession['name'], { isRestorable, url }) => { + render: (name: UISession['name'], { restoreUrl, reloadUrl, status }) => { + const isRestorable = isSessionRestorable(status); const notRestorableWarning = isRestorable ? null : ( <> {' '} @@ -92,7 +97,10 @@ export const getColumns = ( ); return ( - + {name} {notRestorableWarning} @@ -146,7 +154,11 @@ export const getColumns = ( }), sortable: true, render: (expires: UISession['expires'], { id, status }) => { - if (expires && status !== STATUS.IN_PROGRESS && status !== STATUS.ERROR) { + if ( + expires && + status !== SearchSessionStatus.IN_PROGRESS && + status !== SearchSessionStatus.ERROR + ) { try { const expiresOn = dateString(expires, timezone); @@ -207,7 +219,7 @@ export const getColumns = ( api={api} key={`popkey-${session.id}`} session={session} - handleAction={handleAction} + onActionComplete={onActionComplete} /> From 49a94334fdea2712ba6251ca52b280f0a4559eb5 Mon Sep 17 00:00:00 2001 From: Liza K Date: Sun, 17 Jan 2021 19:13:51 +0200 Subject: [PATCH 47/52] Added functional tests for restoring, reloading and canceling a dashboard Renamed search session test service --- .../components/actions/popover_actions.tsx | 2 +- .../sessions_mgmt/components/status.test.tsx | 6 +- .../sessions_mgmt/components/status.tsx | 5 +- .../components/table/table.test.tsx | 2 +- .../sessions_mgmt/components/table/table.tsx | 5 +- .../sessions_mgmt/lib/get_columns.test.tsx | 2 +- .../search/sessions_mgmt/lib/get_columns.tsx | 14 +- x-pack/test/functional/page_objects/index.ts | 2 + .../search_sessions_management_page.ts | 60 +++++++ .../services/index.ts | 2 +- .../services/send_to_background.ts | 26 +-- .../async_search/send_to_background.ts | 20 +-- .../send_to_background_relative_time.ts | 10 +- .../async_search/sessions_in_space.ts | 10 +- .../tests/apps/discover/sessions_in_space.ts | 10 +- .../search_sessions/sessions_management.ts | 167 ++++++++++-------- 16 files changed, 211 insertions(+), 132 deletions(-) create mode 100644 x-pack/test/functional/page_objects/search_sessions_management_page.ts diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx index 45ac11d20ba7e..fa4e9a3649f7b 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx @@ -107,7 +107,7 @@ export const PopoverActionsMenu = ({ api, onActionComplete, session }: PopoverAc {label} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx index 3be14ce2d89e0..d1400cf6b4bf3 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx @@ -59,7 +59,7 @@ describe('Background Search Session management status labels', () => { ); const label = statusIndicator.find( - `.euiText[data-test-subj="session-mgmt-view-status-label-in_progress"]` + `.euiText[data-test-subj="sessionManagementStatusLabel"][data-status="in_progress"]` ); expect(label.text()).toMatchInlineSnapshot(`"In progress"`); }); @@ -74,7 +74,7 @@ describe('Background Search Session management status labels', () => { ); const label = statusIndicator - .find(`[data-test-subj="session-mgmt-view-status-label-complete"]`) + .find(`[data-test-subj="sessionManagementStatusLabel"][data-status="complete"]`) .first(); expect((label.props() as EuiTextProps).color).toBe('secondary'); expect(label.text()).toBe('Complete'); @@ -105,7 +105,7 @@ describe('Background Search Session management status labels', () => { ); const label = statusIndicator - .find(`[data-test-subj="session-mgmt-view-status-label-expired"]`) + .find(`[data-test-subj="sessionManagementStatusLabel"][data-status="expired"]`) .first(); expect(label.text()).toBe('Expired'); }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx index 5785989d11ae2..08c94988dee34 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -170,7 +170,7 @@ export const StatusIndicator = (props: StatusIndicatorProps) => { if (toolTipContent) { label = ( - + {statusDef.label} @@ -183,7 +183,8 @@ export const StatusIndicator = (props: StatusIndicatorProps) => { {label} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index b87251897148f..a96d81bc5f8b6 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -177,7 +177,7 @@ describe('Background Search Session Management Table', () => { ); - const buttonSelector = `[data-test-subj="session-mgmt-table-btn-refresh"] button`; + const buttonSelector = `[data-test-subj="sessionManagementRefreshBtn"] button`; await waitFor(() => { table.find(buttonSelector).first().simulate('click'); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index cb99efeec7053..9f8150dd616ef 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -91,7 +91,7 @@ export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props onClick={doRefresh} disabled={debouncedIsLoading} isLoading={debouncedIsLoading} - data-test-subj="session-mgmt-table-btn-refresh" + data-test-subj="sessionManagementRefreshBtn" > ({ + 'data-test-subj': 'searchSessionsRow', + })} columns={getColumns(core, api, config, timezone, onActionComplete)} items={tableData} pagination={pagination} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index 12cd4a205053c..eca5f543309e2 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -137,7 +137,7 @@ describe('Search Sessions Management table column factory', () => { const statusLine = mount(status.render!(mockSession.status, mockSession) as ReactElement); expect( statusLine - .find('.euiText[data-test-subj="session-mgmt-view-status-tooltip-in_progress"]') + .find('.euiText[data-test-subj="sessionManagementStatusTooltip--in_progress"]') .text() ).toMatchInlineSnapshot(`"In progress"`); }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index 4e773f19002c9..b0411ecb8c43f 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -62,7 +62,7 @@ export const getColumns = ( return ( @@ -99,7 +99,7 @@ export const getColumns = ( {name} @@ -134,7 +134,7 @@ export const getColumns = ( try { const startedOn = dateString(created, timezone); return ( - + {startedOn} ); @@ -164,7 +164,7 @@ export const getColumns = ( // return return ( - + {expiresOn} ); @@ -175,7 +175,7 @@ export const getColumns = ( } } return ( - + -- ); @@ -194,7 +194,7 @@ export const getColumns = ( return ( - + {statusContent} @@ -214,7 +214,7 @@ export const getColumns = ( if (actions && actions.length) { return ( - + { + const $ = await row.parseDomContent(); + const viewCell = await row.findByTestSubject('sessionManagementNameCol'); + const actionsCell = await row.findByTestSubject('sessionManagementActionsCol'); + return { + name: $.findTestSubject('sessionManagementNameCol').text(), + status: $.findTestSubject('sessionManagementStatusLabel').attr('data-test-status'), + mainUrl: $.findTestSubject('sessionManagementNameCol').text(), + created: $.findTestSubject('sessionManagementCreatedCol').text(), + expires: $.findTestSubject('sessionManagementExpiresCol').text(), + app: $.findTestSubject('sessionManagementAppIcon').attr('data-test-app-id'), + view: async () => { + await viewCell.click(); + }, + reload: async () => { + await actionsCell.click(); + await find.clickByCssSelector( + '[data-test-subj="sessionManagementPopoverAction-reload"]' + ); + }, + cancel: async () => { + await actionsCell.click(); + await find.clickByCssSelector( + '[data-test-subj="sessionManagementPopoverAction-cancel"]' + ); + await PageObjects.common.clickConfirmOnModal(); + }, + }; + }) + ); + }, + }; +} diff --git a/x-pack/test/send_search_to_background_integration/services/index.ts b/x-pack/test/send_search_to_background_integration/services/index.ts index 91b0ad502d053..35eed5a218b42 100644 --- a/x-pack/test/send_search_to_background_integration/services/index.ts +++ b/x-pack/test/send_search_to_background_integration/services/index.ts @@ -9,5 +9,5 @@ import { SendToBackgroundProvider } from './send_to_background'; export const services = { ...functionalServices, - sendToBackground: SendToBackgroundProvider, + searchSessions: SendToBackgroundProvider, }; diff --git a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts index 85db45acbc3c2..100a09426049c 100644 --- a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts @@ -8,8 +8,8 @@ import { SavedObjectsFindResponse } from 'src/core/server'; import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; import { FtrProviderContext } from '../ftr_provider_context'; -const SEND_TO_BACKGROUND_TEST_SUBJ = 'searchSessionIndicator'; -const SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ = 'searchSessionIndicatorPopoverContainer'; +const SEARCH_SESSION_INDICATOR_TEST_SUBJ = 'searchSessionIndicator'; +const SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ = 'searchSessionIndicatorPopoverContainer'; type SessionStateType = | 'none' @@ -29,17 +29,17 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { return new (class SendToBackgroundService { public async find(): Promise { - return testSubjects.find(SEND_TO_BACKGROUND_TEST_SUBJ); + return testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ); } public async exists(): Promise { - return testSubjects.exists(SEND_TO_BACKGROUND_TEST_SUBJ); + return testSubjects.exists(SEARCH_SESSION_INDICATOR_TEST_SUBJ); } public async expectState(state: SessionStateType) { - return retry.waitFor(`sendToBackground indicator to get into state = ${state}`, async () => { + return retry.waitFor(`searchSessions indicator to get into state = ${state}`, async () => { const currentState = await ( - await testSubjects.find(SEND_TO_BACKGROUND_TEST_SUBJ) + await testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ) ).getAttribute('data-state'); return currentState === state; }); @@ -73,22 +73,22 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { } private async ensurePopoverOpened() { - const isAlreadyOpen = await testSubjects.exists(SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ); + const isAlreadyOpen = await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); if (isAlreadyOpen) return; - return retry.waitFor(`sendToBackground popover opened`, async () => { - await testSubjects.click(SEND_TO_BACKGROUND_TEST_SUBJ); - return await testSubjects.exists(SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ); + return retry.waitFor(`searchSessions popover opened`, async () => { + await testSubjects.click(SEARCH_SESSION_INDICATOR_TEST_SUBJ); + return await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); }); } private async ensurePopoverClosed() { const isAlreadyClosed = !(await testSubjects.exists( - SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ + SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ )); if (isAlreadyClosed) return; - return retry.waitFor(`sendToBackground popover closed`, async () => { + return retry.waitFor(`searchSessions popover closed`, async () => { await browser.pressKeys(browser.keys.ESCAPE); - return !(await testSubjects.exists(SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ)); + return !(await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ)); }); } diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts index aa9185adfcf60..03635efb6113d 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts @@ -14,7 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'dashboard', 'visChart']); const dashboardPanelActions = getService('dashboardPanelActions'); const browser = getService('browser'); - const sendToBackground = getService('sendToBackground'); + const searchSessions = getService('searchSessions'); describe('send to background', () => { before(async function () { @@ -27,7 +27,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async function () { - await sendToBackground.deleteAllSearchSessions(); + await searchSessions.deleteAllSearchSessions(); }); it('Restore using non-existing sessionId errors out. Refresh starts a new session and completes.', async () => { @@ -37,7 +37,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const savedSessionURL = `${url}&searchSessionId=${fakeSessionId}`; await browser.get(savedSessionURL); await PageObjects.header.waitUntilLoadingHasFinished(); - await sendToBackground.expectState('restored'); + await searchSessions.expectState('restored'); await testSubjects.existOrFail('embeddableErrorLabel'); // expected that panel errors out because of non existing session const session1 = await dashboardPanelActions.getSearchSessionIdByTitle( @@ -45,9 +45,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); expect(session1).to.be(fakeSessionId); - await sendToBackground.refresh(); + await searchSessions.refresh(); await PageObjects.header.waitUntilLoadingHasFinished(); - await sendToBackground.expectState('completed'); + await searchSessions.expectState('completed'); await testSubjects.missingOrFail('embeddableErrorLabel'); const session2 = await dashboardPanelActions.getSearchSessionIdByTitle( 'Sum of Bytes by Extension' @@ -58,9 +58,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('Saves and restores a session', async () => { await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); await PageObjects.dashboard.waitForRenderComplete(); - await sendToBackground.expectState('completed'); - await sendToBackground.save(); - await sendToBackground.expectState('backgroundCompleted'); + await searchSessions.expectState('completed'); + await searchSessions.save(); + await searchSessions.expectState('backgroundCompleted'); const savedSessionId = await dashboardPanelActions.getSearchSessionIdByTitle( 'Sum of Bytes by Extension' ); @@ -73,7 +73,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.waitForRenderComplete(); // Check that session is restored - await sendToBackground.expectState('restored'); + await searchSessions.expectState('restored'); await testSubjects.missingOrFail('embeddableErrorLabel'); const data = await PageObjects.visChart.getBarChartData('Sum of bytes'); expect(data.length).to.be(5); @@ -81,7 +81,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // switching dashboard to edit mode (or any other non-fetch required) state change // should leave session state untouched await PageObjects.dashboard.switchToEditMode(); - await sendToBackground.expectState('restored'); + await searchSessions.expectState('restored'); }); }); } diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background_relative_time.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background_relative_time.ts index 2234ca3e3b034..16a11e13de786 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background_relative_time.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background_relative_time.ts @@ -26,7 +26,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardExpect = getService('dashboardExpect'); const queryBar = getService('queryBar'); const browser = getService('browser'); - const sendToBackground = getService('sendToBackground'); + const searchSessions = getService('searchSessions'); describe('send to background with relative time', () => { before(async () => { @@ -63,9 +63,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.waitForRenderComplete(); await checkSampleDashboardLoaded(); - await sendToBackground.expectState('completed'); - await sendToBackground.save(); - await sendToBackground.expectState('backgroundCompleted'); + await searchSessions.expectState('completed'); + await searchSessions.save(); + await searchSessions.expectState('backgroundCompleted'); const savedSessionId = await dashboardPanelActions.getSearchSessionIdByTitle( '[Flights] Airline Carrier' ); @@ -83,7 +83,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await checkSampleDashboardLoaded(); // Check that session is restored - await sendToBackground.expectState('restored'); + await searchSessions.expectState('restored'); }); }); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts index 7d00761b2fa9f..f590e44138642 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts @@ -20,7 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ]); const dashboardPanelActions = getService('dashboardPanelActions'); const browser = getService('browser'); - const sendToBackground = getService('sendToBackground'); + const searchSessions = getService('searchSessions'); describe('dashboard in space', () => { describe('Send to background in space', () => { @@ -73,9 +73,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.waitForRenderComplete(); - await sendToBackground.expectState('completed'); - await sendToBackground.save(); - await sendToBackground.expectState('backgroundCompleted'); + await searchSessions.expectState('completed'); + await searchSessions.save(); + await searchSessions.expectState('backgroundCompleted'); const savedSessionId = await dashboardPanelActions.getSearchSessionIdByTitle( 'A Pie in another space' ); @@ -88,7 +88,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.waitForRenderComplete(); // Check that session is restored - await sendToBackground.expectState('restored'); + await searchSessions.expectState('restored'); await testSubjects.missingOrFail('embeddableErrorLabel'); }); }); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts b/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts index 5c94a50e0a84d..6384afb179593 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts @@ -20,7 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'timePicker', ]); const browser = getService('browser'); - const sendToBackground = getService('sendToBackground'); + const searchSessions = getService('searchSessions'); describe('discover in space', () => { describe('Send to background in space', () => { @@ -74,9 +74,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitForDocTableLoadingComplete(); - await sendToBackground.expectState('completed'); - await sendToBackground.save(); - await sendToBackground.expectState('backgroundCompleted'); + await searchSessions.expectState('completed'); + await searchSessions.save(); + await searchSessions.expectState('backgroundCompleted'); await inspector.open(); const savedSessionId = await ( @@ -92,7 +92,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitForDocTableLoadingComplete(); // Check that session is restored - await sendToBackground.expectState('restored'); + await searchSessions.expectState('restored'); await testSubjects.missingOrFail('embeddableErrorLabel'); }); }); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts index abc61b55a6c91..94aaf7d7d1631 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts @@ -9,9 +9,16 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'header', 'dashboard', 'visChart']); - const sendToBackground = getService('sendToBackground'); + const PageObjects = getPageObjects([ + 'common', + 'header', + 'dashboard', + 'visChart', + 'searchSessionsManagement', + ]); + const searchSessions = getService('searchSessions'); const esArchiver = getService('esArchiver'); + const retry = getService('retry'); describe('Search search sessions Management UI', () => { describe('New search sessions', () => { @@ -20,113 +27,119 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { - await sendToBackground.deleteAllSearchSessions(); + await searchSessions.deleteAllSearchSessions(); }); it('Saves a session and verifies it in the Management app', async () => { await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); await PageObjects.dashboard.waitForRenderComplete(); - await sendToBackground.expectState('completed'); - await sendToBackground.save(); - await sendToBackground.expectState('backgroundCompleted'); + await searchSessions.expectState('completed'); + await searchSessions.save(); + await searchSessions.expectState('backgroundCompleted'); - await sendToBackground.openPopover(); - await sendToBackground.viewSearchSessions(); + await searchSessions.openPopover(); + await searchSessions.viewSearchSessions(); - // wait until completed - await testSubjects.existOrFail('session-mgmt-view-status-label-complete'); - await testSubjects.existOrFail('session-mgmt-view-status-tooltip-complete'); - await testSubjects.existOrFail('session-mgmt-table-col-created'); + await retry.waitFor(`wait for first item to complete`, async function () { + const s = await PageObjects.searchSessionsManagement.getList(); + return s[0] && s[0].status === 'complete'; + }); // find there is only one item in the table which is the newly saved session - const names = await testSubjects.findAll('session-mgmt-table-col-name'); - expect(names.length).to.be(1); - expect(await Promise.all(names.map((n) => n.getVisibleText()))).to.eql(['Not Delayed']); + const searchSessionList = await PageObjects.searchSessionsManagement.getList(); + expect(searchSessionList.length).to.be(1); + expect(searchSessionList[0].expires).not.to.eql('--'); + expect(searchSessionList[0].name).to.eql('Not Delayed'); // navigate to dashboard - await testSubjects.click('session-mgmt-table-col-name'); + await searchSessionList[0].view(); // embeddable has loaded await testSubjects.existOrFail('embeddablePanelHeading-SumofBytesbyExtension'); await PageObjects.dashboard.waitForRenderComplete(); - // background session was restored - await sendToBackground.expectState('restored'); + // search session was restored + await searchSessions.expectState('restored'); + }); + + it('Reloads as new session from management', async () => { + await PageObjects.searchSessionsManagement.goTo(); + + const searchSessionList = await PageObjects.searchSessionsManagement.getList(); + + expect(searchSessionList.length).to.be(1); + await searchSessionList[0].reload(); + + // embeddable has loaded + await PageObjects.dashboard.waitForRenderComplete(); + + // new search session was completed + await searchSessions.expectState('completed'); + }); + + it('Cancels a session from management', async () => { + await PageObjects.searchSessionsManagement.goTo(); + + const searchSessionList = await PageObjects.searchSessionsManagement.getList(); + + expect(searchSessionList.length).to.be(1); + await searchSessionList[0].cancel(); + + // TODO: update this once canceling doesn't delete the object! + await retry.waitFor(`wait for list to be empty`, async function () { + const s = await PageObjects.searchSessionsManagement.getList(); + + return s.length === 0; + }); }); }); describe('Archived search sessions', () => { before(async () => { - await PageObjects.common.navigateToApp('management/kibana/search_sessions'); + await PageObjects.searchSessionsManagement.goTo(); }); after(async () => { - await sendToBackground.deleteAllSearchSessions(); + await searchSessions.deleteAllSearchSessions(); }); it('shows no items found', async () => { - expectSnapshot( - await testSubjects.find('searchSessionsMgmtTable').then((n) => n.getVisibleText()) - ).toMatchInline(` - "App - Name - Status - Created - Expiration - No items found" - `); + const searchSessionList = await PageObjects.searchSessionsManagement.getList(); + expect(searchSessionList.length).to.be(0); }); it('autorefreshes and shows items on the server', async () => { await esArchiver.load('data/search_sessions'); - const nameColumnText = await testSubjects - .findAll('session-mgmt-table-col-name') - .then((nCol) => Promise.all(nCol.map((n) => n.getVisibleText()))); - - expect(nameColumnText.length).to.be(10); - - const createdColText = await testSubjects - .findAll('session-mgmt-table-col-created') - .then((nCol) => Promise.all(nCol.map((n) => n.getVisibleText()))); - - expect(createdColText.length).to.be(10); - - expectSnapshot(createdColText).toMatchInline(` - Array [ - "25 Dec, 2020, 00:00:00", - "24 Dec, 2020, 00:00:00", - "23 Dec, 2020, 00:00:00", - "22 Dec, 2020, 00:00:00", - "21 Dec, 2020, 00:00:00", - "20 Dec, 2020, 00:00:00", - "19 Dec, 2020, 00:00:00", - "18 Dec, 2020, 00:00:00", - "17 Dec, 2020, 00:00:00", - "16 Dec, 2020, 00:00:00", - ] - `); - - const expiresColText = await testSubjects - .findAll('session-mgmt-table-col-expires') - .then((nCol) => Promise.all(nCol.map((n) => n.getVisibleText()))); - - expect(expiresColText.length).to.be(10); - - expectSnapshot(expiresColText).toMatchInline(` - Array [ - "--", - "25 Dec, 2020, 00:00:00", - "24 Dec, 2020, 00:00:00", - "23 Dec, 2020, 00:00:00", - "--", - "--", - "20 Dec, 2020, 00:00:00", - "19 Dec, 2020, 00:00:00", - "18 Dec, 2020, 00:00:00", - "--", - ] - `); + const searchSessionList = await PageObjects.searchSessionsManagement.getList(); + + expect(searchSessionList.length).to.be(10); + + expect(searchSessionList.map((ss) => ss.created)).to.eql([ + '25 Dec, 2020, 00:00:00', + '24 Dec, 2020, 00:00:00', + '23 Dec, 2020, 00:00:00', + '22 Dec, 2020, 00:00:00', + '21 Dec, 2020, 00:00:00', + '20 Dec, 2020, 00:00:00', + '19 Dec, 2020, 00:00:00', + '18 Dec, 2020, 00:00:00', + '17 Dec, 2020, 00:00:00', + '16 Dec, 2020, 00:00:00', + ]); + + expect(searchSessionList.map((ss) => ss.expires)).to.eql([ + '--', + '25 Dec, 2020, 00:00:00', + '24 Dec, 2020, 00:00:00', + '23 Dec, 2020, 00:00:00', + '--', + '--', + '20 Dec, 2020, 00:00:00', + '19 Dec, 2020, 00:00:00', + '18 Dec, 2020, 00:00:00', + '--', + ]); await esArchiver.unload('data/search_sessions'); }); From 706129c821a3ca5de6ae943673ecd971e4e768df Mon Sep 17 00:00:00 2001 From: Liza K Date: Sun, 17 Jan 2021 19:41:02 +0200 Subject: [PATCH 48/52] Don't show expiration time for canceled, expired or errored sessions --- .../public/search/sessions_mgmt/lib/get_columns.tsx | 3 ++- .../management/search_sessions/sessions_management.ts | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index b0411ecb8c43f..3810c8a7b19b2 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -156,7 +156,8 @@ export const getColumns = ( render: (expires: UISession['expires'], { id, status }) => { if ( expires && - status !== SearchSessionStatus.IN_PROGRESS && + status !== SearchSessionStatus.EXPIRED && + status !== SearchSessionStatus.CANCELLED && status !== SearchSessionStatus.ERROR ) { try { diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts index 94aaf7d7d1631..f06e8eba0bf68 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts @@ -130,15 +130,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(searchSessionList.map((ss) => ss.expires)).to.eql([ '--', - '25 Dec, 2020, 00:00:00', - '24 Dec, 2020, 00:00:00', + '--', + '--', '23 Dec, 2020, 00:00:00', + '22 Dec, 2020, 00:00:00', '--', '--', - '20 Dec, 2020, 00:00:00', - '19 Dec, 2020, 00:00:00', - '18 Dec, 2020, 00:00:00', '--', + '18 Dec, 2020, 00:00:00', + '17 Dec, 2020, 00:00:00', ]); await esArchiver.unload('data/search_sessions'); From b7d91eea40fd65632885c7fdf479a311078ccdcb Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 18 Jan 2021 10:18:45 +0200 Subject: [PATCH 49/52] fix jest --- .../public/search/sessions_mgmt/components/status.test.tsx | 6 +++--- .../search/sessions_mgmt/components/table/table.test.tsx | 2 +- .../public/search/sessions_mgmt/lib/get_columns.test.tsx | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx index d1400cf6b4bf3..45a1f3e1b3ba2 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx @@ -59,7 +59,7 @@ describe('Background Search Session management status labels', () => { ); const label = statusIndicator.find( - `.euiText[data-test-subj="sessionManagementStatusLabel"][data-status="in_progress"]` + `.euiText[data-test-subj="sessionManagementStatusLabel"][data-test-status="in_progress"]` ); expect(label.text()).toMatchInlineSnapshot(`"In progress"`); }); @@ -74,7 +74,7 @@ describe('Background Search Session management status labels', () => { ); const label = statusIndicator - .find(`[data-test-subj="sessionManagementStatusLabel"][data-status="complete"]`) + .find(`[data-test-subj="sessionManagementStatusLabel"][data-test-status="complete"]`) .first(); expect((label.props() as EuiTextProps).color).toBe('secondary'); expect(label.text()).toBe('Complete'); @@ -105,7 +105,7 @@ describe('Background Search Session management status labels', () => { ); const label = statusIndicator - .find(`[data-test-subj="sessionManagementStatusLabel"][data-status="expired"]`) + .find(`[data-test-subj="sessionManagementStatusLabel"][data-test-status="expired"]`) .first(); expect(label.text()).toBe('Expired'); }); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx index a96d81bc5f8b6..357f17649394b 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.test.tsx @@ -119,7 +119,7 @@ describe('Background Search Session Management Table', () => { "Namevery background search", "StatusIn progress", "Created2 Dec, 2020, 00:19:32", - "Expiration--", + "Expiration7 Dec, 2020, 00:19:32", "", "", ] diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index eca5f543309e2..79b29ac6b33f6 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -136,9 +136,7 @@ describe('Search Sessions Management table column factory', () => { const statusLine = mount(status.render!(mockSession.status, mockSession) as ReactElement); expect( - statusLine - .find('.euiText[data-test-subj="sessionManagementStatusTooltip--in_progress"]') - .text() + statusLine.find('.euiText[data-test-subj="sessionManagementStatusTooltip"]').text() ).toMatchInlineSnapshot(`"In progress"`); }); From ba11297088b71aceb83695f53bec947e0eff87e8 Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 18 Jan 2021 11:23:36 +0200 Subject: [PATCH 50/52] Moved UISession to public --- .../data_enhanced/common/search/index.ts | 1 - .../common/search/sessions_mgmt/constants.ts | 13 ----------- .../common/search/sessions_mgmt/index.ts | 22 ------------------- .../components/actions/get_action.tsx | 3 +-- .../components/actions/index.tsx | 2 +- .../components/actions/popover_actions.tsx | 3 +-- .../sessions_mgmt/components/actions/types.ts | 22 +++++++++++++++++++ .../sessions_mgmt/components/status.test.tsx | 3 ++- .../sessions_mgmt/components/status.tsx | 3 ++- .../components/table/app_filter.tsx | 2 +- .../components/table/status_filter.tsx | 2 +- .../sessions_mgmt/components/table/table.tsx | 3 +-- .../public/search/sessions_mgmt/lib/api.ts | 3 ++- .../search/sessions_mgmt/lib/date_string.ts | 2 +- .../sessions_mgmt/lib/get_columns.test.tsx | 3 ++- .../search/sessions_mgmt/lib/get_columns.tsx | 3 ++- 16 files changed, 39 insertions(+), 51 deletions(-) delete mode 100644 x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts delete mode 100644 x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts diff --git a/x-pack/plugins/data_enhanced/common/search/index.ts b/x-pack/plugins/data_enhanced/common/search/index.ts index cf4c3e08ce3ef..5617a1780c269 100644 --- a/x-pack/plugins/data_enhanced/common/search/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/index.ts @@ -7,4 +7,3 @@ export * from './types'; export * from './poll_search'; export * from './session'; -export * from './sessions_mgmt'; diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts deleted file mode 100644 index 2ed8cbed59329..0000000000000 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/constants.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export enum ACTION { - EXTEND = 'extend', - CANCEL = 'cancel', - RELOAD = 'reload', -} - -export const DATE_STRING_FORMAT = 'D MMM, YYYY, HH:mm:ss'; diff --git a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts deleted file mode 100644 index 7172e2cfaa294..0000000000000 --- a/x-pack/plugins/data_enhanced/common/search/sessions_mgmt/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ACTION } from './constants'; -import { SearchSessionStatus } from '..'; - -export interface UISession { - id: string; - name: string; - appId: string; - created: string; - expires: string | null; - status: SearchSessionStatus; - actions?: ACTION[]; - reloadUrl: string; - restoreUrl: string; -} - -export * from './constants'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx index c67c46190088e..6312d4c7d3c68 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx @@ -6,13 +6,12 @@ import React from 'react'; import { IClickActionDescriptor } from '../'; -import { ACTION, UISession } from '../../../../../common/search'; import extendSessionIcon from '../../icons/extend_session.svg'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { CancelButton } from './cancel_button'; import { ExtendButton } from './extend_button'; import { ReloadButton } from './reload_button'; -import { OnActionComplete } from './types'; +import { ACTION, UISession, OnActionComplete } from './types'; export const getAction = ( api: SearchSessionsMgmtAPI, diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx index a18d905131567..82b4d84aa7ea2 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/index.tsx @@ -5,4 +5,4 @@ */ export { PopoverActionsMenu } from './popover_actions'; -export { OnActionComplete } from './types'; +export * from './types'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx index fa4e9a3649f7b..8203144eb5a3b 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx @@ -28,10 +28,9 @@ import { import { i18n } from '@kbn/i18n'; import React, { ReactElement, useState } from 'react'; import { TableText } from '../'; -import { ACTION, UISession } from '../../../../../common/search'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { getAction } from './get_action'; -import { OnActionComplete } from './types'; +import { ACTION, UISession, OnActionComplete } from './types'; // interfaces interface PopoverActionProps { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts index 7d761cec46df0..b2124a641a9aa 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts @@ -4,4 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SearchSessionStatus } from '../../../../../common'; + export type OnActionComplete = () => void; + +export enum ACTION { + EXTEND = 'extend', + CANCEL = 'cancel', + RELOAD = 'reload', +} + +export const DATE_STRING_FORMAT = 'D MMM, YYYY, HH:mm:ss'; + +export interface UISession { + id: string; + name: string; + appId: string; + created: string; + expires: string | null; + status: SearchSessionStatus; + actions?: ACTION[]; + reloadUrl: string; + restoreUrl: string; +} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx index 45a1f3e1b3ba2..6c18dd04d0942 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx @@ -7,8 +7,9 @@ import { EuiTextProps, EuiToolTipProps } from '@elastic/eui'; import { mount } from 'enzyme'; import React from 'react'; -import { SearchSessionStatus, UISession } from '../../../../common/search'; +import { SearchSessionStatus } from '../../../../common/search'; import { LocaleWrapper } from '../__mocks__'; +import { UISession } from './actions'; import { getStatusText, StatusIndicator } from './status'; let tz: string; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx index 08c94988dee34..92ee00c8e8a7f 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -7,9 +7,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { ReactElement } from 'react'; -import { SearchSessionStatus, UISession } from '../../../../common/search'; +import { SearchSessionStatus } from '../../../../common/search'; import { dateString } from '../lib/date_string'; import { StatusDef as StatusAttributes, TableText } from './'; +import { UISession } from './actions'; // Shared helper function export const getStatusText = (statusType: string): string => { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx index 232241569a5c3..595da6628d397 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx @@ -7,7 +7,7 @@ import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { capitalize } from 'lodash'; -import { UISession } from '../../../../../common/search'; +import { UISession } from '../actions'; export const getAppFilter: (tableData: UISession[]) => SearchFilterConfig = (tableData) => ({ type: 'field_value_selection', diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx index c1bb2ea5782ba..7b103d6efe363 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx @@ -8,7 +8,7 @@ import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { TableText } from '../'; -import { UISession } from '../../../../../common/search'; +import { UISession } from '../actions'; import { getStatusText } from '../status'; export const getStatusFilter: (tableData: UISession[]) => SearchFilterConfig = (tableData) => ({ diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index 9f8150dd616ef..dea1813d41326 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -13,10 +13,9 @@ import useDebounce from 'react-use/lib/useDebounce'; import useInterval from 'react-use/lib/useInterval'; import { TableText } from '../'; import { SessionsMgmtConfigSchema } from '../..'; -import { UISession } from '../../../../../common/search'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { getColumns } from '../../lib/get_columns'; -import { OnActionComplete } from '../actions'; +import { OnActionComplete, UISession } from '../actions'; import { getAppFilter } from './app_filter'; import { getStatusFilter } from './status_filter'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index a3702ad255b46..1030c7d2427f1 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -13,7 +13,8 @@ import type { SharePluginStart } from 'src/plugins/share/public'; import { SessionsMgmtConfigSchema } from '../'; import type { ISessionsClient } from '../../../../../../../src/plugins/data/public'; import type { SearchSessionSavedObjectAttributes } from '../../../../common'; -import { ACTION, SearchSessionStatus, UISession } from '../../../../common/search'; +import { SearchSessionStatus } from '../../../../common/search'; +import { ACTION, UISession } from '../components/actions'; type UrlGeneratorsStart = SharePluginStart['urlGenerators']; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts index b714297ed9eb1..dc0b58abba362 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts @@ -5,7 +5,7 @@ */ import moment from 'moment'; -import { DATE_STRING_FORMAT } from '../../../../common/search'; +import { DATE_STRING_FORMAT } from '../components/actions'; export const dateString = (inputString: string, tz: string): string => { if (inputString == null) { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index 79b29ac6b33f6..98585d1d7dcf3 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -13,8 +13,9 @@ import { ReactElement } from 'react'; import { coreMock } from 'src/core/public/mocks'; import { SessionsClient } from 'src/plugins/data/public/search'; import { SessionsMgmtConfigSchema } from '../'; -import { SearchSessionStatus, UISession } from '../../../../common/search'; +import { SearchSessionStatus } from '../../../../common/search'; import { OnActionComplete } from '../components'; +import { UISession } from '../components/actions'; import { mockUrls } from '../__mocks__'; import { SearchSessionsMgmtAPI } from './api'; import { getColumns } from './get_columns'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index 3810c8a7b19b2..c0294989bf1e3 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -21,13 +21,14 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; import { SessionsMgmtConfigSchema } from '../'; -import { SearchSessionStatus, UISession } from '../../../../common/search'; +import { SearchSessionStatus } from '../../../../common/search'; import { TableText } from '../components'; import { OnActionComplete, PopoverActionsMenu } from '../components'; import { StatusIndicator } from '../components/status'; import { dateString } from '../lib/date_string'; import { SearchSessionsMgmtAPI } from './api'; import { getExpirationStatus } from './get_expiration_status'; +import { UISession } from '../components/actions'; // Helper function: translate an app string to EuiIcon-friendly string const appToIcon = (app: string) => { From cdf1af6d1d7f78fab8f6c01e7378db5a74d85ca6 Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 20 Jan 2021 12:28:41 +0200 Subject: [PATCH 51/52] @tsullivan code review --- .../components/actions/get_action.tsx | 3 ++- .../components/actions/popover_actions.tsx | 3 ++- .../sessions_mgmt/components/actions/types.ts | 16 -------------- .../sessions_mgmt/components/status.test.tsx | 2 +- .../sessions_mgmt/components/status.tsx | 2 +- .../components/table/app_filter.tsx | 2 +- .../components/table/status_filter.tsx | 2 +- .../sessions_mgmt/components/table/table.tsx | 5 ++--- .../search/sessions_mgmt/lib/date_string.ts | 2 +- .../sessions_mgmt/lib/get_columns.test.tsx | 2 +- .../search/sessions_mgmt/lib/get_columns.tsx | 2 +- .../public/search/sessions_mgmt/types.ts | 22 +++++++++++++++++++ .../services/send_to_background.ts | 2 +- 13 files changed, 36 insertions(+), 29 deletions(-) create mode 100644 x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx index 6312d4c7d3c68..5bf0fbda5b5cc 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/get_action.tsx @@ -8,10 +8,11 @@ import React from 'react'; import { IClickActionDescriptor } from '../'; import extendSessionIcon from '../../icons/extend_session.svg'; import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { UISession } from '../../types'; import { CancelButton } from './cancel_button'; import { ExtendButton } from './extend_button'; import { ReloadButton } from './reload_button'; -import { ACTION, UISession, OnActionComplete } from './types'; +import { ACTION, OnActionComplete } from './types'; export const getAction = ( api: SearchSessionsMgmtAPI, diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx index 8203144eb5a3b..b9b915c0b17cb 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/popover_actions.tsx @@ -29,8 +29,9 @@ import { i18n } from '@kbn/i18n'; import React, { ReactElement, useState } from 'react'; import { TableText } from '../'; import { SearchSessionsMgmtAPI } from '../../lib/api'; +import { UISession } from '../../types'; import { getAction } from './get_action'; -import { ACTION, UISession, OnActionComplete } from './types'; +import { ACTION, OnActionComplete } from './types'; // interfaces interface PopoverActionProps { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts index b2124a641a9aa..4b81fd7fda9a0 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchSessionStatus } from '../../../../../common'; - export type OnActionComplete = () => void; export enum ACTION { @@ -13,17 +11,3 @@ export enum ACTION { CANCEL = 'cancel', RELOAD = 'reload', } - -export const DATE_STRING_FORMAT = 'D MMM, YYYY, HH:mm:ss'; - -export interface UISession { - id: string; - name: string; - appId: string; - created: string; - expires: string | null; - status: SearchSessionStatus; - actions?: ACTION[]; - reloadUrl: string; - restoreUrl: string; -} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx index 6c18dd04d0942..706001ac42146 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.test.tsx @@ -8,8 +8,8 @@ import { EuiTextProps, EuiToolTipProps } from '@elastic/eui'; import { mount } from 'enzyme'; import React from 'react'; import { SearchSessionStatus } from '../../../../common/search'; +import { UISession } from '../types'; import { LocaleWrapper } from '../__mocks__'; -import { UISession } from './actions'; import { getStatusText, StatusIndicator } from './status'; let tz: string; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx index 92ee00c8e8a7f..8e0946c140287 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/status.tsx @@ -9,8 +9,8 @@ import { i18n } from '@kbn/i18n'; import React, { ReactElement } from 'react'; import { SearchSessionStatus } from '../../../../common/search'; import { dateString } from '../lib/date_string'; +import { UISession } from '../types'; import { StatusDef as StatusAttributes, TableText } from './'; -import { UISession } from './actions'; // Shared helper function export const getStatusText = (statusType: string): string => { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx index 595da6628d397..236fc492031c0 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/app_filter.tsx @@ -7,7 +7,7 @@ import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { capitalize } from 'lodash'; -import { UISession } from '../actions'; +import { UISession } from '../../types'; export const getAppFilter: (tableData: UISession[]) => SearchFilterConfig = (tableData) => ({ type: 'field_value_selection', diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx index 7b103d6efe363..04421ad66e588 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/status_filter.tsx @@ -8,7 +8,7 @@ import { FieldValueOptionType, SearchFilterConfig } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { TableText } from '../'; -import { UISession } from '../actions'; +import { UISession } from '../../types'; import { getStatusText } from '../status'; export const getStatusFilter: (tableData: UISession[]) => SearchFilterConfig = (tableData) => ({ diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx index dea1813d41326..f7aecdbd58a23 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/table/table.tsx @@ -15,7 +15,8 @@ import { TableText } from '../'; import { SessionsMgmtConfigSchema } from '../..'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { getColumns } from '../../lib/get_columns'; -import { OnActionComplete, UISession } from '../actions'; +import { UISession } from '../../types'; +import { OnActionComplete } from '../actions'; import { getAppFilter } from './app_filter'; import { getStatusFilter } from './status_filter'; @@ -72,8 +73,6 @@ export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props useInterval(doRefresh, refreshInterval); - // When action such as cancel, delete, extend occurs, use the async return - // value to refresh the table const onActionComplete: OnActionComplete = () => { doRefresh(); }; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts index dc0b58abba362..7640d8b80766e 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/date_string.ts @@ -5,7 +5,7 @@ */ import moment from 'moment'; -import { DATE_STRING_FORMAT } from '../components/actions'; +import { DATE_STRING_FORMAT } from '../types'; export const dateString = (inputString: string, tz: string): string => { if (inputString == null) { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index 98585d1d7dcf3..ce441efea7385 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -15,7 +15,7 @@ import { SessionsClient } from 'src/plugins/data/public/search'; import { SessionsMgmtConfigSchema } from '../'; import { SearchSessionStatus } from '../../../../common/search'; import { OnActionComplete } from '../components'; -import { UISession } from '../components/actions'; +import { UISession } from '../types'; import { mockUrls } from '../__mocks__'; import { SearchSessionsMgmtAPI } from './api'; import { getColumns } from './get_columns'; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index c0294989bf1e3..090336c37a98f 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -28,7 +28,7 @@ import { StatusIndicator } from '../components/status'; import { dateString } from '../lib/date_string'; import { SearchSessionsMgmtAPI } from './api'; import { getExpirationStatus } from './get_expiration_status'; -import { UISession } from '../components/actions'; +import { UISession } from '../types'; // Helper function: translate an app string to EuiIcon-friendly string const appToIcon = (app: string) => { diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts new file mode 100644 index 0000000000000..78b91f7ca8ac2 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SearchSessionStatus } from '../../../common'; +import { ACTION } from './components/actions'; + +export const DATE_STRING_FORMAT = 'D MMM, YYYY, HH:mm:ss'; + +export interface UISession { + id: string; + name: string; + appId: string; + created: string; + expires: string | null; + status: SearchSessionStatus; + actions?: ACTION[]; + reloadUrl: string; + restoreUrl: string; +} diff --git a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts index 100a09426049c..8c3261c2074ae 100644 --- a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts @@ -100,7 +100,7 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { public async deleteAllSearchSessions() { log.debug('Deleting created background sessions'); // ignores 409 errs and keeps retrying - await retry.tryForTime(5000, async () => { + await retry.tryForTime(10000, async () => { const { body } = await supertest .post('/internal/session/_find') .set('kbn-xsrf', 'anything') From 56c39f83e41c2126b8ff07ae957b3920da47be1d Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 20 Jan 2021 13:32:43 +0200 Subject: [PATCH 52/52] Fixed import --- .../data_enhanced/public/search/sessions_mgmt/lib/api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index 1030c7d2427f1..a2bd6b1a549be 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -14,7 +14,8 @@ import { SessionsMgmtConfigSchema } from '../'; import type { ISessionsClient } from '../../../../../../../src/plugins/data/public'; import type { SearchSessionSavedObjectAttributes } from '../../../../common'; import { SearchSessionStatus } from '../../../../common/search'; -import { ACTION, UISession } from '../components/actions'; +import { ACTION } from '../components/actions'; +import { UISession } from '../types'; type UrlGeneratorsStart = SharePluginStart['urlGenerators'];