diff --git a/src/core/server/saved_objects/migrations/core/call_cluster.ts b/src/core/server/saved_objects/migrations/core/call_cluster.ts index ba6027f711b62..2cb1656833728 100644 --- a/src/core/server/saved_objects/migrations/core/call_cluster.ts +++ b/src/core/server/saved_objects/migrations/core/call_cluster.ts @@ -18,7 +18,7 @@ export interface CallCluster { (path: 'bulk', opts: { body: object[] }): Promise; (path: 'count', opts: CountOpts): Promise<{ count: number; _shards: ShardsInfo }>; (path: 'clearScroll', opts: { scrollId: string }): Promise; - (path: 'indices.create' | 'indices.delete', opts: IndexCreationOpts): Promise; + (path: 'indices.create', opts: IndexCreationOpts): Promise; (path: 'indices.exists', opts: IndexOpts): Promise; (path: 'indices.existsAlias', opts: { name: string }): Promise; (path: 'indices.get', opts: IndexOpts & Ignorable): Promise; diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.test.ts b/src/core/server/saved_objects/migrations/core/elastic_index.test.ts index 32ecea94826ff..4b35b017a7063 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.test.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.test.ts @@ -107,17 +107,6 @@ describe('ElasticIndex', () => { }); }); - describe('deleteIndex', () => { - test('calls indices.delete', async () => { - await Index.deleteIndex(client, '.lotr'); - - expect(client.indices.delete).toHaveBeenCalledTimes(1); - expect(client.indices.delete).toHaveBeenCalledWith({ - index: '.lotr', - }); - }); - }); - describe('claimAlias', () => { test('handles unaliased indices', async () => { client.indices.getAlias.mockResolvedValue( diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.ts b/src/core/server/saved_objects/migrations/core/elastic_index.ts index 9cdec926a56ba..aa7802320dfb7 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.ts @@ -221,10 +221,6 @@ export async function createIndex( }); } -export async function deleteIndex(client: MigrationEsClient, index: string) { - await client.indices.delete({ index }); -} - /** * Converts an index to an alias. The `alias` parameter is the desired alias name which currently * is a concrete index. This function will reindex `alias` into a new index, delete the `alias` diff --git a/src/core/server/saved_objects/migrations/core/migration_es_client.ts b/src/core/server/saved_objects/migrations/core/migration_es_client.ts index 2653e96e1b8d5..c4bb19d8223ff 100644 --- a/src/core/server/saved_objects/migrations/core/migration_es_client.ts +++ b/src/core/server/saved_objects/migrations/core/migration_es_client.ts @@ -20,7 +20,6 @@ const methods = [ 'clearScroll', 'count', 'indices.create', - 'indices.delete', 'indices.deleteTemplate', 'indices.get', 'indices.getAlias', diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index add7a966fee34..edcdea3997c8e 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -66,8 +66,8 @@ export class DataEnhancedPlugin this.config = this.initializerContext.config.get(); if (this.config.search.sessions.enabled) { - const { management: sessionsMgmtConfig } = this.config.search.sessions; - registerSearchSessionsMgmt(core, sessionsMgmtConfig, { management }); + const sessionsConfig = this.config.search.sessions; + registerSearchSessionsMgmt(core, sessionsConfig, { 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 27f1482a4d20d..7347f070e91c3 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 @@ -10,7 +10,7 @@ import type { AppDependencies, IManagementSectionsPluginsSetup, IManagementSectionsPluginsStart, - SessionsMgmtConfigSchema, + SessionsConfigSchema, } from '../'; import { APP } from '../'; import { SearchSessionsMgmtAPI } from '../lib/api'; @@ -20,7 +20,7 @@ import { renderApp } from './render'; export class SearchSessionsMgmtApp { constructor( private coreSetup: CoreSetup, - private config: SessionsMgmtConfigSchema, + private config: SessionsConfigSchema, private params: ManagementAppMountParams, private pluginsSetup: IManagementSectionsPluginsSetup ) {} 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 index 4c8a7b0217688..d9c2bdabcbac1 100644 --- 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 @@ -8,6 +8,8 @@ import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState } from 'react'; +import { Duration } from 'moment'; +import moment from 'moment'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { TableText } from '../'; import { OnActionComplete } from './types'; @@ -15,6 +17,8 @@ import { OnActionComplete } from './types'; interface ExtendButtonProps { id: string; name: string; + expires: string | null; + extendBy: Duration; api: SearchSessionsMgmtAPI; onActionComplete: OnActionComplete; } @@ -23,8 +27,11 @@ const ExtendConfirm = ({ onConfirmDismiss, ...props }: ExtendButtonProps & { onConfirmDismiss: () => void }) => { - const { id, name, api, onActionComplete } = props; + const { id, name, expires, api, extendBy, onActionComplete } = props; const [isLoading, setIsLoading] = useState(false); + const extendByDuration = moment.duration(extendBy); + + const newExpiration = moment(expires).add(extendByDuration); const title = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.title', { defaultMessage: 'Extend search session expiration', @@ -36,9 +43,10 @@ const ExtendConfirm = ({ defaultMessage: 'Cancel', }); const message = i18n.translate('xpack.data.mgmt.searchSessions.extendModal.extendMessage', { - defaultMessage: "When would you like the search session '{name}' to expire?", + defaultMessage: "The search session '{name}' expiration would be extended until {newExpires}.", values: { name, + newExpires: newExpiration.toLocaleString(), }, }); @@ -49,7 +57,7 @@ const ExtendConfirm = ({ onCancel={onConfirmDismiss} onConfirm={async () => { setIsLoading(true); - await api.sendExtend(id, '1'); + await api.sendExtend(id, `${extendByDuration.asMilliseconds()}ms`); onActionComplete(); }} confirmButtonText={confirm} 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 5bf0fbda5b5cc..c80cf6c244895 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 @@ -17,7 +17,7 @@ import { ACTION, OnActionComplete } from './types'; export const getAction = ( api: SearchSessionsMgmtAPI, actionType: string, - { id, name, reloadUrl }: UISession, + { id, name, expires, reloadUrl }: UISession, onActionComplete: OnActionComplete ): IClickActionDescriptor | null => { switch (actionType) { @@ -39,7 +39,16 @@ export const getAction = ( return { iconType: extendSessionIcon, textColor: 'default', - label: , + label: ( + + ), }; default: 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 e01d1a28c5e54..14aea4bcbf59b 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 @@ -12,7 +12,7 @@ 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 { SessionsConfigSchema } from '..'; import { SearchSessionsMgmtAPI } from '../lib/api'; import { AsyncSearchIntroDocumentation } from '../lib/documentation'; import { LocaleWrapper, mockUrls } from '../__mocks__'; @@ -20,7 +20,7 @@ import { SearchSessionsMgmtMain } from './main'; let mockCoreSetup: MockedKeys; let mockCoreStart: MockedKeys; -let mockConfig: SessionsMgmtConfigSchema; +let mockConfig: SessionsConfigSchema; let sessionsClient: SessionsClient; let api: SearchSessionsMgmtAPI; @@ -29,11 +29,14 @@ describe('Background Search Session Management Main', () => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); mockConfig = { - expiresSoonWarning: moment.duration(1, 'days'), - maxSessions: 2000, - refreshInterval: moment.duration(1, 'seconds'), - refreshTimeout: moment.duration(10, 'minutes'), - }; + defaultExpiration: moment.duration('7d'), + management: { + expiresSoonWarning: moment.duration(1, 'days'), + maxSessions: 2000, + refreshInterval: moment.duration(1, 'seconds'), + refreshTimeout: moment.duration(10, 'minutes'), + }, + } as any; sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); 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 80c6a580dd183..cdf92d69f6438 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 @@ -17,7 +17,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import type { CoreStart, HttpStart } from 'kibana/public'; import React from 'react'; -import type { SessionsMgmtConfigSchema } from '../'; +import type { SessionsConfigSchema } from '../'; import type { SearchSessionsMgmtAPI } from '../lib/api'; import type { AsyncSearchIntroDocumentation } from '../lib/documentation'; import { TableText } from './'; @@ -29,7 +29,7 @@ interface Props { api: SearchSessionsMgmtAPI; http: HttpStart; timezone: string; - config: SessionsMgmtConfigSchema; + config: SessionsConfigSchema; } export function SearchSessionsMgmtMain({ 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 51cec8f2afeff..a99fc26889a24 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,14 +13,14 @@ 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 { SessionsConfigSchema } from '../../'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { LocaleWrapper, mockUrls } from '../../__mocks__'; import { SearchSessionsMgmtTable } from './table'; let mockCoreSetup: MockedKeys; let mockCoreStart: CoreStart; -let mockConfig: SessionsMgmtConfigSchema; +let mockConfig: SessionsConfigSchema; let sessionsClient: SessionsClient; let api: SearchSessionsMgmtAPI; @@ -29,11 +29,14 @@ describe('Background Search Session Management Table', () => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); mockConfig = { - expiresSoonWarning: moment.duration(1, 'days'), - maxSessions: 2000, - refreshInterval: moment.duration(1, 'seconds'), - refreshTimeout: moment.duration(10, 'minutes'), - }; + defaultExpiration: moment.duration('7d'), + management: { + expiresSoonWarning: moment.duration(1, 'days'), + maxSessions: 2000, + refreshInterval: moment.duration(1, 'seconds'), + refreshTimeout: moment.duration(10, 'minutes'), + }, + } as any; sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { @@ -134,7 +137,10 @@ describe('Background Search Session Management Table', () => { sessionsClient.find = jest.fn(); mockConfig = { ...mockConfig, - refreshInterval: moment.duration(10, 'seconds'), + management: { + ...mockConfig.management, + refreshInterval: moment.duration(10, 'seconds'), + }, }; await act(async () => { @@ -162,8 +168,11 @@ describe('Background Search Session Management Table', () => { mockConfig = { ...mockConfig, - refreshInterval: moment.duration(1, 'day'), - refreshTimeout: moment.duration(2, 'days'), + management: { + ...mockConfig.management, + refreshInterval: moment.duration(1, 'day'), + refreshTimeout: moment.duration(2, 'days'), + }, }; await act(async () => { 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 f7aecdbd58a23..290fa4d74dfeb 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 @@ -12,7 +12,7 @@ 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 '../'; -import { SessionsMgmtConfigSchema } from '../..'; +import { SessionsConfigSchema } from '../..'; import { SearchSessionsMgmtAPI } from '../../lib/api'; import { getColumns } from '../../lib/get_columns'; import { UISession } from '../../types'; @@ -26,7 +26,7 @@ interface Props { core: CoreStart; api: SearchSessionsMgmtAPI; timezone: string; - config: SessionsMgmtConfigSchema; + config: SessionsConfigSchema; } export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props }: Props) { @@ -35,9 +35,10 @@ export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props const [debouncedIsLoading, setDebouncedIsLoading] = useState(false); const [pagination, setPagination] = useState({ pageIndex: 0 }); const showLatestResultsHandler = useRef(); - const refreshInterval = useMemo(() => moment.duration(config.refreshInterval).asMilliseconds(), [ - config.refreshInterval, - ]); + const refreshInterval = useMemo( + () => moment.duration(config.management.refreshInterval).asMilliseconds(), + [config.management.refreshInterval] + ); // Debounce rendering the state of the Refresh button useDebounce( 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 76a5d440cd898..695252462794b 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 @@ -33,7 +33,7 @@ export interface AppDependencies { api: SearchSessionsMgmtAPI; http: HttpStart; i18n: I18nStart; - config: SessionsMgmtConfigSchema; + config: SessionsConfigSchema; } export const APP = { @@ -44,11 +44,11 @@ export const APP = { }), }; -export type SessionsMgmtConfigSchema = ConfigSchema['search']['sessions']['management']; +export type SessionsConfigSchema = ConfigSchema['search']['sessions']; export function registerSearchSessionsMgmt( coreSetup: CoreSetup, - config: SessionsMgmtConfigSchema, + config: SessionsConfigSchema, services: IManagementSectionsPluginsSetup ) { services.management.sections.section.kibana.registerApp({ 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 5b337dfd03eb1..068225d0df8c3 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,14 +11,14 @@ 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 type { SessionsConfigSchema } from '../'; import { SearchSessionStatus } from '../../../../common/search'; import { mockUrls } from '../__mocks__'; import { SearchSessionsMgmtAPI } from './api'; let mockCoreSetup: MockedKeys; let mockCoreStart: MockedKeys; -let mockConfig: SessionsMgmtConfigSchema; +let mockConfig: SessionsConfigSchema; let sessionsClient: SessionsClient; describe('Search Sessions Management API', () => { @@ -26,11 +26,14 @@ describe('Search Sessions Management API', () => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); mockConfig = { - expiresSoonWarning: moment.duration('1d'), - maxSessions: 2000, - refreshInterval: moment.duration('1s'), - refreshTimeout: moment.duration('10m'), - }; + defaultExpiration: moment.duration('7d'), + management: { + expiresSoonWarning: moment.duration(1, 'days'), + maxSessions: 2000, + refreshInterval: moment.duration(1, 'seconds'), + refreshTimeout: moment.duration(10, 'minutes'), + }, + } as any; sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); }); @@ -93,8 +96,11 @@ describe('Search Sessions Management API', () => { test('handle timeout error', async () => { mockConfig = { ...mockConfig, - refreshInterval: moment.duration(1, 'hours'), - refreshTimeout: moment.duration(1, 'seconds'), + management: { + ...mockConfig.management, + refreshInterval: moment.duration(1, 'hours'), + refreshTimeout: moment.duration(1, 'seconds'), + }, }; sessionsClient.find = jest.fn().mockImplementation(async () => { 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 a2bd6b1a549be..c6a3d088b3cda 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 @@ -10,7 +10,7 @@ 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 { SessionsConfigSchema } from '../'; import type { ISessionsClient } from '../../../../../../../src/plugins/data/public'; import type { SearchSessionSavedObjectAttributes } from '../../../../common'; import { SearchSessionStatus } from '../../../../common/search'; @@ -47,10 +47,9 @@ async function getUrlFromState( } // Helper: factory for a function to map server objects to UI objects -const mapToUISession = ( - urls: UrlGeneratorsStart, - { expiresSoonWarning }: SessionsMgmtConfigSchema -) => async (savedObject: SavedObject): Promise => { +const mapToUISession = (urls: UrlGeneratorsStart, config: SessionsConfigSchema) => async ( + savedObject: SavedObject +): Promise => { const { name, appId, @@ -92,7 +91,7 @@ interface SearcgSessuibManagementDeps { export class SearchSessionsMgmtAPI { constructor( private sessionsClient: ISessionsClient, - private config: SessionsMgmtConfigSchema, + private config: SessionsConfigSchema, private deps: SearcgSessuibManagementDeps ) {} @@ -101,12 +100,14 @@ export class SearchSessionsMgmtAPI { saved_objects: object[]; } - const refreshTimeout = moment.duration(this.config.refreshTimeout); + const mgmtConfig = this.config.management; + + const refreshTimeout = moment.duration(mgmtConfig.refreshTimeout); const fetch$ = from( this.sessionsClient.find({ page: 1, - perPage: this.config.maxSessions, + perPage: mgmtConfig.maxSessions, sortField: 'created', sortOrder: 'asc', }) @@ -149,6 +150,10 @@ export class SearchSessionsMgmtAPI { this.deps.application.navigateToUrl(reloadUrl); } + public getExtendByDuration() { + return this.config.defaultExpiration; + } + // Cancel and expire public async sendCancel(id: string): Promise { try { 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 ce441efea7385..ec4f2f63ceed1 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 @@ -12,7 +12,7 @@ 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 { SessionsConfigSchema } from '../'; import { SearchSessionStatus } from '../../../../common/search'; import { OnActionComplete } from '../components'; import { UISession } from '../types'; @@ -22,7 +22,7 @@ import { getColumns } from './get_columns'; let mockCoreSetup: MockedKeys; let mockCoreStart: CoreStart; -let mockConfig: SessionsMgmtConfigSchema; +let mockConfig: SessionsConfigSchema; let api: SearchSessionsMgmtAPI; let sessionsClient: SessionsClient; let handleAction: OnActionComplete; @@ -35,11 +35,14 @@ describe('Search Sessions Management table column factory', () => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); mockConfig = { - expiresSoonWarning: moment.duration(1, 'days'), - maxSessions: 2000, - refreshInterval: moment.duration(1, 'seconds'), - refreshTimeout: moment.duration(10, 'minutes'), - }; + defaultExpiration: moment.duration('7d'), + management: { + expiresSoonWarning: moment.duration(1, 'days'), + maxSessions: 2000, + refreshInterval: moment.duration(1, 'seconds'), + refreshTimeout: moment.duration(10, 'minutes'), + }, + } as any; sessionsClient = new SessionsClient({ http: mockCoreSetup.http }); api = new SearchSessionsMgmtAPI(sessionsClient, mockConfig, { 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 090336c37a98f..1ced354a28039 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 { capitalize } from 'lodash'; import React from 'react'; import { FormattedMessage } from 'react-intl'; import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; -import { SessionsMgmtConfigSchema } from '../'; +import { SessionsConfigSchema } from '../'; import { SearchSessionStatus } from '../../../../common/search'; import { TableText } from '../components'; import { OnActionComplete, PopoverActionsMenu } from '../components'; @@ -45,7 +45,7 @@ function isSessionRestorable(status: SearchSessionStatus) { export const getColumns = ( core: CoreStart, api: SearchSessionsMgmtAPI, - config: SessionsMgmtConfigSchema, + config: SessionsConfigSchema, timezone: string, onActionComplete: OnActionComplete ): Array> => { 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 3c167d6dbe41a..5a52fce760d78 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,9 +6,9 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment'; -import { SessionsMgmtConfigSchema } from '../'; +import { SessionsConfigSchema } from '../'; -export const getExpirationStatus = (config: SessionsMgmtConfigSchema, expires: string | null) => { +export const getExpirationStatus = (config: SessionsConfigSchema, expires: string | null) => { const tNow = moment.utc().valueOf(); const tFuture = moment.utc(expires).valueOf(); @@ -16,7 +16,7 @@ export const getExpirationStatus = (config: SessionsMgmtConfigSchema, expires: s // 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()); + const sufficientDays = Math.ceil(moment.duration(config.management.expiresSoonWarning).asDays()); let toolTipContent = i18n.translate('xpack.data.mgmt.searchSessions.status.expiresSoonInDays', { defaultMessage: 'Expires in {numDays} days', diff --git a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts index d32dcf72a4205..332e69b119bb6 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts @@ -76,7 +76,7 @@ export async function scheduleSearchSessionsTasks( params: {}, }); - logger.debug(`Background search task, scheduled to run`); + logger.debug(`Search sessions task, scheduled to run`); } catch (e) { logger.debug(`Error scheduling task, received ${e.message}`); } diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx index 87e16b0d7bfe0..afafdb2c6cda6 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx @@ -23,7 +23,7 @@ import { notificationService } from '../../../public/application/services/notifi import { ExtensionsService } from '../../../public/services'; import { UiMetricService } from '../../../public/application/services/ui_metric'; import { setUiMetricService } from '../../../public/application/services/api'; -import { setExtensionsService } from '../../../public/application/store/selectors'; +import { setExtensionsService } from '../../../public/application/store/selectors/extension_service'; import { MappingsEditorProvider, ComponentTemplatesProvider, diff --git a/x-pack/plugins/index_management/__jest__/components/index_table.test.js b/x-pack/plugins/index_management/__jest__/components/index_table.test.js index b2526d6b4db5e..2649986c4229e 100644 --- a/x-pack/plugins/index_management/__jest__/components/index_table.test.js +++ b/x-pack/plugins/index_management/__jest__/components/index_table.test.js @@ -27,7 +27,7 @@ import { notificationService } from '../../public/application/services/notificat import { httpService } from '../../public/application/services/http'; import { setUiMetricService } from '../../public/application/services/api'; import { indexManagementStore } from '../../public/application/store'; -import { setExtensionsService } from '../../public/application/store/selectors'; +import { setExtensionsService } from '../../public/application/store/selectors/extension_service'; import { BASE_PATH, API_BASE_PATH } from '../../common/constants'; import { ExtensionsService } from '../../public/services'; import sinon from 'sinon'; diff --git a/x-pack/plugins/index_management/common/constants/index.ts b/x-pack/plugins/index_management/common/constants/index.ts index 11240271503e2..5adb9f6098348 100644 --- a/x-pack/plugins/index_management/common/constants/index.ts +++ b/x-pack/plugins/index_management/common/constants/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { PLUGIN } from './plugin'; export { BASE_PATH } from './base_path'; export { API_BASE_PATH } from './api_base_path'; export { INVALID_INDEX_PATTERN_CHARS, INVALID_TEMPLATE_NAME_CHARS } from './invalid_characters'; diff --git a/x-pack/plugins/index_management/common/index.ts b/x-pack/plugins/index_management/common/index.ts index 119d4e0c54edd..ea5ee5ee8e001 100644 --- a/x-pack/plugins/index_management/common/index.ts +++ b/x-pack/plugins/index_management/common/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export { PLUGIN, API_BASE_PATH, BASE_PATH } from './constants'; +export { API_BASE_PATH, BASE_PATH } from './constants'; export { getTemplateParameter } from './lib'; diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index ff7fc03ef7ae6..b94718c14d3aa 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -10,28 +10,46 @@ import { ManagementAppMountParams } from 'src/plugins/management/public/'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { FleetSetup } from '../../../fleet/public'; -import { PLUGIN } from '../../common/constants'; +import { UIM_APP_NAME } from '../../common/constants'; +import { PLUGIN } from '../../common/constants/plugin'; import { ExtensionsService } from '../services'; import { StartDependencies } from '../types'; import { AppDependencies } from './app_context'; import { breadcrumbService } from './services/breadcrumbs'; import { documentationService } from './services/documentation'; -import { HttpService, NotificationService, UiMetricService } from './services'; +import { UiMetricService } from './services'; import { renderApp } from '.'; +import { setUiMetricService } from './services/api'; +import { notificationService } from './services/notification'; +import { httpService } from './services/http'; -interface InternalServices { - httpService: HttpService; - notificationService: NotificationService; - uiMetricService: UiMetricService; - extensionsService: ExtensionsService; +function initSetup({ + usageCollection, + coreSetup, +}: { + coreSetup: CoreSetup; + usageCollection: UsageCollectionSetup; +}) { + const { http, notifications } = coreSetup; + + httpService.setup(http); + notificationService.setup(notifications); + + const uiMetricService = new UiMetricService(UIM_APP_NAME); + + setUiMetricService(uiMetricService); + + uiMetricService.setup(usageCollection); + + return { uiMetricService }; } export async function mountManagementSection( coreSetup: CoreSetup, usageCollection: UsageCollectionSetup, - services: InternalServices, params: ManagementAppMountParams, + extensionsService: ExtensionsService, fleet?: FleetSetup ) { const { element, setBreadcrumbs, history } = params; @@ -50,6 +68,11 @@ export async function mountManagementSection( breadcrumbService.setup(setBreadcrumbs); documentationService.setup(docLinks); + const { uiMetricService } = initSetup({ + usageCollection, + coreSetup, + }); + const appDependencies: AppDependencies = { core: { fatalErrors, @@ -59,7 +82,7 @@ export async function mountManagementSection( usageCollection, fleet, }, - services, + services: { httpService, notificationService, uiMetricService, extensionsService }, history, setBreadcrumbs, uiSettings, diff --git a/x-pack/plugins/index_management/public/application/store/selectors/extension_service.ts b/x-pack/plugins/index_management/public/application/store/selectors/extension_service.ts new file mode 100644 index 0000000000000..c5ce1c359b99b --- /dev/null +++ b/x-pack/plugins/index_management/public/application/store/selectors/extension_service.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ExtensionsService } from '../../../services'; + +// Temporary hack to provide the extensionsService instance to this file. +// TODO: Refactor and export all the app selectors through the app dependencies context + +let extensionsService; +export const setExtensionsService = (_extensionsService: ExtensionsService) => { + extensionsService = _extensionsService; +}; + +export { extensionsService }; + +// End hack diff --git a/x-pack/plugins/index_management/public/application/store/selectors/index.js b/x-pack/plugins/index_management/public/application/store/selectors/index.js index c80658581dbee..800cf1473d1a7 100644 --- a/x-pack/plugins/index_management/public/application/store/selectors/index.js +++ b/x-pack/plugins/index_management/public/application/store/selectors/index.js @@ -9,15 +9,9 @@ import { createSelector } from 'reselect'; import * as qs from 'query-string'; import { indexStatusLabels } from '../../lib/index_status_labels'; import { sortTable } from '../../services'; +import { extensionsService } from './extension_service'; -// Temporary hack to provide the extensionsService instance to this file. -// TODO: Refactor and export all the app selectors through the app dependencies context - -let extensionsService; -export const setExtensionsService = (_extensionsService) => { - extensionsService = _extensionsService; -}; -// End hack +export { extensionsService }; export const getDetailPanelData = (state) => state.detailPanel.data; export const getDetailPanelError = (state) => state.detailPanel.error; diff --git a/x-pack/plugins/index_management/public/application/store/selectors/indices_filter.test.ts b/x-pack/plugins/index_management/public/application/store/selectors/indices_filter.test.ts index f230ddd18e9eb..ca63f305b8c60 100644 --- a/x-pack/plugins/index_management/public/application/store/selectors/indices_filter.test.ts +++ b/x-pack/plugins/index_management/public/application/store/selectors/indices_filter.test.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ import { ExtensionsService } from '../../../services'; -import { getFilteredIndices, setExtensionsService } from '.'; +import { getFilteredIndices } from '.'; // @ts-ignore import { defaultTableState } from '../reducers/table_state'; +import { setExtensionsService } from './extension_service'; describe('getFilteredIndices selector', () => { let extensionService: ExtensionsService; diff --git a/x-pack/plugins/index_management/public/index.ts b/x-pack/plugins/index_management/public/index.ts index da6d90f22a384..d40d16ad4f74a 100644 --- a/x-pack/plugins/index_management/public/index.ts +++ b/x-pack/plugins/index_management/public/index.ts @@ -15,4 +15,4 @@ export { IndexManagementPluginSetup } from './types'; export { getIndexListUri } from './application/services/routing'; -export { Index } from '../common'; +export type { Index } from '../common'; diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index 9eeeaa9d3c723..35413bd4f9ca1 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -6,54 +6,43 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup } from '../../../../src/core/public'; +import { setExtensionsService } from './application/store/selectors/extension_service'; -import { UIM_APP_NAME, PLUGIN } from '../common/constants'; - -import { httpService } from './application/services/http'; -import { notificationService } from './application/services/notification'; -import { UiMetricService } from './application/services/ui_metric'; - -import { setExtensionsService } from './application/store/selectors'; -import { setUiMetricService } from './application/services/api'; +import { ExtensionsService } from './services'; import { IndexManagementPluginSetup, SetupDependencies, StartDependencies } from './types'; -import { ExtensionsService } from './services'; + +// avoid import from index files in plugin.ts, use specific import paths +import { PLUGIN } from '../common/constants/plugin'; export class IndexMgmtUIPlugin { - private uiMetricService = new UiMetricService(UIM_APP_NAME); private extensionsService = new ExtensionsService(); constructor() { // Temporary hack to provide the service instances in module files in order to avoid a big refactor // For the selectors we should expose them through app dependencies and read them from there on each container component. setExtensionsService(this.extensionsService); - setUiMetricService(this.uiMetricService); } public setup( coreSetup: CoreSetup, plugins: SetupDependencies ): IndexManagementPluginSetup { - const { http, notifications } = coreSetup; const { fleet, usageCollection, management } = plugins; - httpService.setup(http); - notificationService.setup(notifications); - this.uiMetricService.setup(usageCollection); - management.sections.section.data.registerApp({ id: PLUGIN.id, title: i18n.translate('xpack.idxMgmt.appTitle', { defaultMessage: 'Index Management' }), order: 0, mount: async (params) => { const { mountManagementSection } = await import('./application/mount_management_section'); - const services = { - httpService, - notificationService, - uiMetricService: this.uiMetricService, - extensionsService: this.extensionsService, - }; - return mountManagementSection(coreSetup, usageCollection, services, params, fleet); + return mountManagementSection( + coreSetup, + usageCollection, + params, + this.extensionsService, + fleet + ); }, }); diff --git a/x-pack/plugins/index_management/server/plugin.ts b/x-pack/plugins/index_management/server/plugin.ts index 3717e7e94d29f..aaa11bc2d47cc 100644 --- a/x-pack/plugins/index_management/server/plugin.ts +++ b/x-pack/plugins/index_management/server/plugin.ts @@ -13,7 +13,7 @@ import { ILegacyCustomClusterClient, } from 'src/core/server'; -import { PLUGIN } from '../common'; +import { PLUGIN } from '../common/constants/plugin'; import { Dependencies } from './types'; import { ApiRoutes } from './routes'; import { License, IndexDataEnricher } from './services'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 6718ff2d1f15f..6ff8b61f4120a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -14,11 +14,7 @@ import { SignalHit, WrappedSignalHit, } from '../types'; -import { - Logger, - SavedObject, - SavedObjectsFindResponse, -} from '../../../../../../../../src/core/server'; +import { SavedObject, SavedObjectsFindResponse } from '../../../../../../../../src/core/server'; import { loggingSystemMock } from '../../../../../../../../src/core/server/mocks'; import { RuleTypeParams } from '../../types'; import { IRuleStatusSOAttributes } from '../../rules/types'; @@ -615,7 +611,7 @@ export const exampleFindRuleStatusResponse: ( saved_objects: mockStatuses.map((obj) => ({ ...obj, score: 1 })), }); -export const mockLogger: Logger = loggingSystemMock.createLogger(); +export const mockLogger = loggingSystemMock.createLogger(); export const sampleBulkErrorItem = ( { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index f00900255a968..cce781f82e64f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -538,6 +538,35 @@ describe('rules_notification_alert_type', () => { expect(ruleStatusService.success).toHaveBeenCalled(); }); + it('should not call checkPrivileges if ML rule', async () => { + const ruleAlert = getMlResult(); + payload = getPayload(ruleAlert, alertServices) as jest.Mocked; + jobsSummaryMock.mockResolvedValue([ + { + id: 'some_job_id', + jobState: 'started', + datafeedState: 'started', + }, + ]); + (findMlSignals as jest.Mock).mockResolvedValue({ + _shards: { failed: 0 }, + hits: { + hits: [{}], + }, + }); + (bulkCreateMlSignals as jest.Mock).mockResolvedValue({ + success: true, + bulkCreateDuration: 1, + createdItemsCount: 1, + errors: [], + }); + (checkPrivileges as jest.Mock).mockClear(); + + await alert.executor(payload); + expect(checkPrivileges).toHaveBeenCalledTimes(0); + expect(ruleStatusService.success).toHaveBeenCalled(); + }); + it('should call scheduleActions if signalsCount was greater than 0 and rule has actions defined', async () => { const ruleAlert = getMlResult(); ruleAlert.actions = [ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 370f2555b02b8..d08ab66af5683 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -186,49 +186,52 @@ export const signalRulesAlertType = ({ // move this collection of lines into a function in utils // so that we can use it in create rules route, bulk, etc. try { - const hasTimestampOverride = timestampOverride != null && !isEmpty(timestampOverride); - const [privileges, timestampFieldCaps] = await Promise.all([ - pipe( - { services, version, index }, - ({ services: svc, version: ver, index: idx }) => - pipe( - tryCatch(() => getInputIndex(svc, ver, idx), toError), - chain((indices) => tryCatch(() => checkPrivileges(svc, indices), toError)) - ), - toPromise - ), - services.scopedClusterClient.fieldCaps({ - index, - fields: hasTimestampOverride - ? ['@timestamp', timestampOverride as string] - : ['@timestamp'], - allow_no_indices: false, - include_unmapped: true, - }), - ]); + if (!isEmpty(index)) { + const hasTimestampOverride = timestampOverride != null && !isEmpty(timestampOverride); + const [privileges, timestampFieldCaps] = await Promise.all([ + pipe( + { services, version, index }, + ({ services: svc, version: ver, index: idx }) => + pipe( + tryCatch(() => getInputIndex(svc, ver, idx), toError), + chain((indices) => tryCatch(() => checkPrivileges(svc, indices), toError)) + ), + toPromise + ), + services.scopedClusterClient.fieldCaps({ + index, + fields: hasTimestampOverride + ? ['@timestamp', timestampOverride as string] + : ['@timestamp'], + allow_no_indices: false, + include_unmapped: true, + }), + ]); - wrotePartialFailureStatus = await flow( - () => - tryCatch( - () => hasReadIndexPrivileges(privileges, logger, buildRuleMessage, ruleStatusService), - toError + wrotePartialFailureStatus = await flow( + () => + tryCatch( + () => + hasReadIndexPrivileges(privileges, logger, buildRuleMessage, ruleStatusService), + toError + ), + chain((wroteStatus) => + tryCatch( + () => + hasTimestampFields( + wroteStatus, + hasTimestampOverride ? (timestampOverride as string) : '@timestamp', + timestampFieldCaps, + ruleStatusService, + logger, + buildRuleMessage + ), + toError + ) ), - chain((wroteStatus) => - tryCatch( - () => - hasTimestampFields( - wroteStatus, - hasTimestampOverride ? (timestampOverride as string) : '@timestamp', - timestampFieldCaps, - ruleStatusService, - logger, - buildRuleMessage - ), - toError - ) - ), - toPromise - )(); + toPromise + )(); + } } catch (exc) { logger.error(buildRuleMessage(`Check privileges failed to execute ${exc}`)); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index ac004a7fff1fa..ec55ad7f588e8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -6,6 +6,7 @@ import moment from 'moment'; import sinon from 'sinon'; +import { ApiResponse, Context } from '@elastic/elasticsearch/lib/Transport'; import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mocks'; import { listMock } from '../../../../../lists/server/mocks'; @@ -28,6 +29,7 @@ import { getListsClient, getSignalTimeTuples, getExceptions, + hasTimestampFields, wrapBuildingBlocks, generateSignalId, createErrorsFromShard, @@ -61,6 +63,14 @@ const buildRuleMessage = buildRuleMessageFactory({ name: 'fake name', }); +const ruleStatusServiceMock = { + success: jest.fn(), + find: jest.fn(), + goingToRun: jest.fn(), + error: jest.fn(), + partialFailure: jest.fn(), +}; + describe('utils', () => { const anchor = '2020-01-01T06:06:06.666Z'; const unix = moment(anchor).valueOf(); @@ -803,6 +813,85 @@ describe('utils', () => { }); }); + describe('hasTimestampFields', () => { + test('returns true when missing timestamp override field', async () => { + const timestampField = 'event.ingested'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const timestampFieldCapsResponse: Partial, Context>> = { + body: { + fields: { + [timestampField]: { + date: { + type: 'date', + searchable: true, + aggregatable: true, + indices: ['myfakeindex-3', 'myfakeindex-4'], + }, + unmapped: { + type: 'unmapped', + searchable: false, + aggregatable: false, + indices: ['myfakeindex-1', 'myfakeindex-2'], + }, + }, + }, + }, + }; + mockLogger.error.mockClear(); + const res = await hasTimestampFields( + false, + timestampField, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + timestampFieldCapsResponse as ApiResponse>, + ruleStatusServiceMock, + mockLogger, + buildRuleMessage + ); + expect(mockLogger.error).toHaveBeenCalledWith( + 'The following indices are missing the timestamp override field "event.ingested": ["myfakeindex-1","myfakeindex-2"] name: "fake name" id: "fake id" rule id: "fake rule id" signals index: "fakeindex"' + ); + expect(res).toBeTruthy(); + }); + test('returns true when missing timestamp field', async () => { + const timestampField = '@timestamp'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const timestampFieldCapsResponse: Partial, Context>> = { + body: { + fields: { + [timestampField]: { + date: { + type: 'date', + searchable: true, + aggregatable: true, + indices: ['myfakeindex-3', 'myfakeindex-4'], + }, + unmapped: { + type: 'unmapped', + searchable: false, + aggregatable: false, + indices: ['myfakeindex-1', 'myfakeindex-2'], + }, + }, + }, + }, + }; + mockLogger.error.mockClear(); + const res = await hasTimestampFields( + false, + timestampField, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + timestampFieldCapsResponse as ApiResponse>, + ruleStatusServiceMock, + mockLogger, + buildRuleMessage + ); + expect(mockLogger.error).toHaveBeenCalledWith( + 'The following indices are missing the timestamp field "@timestamp": ["myfakeindex-1","myfakeindex-2"] name: "fake name" id: "fake id" rule id: "fake rule id" signals index: "fakeindex"' + ); + expect(res).toBeTruthy(); + }); + }); + describe('wrapBuildingBlocks', () => { it('should generate a unique id for each building block', () => { const wrappedBlocks = wrapBuildingBlocks( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 23c5e6499c8ad..0ad502b67fbe6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -120,8 +120,10 @@ export const hasTimestampFields = async ( // if there is a timestamp override and the unmapped array for the timestamp override key is not empty, // partial failure const errorString = `The following indices are missing the ${ - timestampField === '@timestamp' ? 'timestamp field "@timestamp"' : 'timestamp override field' - } "${timestampField}": ${JSON.stringify( + timestampField === '@timestamp' + ? 'timestamp field "@timestamp"' + : `timestamp override field "${timestampField}"` + }: ${JSON.stringify( isEmpty(timestampFieldCapsResponse.body.fields) ? timestampFieldCapsResponse.body.indices : timestampFieldCapsResponse.body.fields[timestampField].unmapped.indices diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index 13f1aea91c7ac..731152f4a9d63 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -796,6 +796,42 @@ describe('edit button', () => { }); }); +describe('refresh button', () => { + it('should call requestRefresh when clicked', () => { + const alert = mockAlert(); + const alertType: AlertType = { + id: '.noop', + name: 'No Op', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup, + actionVariables: { context: [], state: [], params: [] }, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + producer: ALERTS_FEATURE_ID, + authorizedConsumers, + enabledInLicense: true, + }; + + const requestRefresh = jest.fn(); + const refreshButton = shallow( + + ) + .find('[data-test-subj="refreshAlertsButton"]') + .first(); + + expect(refreshButton.exists()).toBeTruthy(); + + refreshButton.simulate('click'); + expect(requestRefresh).toHaveBeenCalledTimes(1); + }); +}); + function mockAlert(overloads: Partial = {}): Alert { return { id: uuid.v4(), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx index 5bb8d47988eed..c2a64bfa3a207 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx @@ -163,6 +163,20 @@ export const AlertDetails: React.FunctionComponent = ({ ) : null} + + + + + diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index 875268bd93112..8ca3edb1c68df 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -270,6 +270,13 @@ describe('alerts_list component with items', () => { expect(wrapper.find('[data-test-subj="alertStatus-ok"]').length).toBeGreaterThan(0); expect(wrapper.find('[data-test-subj="alertStatus-pending"]').length).toBeGreaterThan(0); expect(wrapper.find('[data-test-subj="alertStatus-unknown"]').length).toBe(0); + expect(wrapper.find('[data-test-subj="refreshAlertsButton"]').exists()).toBeTruthy(); + }); + + it('loads alerts when refresh button is clicked', async () => { + await setup(); + wrapper.find('[data-test-subj="refreshAlertsButton"]').first().simulate('click'); + expect(loadAlerts).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index a1a4cfcab18ef..293d471560503 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -406,6 +406,18 @@ export const AlertsList: React.FunctionComponent = () => { selectedStatuses={alertStatusesFilter} onChange={(ids: string[]) => setAlertStatusesFilter(ids)} />, + + + , ]; const authorizedToModifySelectedAlerts = selectedIds.length