From 28c5f27513515fcfa632a1ad5c451d59694b5d41 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Tue, 8 Sep 2020 12:34:40 +0300 Subject: [PATCH 01/19] [Search] Use async es client endpoints (#76872) * Use ES Client asyncSearch * Rename to queryOptions * Simplify options * Update jest test and use delete route * Common async options --- .../server/search/es_search_strategy.test.ts | 61 +++++++++---------- .../server/search/es_search_strategy.ts | 47 ++++++-------- 2 files changed, 47 insertions(+), 61 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts index 054baa6ac81d1..a287f72ca9161 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts @@ -35,12 +35,24 @@ const mockRollupResponse = { describe('ES search strategy', () => { const mockApiCaller = jest.fn(); + const mockGetCaller = jest.fn(); + const mockSubmitCaller = jest.fn(); const mockLogger: any = { debug: () => {}, }; const mockContext = { core: { - elasticsearch: { client: { asCurrentUser: { transport: { request: mockApiCaller } } } }, + elasticsearch: { + client: { + asCurrentUser: { + asyncSearch: { + get: mockGetCaller, + submit: mockSubmitCaller, + }, + transport: { request: mockApiCaller }, + }, + }, + }, }, }; const mockConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$; @@ -56,47 +68,32 @@ describe('ES search strategy', () => { }); it('makes a POST request to async search with params when no ID is provided', async () => { - mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); + mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'logstash-*', body: { query: {} } }; const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); - expect(mockApiCaller).toBeCalled(); - const { method, path, body } = mockApiCaller.mock.calls[0][0]; - expect(method).toBe('POST'); - expect(path).toBe('/logstash-*/_async_search'); - expect(body).toEqual({ query: {} }); + expect(mockSubmitCaller).toBeCalled(); + const request = mockSubmitCaller.mock.calls[0][0]; + expect(request.index).toEqual(params.index); + expect(request.body).toEqual(params.body); }); it('makes a GET request to async search with ID when ID is provided', async () => { - mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); + mockGetCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'logstash-*', body: { query: {} } }; const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { id: 'foo', params }); - expect(mockApiCaller).toBeCalled(); - const { method, path, body } = mockApiCaller.mock.calls[0][0]; - expect(method).toBe('GET'); - expect(path).toBe('/_async_search/foo'); - expect(body).toEqual(undefined); - }); - - it('encodes special characters in the path', async () => { - mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); - - const params = { index: 'foo-程', body: {} }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); - - await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); - - expect(mockApiCaller).toBeCalled(); - const { method, path } = mockApiCaller.mock.calls[0][0]; - expect(method).toBe('POST'); - expect(path).toBe('/foo-%E7%A8%8B/_async_search'); + expect(mockGetCaller).toBeCalled(); + const request = mockGetCaller.mock.calls[0][0]; + expect(request.id).toEqual('foo'); + expect(request).toHaveProperty('wait_for_completion_timeout'); + expect(request).toHaveProperty('keep_alive'); }); it('calls the rollup API if the index is a rollup type', async () => { @@ -117,16 +114,16 @@ describe('ES search strategy', () => { }); it('sets wait_for_completion_timeout and keep_alive in the request', async () => { - mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); + mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { index: 'foo-*', body: {} }; const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params }); - expect(mockApiCaller).toBeCalled(); - const { querystring } = mockApiCaller.mock.calls[0][0]; - expect(querystring).toHaveProperty('wait_for_completion_timeout'); - expect(querystring).toHaveProperty('keep_alive'); + expect(mockSubmitCaller).toBeCalled(); + const request = mockSubmitCaller.mock.calls[0][0]; + expect(request).toHaveProperty('wait_for_completion_timeout'); + expect(request).toHaveProperty('keep_alive'); }); }); diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 67a42b9954c9d..4ace1c4c5385b 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -70,9 +70,8 @@ export const enhancedEsSearchStrategyProvider = ( const cancel = async (context: RequestHandlerContext, id: string) => { logger.debug(`cancel ${id}`); - await context.core.elasticsearch.client.asCurrentUser.transport.request({ - method: 'DELETE', - path: encodeURI(`/_async_search/${id}`), + await context.core.elasticsearch.client.asCurrentUser.asyncSearch.delete({ + id, }); }; @@ -84,39 +83,29 @@ async function asyncSearch( request: IEnhancedEsSearchRequest, options?: ISearchOptions ): Promise { - const { timeout = undefined, restTotalHitsAsInt = undefined, ...params } = { - ...request.params, - }; - - params.trackTotalHits = true; // Get the exact count of hits - - // If we have an ID, then just poll for that ID, otherwise send the entire request body - const { body = undefined, index = undefined, ...queryParams } = request.id ? {} : params; - - const method = request.id ? 'GET' : 'POST'; - const path = encodeURI(request.id ? `/_async_search/${request.id}` : `/${index}/_async_search`); - - // Only report partial results every 64 shards; this should be reduced when we actually display partial results - const batchedReduceSize = request.id ? undefined : 64; + let esResponse; const asyncOptions = { waitForCompletionTimeout: '100ms', // Wait up to 100ms for the response to return keepAlive: '1m', // Extend the TTL for this search request by one minute }; - const querystring = toSnakeCase({ - ...asyncOptions, - ...(batchedReduceSize && { batchedReduceSize }), - ...queryParams, - }); + // If we have an ID, then just poll for that ID, otherwise send the entire request body + if (!request.id) { + const submitOptions = toSnakeCase({ + batchedReduceSize: 64, // Only report partial results every 64 shards; this should be reduced when we actually display partial results + trackTotalHits: true, // Get the exact count of hits + ...asyncOptions, + ...request.params, + }); - // TODO: replace with async endpoints once https://github.com/elastic/elasticsearch-js/issues/1280 is resolved - const esResponse = await client.transport.request({ - method, - path, - body, - querystring, - }); + esResponse = await client.asyncSearch.submit(submitOptions); + } else { + esResponse = await client.asyncSearch.get({ + id: request.id, + ...toSnakeCase(asyncOptions), + }); + } const { id, response, is_partial: isPartial, is_running: isRunning } = esResponse.body; return { From f9d9538fc0618222c3625775061af79be2f9b09e Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Tue, 8 Sep 2020 13:03:00 +0200 Subject: [PATCH 02/19] GS: use the request's basePath when processing server-side result urls (#76747) --- x-pack/plugins/global_search/common/types.ts | 4 +-- .../server/services/search_service.test.ts | 6 ++-- .../server/services/search_service.ts | 4 ++- .../server/services/utils.test.ts | 34 +++++++++++++++++++ .../global_search/server/services/utils.ts | 20 +++++++++++ 5 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/global_search/server/services/utils.test.ts create mode 100644 x-pack/plugins/global_search/server/services/utils.ts diff --git a/x-pack/plugins/global_search/common/types.ts b/x-pack/plugins/global_search/common/types.ts index 26940806a4ecd..a08ecaf41b213 100644 --- a/x-pack/plugins/global_search/common/types.ts +++ b/x-pack/plugins/global_search/common/types.ts @@ -51,12 +51,12 @@ export interface GlobalSearchProviderResult { icon?: string; /** * The url associated with this result. - * This can be either an absolute url, a path relative to the basePath, or a structure specifying if the basePath should be prepended. + * This can be either an absolute url, a path relative to the incoming request's basePath, or a structure specifying if the basePath should be prepended. * * @example * `result.url = 'https://kibana-instance:8080/base-path/app/my-app/my-result-type/id';` * `result.url = '/app/my-app/my-result-type/id';` - * `result.url = { path: '/base-path/app/my-app/my-result-type/id', prependBasePath: false };` + * `result.url = { path: '/base-path/s/my-other-space/app/my-app/my-result-type/id', prependBasePath: false };` */ url: GlobalSearchProviderResultUrl; /** the score of the result, from 1 (lowest) to 100 (highest) */ diff --git a/x-pack/plugins/global_search/server/services/search_service.test.ts b/x-pack/plugins/global_search/server/services/search_service.test.ts index fd705b4286680..2460100a46dbb 100644 --- a/x-pack/plugins/global_search/server/services/search_service.test.ts +++ b/x-pack/plugins/global_search/server/services/search_service.test.ts @@ -62,8 +62,8 @@ describe('SearchService', () => { beforeEach(() => { service = new SearchService(); - basePath = httpServiceMock.createBasePath(); - basePath.prepend.mockImplementation((path) => `/base-path${path}`); + basePath = httpServiceMock.createBasePath('/base-path'); + basePath.get.mockReturnValue('/base-path/s/space'); coreStart = coreMock.createStart(); licenseChecker = licenseCheckerMock.create(); }); @@ -283,7 +283,7 @@ describe('SearchService', () => { expect(batch.results).toHaveLength(2); expect(batch.results[0]).toEqual({ ...resultA, - url: '/base-path/foo/bar', + url: '/base-path/s/space/foo/bar', }); expect(batch.results[1]).toEqual({ ...resultB, diff --git a/x-pack/plugins/global_search/server/services/search_service.ts b/x-pack/plugins/global_search/server/services/search_service.ts index 12eada2a1385e..d79f3781c6bec 100644 --- a/x-pack/plugins/global_search/server/services/search_service.ts +++ b/x-pack/plugins/global_search/server/services/search_service.ts @@ -17,6 +17,7 @@ import { processProviderResult } from '../../common/process_result'; import { GlobalSearchConfigType } from '../config'; import { getContextFactory, GlobalSearchContextFactory } from './context'; import { GlobalSearchResultProvider, GlobalSearchFindOptions } from '../types'; +import { getRequestBasePath } from './utils'; /** @public */ export interface SearchServiceSetup { @@ -132,6 +133,7 @@ export class SearchService { } const context = this.contextFactory!(request); + const basePath = getRequestBasePath(request, this.basePath!); const timeout$ = timer(this.config!.search_timeout.asMilliseconds()).pipe(map(mapToUndefined)); const aborted$ = options.aborted$ ? merge(options.aborted$, timeout$) : timeout$; @@ -143,7 +145,7 @@ export class SearchService { }; const processResult = (result: GlobalSearchProviderResult) => - processProviderResult(result, this.basePath!); + processProviderResult(result, basePath); const providersResults$ = [...this.providers.values()].map((provider) => provider.find(term, providerOptions, context).pipe( diff --git a/x-pack/plugins/global_search/server/services/utils.test.ts b/x-pack/plugins/global_search/server/services/utils.test.ts new file mode 100644 index 0000000000000..232f72818f330 --- /dev/null +++ b/x-pack/plugins/global_search/server/services/utils.test.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 { httpServiceMock, httpServerMock } from '../../../../../src/core/server/mocks'; +import { getRequestBasePath } from './utils'; + +describe('getRequestBasePath', () => { + let basePath: ReturnType; + let request: ReturnType; + + beforeEach(() => { + basePath = httpServiceMock.createBasePath(); + request = httpServerMock.createKibanaRequest(); + }); + + it('return a IBasePath prepending the request basePath', () => { + basePath.get.mockReturnValue('/base-path/s/my-space'); + const requestBasePath = getRequestBasePath(request, basePath); + + const fullPath = requestBasePath.prepend('/app/dashboard/some-id'); + + expect(fullPath).toBe('/base-path/s/my-space/app/dashboard/some-id'); + + expect(basePath.get).toHaveBeenCalledTimes(1); + expect(basePath.get).toHaveBeenCalledWith(request); + + expect(basePath.prepend).not.toHaveBeenCalled(); + }); +}); + +httpServiceMock.createBasePath(); diff --git a/x-pack/plugins/global_search/server/services/utils.ts b/x-pack/plugins/global_search/server/services/utils.ts new file mode 100644 index 0000000000000..18a01cfbe9757 --- /dev/null +++ b/x-pack/plugins/global_search/server/services/utils.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import type { IBasePath, KibanaRequest } from 'src/core/server'; +import type { IBasePath as BasePathAccessor } from '../../common/utils'; + +export const getRequestBasePath = ( + request: KibanaRequest, + basePath: IBasePath +): BasePathAccessor => { + const requestBasePath = basePath.get(request); + return { + prepend: (path) => { + return `${requestBasePath}/${path}`.replace(/\/{2,}/g, '/'); + }, + }; +}; From 39f8fc6b87f3fab49325fe339cf3f916a2fa4f4d Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Tue, 8 Sep 2020 13:12:26 +0200 Subject: [PATCH 03/19] [Ingest Manager] Remove package cache on package uninstall (#76873) * On uninstall, remove package from cache. * Really remove package archive and contents from cache. * Refactor * move deletePackageCache to registry/index.ts * clean up imports --- .../server/services/epm/packages/remove.ts | 9 +++++-- .../server/services/epm/registry/cache.ts | 3 +++ .../server/services/epm/registry/index.ts | 24 ++++++++++++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts index bc71ead34c3d4..71eee1ee82c90 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts @@ -13,7 +13,7 @@ import { getInstallation, savedObjectTypes } from './index'; import { deletePipeline } from '../elasticsearch/ingest_pipeline/'; import { installIndexPatterns } from '../kibana/index_pattern/install'; import { packagePolicyService, appContextService } from '../..'; -import { splitPkgKey } from '../registry'; +import { splitPkgKey, deletePackageCache, getArchiveInfo } from '../registry'; export async function removeInstallation(options: { savedObjectsClient: SavedObjectsClientContract; @@ -22,7 +22,7 @@ export async function removeInstallation(options: { }): Promise { const { savedObjectsClient, pkgkey, callCluster } = options; // TODO: the epm api should change to /name/version so we don't need to do this - const { pkgName } = splitPkgKey(pkgkey); + const { pkgName, pkgVersion } = splitPkgKey(pkgkey); const installation = await getInstallation({ savedObjectsClient, pkgName }); if (!installation) throw Boom.badRequest(`${pkgName} is not installed`); if (installation.removable === false) @@ -50,6 +50,11 @@ export async function removeInstallation(options: { // could also update with [] or some other state await savedObjectsClient.delete(PACKAGES_SAVED_OBJECT_TYPE, pkgName); + // remove the package archive and its contents from the cache so that a reinstall fetches + // a fresh copy from the registry + const paths = await getArchiveInfo(pkgName, pkgVersion); + deletePackageCache(pkgName, pkgVersion, paths); + // successful delete's in SO client return {}. return something more useful return installedAssets; } diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts index e9c8317a6251d..b7c1e8c2069d6 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/cache.ts @@ -18,3 +18,6 @@ export const getArchiveLocation = (name: string, version: string) => export const setArchiveLocation = (name: string, version: string, location: string) => archiveLocationCache.set(pkgToPkgKey({ name, version }), location); + +export const deleteArchiveLocation = (name: string, version: string) => + archiveLocationCache.delete(pkgToPkgKey({ name, version })); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts index 61c8cd4aabb7b..96f7530641390 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts @@ -17,7 +17,15 @@ import { RegistrySearchResults, RegistrySearchResult, } from '../../../types'; -import { cacheGet, cacheSet, cacheHas, getArchiveLocation, setArchiveLocation } from './cache'; +import { + cacheGet, + cacheSet, + cacheDelete, + cacheHas, + getArchiveLocation, + setArchiveLocation, + deleteArchiveLocation, +} from './cache'; import { ArchiveEntry, untarBuffer, unzipBuffer } from './extract'; import { fetchUrl, getResponse, getResponseStream } from './requests'; import { streamToBuffer } from './streams'; @@ -241,3 +249,17 @@ export function groupPathsByService(paths: string[]): AssetsGroupedByServiceByTy // elasticsearch: assets.elasticsearch, }; } + +export const deletePackageCache = (name: string, version: string, paths: string[]) => { + const archiveLocation = getArchiveLocation(name, version); + if (archiveLocation) { + // delete cached archive + cacheDelete(archiveLocation); + + // delete cached archive location + deleteArchiveLocation(name, version); + } + // delete cached archive contents + // this has been populated in Registry.getArchiveInfo() + paths.forEach((path) => cacheDelete(path)); +}; From 2f1c0127e2adf8407109cf8c65343f99aeb2e4e2 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 8 Sep 2020 14:28:29 +0300 Subject: [PATCH 04/19] Deterministic output for doc types (#76890) * disable incremental builds for public type generation. They generate non-deterministic types https://github.com/microsoft/rushstack/issues/1958 * do not infer public types --- ...a-plugin-core-server.appenderconfigtype.md | 2 +- src/core/server/index.ts | 13 +- .../logging/appenders/legacy_appender.ts | 5 + .../server/logging/appenders/appenders.ts | 13 +- .../appenders/console/console_appender.ts | 8 +- .../logging/appenders/file/file_appender.ts | 8 +- .../server/logging/layouts/json_layout.ts | 6 +- src/core/server/logging/layouts/layouts.ts | 2 +- .../server/logging/layouts/pattern_layout.ts | 8 +- src/core/server/logging/logging_config.ts | 4 +- src/core/server/server.api.md | 123 +++++------------- tsconfig.types.json | 1 + 12 files changed, 77 insertions(+), 116 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-core-server.appenderconfigtype.md b/docs/development/core/server/kibana-plugin-core-server.appenderconfigtype.md index 9c70e658014b3..0838572f26f49 100644 --- a/docs/development/core/server/kibana-plugin-core-server.appenderconfigtype.md +++ b/docs/development/core/server/kibana-plugin-core-server.appenderconfigtype.md @@ -8,5 +8,5 @@ Signature: ```typescript -export declare type AppenderConfigType = TypeOf; +export declare type AppenderConfigType = ConsoleAppenderConfig | FileAppenderConfig | LegacyAppenderConfig; ``` diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 5422cbc2180ef..c17d3d7546779 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -39,6 +39,7 @@ * @packageDocumentation */ +import { Type } from '@kbn/config-schema'; import { ElasticsearchServiceSetup, ILegacyScopedClusterClient, @@ -46,7 +47,6 @@ import { ElasticsearchServiceStart, IScopedClusterClient, } from './elasticsearch'; - import { HttpServiceSetup, HttpServiceStart } from './http'; import { HttpResources } from './http_resources'; @@ -63,12 +63,7 @@ import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; import { MetricsServiceStart } from './metrics'; import { StatusServiceSetup } from './status'; import { Auditor, AuditTrailSetup, AuditTrailStart } from './audit_trail'; -import { - LoggingServiceSetup, - appendersSchema, - loggerContextConfigSchema, - loggerSchema, -} from './logging'; +import { AppenderConfigType, appendersSchema, LoggingServiceSetup } from './logging'; export { AuditableEvent, Auditor, AuditorFactory, AuditTrailSetup } from './audit_trail'; export { bootstrap } from './bootstrap'; @@ -497,8 +492,6 @@ export const config = { schema: elasticsearchConfigSchema, }, logging: { - appenders: appendersSchema, - loggers: loggerSchema, - loggerContext: loggerContextConfigSchema, + appenders: appendersSchema as Type, }, }; diff --git a/src/core/server/legacy/logging/appenders/legacy_appender.ts b/src/core/server/legacy/logging/appenders/legacy_appender.ts index 0c2f4ce93c3b8..a5d36423ba4c6 100644 --- a/src/core/server/legacy/logging/appenders/legacy_appender.ts +++ b/src/core/server/legacy/logging/appenders/legacy_appender.ts @@ -23,6 +23,11 @@ import { LogRecord } from '../../../logging/log_record'; import { LegacyLoggingServer } from '../legacy_logging_server'; import { LegacyVars } from '../../types'; +export interface LegacyAppenderConfig { + kind: 'legacy-appender'; + legacyLoggingConfig?: any; +} + /** * Simple appender that just forwards `LogRecord` to the legacy KbnServer log. * @internal diff --git a/src/core/server/logging/appenders/appenders.ts b/src/core/server/logging/appenders/appenders.ts index 3b90a10a1a76c..edfce4988275a 100644 --- a/src/core/server/logging/appenders/appenders.ts +++ b/src/core/server/logging/appenders/appenders.ts @@ -17,14 +17,17 @@ * under the License. */ -import { schema, TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; import { assertNever } from '../../../utils'; -import { LegacyAppender } from '../../legacy/logging/appenders/legacy_appender'; +import { + LegacyAppender, + LegacyAppenderConfig, +} from '../../legacy/logging/appenders/legacy_appender'; import { Layouts } from '../layouts/layouts'; import { LogRecord } from '../log_record'; -import { ConsoleAppender } from './console/console_appender'; -import { FileAppender } from './file/file_appender'; +import { ConsoleAppender, ConsoleAppenderConfig } from './console/console_appender'; +import { FileAppender, FileAppenderConfig } from './file/file_appender'; /** * Config schema for validting the shape of the `appenders` key in in {@link LoggerContextConfigType} or @@ -39,7 +42,7 @@ export const appendersSchema = schema.oneOf([ ]); /** @public */ -export type AppenderConfigType = TypeOf; +export type AppenderConfigType = ConsoleAppenderConfig | FileAppenderConfig | LegacyAppenderConfig; /** * Entity that can append `LogRecord` instances to file, stdout, memory or whatever diff --git a/src/core/server/logging/appenders/console/console_appender.ts b/src/core/server/logging/appenders/console/console_appender.ts index b4420c12a23ca..a54674b1d347c 100644 --- a/src/core/server/logging/appenders/console/console_appender.ts +++ b/src/core/server/logging/appenders/console/console_appender.ts @@ -19,13 +19,19 @@ import { schema } from '@kbn/config-schema'; -import { Layout, Layouts } from '../../layouts/layouts'; +import { Layout, Layouts, LayoutConfigType } from '../../layouts/layouts'; import { LogRecord } from '../../log_record'; import { DisposableAppender } from '../appenders'; const { literal, object } = schema; +export interface ConsoleAppenderConfig { + kind: 'console'; + layout: LayoutConfigType; +} + /** + * * Appender that formats all the `LogRecord` instances it receives and logs them via built-in `console`. * @internal */ diff --git a/src/core/server/logging/appenders/file/file_appender.ts b/src/core/server/logging/appenders/file/file_appender.ts index 728e82ebcec9a..a0e484cd87c8f 100644 --- a/src/core/server/logging/appenders/file/file_appender.ts +++ b/src/core/server/logging/appenders/file/file_appender.ts @@ -20,10 +20,16 @@ import { schema } from '@kbn/config-schema'; import { createWriteStream, WriteStream } from 'fs'; -import { Layout, Layouts } from '../../layouts/layouts'; +import { Layout, Layouts, LayoutConfigType } from '../../layouts/layouts'; import { LogRecord } from '../../log_record'; import { DisposableAppender } from '../appenders'; +export interface FileAppenderConfig { + kind: 'file'; + layout: LayoutConfigType; + path: string; +} + /** * Appender that formats all the `LogRecord` instances it receives and writes them to the specified file. * @internal diff --git a/src/core/server/logging/layouts/json_layout.ts b/src/core/server/logging/layouts/json_layout.ts index 04416184a5957..37eb6b8c4806e 100644 --- a/src/core/server/logging/layouts/json_layout.ts +++ b/src/core/server/logging/layouts/json_layout.ts @@ -19,7 +19,7 @@ import moment from 'moment-timezone'; import { merge } from 'lodash'; -import { schema, TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; import { LogRecord } from '../log_record'; import { Layout } from './layouts'; @@ -31,7 +31,9 @@ const jsonLayoutSchema = object({ }); /** @internal */ -export type JsonLayoutConfigType = TypeOf; +export interface JsonLayoutConfigType { + kind: 'json'; +} /** * Layout that just converts `LogRecord` into JSON string. diff --git a/src/core/server/logging/layouts/layouts.ts b/src/core/server/logging/layouts/layouts.ts index 0e6a6360cab2e..124c007bae104 100644 --- a/src/core/server/logging/layouts/layouts.ts +++ b/src/core/server/logging/layouts/layouts.ts @@ -26,7 +26,7 @@ import { PatternLayout, PatternLayoutConfigType } from './pattern_layout'; const { oneOf } = schema; -type LayoutConfigType = PatternLayoutConfigType | JsonLayoutConfigType; +export type LayoutConfigType = PatternLayoutConfigType | JsonLayoutConfigType; /** * Entity that can format `LogRecord` instance into a string. diff --git a/src/core/server/logging/layouts/pattern_layout.ts b/src/core/server/logging/layouts/pattern_layout.ts index 7839345a3703b..5dfc8aca77f18 100644 --- a/src/core/server/logging/layouts/pattern_layout.ts +++ b/src/core/server/logging/layouts/pattern_layout.ts @@ -17,7 +17,7 @@ * under the License. */ -import { schema, TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; import { LogRecord } from '../log_record'; import { Layout } from './layouts'; @@ -58,7 +58,11 @@ const conversions: Conversion[] = [ ]; /** @internal */ -export type PatternLayoutConfigType = TypeOf; +export interface PatternLayoutConfigType { + kind: 'pattern'; + highlight?: boolean; + pattern?: string; +} /** * Layout that formats `LogRecord` using the `pattern` string with optional diff --git a/src/core/server/logging/logging_config.ts b/src/core/server/logging/logging_config.ts index a6aafabeb970c..a6ab15dc29fdf 100644 --- a/src/core/server/logging/logging_config.ts +++ b/src/core/server/logging/logging_config.ts @@ -96,7 +96,9 @@ export const config = { }), }; -export type LoggingConfigType = TypeOf; +export type LoggingConfigType = Omit, 'appenders'> & { + appenders: Map; +}; /** * Config schema for validating the inputs to the {@link LoggingServiceStart.configure} API. diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 3270e5a09afde..081554cd17f25 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -153,10 +153,12 @@ import { UpdateDocumentByQueryParams } from 'elasticsearch'; import { UpdateDocumentParams } from 'elasticsearch'; import { Url } from 'url'; -// Warning: (ae-forgotten-export) The symbol "appendersSchema" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "ConsoleAppenderConfig" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "FileAppenderConfig" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "LegacyAppenderConfig" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type AppenderConfigType = TypeOf; +export type AppenderConfigType = ConsoleAppenderConfig | FileAppenderConfig | LegacyAppenderConfig; // @public export function assertNever(x: never): never; @@ -325,108 +327,45 @@ export type CapabilitiesSwitcher = (request: KibanaRequest, uiCapabilities: Capa export const config: { elasticsearch: { schema: import("@kbn/config-schema").ObjectType<{ - sniffOnStart: import("@kbn/config-schema").Type; - sniffInterval: import("@kbn/config-schema").Type; - sniffOnConnectionFault: import("@kbn/config-schema").Type; - hosts: import("@kbn/config-schema").Type; - preserveHost: import("@kbn/config-schema").Type; - username: import("@kbn/config-schema").Type; - password: import("@kbn/config-schema").Type; - requestHeadersWhitelist: import("@kbn/config-schema").Type; - customHeaders: import("@kbn/config-schema").Type>; - shardTimeout: import("@kbn/config-schema").Type; - requestTimeout: import("@kbn/config-schema").Type; - pingTimeout: import("@kbn/config-schema").Type; - startupTimeout: import("@kbn/config-schema").Type; - logQueries: import("@kbn/config-schema").Type; + sniffOnStart: Type; + sniffInterval: Type; + sniffOnConnectionFault: Type; + hosts: Type; + preserveHost: Type; + username: Type; + password: Type; + requestHeadersWhitelist: Type; + customHeaders: Type>; + shardTimeout: Type; + requestTimeout: Type; + pingTimeout: Type; + startupTimeout: Type; + logQueries: Type; ssl: import("@kbn/config-schema").ObjectType<{ - verificationMode: import("@kbn/config-schema").Type<"none" | "certificate" | "full">; - certificateAuthorities: import("@kbn/config-schema").Type; - certificate: import("@kbn/config-schema").Type; - key: import("@kbn/config-schema").Type; - keyPassphrase: import("@kbn/config-schema").Type; + verificationMode: Type<"none" | "certificate" | "full">; + certificateAuthorities: Type; + certificate: Type; + key: Type; + keyPassphrase: Type; keystore: import("@kbn/config-schema").ObjectType<{ - path: import("@kbn/config-schema").Type; - password: import("@kbn/config-schema").Type; + path: Type; + password: Type; }>; truststore: import("@kbn/config-schema").ObjectType<{ - path: import("@kbn/config-schema").Type; - password: import("@kbn/config-schema").Type; + path: Type; + password: Type; }>; - alwaysPresentCertificate: import("@kbn/config-schema").Type; + alwaysPresentCertificate: Type; }>; - apiVersion: import("@kbn/config-schema").Type; + apiVersion: Type; healthCheck: import("@kbn/config-schema").ObjectType<{ - delay: import("@kbn/config-schema").Type; + delay: Type; }>; ignoreVersionMismatch: import("@kbn/config-schema/target/types/types").ConditionalType; }>; }; logging: { - appenders: import("@kbn/config-schema").Type | Readonly<{ - pattern?: string | undefined; - highlight?: boolean | undefined; - } & { - kind: "pattern"; - }>; - kind: "console"; - }> | Readonly<{} & { - path: string; - layout: Readonly<{} & { - kind: "json"; - }> | Readonly<{ - pattern?: string | undefined; - highlight?: boolean | undefined; - } & { - kind: "pattern"; - }>; - kind: "file"; - }> | Readonly<{ - legacyLoggingConfig?: any; - } & { - kind: "legacy-appender"; - }>>; - loggers: import("@kbn/config-schema").ObjectType<{ - appenders: import("@kbn/config-schema").Type; - context: import("@kbn/config-schema").Type; - level: import("@kbn/config-schema").Type; - }>; - loggerContext: import("@kbn/config-schema").ObjectType<{ - appenders: import("@kbn/config-schema").Type | Readonly<{ - pattern?: string | undefined; - highlight?: boolean | undefined; - } & { - kind: "pattern"; - }>; - kind: "console"; - }> | Readonly<{} & { - path: string; - layout: Readonly<{} & { - kind: "json"; - }> | Readonly<{ - pattern?: string | undefined; - highlight?: boolean | undefined; - } & { - kind: "pattern"; - }>; - kind: "file"; - }> | Readonly<{ - legacyLoggingConfig?: any; - } & { - kind: "legacy-appender"; - }>>>; - loggers: import("@kbn/config-schema").Type[]>; - }>; + appenders: Type; }; }; diff --git a/tsconfig.types.json b/tsconfig.types.json index e8cd0a5209bbe..4b7dfa2d014a3 100644 --- a/tsconfig.types.json +++ b/tsconfig.types.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { + "incremental": false, "declaration": true, "outDir": "./target/types", "stripInternal": false, From dc28e0ec48f58c1fc42e1c4aa8ad28eaec6fff3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 8 Sep 2020 14:02:45 +0100 Subject: [PATCH 05/19] [APM] Trace timeline: Multi-fold function doesn't update when all accordions are collapsed or expanded (#76899) * update outside state when expanding or collapsing the entry transaction * reverting icons --- .../Waterfall/accordion_waterfall.tsx | 12 ++++++++---- .../WaterfallContainer/Waterfall/index.tsx | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/accordion_waterfall.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/accordion_waterfall.tsx index 833937835f870..c447d7fba86b8 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/accordion_waterfall.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/accordion_waterfall.tsx @@ -25,9 +25,7 @@ interface AccordionWaterfallProps { location: Location; errorsPerTransaction: IWaterfall['errorsPerTransaction']; childrenByParentId: Record; - onToggleEntryTransaction?: ( - nextState: EuiAccordionProps['forceState'] - ) => void; + onToggleEntryTransaction?: () => void; timelineMargins: Margins; onClickWaterfallItem: (item: IWaterfallItem) => void; } @@ -106,6 +104,7 @@ export function AccordionWaterfall(props: AccordionWaterfallProps) { errorsPerTransaction, timelineMargins, onClickWaterfallItem, + onToggleEntryTransaction, } = props; const nextLevel = level + 1; @@ -147,7 +146,12 @@ export function AccordionWaterfall(props: AccordionWaterfallProps) { arrowDisplay={isEmpty(children) ? 'none' : 'left'} initialIsOpen={true} forceState={isOpen ? 'open' : 'closed'} - onToggle={() => setIsOpen((isCurrentOpen) => !isCurrentOpen)} + onToggle={() => { + setIsOpen((isCurrentOpen) => !isCurrentOpen); + if (onToggleEntryTransaction) { + onToggleEntryTransaction(); + } + }} > {children.map((child) => ( toggleFlyout({ history, item, location }) } + onToggleEntryTransaction={() => setIsAccordionOpen((isOpen) => !isOpen)} /> ); } From bc1c1d97496a220bd68ea77b52b9fda384969a1a Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 8 Sep 2020 14:42:58 +0100 Subject: [PATCH 06/19] add unit test for uncommon processes search strategy (#76893) Co-authored-by: Elastic Machine --- .../uncommon_processes/__mocks__/index.ts | 4420 +++++++++++++++++ .../uncommon_processes/dsl/query.dsl.test.ts | 13 + .../hosts/uncommon_processes/helpers.test.ts | 269 + .../hosts/uncommon_processes/index.test.ts | 52 + 4 files changed, 4754 insertions(+) create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/__mocks__/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/dsl/query.dsl.test.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.test.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.test.ts diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/__mocks__/index.ts new file mode 100644 index 0000000000000..5f0e2af8ae921 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/__mocks__/index.ts @@ -0,0 +1,4420 @@ +/* + * 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 { SortField, HostsQueries } from '../../../../../../../common/search_strategy'; + +export const mockOptions = { + defaultIndex: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + docValueFields: [], + factoryQueryType: HostsQueries.uncommonProcesses, + filterQuery: + '{"bool":{"must":[],"filter":[{"match_all":{}},{"match_phrase":{"host.name":{"query":"siem-kibana"}}}],"should":[],"must_not":[]}}', + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 50, + querySize: 10, + }, + timerange: { + interval: '12h', + from: '2020-09-06T15:23:52.757Z', + to: '2020-09-07T15:23:52.757Z', + }, + sort: {} as SortField, +}; + +export const mockSearchStrategyResponse = { + isPartial: false, + isRunning: false, + rawResponse: { + took: 39, + timed_out: false, + _shards: { + total: 21, + successful: 21, + skipped: 0, + failed: 0, + }, + hits: { + total: -1, + max_score: 0, + hits: [], + }, + aggregations: { + process_count: { + value: 92, + }, + group_by_process: { + doc_count_error_upper_bound: -1, + sum_other_doc_count: 35043, + buckets: [ + { + key: 'AM_Delta_Patch_1.323.631.0.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'ayrMZnQBB-gskcly0w7l', + _score: null, + _source: { + process: { + args: [ + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.631.0.exe', + 'WD', + '/q', + ], + name: 'AM_Delta_Patch_1.323.631.0.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599452531834], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'ayrMZnQBB-gskcly0w7l', + _score: 0, + _source: { + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + name: 'siem-windows', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + process: { + args: [ + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.631.0.exe', + 'WD', + '/q', + ], + parent: { + args: [ + 'C:\\Windows\\system32\\wuauclt.exe', + '/RunHandlerComServer', + ], + name: 'wuauclt.exe', + pid: 4844, + entity_id: '{ce1d3c9b-b573-5f55-b115-000000000b00}', + executable: 'C:\\Windows\\System32\\wuauclt.exe', + command_line: + '"C:\\Windows\\system32\\wuauclt.exe" /RunHandlerComServer', + }, + pe: { + imphash: 'f96ec1e772808eb81774fb67a4ac229e', + }, + name: 'AM_Delta_Patch_1.323.631.0.exe', + pid: 4608, + working_directory: + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\', + entity_id: '{ce1d3c9b-b573-5f55-b215-000000000b00}', + executable: + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.631.0.exe', + command_line: + '"C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.631.0.exe" WD /q', + hash: { + sha1: '94eb7f83ddee6942ec5bdb8e218b5bc942158cb3', + sha256: + '562c58193ba7878b396ebc3fb2dccece7ea0d5c6c7d52fc3ac10b62b894260eb', + md5: '5608b911376da958ed93a7f9428ad0b9', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + Description: 'Microsoft Antimalware WU Stub', + OriginalFileName: 'AM_Delta_Patch_1.323.631.0.exe', + IntegrityLevel: 'System', + TerminalSessionId: '0', + FileVersion: '1.323.673.0', + Product: 'Microsoft Malware Protection', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 222529, + event_id: 1, + task: 'Process Create (rule: ProcessCreate)', + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-07 04:22:11.834\nProcessGuid: {ce1d3c9b-b573-5f55-b215-000000000b00}\nProcessId: 4608\nImage: C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.631.0.exe\nFileVersion: 1.323.673.0\nDescription: Microsoft Antimalware WU Stub\nProduct: Microsoft Malware Protection\nCompany: Microsoft Corporation\nOriginalFileName: AM_Delta_Patch_1.323.631.0.exe\nCommandLine: "C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.631.0.exe" WD /q\nCurrentDirectory: C:\\Windows\\SoftwareDistribution\\Download\\Install\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=94EB7F83DDEE6942EC5BDB8E218B5BC942158CB3,MD5=5608B911376DA958ED93A7F9428AD0B9,SHA256=562C58193BA7878B396EBC3FB2DCCECE7EA0D5C6C7D52FC3AC10B62B894260EB,IMPHASH=F96EC1E772808EB81774FB67A4AC229E\nParentProcessGuid: {ce1d3c9b-b573-5f55-b115-000000000b00}\nParentProcessId: 4844\nParentImage: C:\\Windows\\System32\\wuauclt.exe\nParentCommandLine: "C:\\Windows\\system32\\wuauclt.exe" /RunHandlerComServer', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-07T04:22:11.834Z', + ecs: { + version: '1.5.0', + }, + related: { + user: 'SYSTEM', + hash: [ + '94eb7f83ddee6942ec5bdb8e218b5bc942158cb3', + '5608b911376da958ed93a7f9428ad0b9', + '562c58193ba7878b396ebc3fb2dccece7ea0d5c6c7d52fc3ac10b62b894260eb', + 'f96ec1e772808eb81774fb67a4ac229e', + ], + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + created: '2020-09-07T04:22:12.727Z', + kind: 'event', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + type: ['start', 'process_start'], + category: ['process'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: '94eb7f83ddee6942ec5bdb8e218b5bc942158cb3', + imphash: 'f96ec1e772808eb81774fb67a4ac229e', + sha256: + '562c58193ba7878b396ebc3fb2dccece7ea0d5c6c7d52fc3ac10b62b894260eb', + md5: '5608b911376da958ed93a7f9428ad0b9', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'AM_Delta_Patch_1.323.673.0.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'M-GvaHQBA6bGZw2uBoYz', + _score: null, + _source: { + process: { + args: [ + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.673.0.exe', + 'WD', + '/q', + ], + name: 'AM_Delta_Patch_1.323.673.0.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599484132366], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'M-GvaHQBA6bGZw2uBoYz', + _score: 0, + _source: { + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + name: 'siem-windows', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + process: { + args: [ + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.673.0.exe', + 'WD', + '/q', + ], + parent: { + args: [ + 'C:\\Windows\\system32\\wuauclt.exe', + '/RunHandlerComServer', + ], + name: 'wuauclt.exe', + pid: 4548, + entity_id: '{ce1d3c9b-30e3-5f56-ca15-000000000b00}', + executable: 'C:\\Windows\\System32\\wuauclt.exe', + command_line: + '"C:\\Windows\\system32\\wuauclt.exe" /RunHandlerComServer', + }, + pe: { + imphash: 'f96ec1e772808eb81774fb67a4ac229e', + }, + name: 'AM_Delta_Patch_1.323.673.0.exe', + working_directory: + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\', + pid: 4684, + entity_id: '{ce1d3c9b-30e4-5f56-cb15-000000000b00}', + executable: + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.673.0.exe', + command_line: + '"C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.673.0.exe" WD /q', + hash: { + sha1: 'ae1e653f1e53dcd34415a35335f9e44d2a33be65', + sha256: + '4382c96613850568d003c02ba0a285f6d2ef9b8c20790ffa2b35641bc831293f', + md5: 'd088fcf98bb9aa1e8f07a36b05011555', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + Description: 'Microsoft Antimalware WU Stub', + OriginalFileName: 'AM_Delta_Patch_1.323.673.0.exe', + IntegrityLevel: 'System', + TerminalSessionId: '0', + FileVersion: '1.323.693.0', + Product: 'Microsoft Malware Protection', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 223146, + event_id: 1, + task: 'Process Create (rule: ProcessCreate)', + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-07 13:08:52.366\nProcessGuid: {ce1d3c9b-30e4-5f56-cb15-000000000b00}\nProcessId: 4684\nImage: C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.673.0.exe\nFileVersion: 1.323.693.0\nDescription: Microsoft Antimalware WU Stub\nProduct: Microsoft Malware Protection\nCompany: Microsoft Corporation\nOriginalFileName: AM_Delta_Patch_1.323.673.0.exe\nCommandLine: "C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.673.0.exe" WD /q\nCurrentDirectory: C:\\Windows\\SoftwareDistribution\\Download\\Install\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=AE1E653F1E53DCD34415A35335F9E44D2A33BE65,MD5=D088FCF98BB9AA1E8F07A36B05011555,SHA256=4382C96613850568D003C02BA0A285F6D2EF9B8C20790FFA2B35641BC831293F,IMPHASH=F96EC1E772808EB81774FB67A4AC229E\nParentProcessGuid: {ce1d3c9b-30e3-5f56-ca15-000000000b00}\nParentProcessId: 4548\nParentImage: C:\\Windows\\System32\\wuauclt.exe\nParentCommandLine: "C:\\Windows\\system32\\wuauclt.exe" /RunHandlerComServer', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-07T13:08:52.366Z', + ecs: { + version: '1.5.0', + }, + related: { + user: 'SYSTEM', + hash: [ + 'ae1e653f1e53dcd34415a35335f9e44d2a33be65', + 'd088fcf98bb9aa1e8f07a36b05011555', + '4382c96613850568d003c02ba0a285f6d2ef9b8c20790ffa2b35641bc831293f', + 'f96ec1e772808eb81774fb67a4ac229e', + ], + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + created: '2020-09-07T13:08:53.889Z', + kind: 'event', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + category: ['process'], + type: ['start', 'process_start'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: 'ae1e653f1e53dcd34415a35335f9e44d2a33be65', + imphash: 'f96ec1e772808eb81774fb67a4ac229e', + sha256: + '4382c96613850568d003c02ba0a285f6d2ef9b8c20790ffa2b35641bc831293f', + md5: 'd088fcf98bb9aa1e8f07a36b05011555', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'DeviceCensus.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'cinEZnQBB-gskclyvNmU', + _score: null, + _source: { + process: { + args: ['C:\\Windows\\system32\\devicecensus.exe'], + name: 'DeviceCensus.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599452000791], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'cinEZnQBB-gskclyvNmU', + _score: 0, + _source: { + process: { + args: ['C:\\Windows\\system32\\devicecensus.exe'], + parent: { + args: ['C:\\Windows\\system32\\svchost.exe', '-k', 'netsvcs', '-p'], + name: 'svchost.exe', + pid: 1060, + entity_id: '{ce1d3c9b-b9b1-5f34-1c00-000000000b00}', + executable: 'C:\\Windows\\System32\\svchost.exe', + command_line: 'C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + }, + pe: { + imphash: '0cdb6b589f0a125609d8df646de0ea86', + }, + name: 'DeviceCensus.exe', + pid: 5016, + working_directory: 'C:\\Windows\\system32\\', + entity_id: '{ce1d3c9b-b360-5f55-a115-000000000b00}', + executable: 'C:\\Windows\\System32\\DeviceCensus.exe', + command_line: 'C:\\Windows\\system32\\devicecensus.exe', + hash: { + sha1: '9e488437b2233e5ad9abd3151ec28ea51eb64c2d', + sha256: + 'dbea7473d5e7b3b4948081dacc6e35327d5a588f4fd0a2d68184bffd10439296', + md5: '8159944c79034d2bcabf73d461a7e643', + }, + }, + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + name: 'siem-windows', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + Description: 'Device Census', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + OriginalFileName: 'DeviceCensus.exe', + TerminalSessionId: '0', + IntegrityLevel: 'System', + FileVersion: '10.0.18362.1035 (WinBuild.160101.0800)', + Product: 'Microsoft® Windows® Operating System', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 222507, + task: 'Process Create (rule: ProcessCreate)', + event_id: 1, + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-07 04:13:20.791\nProcessGuid: {ce1d3c9b-b360-5f55-a115-000000000b00}\nProcessId: 5016\nImage: C:\\Windows\\System32\\DeviceCensus.exe\nFileVersion: 10.0.18362.1035 (WinBuild.160101.0800)\nDescription: Device Census\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: DeviceCensus.exe\nCommandLine: C:\\Windows\\system32\\devicecensus.exe\nCurrentDirectory: C:\\Windows\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=9E488437B2233E5AD9ABD3151EC28EA51EB64C2D,MD5=8159944C79034D2BCABF73D461A7E643,SHA256=DBEA7473D5E7B3B4948081DACC6E35327D5A588F4FD0A2D68184BFFD10439296,IMPHASH=0CDB6B589F0A125609D8DF646DE0EA86\nParentProcessGuid: {ce1d3c9b-b9b1-5f34-1c00-000000000b00}\nParentProcessId: 1060\nParentImage: C:\\Windows\\System32\\svchost.exe\nParentCommandLine: C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-07T04:13:20.791Z', + related: { + user: 'SYSTEM', + hash: [ + '9e488437b2233e5ad9abd3151ec28ea51eb64c2d', + '8159944c79034d2bcabf73d461a7e643', + 'dbea7473d5e7b3b4948081dacc6e35327d5a588f4fd0a2d68184bffd10439296', + '0cdb6b589f0a125609d8df646de0ea86', + ], + }, + ecs: { + version: '1.5.0', + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + created: '2020-09-07T04:13:22.458Z', + kind: 'event', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + category: ['process'], + type: ['start', 'process_start'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: '9e488437b2233e5ad9abd3151ec28ea51eb64c2d', + imphash: '0cdb6b589f0a125609d8df646de0ea86', + sha256: + 'dbea7473d5e7b3b4948081dacc6e35327d5a588f4fd0a2d68184bffd10439296', + md5: '8159944c79034d2bcabf73d461a7e643', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'DiskSnapshot.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'HNKSZHQBA6bGZw2uCtRk', + _score: null, + _source: { + process: { + args: ['C:\\Windows\\system32\\disksnapshot.exe', '-z'], + name: 'DiskSnapshot.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599415124040], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'HNKSZHQBA6bGZw2uCtRk', + _score: 0, + _source: { + process: { + args: ['C:\\Windows\\system32\\disksnapshot.exe', '-z'], + parent: { + args: ['C:\\Windows\\system32\\svchost.exe', '-k', 'netsvcs', '-p'], + name: 'svchost.exe', + pid: 1060, + entity_id: '{ce1d3c9b-b9b1-5f34-1c00-000000000b00}', + executable: 'C:\\Windows\\System32\\svchost.exe', + command_line: 'C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + }, + pe: { + imphash: '69bdabb73b409f40ad05f057cec29380', + }, + name: 'DiskSnapshot.exe', + pid: 3120, + working_directory: 'C:\\Windows\\system32\\', + entity_id: '{ce1d3c9b-2354-5f55-6415-000000000b00}', + command_line: 'C:\\Windows\\system32\\disksnapshot.exe -z', + executable: 'C:\\Windows\\System32\\DiskSnapshot.exe', + hash: { + sha1: '61b4d8d4757e15259e1e92c8236f37237b5380d1', + sha256: + 'c7b9591eb4dd78286615401c138c7c1a89f0e358caae1786de2c3b08e904ffdc', + md5: 'ece311ff51bd847a3874bfac85449c6b', + }, + }, + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + name: 'siem-windows', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + Description: 'DiskSnapshot.exe', + OriginalFileName: 'DiskSnapshot.exe', + TerminalSessionId: '0', + IntegrityLevel: 'System', + FileVersion: '10.0.17763.652 (WinBuild.160101.0800)', + Product: 'Microsoft® Windows® Operating System', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 221799, + event_id: 1, + task: 'Process Create (rule: ProcessCreate)', + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-06 17:58:44.040\nProcessGuid: {ce1d3c9b-2354-5f55-6415-000000000b00}\nProcessId: 3120\nImage: C:\\Windows\\System32\\DiskSnapshot.exe\nFileVersion: 10.0.17763.652 (WinBuild.160101.0800)\nDescription: DiskSnapshot.exe\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: DiskSnapshot.exe\nCommandLine: C:\\Windows\\system32\\disksnapshot.exe -z\nCurrentDirectory: C:\\Windows\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=61B4D8D4757E15259E1E92C8236F37237B5380D1,MD5=ECE311FF51BD847A3874BFAC85449C6B,SHA256=C7B9591EB4DD78286615401C138C7C1A89F0E358CAAE1786DE2C3B08E904FFDC,IMPHASH=69BDABB73B409F40AD05F057CEC29380\nParentProcessGuid: {ce1d3c9b-b9b1-5f34-1c00-000000000b00}\nParentProcessId: 1060\nParentImage: C:\\Windows\\System32\\svchost.exe\nParentCommandLine: C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-06T17:58:44.040Z', + related: { + user: 'SYSTEM', + hash: [ + '61b4d8d4757e15259e1e92c8236f37237b5380d1', + 'ece311ff51bd847a3874bfac85449c6b', + 'c7b9591eb4dd78286615401c138c7c1a89f0e358caae1786de2c3b08e904ffdc', + '69bdabb73b409f40ad05f057cec29380', + ], + }, + ecs: { + version: '1.5.0', + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + created: '2020-09-06T17:58:45.606Z', + kind: 'event', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + category: ['process'], + type: ['start', 'process_start'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: '61b4d8d4757e15259e1e92c8236f37237b5380d1', + imphash: '69bdabb73b409f40ad05f057cec29380', + sha256: + 'c7b9591eb4dd78286615401c138c7c1a89f0e358caae1786de2c3b08e904ffdc', + md5: 'ece311ff51bd847a3874bfac85449c6b', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'DismHost.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: '2zncaHQBB-gskcly1QaD', + _score: null, + _source: { + process: { + args: [ + 'C:\\Windows\\TEMP\\88C4F57A-8744-4EA6-824E-88FEF8A0E9DD\\dismhost.exe', + '{6BB79B50-2038-4A10-B513-2FAC72FF213E}', + ], + name: 'DismHost.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599487135371], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: '2zncaHQBB-gskcly1QaD', + _score: 0, + _source: { + process: { + args: [ + 'C:\\Windows\\TEMP\\88C4F57A-8744-4EA6-824E-88FEF8A0E9DD\\dismhost.exe', + '{6BB79B50-2038-4A10-B513-2FAC72FF213E}', + ], + parent: { + args: [ + 'C:\\ProgramData\\Microsoft\\Windows Defender\\platform\\4.18.2008.9-0\\MsMpEng.exe', + ], + name: 'MsMpEng.exe', + pid: 184, + entity_id: '{ce1d3c9b-1b55-5f4f-4913-000000000b00}', + executable: + 'C:\\ProgramData\\Microsoft\\Windows Defender\\Platform\\4.18.2008.9-0\\MsMpEng.exe', + command_line: + '"C:\\ProgramData\\Microsoft\\Windows Defender\\platform\\4.18.2008.9-0\\MsMpEng.exe"', + }, + pe: { + imphash: 'a644b5814b05375757429dfb05524479', + }, + name: 'DismHost.exe', + pid: 1500, + working_directory: 'C:\\Windows\\system32\\', + entity_id: '{ce1d3c9b-3c9f-5f56-d315-000000000b00}', + executable: + 'C:\\Windows\\Temp\\88C4F57A-8744-4EA6-824E-88FEF8A0E9DD\\DismHost.exe', + command_line: + 'C:\\Windows\\TEMP\\88C4F57A-8744-4EA6-824E-88FEF8A0E9DD\\dismhost.exe {6BB79B50-2038-4A10-B513-2FAC72FF213E}', + hash: { + sha1: 'a8a65b6a45a988f06e17ebd04e5462ca730d2337', + sha256: + 'b94317b7c665f1cec965e3322e0aa26c8be29eaf5830fb7fcd7e14ae88a8cf22', + md5: '5867dc628a444f2393f7eff007bd4417', + }, + }, + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + name: 'siem-windows', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + type: 'winlogbeat', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + Description: 'Dism Host Servicing Process', + OriginalFileName: 'DismHost.exe', + TerminalSessionId: '0', + IntegrityLevel: 'System', + FileVersion: '10.0.17763.771 (WinBuild.160101.0800)', + Product: 'Microsoft® Windows® Operating System', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 223274, + task: 'Process Create (rule: ProcessCreate)', + event_id: 1, + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-07 13:58:55.371\nProcessGuid: {ce1d3c9b-3c9f-5f56-d315-000000000b00}\nProcessId: 1500\nImage: C:\\Windows\\Temp\\88C4F57A-8744-4EA6-824E-88FEF8A0E9DD\\DismHost.exe\nFileVersion: 10.0.17763.771 (WinBuild.160101.0800)\nDescription: Dism Host Servicing Process\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: DismHost.exe\nCommandLine: C:\\Windows\\TEMP\\88C4F57A-8744-4EA6-824E-88FEF8A0E9DD\\dismhost.exe {6BB79B50-2038-4A10-B513-2FAC72FF213E}\nCurrentDirectory: C:\\Windows\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=A8A65B6A45A988F06E17EBD04E5462CA730D2337,MD5=5867DC628A444F2393F7EFF007BD4417,SHA256=B94317B7C665F1CEC965E3322E0AA26C8BE29EAF5830FB7FCD7E14AE88A8CF22,IMPHASH=A644B5814B05375757429DFB05524479\nParentProcessGuid: {ce1d3c9b-1b55-5f4f-4913-000000000b00}\nParentProcessId: 184\nParentImage: C:\\ProgramData\\Microsoft\\Windows Defender\\Platform\\4.18.2008.9-0\\MsMpEng.exe\nParentCommandLine: "C:\\ProgramData\\Microsoft\\Windows Defender\\platform\\4.18.2008.9-0\\MsMpEng.exe"', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-07T13:58:55.371Z', + related: { + user: 'SYSTEM', + hash: [ + 'a8a65b6a45a988f06e17ebd04e5462ca730d2337', + '5867dc628a444f2393f7eff007bd4417', + 'b94317b7c665f1cec965e3322e0aa26c8be29eaf5830fb7fcd7e14ae88a8cf22', + 'a644b5814b05375757429dfb05524479', + ], + }, + ecs: { + version: '1.5.0', + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + created: '2020-09-07T13:58:56.138Z', + kind: 'event', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + category: ['process'], + type: ['start', 'process_start'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: 'a8a65b6a45a988f06e17ebd04e5462ca730d2337', + imphash: 'a644b5814b05375757429dfb05524479', + sha256: + 'b94317b7c665f1cec965e3322e0aa26c8be29eaf5830fb7fcd7e14ae88a8cf22', + md5: '5867dc628a444f2393f7eff007bd4417', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'SIHClient.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'gdVuZXQBA6bGZw2uFsPP', + _score: null, + _source: { + process: { + args: [ + 'C:\\Windows\\System32\\sihclient.exe', + '/cv', + '33nfV21X50ie84HvATAt1w.0.1', + ], + name: 'SIHClient.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599429545370], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'gdVuZXQBA6bGZw2uFsPP', + _score: 0, + _source: { + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + name: 'siem-windows', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + process: { + args: [ + 'C:\\Windows\\System32\\sihclient.exe', + '/cv', + '33nfV21X50ie84HvATAt1w.0.1', + ], + parent: { + args: [ + 'C:\\Windows\\System32\\Upfc.exe', + '/launchtype', + 'periodic', + '/cv', + '33nfV21X50ie84HvATAt1w.0', + ], + name: 'upfc.exe', + pid: 4328, + entity_id: '{ce1d3c9b-5b8b-5f55-7815-000000000b00}', + executable: 'C:\\Windows\\System32\\upfc.exe', + command_line: + 'C:\\Windows\\System32\\Upfc.exe /launchtype periodic /cv 33nfV21X50ie84HvATAt1w.0', + }, + pe: { + imphash: '3bbd1eea2778ee3dcd883a4d5533aec3', + }, + name: 'SIHClient.exe', + pid: 2780, + working_directory: 'C:\\Windows\\system32\\', + entity_id: '{ce1d3c9b-5ba9-5f55-8815-000000000b00}', + executable: 'C:\\Windows\\System32\\SIHClient.exe', + command_line: + 'C:\\Windows\\System32\\sihclient.exe /cv 33nfV21X50ie84HvATAt1w.0.1', + hash: { + sha1: '145ef8d82cf1e451381584cd9565a2d35a442504', + sha256: + '0e0bb70ae1888060b3ffb9a320963551b56dd0d4ce0b5dc1c8fadda4b7bf3f6a', + md5: 'dc1e380b36f4a8309f363d3809e607b8', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + Description: 'SIH Client', + OriginalFileName: 'sihclient.exe', + TerminalSessionId: '0', + IntegrityLevel: 'System', + FileVersion: '10.0.17763.1217 (WinBuild.160101.0800)', + Product: 'Microsoft® Windows® Operating System', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 222106, + event_id: 1, + task: 'Process Create (rule: ProcessCreate)', + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-06 21:59:05.370\nProcessGuid: {ce1d3c9b-5ba9-5f55-8815-000000000b00}\nProcessId: 2780\nImage: C:\\Windows\\System32\\SIHClient.exe\nFileVersion: 10.0.17763.1217 (WinBuild.160101.0800)\nDescription: SIH Client\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: sihclient.exe\nCommandLine: C:\\Windows\\System32\\sihclient.exe /cv 33nfV21X50ie84HvATAt1w.0.1\nCurrentDirectory: C:\\Windows\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=145EF8D82CF1E451381584CD9565A2D35A442504,MD5=DC1E380B36F4A8309F363D3809E607B8,SHA256=0E0BB70AE1888060B3FFB9A320963551B56DD0D4CE0B5DC1C8FADDA4B7BF3F6A,IMPHASH=3BBD1EEA2778EE3DCD883A4D5533AEC3\nParentProcessGuid: {ce1d3c9b-5b8b-5f55-7815-000000000b00}\nParentProcessId: 4328\nParentImage: C:\\Windows\\System32\\upfc.exe\nParentCommandLine: C:\\Windows\\System32\\Upfc.exe /launchtype periodic /cv 33nfV21X50ie84HvATAt1w.0', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-06T21:59:05.370Z', + related: { + user: 'SYSTEM', + hash: [ + '145ef8d82cf1e451381584cd9565a2d35a442504', + 'dc1e380b36f4a8309f363d3809e607b8', + '0e0bb70ae1888060b3ffb9a320963551b56dd0d4ce0b5dc1c8fadda4b7bf3f6a', + '3bbd1eea2778ee3dcd883a4d5533aec3', + ], + }, + ecs: { + version: '1.5.0', + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + kind: 'event', + created: '2020-09-06T21:59:06.713Z', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + category: ['process'], + type: ['start', 'process_start'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: '145ef8d82cf1e451381584cd9565a2d35a442504', + imphash: '3bbd1eea2778ee3dcd883a4d5533aec3', + sha256: + '0e0bb70ae1888060b3ffb9a320963551b56dd0d4ce0b5dc1c8fadda4b7bf3f6a', + md5: 'dc1e380b36f4a8309f363d3809e607b8', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'SpeechModelDownload.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: '6NmKZnQBA6bGZw2uma12', + _score: null, + _source: { + process: { + args: [ + 'C:\\Windows\\system32\\speech_onecore\\common\\SpeechModelDownload.exe', + ], + name: 'SpeechModelDownload.exe', + }, + user: { + name: 'NETWORK SERVICE', + }, + }, + sort: [1599448191225], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: '6NmKZnQBA6bGZw2uma12', + _score: 0, + _source: { + process: { + args: [ + 'C:\\Windows\\system32\\speech_onecore\\common\\SpeechModelDownload.exe', + ], + parent: { + args: ['C:\\Windows\\system32\\svchost.exe', '-k', 'netsvcs', '-p'], + name: 'svchost.exe', + pid: 1060, + entity_id: '{ce1d3c9b-b9b1-5f34-1c00-000000000b00}', + executable: 'C:\\Windows\\System32\\svchost.exe', + command_line: 'C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + }, + pe: { + imphash: '23bd5f904494d14029d9263cebae088d', + }, + name: 'SpeechModelDownload.exe', + working_directory: 'C:\\Windows\\system32\\', + pid: 4328, + entity_id: '{ce1d3c9b-a47f-5f55-9915-000000000b00}', + hash: { + sha1: '03e6e81192621dfd873814de3787c6e7d6af1509', + sha256: + '963fd9dc1b82c44d00eb91d61e2cb442af7357e3a603c23d469df53a6376f073', + md5: '3fd687e97e03d303e02bb37ec85de962', + }, + executable: + 'C:\\Windows\\System32\\Speech_OneCore\\common\\SpeechModelDownload.exe', + command_line: + 'C:\\Windows\\system32\\speech_onecore\\common\\SpeechModelDownload.exe', + }, + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + name: 'siem-windows', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + LogonGuid: '{ce1d3c9b-b9ac-5f34-e403-000000000000}', + Description: 'Speech Model Download Executable', + OriginalFileName: 'SpeechModelDownload.exe', + IntegrityLevel: 'System', + TerminalSessionId: '0', + FileVersion: '10.0.17763.1369 (WinBuild.160101.0800)', + Product: 'Microsoft® Windows® Operating System', + LogonId: '0x3e4', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 222431, + event_id: 1, + task: 'Process Create (rule: ProcessCreate)', + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-07 03:09:51.225\nProcessGuid: {ce1d3c9b-a47f-5f55-9915-000000000b00}\nProcessId: 4328\nImage: C:\\Windows\\System32\\Speech_OneCore\\common\\SpeechModelDownload.exe\nFileVersion: 10.0.17763.1369 (WinBuild.160101.0800)\nDescription: Speech Model Download Executable\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: SpeechModelDownload.exe\nCommandLine: C:\\Windows\\system32\\speech_onecore\\common\\SpeechModelDownload.exe\nCurrentDirectory: C:\\Windows\\system32\\\nUser: NT AUTHORITY\\NETWORK SERVICE\nLogonGuid: {ce1d3c9b-b9ac-5f34-e403-000000000000}\nLogonId: 0x3E4\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=03E6E81192621DFD873814DE3787C6E7D6AF1509,MD5=3FD687E97E03D303E02BB37EC85DE962,SHA256=963FD9DC1B82C44D00EB91D61E2CB442AF7357E3A603C23D469DF53A6376F073,IMPHASH=23BD5F904494D14029D9263CEBAE088D\nParentProcessGuid: {ce1d3c9b-b9b1-5f34-1c00-000000000b00}\nParentProcessId: 1060\nParentImage: C:\\Windows\\System32\\svchost.exe\nParentCommandLine: C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-07T03:09:51.225Z', + related: { + user: 'NETWORK SERVICE', + hash: [ + '03e6e81192621dfd873814de3787c6e7d6af1509', + '3fd687e97e03d303e02bb37ec85de962', + '963fd9dc1b82c44d00eb91d61e2cb442af7357e3a603c23d469df53a6376f073', + '23bd5f904494d14029d9263cebae088d', + ], + }, + ecs: { + version: '1.5.0', + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + kind: 'event', + created: '2020-09-07T03:09:52.370Z', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + type: ['start', 'process_start'], + category: ['process'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'NETWORK SERVICE', + }, + hash: { + sha1: '03e6e81192621dfd873814de3787c6e7d6af1509', + imphash: '23bd5f904494d14029d9263cebae088d', + sha256: + '963fd9dc1b82c44d00eb91d61e2cb442af7357e3a603c23d469df53a6376f073', + md5: '3fd687e97e03d303e02bb37ec85de962', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'UsoClient.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'Pi68Z3QBc39KFIJb3txa', + _score: null, + _source: { + process: { + args: ['C:\\Windows\\system32\\usoclient.exe', 'StartScan'], + name: 'UsoClient.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599468262455], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'Pi68Z3QBc39KFIJb3txa', + _score: 0, + _source: { + process: { + args: ['C:\\Windows\\system32\\usoclient.exe', 'StartScan'], + parent: { + args: ['C:\\Windows\\system32\\svchost.exe', '-k', 'netsvcs', '-p'], + name: 'svchost.exe', + pid: 1060, + entity_id: '{ce1d3c9b-b9b1-5f34-1c00-000000000b00}', + executable: 'C:\\Windows\\System32\\svchost.exe', + command_line: 'C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + }, + pe: { + imphash: '2510e8a4554aef2caf0a913be015929f', + }, + name: 'UsoClient.exe', + pid: 3864, + working_directory: 'C:\\Windows\\system32\\', + entity_id: '{ce1d3c9b-f2e6-5f55-bc15-000000000b00}', + command_line: 'C:\\Windows\\system32\\usoclient.exe StartScan', + executable: 'C:\\Windows\\System32\\UsoClient.exe', + hash: { + sha1: 'ebf56ad89d4740359d5d3d5370b31e56614bbb79', + sha256: + 'df3900cdc3c6f023037aaf2d4407c4e8aaa909013a69539fb4688e2bd099db85', + md5: '39750d33d277617b322adbb917f7b626', + }, + }, + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + name: 'siem-windows', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + Description: 'UsoClient', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + OriginalFileName: 'UsoClient', + TerminalSessionId: '0', + IntegrityLevel: 'System', + FileVersion: '10.0.17763.1007 (WinBuild.160101.0800)', + Product: 'Microsoft® Windows® Operating System', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 222846, + event_id: 1, + task: 'Process Create (rule: ProcessCreate)', + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-07 08:44:22.455\nProcessGuid: {ce1d3c9b-f2e6-5f55-bc15-000000000b00}\nProcessId: 3864\nImage: C:\\Windows\\System32\\UsoClient.exe\nFileVersion: 10.0.17763.1007 (WinBuild.160101.0800)\nDescription: UsoClient\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: UsoClient\nCommandLine: C:\\Windows\\system32\\usoclient.exe StartScan\nCurrentDirectory: C:\\Windows\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=EBF56AD89D4740359D5D3D5370B31E56614BBB79,MD5=39750D33D277617B322ADBB917F7B626,SHA256=DF3900CDC3C6F023037AAF2D4407C4E8AAA909013A69539FB4688E2BD099DB85,IMPHASH=2510E8A4554AEF2CAF0A913BE015929F\nParentProcessGuid: {ce1d3c9b-b9b1-5f34-1c00-000000000b00}\nParentProcessId: 1060\nParentImage: C:\\Windows\\System32\\svchost.exe\nParentCommandLine: C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-07T08:44:22.455Z', + related: { + user: 'SYSTEM', + hash: [ + 'ebf56ad89d4740359d5d3d5370b31e56614bbb79', + '39750d33d277617b322adbb917f7b626', + 'df3900cdc3c6f023037aaf2d4407c4e8aaa909013a69539fb4688e2bd099db85', + '2510e8a4554aef2caf0a913be015929f', + ], + }, + ecs: { + version: '1.5.0', + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + created: '2020-09-07T08:44:24.029Z', + kind: 'event', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + category: ['process'], + type: ['start', 'process_start'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: 'ebf56ad89d4740359d5d3d5370b31e56614bbb79', + imphash: '2510e8a4554aef2caf0a913be015929f', + sha256: + 'df3900cdc3c6f023037aaf2d4407c4e8aaa909013a69539fb4688e2bd099db85', + md5: '39750d33d277617b322adbb917f7b626', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'apt-compat', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: '.ds-logs-endpoint.events.process-default-000001', + _id: 'Ziw-Z3QBB-gskcly0vqU', + _score: null, + _source: { + process: { + args: ['/etc/cron.daily/apt-compat'], + name: 'apt-compat', + }, + user: { + name: 'root', + id: 0, + }, + }, + sort: [1599459901154], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-kibana', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: '.ds-logs-endpoint.events.process-default-000001', + _id: 'Ziw-Z3QBB-gskcly0vqU', + _score: 0, + _source: { + agent: { + id: 'b1e3298e-10be-4032-b1ee-5a4cbb280aa1', + type: 'endpoint', + version: '7.9.1', + }, + process: { + Ext: { + ancestry: [ + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYyLTEzMjQzOTMzNTAxLjUzOTIzMzAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYxLTEzMjQzOTMzNTAxLjUzMjIzMTAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYxLTEzMjQzOTMzNTAxLjUyODg0MzAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYwLTEzMjQzOTMzNTAxLjUyMDI5ODAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYwLTEzMjQzOTMzNTAxLjUwNzM4MjAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODU5LTEzMjQzOTMzNTAxLjc3NTM1MDAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTUyNC0xMzIzNjA4NTMzMC4w', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEtMTMyMzYwODUzMjIuMA==', + ], + }, + args: ['/etc/cron.daily/apt-compat'], + parent: { + name: 'run-parts', + pid: 13861, + entity_id: + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYyLTEzMjQzOTMzNTAxLjUzOTIzMzAw', + executable: '/bin/run-parts', + }, + name: 'apt-compat', + pid: 13862, + args_count: 1, + entity_id: + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYyLTEzMjQzOTMzNTAxLjU0NDY0MDAw', + command_line: '/etc/cron.daily/apt-compat', + executable: '/etc/cron.daily/apt-compat', + hash: { + sha1: '61445721d0b5d86ac0a8386a4ceef450118f4fbb', + sha256: + '8eeae3a9df22621d51062e4dadfc5c63b49732b38a37b5d4e52c99c2237e5767', + md5: 'bc4a71cbcaeed4179f25d798257fa980', + }, + }, + message: 'Endpoint process event', + '@timestamp': '2020-09-07T06:25:01.154464000Z', + ecs: { + version: '1.5.0', + }, + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'endpoint.events.process', + }, + elastic: { + agent: { + id: 'ebee9a13-9ae3-4a55-9cb7-72ddf053055f', + }, + }, + host: { + hostname: 'siem-kibana', + os: { + Ext: { + variant: 'Debian', + }, + kernel: '4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 (2018-10-27)', + name: 'Linux', + family: 'debian', + version: '9', + platform: 'debian', + full: 'Debian 9', + }, + ip: ['127.0.0.1', '::1', '10.142.0.7', 'fe80::4001:aff:fe8e:7'], + name: 'siem-kibana', + id: 'e50acb49-820b-c60a-392d-2ef75f276301', + mac: ['42:01:0a:8e:00:07'], + architecture: 'x86_64', + }, + event: { + sequence: 197060, + ingested: '2020-09-07T06:26:44.476888Z', + created: '2020-09-07T06:25:01.154464000Z', + kind: 'event', + module: 'endpoint', + action: 'exec', + id: 'Lp6oofT0fzv0Auzq+++/kwCO', + category: ['process'], + type: ['start'], + dataset: 'endpoint.events.process', + }, + user: { + Ext: { + real: { + name: 'root', + id: 0, + }, + }, + name: 'root', + id: 0, + }, + group: { + Ext: { + real: { + name: 'root', + id: 0, + }, + }, + name: 'root', + id: 0, + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'bsdmainutils', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: '.ds-logs-endpoint.events.process-default-000001', + _id: 'aSw-Z3QBB-gskcly0vqU', + _score: null, + _source: { + process: { + args: ['/etc/cron.daily/bsdmainutils'], + name: 'bsdmainutils', + }, + user: { + name: 'root', + id: 0, + }, + }, + sort: [1599459901155], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-kibana', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: '.ds-logs-endpoint.events.process-default-000001', + _id: 'aSw-Z3QBB-gskcly0vqU', + _score: 0, + _source: { + agent: { + id: 'b1e3298e-10be-4032-b1ee-5a4cbb280aa1', + type: 'endpoint', + version: '7.9.1', + }, + process: { + Ext: { + ancestry: [ + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYzLTEzMjQzOTMzNTAxLjU1MzMwMzAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYxLTEzMjQzOTMzNTAxLjUzMjIzMTAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYxLTEzMjQzOTMzNTAxLjUyODg0MzAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYwLTEzMjQzOTMzNTAxLjUyMDI5ODAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYwLTEzMjQzOTMzNTAxLjUwNzM4MjAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODU5LTEzMjQzOTMzNTAxLjc3NTM1MDAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTUyNC0xMzIzNjA4NTMzMC4w', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEtMTMyMzYwODUzMjIuMA==', + ], + }, + args: ['/etc/cron.daily/bsdmainutils'], + parent: { + name: 'run-parts', + pid: 13861, + entity_id: + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYzLTEzMjQzOTMzNTAxLjU1MzMwMzAw', + executable: '/bin/run-parts', + }, + name: 'bsdmainutils', + pid: 13863, + args_count: 1, + entity_id: + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYzLTEzMjQzOTMzNTAxLjU1ODEyMDAw', + command_line: '/etc/cron.daily/bsdmainutils', + executable: '/etc/cron.daily/bsdmainutils', + hash: { + sha1: 'fd24f1f3986e5527e804c4dccddee29ff42cb682', + sha256: + 'a68002bf1dc9f42a150087b00437448a46f7cae6755ecddca70a6d3c9d20a14b', + md5: '559387f792462a62e3efb1d573e38d11', + }, + }, + message: 'Endpoint process event', + '@timestamp': '2020-09-07T06:25:01.155812000Z', + ecs: { + version: '1.5.0', + }, + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'endpoint.events.process', + }, + elastic: { + agent: { + id: 'ebee9a13-9ae3-4a55-9cb7-72ddf053055f', + }, + }, + host: { + hostname: 'siem-kibana', + os: { + Ext: { + variant: 'Debian', + }, + kernel: '4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 (2018-10-27)', + name: 'Linux', + family: 'debian', + version: '9', + platform: 'debian', + full: 'Debian 9', + }, + ip: ['127.0.0.1', '::1', '10.142.0.7', 'fe80::4001:aff:fe8e:7'], + name: 'siem-kibana', + id: 'e50acb49-820b-c60a-392d-2ef75f276301', + mac: ['42:01:0a:8e:00:07'], + architecture: 'x86_64', + }, + event: { + sequence: 197063, + ingested: '2020-09-07T06:26:44.477164Z', + created: '2020-09-07T06:25:01.155812000Z', + kind: 'event', + module: 'endpoint', + action: 'exec', + id: 'Lp6oofT0fzv0Auzq+++/kwCZ', + category: ['process'], + type: ['start'], + dataset: 'endpoint.events.process', + }, + user: { + Ext: { + real: { + name: 'root', + id: 0, + }, + }, + name: 'root', + id: 0, + }, + group: { + Ext: { + real: { + name: 'root', + id: 0, + }, + }, + name: 'root', + id: 0, + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + ], + }, + }, + }, + total: 21, + loaded: 21, +}; +export const formattedSearchStrategyResponse = { + isPartial: false, + isRunning: false, + rawResponse: { + took: 39, + timed_out: false, + _shards: { + total: 21, + successful: 21, + skipped: 0, + failed: 0, + }, + hits: { + total: -1, + max_score: 0, + hits: [], + }, + aggregations: { + process_count: { + value: 92, + }, + group_by_process: { + doc_count_error_upper_bound: -1, + sum_other_doc_count: 35043, + buckets: [ + { + key: 'AM_Delta_Patch_1.323.631.0.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'ayrMZnQBB-gskcly0w7l', + _score: null, + _source: { + process: { + args: [ + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.631.0.exe', + 'WD', + '/q', + ], + name: 'AM_Delta_Patch_1.323.631.0.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599452531834], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'ayrMZnQBB-gskcly0w7l', + _score: 0, + _source: { + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + name: 'siem-windows', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + process: { + args: [ + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.631.0.exe', + 'WD', + '/q', + ], + parent: { + args: [ + 'C:\\Windows\\system32\\wuauclt.exe', + '/RunHandlerComServer', + ], + name: 'wuauclt.exe', + pid: 4844, + entity_id: '{ce1d3c9b-b573-5f55-b115-000000000b00}', + executable: 'C:\\Windows\\System32\\wuauclt.exe', + command_line: + '"C:\\Windows\\system32\\wuauclt.exe" /RunHandlerComServer', + }, + pe: { + imphash: 'f96ec1e772808eb81774fb67a4ac229e', + }, + name: 'AM_Delta_Patch_1.323.631.0.exe', + pid: 4608, + working_directory: + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\', + entity_id: '{ce1d3c9b-b573-5f55-b215-000000000b00}', + executable: + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.631.0.exe', + command_line: + '"C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.631.0.exe" WD /q', + hash: { + sha1: '94eb7f83ddee6942ec5bdb8e218b5bc942158cb3', + sha256: + '562c58193ba7878b396ebc3fb2dccece7ea0d5c6c7d52fc3ac10b62b894260eb', + md5: '5608b911376da958ed93a7f9428ad0b9', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + Description: 'Microsoft Antimalware WU Stub', + OriginalFileName: 'AM_Delta_Patch_1.323.631.0.exe', + IntegrityLevel: 'System', + TerminalSessionId: '0', + FileVersion: '1.323.673.0', + Product: 'Microsoft Malware Protection', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 222529, + event_id: 1, + task: 'Process Create (rule: ProcessCreate)', + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-07 04:22:11.834\nProcessGuid: {ce1d3c9b-b573-5f55-b215-000000000b00}\nProcessId: 4608\nImage: C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.631.0.exe\nFileVersion: 1.323.673.0\nDescription: Microsoft Antimalware WU Stub\nProduct: Microsoft Malware Protection\nCompany: Microsoft Corporation\nOriginalFileName: AM_Delta_Patch_1.323.631.0.exe\nCommandLine: "C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.631.0.exe" WD /q\nCurrentDirectory: C:\\Windows\\SoftwareDistribution\\Download\\Install\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=94EB7F83DDEE6942EC5BDB8E218B5BC942158CB3,MD5=5608B911376DA958ED93A7F9428AD0B9,SHA256=562C58193BA7878B396EBC3FB2DCCECE7EA0D5C6C7D52FC3AC10B62B894260EB,IMPHASH=F96EC1E772808EB81774FB67A4AC229E\nParentProcessGuid: {ce1d3c9b-b573-5f55-b115-000000000b00}\nParentProcessId: 4844\nParentImage: C:\\Windows\\System32\\wuauclt.exe\nParentCommandLine: "C:\\Windows\\system32\\wuauclt.exe" /RunHandlerComServer', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-07T04:22:11.834Z', + ecs: { + version: '1.5.0', + }, + related: { + user: 'SYSTEM', + hash: [ + '94eb7f83ddee6942ec5bdb8e218b5bc942158cb3', + '5608b911376da958ed93a7f9428ad0b9', + '562c58193ba7878b396ebc3fb2dccece7ea0d5c6c7d52fc3ac10b62b894260eb', + 'f96ec1e772808eb81774fb67a4ac229e', + ], + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + created: '2020-09-07T04:22:12.727Z', + kind: 'event', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + type: ['start', 'process_start'], + category: ['process'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: '94eb7f83ddee6942ec5bdb8e218b5bc942158cb3', + imphash: 'f96ec1e772808eb81774fb67a4ac229e', + sha256: + '562c58193ba7878b396ebc3fb2dccece7ea0d5c6c7d52fc3ac10b62b894260eb', + md5: '5608b911376da958ed93a7f9428ad0b9', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'AM_Delta_Patch_1.323.673.0.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'M-GvaHQBA6bGZw2uBoYz', + _score: null, + _source: { + process: { + args: [ + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.673.0.exe', + 'WD', + '/q', + ], + name: 'AM_Delta_Patch_1.323.673.0.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599484132366], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'M-GvaHQBA6bGZw2uBoYz', + _score: 0, + _source: { + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + name: 'siem-windows', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + process: { + args: [ + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.673.0.exe', + 'WD', + '/q', + ], + parent: { + args: [ + 'C:\\Windows\\system32\\wuauclt.exe', + '/RunHandlerComServer', + ], + name: 'wuauclt.exe', + pid: 4548, + entity_id: '{ce1d3c9b-30e3-5f56-ca15-000000000b00}', + executable: 'C:\\Windows\\System32\\wuauclt.exe', + command_line: + '"C:\\Windows\\system32\\wuauclt.exe" /RunHandlerComServer', + }, + pe: { + imphash: 'f96ec1e772808eb81774fb67a4ac229e', + }, + name: 'AM_Delta_Patch_1.323.673.0.exe', + working_directory: + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\', + pid: 4684, + entity_id: '{ce1d3c9b-30e4-5f56-cb15-000000000b00}', + executable: + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.673.0.exe', + command_line: + '"C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.673.0.exe" WD /q', + hash: { + sha1: 'ae1e653f1e53dcd34415a35335f9e44d2a33be65', + sha256: + '4382c96613850568d003c02ba0a285f6d2ef9b8c20790ffa2b35641bc831293f', + md5: 'd088fcf98bb9aa1e8f07a36b05011555', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + Description: 'Microsoft Antimalware WU Stub', + OriginalFileName: 'AM_Delta_Patch_1.323.673.0.exe', + IntegrityLevel: 'System', + TerminalSessionId: '0', + FileVersion: '1.323.693.0', + Product: 'Microsoft Malware Protection', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 223146, + event_id: 1, + task: 'Process Create (rule: ProcessCreate)', + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-07 13:08:52.366\nProcessGuid: {ce1d3c9b-30e4-5f56-cb15-000000000b00}\nProcessId: 4684\nImage: C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.673.0.exe\nFileVersion: 1.323.693.0\nDescription: Microsoft Antimalware WU Stub\nProduct: Microsoft Malware Protection\nCompany: Microsoft Corporation\nOriginalFileName: AM_Delta_Patch_1.323.673.0.exe\nCommandLine: "C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.673.0.exe" WD /q\nCurrentDirectory: C:\\Windows\\SoftwareDistribution\\Download\\Install\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=AE1E653F1E53DCD34415A35335F9E44D2A33BE65,MD5=D088FCF98BB9AA1E8F07A36B05011555,SHA256=4382C96613850568D003C02BA0A285F6D2EF9B8C20790FFA2B35641BC831293F,IMPHASH=F96EC1E772808EB81774FB67A4AC229E\nParentProcessGuid: {ce1d3c9b-30e3-5f56-ca15-000000000b00}\nParentProcessId: 4548\nParentImage: C:\\Windows\\System32\\wuauclt.exe\nParentCommandLine: "C:\\Windows\\system32\\wuauclt.exe" /RunHandlerComServer', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-07T13:08:52.366Z', + ecs: { + version: '1.5.0', + }, + related: { + user: 'SYSTEM', + hash: [ + 'ae1e653f1e53dcd34415a35335f9e44d2a33be65', + 'd088fcf98bb9aa1e8f07a36b05011555', + '4382c96613850568d003c02ba0a285f6d2ef9b8c20790ffa2b35641bc831293f', + 'f96ec1e772808eb81774fb67a4ac229e', + ], + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + created: '2020-09-07T13:08:53.889Z', + kind: 'event', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + category: ['process'], + type: ['start', 'process_start'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: 'ae1e653f1e53dcd34415a35335f9e44d2a33be65', + imphash: 'f96ec1e772808eb81774fb67a4ac229e', + sha256: + '4382c96613850568d003c02ba0a285f6d2ef9b8c20790ffa2b35641bc831293f', + md5: 'd088fcf98bb9aa1e8f07a36b05011555', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'DeviceCensus.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'cinEZnQBB-gskclyvNmU', + _score: null, + _source: { + process: { + args: ['C:\\Windows\\system32\\devicecensus.exe'], + name: 'DeviceCensus.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599452000791], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'cinEZnQBB-gskclyvNmU', + _score: 0, + _source: { + process: { + args: ['C:\\Windows\\system32\\devicecensus.exe'], + parent: { + args: ['C:\\Windows\\system32\\svchost.exe', '-k', 'netsvcs', '-p'], + name: 'svchost.exe', + pid: 1060, + entity_id: '{ce1d3c9b-b9b1-5f34-1c00-000000000b00}', + executable: 'C:\\Windows\\System32\\svchost.exe', + command_line: 'C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + }, + pe: { + imphash: '0cdb6b589f0a125609d8df646de0ea86', + }, + name: 'DeviceCensus.exe', + pid: 5016, + working_directory: 'C:\\Windows\\system32\\', + entity_id: '{ce1d3c9b-b360-5f55-a115-000000000b00}', + executable: 'C:\\Windows\\System32\\DeviceCensus.exe', + command_line: 'C:\\Windows\\system32\\devicecensus.exe', + hash: { + sha1: '9e488437b2233e5ad9abd3151ec28ea51eb64c2d', + sha256: + 'dbea7473d5e7b3b4948081dacc6e35327d5a588f4fd0a2d68184bffd10439296', + md5: '8159944c79034d2bcabf73d461a7e643', + }, + }, + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + name: 'siem-windows', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + Description: 'Device Census', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + OriginalFileName: 'DeviceCensus.exe', + TerminalSessionId: '0', + IntegrityLevel: 'System', + FileVersion: '10.0.18362.1035 (WinBuild.160101.0800)', + Product: 'Microsoft® Windows® Operating System', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 222507, + task: 'Process Create (rule: ProcessCreate)', + event_id: 1, + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-07 04:13:20.791\nProcessGuid: {ce1d3c9b-b360-5f55-a115-000000000b00}\nProcessId: 5016\nImage: C:\\Windows\\System32\\DeviceCensus.exe\nFileVersion: 10.0.18362.1035 (WinBuild.160101.0800)\nDescription: Device Census\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: DeviceCensus.exe\nCommandLine: C:\\Windows\\system32\\devicecensus.exe\nCurrentDirectory: C:\\Windows\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=9E488437B2233E5AD9ABD3151EC28EA51EB64C2D,MD5=8159944C79034D2BCABF73D461A7E643,SHA256=DBEA7473D5E7B3B4948081DACC6E35327D5A588F4FD0A2D68184BFFD10439296,IMPHASH=0CDB6B589F0A125609D8DF646DE0EA86\nParentProcessGuid: {ce1d3c9b-b9b1-5f34-1c00-000000000b00}\nParentProcessId: 1060\nParentImage: C:\\Windows\\System32\\svchost.exe\nParentCommandLine: C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-07T04:13:20.791Z', + related: { + user: 'SYSTEM', + hash: [ + '9e488437b2233e5ad9abd3151ec28ea51eb64c2d', + '8159944c79034d2bcabf73d461a7e643', + 'dbea7473d5e7b3b4948081dacc6e35327d5a588f4fd0a2d68184bffd10439296', + '0cdb6b589f0a125609d8df646de0ea86', + ], + }, + ecs: { + version: '1.5.0', + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + created: '2020-09-07T04:13:22.458Z', + kind: 'event', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + category: ['process'], + type: ['start', 'process_start'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: '9e488437b2233e5ad9abd3151ec28ea51eb64c2d', + imphash: '0cdb6b589f0a125609d8df646de0ea86', + sha256: + 'dbea7473d5e7b3b4948081dacc6e35327d5a588f4fd0a2d68184bffd10439296', + md5: '8159944c79034d2bcabf73d461a7e643', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'DiskSnapshot.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'HNKSZHQBA6bGZw2uCtRk', + _score: null, + _source: { + process: { + args: ['C:\\Windows\\system32\\disksnapshot.exe', '-z'], + name: 'DiskSnapshot.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599415124040], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'HNKSZHQBA6bGZw2uCtRk', + _score: 0, + _source: { + process: { + args: ['C:\\Windows\\system32\\disksnapshot.exe', '-z'], + parent: { + args: ['C:\\Windows\\system32\\svchost.exe', '-k', 'netsvcs', '-p'], + name: 'svchost.exe', + pid: 1060, + entity_id: '{ce1d3c9b-b9b1-5f34-1c00-000000000b00}', + executable: 'C:\\Windows\\System32\\svchost.exe', + command_line: 'C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + }, + pe: { + imphash: '69bdabb73b409f40ad05f057cec29380', + }, + name: 'DiskSnapshot.exe', + pid: 3120, + working_directory: 'C:\\Windows\\system32\\', + entity_id: '{ce1d3c9b-2354-5f55-6415-000000000b00}', + command_line: 'C:\\Windows\\system32\\disksnapshot.exe -z', + executable: 'C:\\Windows\\System32\\DiskSnapshot.exe', + hash: { + sha1: '61b4d8d4757e15259e1e92c8236f37237b5380d1', + sha256: + 'c7b9591eb4dd78286615401c138c7c1a89f0e358caae1786de2c3b08e904ffdc', + md5: 'ece311ff51bd847a3874bfac85449c6b', + }, + }, + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + name: 'siem-windows', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + Description: 'DiskSnapshot.exe', + OriginalFileName: 'DiskSnapshot.exe', + TerminalSessionId: '0', + IntegrityLevel: 'System', + FileVersion: '10.0.17763.652 (WinBuild.160101.0800)', + Product: 'Microsoft® Windows® Operating System', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 221799, + event_id: 1, + task: 'Process Create (rule: ProcessCreate)', + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-06 17:58:44.040\nProcessGuid: {ce1d3c9b-2354-5f55-6415-000000000b00}\nProcessId: 3120\nImage: C:\\Windows\\System32\\DiskSnapshot.exe\nFileVersion: 10.0.17763.652 (WinBuild.160101.0800)\nDescription: DiskSnapshot.exe\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: DiskSnapshot.exe\nCommandLine: C:\\Windows\\system32\\disksnapshot.exe -z\nCurrentDirectory: C:\\Windows\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=61B4D8D4757E15259E1E92C8236F37237B5380D1,MD5=ECE311FF51BD847A3874BFAC85449C6B,SHA256=C7B9591EB4DD78286615401C138C7C1A89F0E358CAAE1786DE2C3B08E904FFDC,IMPHASH=69BDABB73B409F40AD05F057CEC29380\nParentProcessGuid: {ce1d3c9b-b9b1-5f34-1c00-000000000b00}\nParentProcessId: 1060\nParentImage: C:\\Windows\\System32\\svchost.exe\nParentCommandLine: C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-06T17:58:44.040Z', + related: { + user: 'SYSTEM', + hash: [ + '61b4d8d4757e15259e1e92c8236f37237b5380d1', + 'ece311ff51bd847a3874bfac85449c6b', + 'c7b9591eb4dd78286615401c138c7c1a89f0e358caae1786de2c3b08e904ffdc', + '69bdabb73b409f40ad05f057cec29380', + ], + }, + ecs: { + version: '1.5.0', + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + created: '2020-09-06T17:58:45.606Z', + kind: 'event', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + category: ['process'], + type: ['start', 'process_start'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: '61b4d8d4757e15259e1e92c8236f37237b5380d1', + imphash: '69bdabb73b409f40ad05f057cec29380', + sha256: + 'c7b9591eb4dd78286615401c138c7c1a89f0e358caae1786de2c3b08e904ffdc', + md5: 'ece311ff51bd847a3874bfac85449c6b', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'DismHost.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: '2zncaHQBB-gskcly1QaD', + _score: null, + _source: { + process: { + args: [ + 'C:\\Windows\\TEMP\\88C4F57A-8744-4EA6-824E-88FEF8A0E9DD\\dismhost.exe', + '{6BB79B50-2038-4A10-B513-2FAC72FF213E}', + ], + name: 'DismHost.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599487135371], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: '2zncaHQBB-gskcly1QaD', + _score: 0, + _source: { + process: { + args: [ + 'C:\\Windows\\TEMP\\88C4F57A-8744-4EA6-824E-88FEF8A0E9DD\\dismhost.exe', + '{6BB79B50-2038-4A10-B513-2FAC72FF213E}', + ], + parent: { + args: [ + 'C:\\ProgramData\\Microsoft\\Windows Defender\\platform\\4.18.2008.9-0\\MsMpEng.exe', + ], + name: 'MsMpEng.exe', + pid: 184, + entity_id: '{ce1d3c9b-1b55-5f4f-4913-000000000b00}', + executable: + 'C:\\ProgramData\\Microsoft\\Windows Defender\\Platform\\4.18.2008.9-0\\MsMpEng.exe', + command_line: + '"C:\\ProgramData\\Microsoft\\Windows Defender\\platform\\4.18.2008.9-0\\MsMpEng.exe"', + }, + pe: { + imphash: 'a644b5814b05375757429dfb05524479', + }, + name: 'DismHost.exe', + pid: 1500, + working_directory: 'C:\\Windows\\system32\\', + entity_id: '{ce1d3c9b-3c9f-5f56-d315-000000000b00}', + executable: + 'C:\\Windows\\Temp\\88C4F57A-8744-4EA6-824E-88FEF8A0E9DD\\DismHost.exe', + command_line: + 'C:\\Windows\\TEMP\\88C4F57A-8744-4EA6-824E-88FEF8A0E9DD\\dismhost.exe {6BB79B50-2038-4A10-B513-2FAC72FF213E}', + hash: { + sha1: 'a8a65b6a45a988f06e17ebd04e5462ca730d2337', + sha256: + 'b94317b7c665f1cec965e3322e0aa26c8be29eaf5830fb7fcd7e14ae88a8cf22', + md5: '5867dc628a444f2393f7eff007bd4417', + }, + }, + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + name: 'siem-windows', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + type: 'winlogbeat', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + Description: 'Dism Host Servicing Process', + OriginalFileName: 'DismHost.exe', + TerminalSessionId: '0', + IntegrityLevel: 'System', + FileVersion: '10.0.17763.771 (WinBuild.160101.0800)', + Product: 'Microsoft® Windows® Operating System', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 223274, + task: 'Process Create (rule: ProcessCreate)', + event_id: 1, + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-07 13:58:55.371\nProcessGuid: {ce1d3c9b-3c9f-5f56-d315-000000000b00}\nProcessId: 1500\nImage: C:\\Windows\\Temp\\88C4F57A-8744-4EA6-824E-88FEF8A0E9DD\\DismHost.exe\nFileVersion: 10.0.17763.771 (WinBuild.160101.0800)\nDescription: Dism Host Servicing Process\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: DismHost.exe\nCommandLine: C:\\Windows\\TEMP\\88C4F57A-8744-4EA6-824E-88FEF8A0E9DD\\dismhost.exe {6BB79B50-2038-4A10-B513-2FAC72FF213E}\nCurrentDirectory: C:\\Windows\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=A8A65B6A45A988F06E17EBD04E5462CA730D2337,MD5=5867DC628A444F2393F7EFF007BD4417,SHA256=B94317B7C665F1CEC965E3322E0AA26C8BE29EAF5830FB7FCD7E14AE88A8CF22,IMPHASH=A644B5814B05375757429DFB05524479\nParentProcessGuid: {ce1d3c9b-1b55-5f4f-4913-000000000b00}\nParentProcessId: 184\nParentImage: C:\\ProgramData\\Microsoft\\Windows Defender\\Platform\\4.18.2008.9-0\\MsMpEng.exe\nParentCommandLine: "C:\\ProgramData\\Microsoft\\Windows Defender\\platform\\4.18.2008.9-0\\MsMpEng.exe"', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-07T13:58:55.371Z', + related: { + user: 'SYSTEM', + hash: [ + 'a8a65b6a45a988f06e17ebd04e5462ca730d2337', + '5867dc628a444f2393f7eff007bd4417', + 'b94317b7c665f1cec965e3322e0aa26c8be29eaf5830fb7fcd7e14ae88a8cf22', + 'a644b5814b05375757429dfb05524479', + ], + }, + ecs: { + version: '1.5.0', + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + created: '2020-09-07T13:58:56.138Z', + kind: 'event', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + category: ['process'], + type: ['start', 'process_start'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: 'a8a65b6a45a988f06e17ebd04e5462ca730d2337', + imphash: 'a644b5814b05375757429dfb05524479', + sha256: + 'b94317b7c665f1cec965e3322e0aa26c8be29eaf5830fb7fcd7e14ae88a8cf22', + md5: '5867dc628a444f2393f7eff007bd4417', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'SIHClient.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'gdVuZXQBA6bGZw2uFsPP', + _score: null, + _source: { + process: { + args: [ + 'C:\\Windows\\System32\\sihclient.exe', + '/cv', + '33nfV21X50ie84HvATAt1w.0.1', + ], + name: 'SIHClient.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599429545370], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'gdVuZXQBA6bGZw2uFsPP', + _score: 0, + _source: { + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + name: 'siem-windows', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + process: { + args: [ + 'C:\\Windows\\System32\\sihclient.exe', + '/cv', + '33nfV21X50ie84HvATAt1w.0.1', + ], + parent: { + args: [ + 'C:\\Windows\\System32\\Upfc.exe', + '/launchtype', + 'periodic', + '/cv', + '33nfV21X50ie84HvATAt1w.0', + ], + name: 'upfc.exe', + pid: 4328, + entity_id: '{ce1d3c9b-5b8b-5f55-7815-000000000b00}', + executable: 'C:\\Windows\\System32\\upfc.exe', + command_line: + 'C:\\Windows\\System32\\Upfc.exe /launchtype periodic /cv 33nfV21X50ie84HvATAt1w.0', + }, + pe: { + imphash: '3bbd1eea2778ee3dcd883a4d5533aec3', + }, + name: 'SIHClient.exe', + pid: 2780, + working_directory: 'C:\\Windows\\system32\\', + entity_id: '{ce1d3c9b-5ba9-5f55-8815-000000000b00}', + executable: 'C:\\Windows\\System32\\SIHClient.exe', + command_line: + 'C:\\Windows\\System32\\sihclient.exe /cv 33nfV21X50ie84HvATAt1w.0.1', + hash: { + sha1: '145ef8d82cf1e451381584cd9565a2d35a442504', + sha256: + '0e0bb70ae1888060b3ffb9a320963551b56dd0d4ce0b5dc1c8fadda4b7bf3f6a', + md5: 'dc1e380b36f4a8309f363d3809e607b8', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + Description: 'SIH Client', + OriginalFileName: 'sihclient.exe', + TerminalSessionId: '0', + IntegrityLevel: 'System', + FileVersion: '10.0.17763.1217 (WinBuild.160101.0800)', + Product: 'Microsoft® Windows® Operating System', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 222106, + event_id: 1, + task: 'Process Create (rule: ProcessCreate)', + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-06 21:59:05.370\nProcessGuid: {ce1d3c9b-5ba9-5f55-8815-000000000b00}\nProcessId: 2780\nImage: C:\\Windows\\System32\\SIHClient.exe\nFileVersion: 10.0.17763.1217 (WinBuild.160101.0800)\nDescription: SIH Client\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: sihclient.exe\nCommandLine: C:\\Windows\\System32\\sihclient.exe /cv 33nfV21X50ie84HvATAt1w.0.1\nCurrentDirectory: C:\\Windows\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=145EF8D82CF1E451381584CD9565A2D35A442504,MD5=DC1E380B36F4A8309F363D3809E607B8,SHA256=0E0BB70AE1888060B3FFB9A320963551B56DD0D4CE0B5DC1C8FADDA4B7BF3F6A,IMPHASH=3BBD1EEA2778EE3DCD883A4D5533AEC3\nParentProcessGuid: {ce1d3c9b-5b8b-5f55-7815-000000000b00}\nParentProcessId: 4328\nParentImage: C:\\Windows\\System32\\upfc.exe\nParentCommandLine: C:\\Windows\\System32\\Upfc.exe /launchtype periodic /cv 33nfV21X50ie84HvATAt1w.0', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-06T21:59:05.370Z', + related: { + user: 'SYSTEM', + hash: [ + '145ef8d82cf1e451381584cd9565a2d35a442504', + 'dc1e380b36f4a8309f363d3809e607b8', + '0e0bb70ae1888060b3ffb9a320963551b56dd0d4ce0b5dc1c8fadda4b7bf3f6a', + '3bbd1eea2778ee3dcd883a4d5533aec3', + ], + }, + ecs: { + version: '1.5.0', + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + kind: 'event', + created: '2020-09-06T21:59:06.713Z', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + category: ['process'], + type: ['start', 'process_start'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: '145ef8d82cf1e451381584cd9565a2d35a442504', + imphash: '3bbd1eea2778ee3dcd883a4d5533aec3', + sha256: + '0e0bb70ae1888060b3ffb9a320963551b56dd0d4ce0b5dc1c8fadda4b7bf3f6a', + md5: 'dc1e380b36f4a8309f363d3809e607b8', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'SpeechModelDownload.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: '6NmKZnQBA6bGZw2uma12', + _score: null, + _source: { + process: { + args: [ + 'C:\\Windows\\system32\\speech_onecore\\common\\SpeechModelDownload.exe', + ], + name: 'SpeechModelDownload.exe', + }, + user: { + name: 'NETWORK SERVICE', + }, + }, + sort: [1599448191225], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: '6NmKZnQBA6bGZw2uma12', + _score: 0, + _source: { + process: { + args: [ + 'C:\\Windows\\system32\\speech_onecore\\common\\SpeechModelDownload.exe', + ], + parent: { + args: ['C:\\Windows\\system32\\svchost.exe', '-k', 'netsvcs', '-p'], + name: 'svchost.exe', + pid: 1060, + entity_id: '{ce1d3c9b-b9b1-5f34-1c00-000000000b00}', + executable: 'C:\\Windows\\System32\\svchost.exe', + command_line: 'C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + }, + pe: { + imphash: '23bd5f904494d14029d9263cebae088d', + }, + name: 'SpeechModelDownload.exe', + working_directory: 'C:\\Windows\\system32\\', + pid: 4328, + entity_id: '{ce1d3c9b-a47f-5f55-9915-000000000b00}', + hash: { + sha1: '03e6e81192621dfd873814de3787c6e7d6af1509', + sha256: + '963fd9dc1b82c44d00eb91d61e2cb442af7357e3a603c23d469df53a6376f073', + md5: '3fd687e97e03d303e02bb37ec85de962', + }, + executable: + 'C:\\Windows\\System32\\Speech_OneCore\\common\\SpeechModelDownload.exe', + command_line: + 'C:\\Windows\\system32\\speech_onecore\\common\\SpeechModelDownload.exe', + }, + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + name: 'siem-windows', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + LogonGuid: '{ce1d3c9b-b9ac-5f34-e403-000000000000}', + Description: 'Speech Model Download Executable', + OriginalFileName: 'SpeechModelDownload.exe', + IntegrityLevel: 'System', + TerminalSessionId: '0', + FileVersion: '10.0.17763.1369 (WinBuild.160101.0800)', + Product: 'Microsoft® Windows® Operating System', + LogonId: '0x3e4', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 222431, + event_id: 1, + task: 'Process Create (rule: ProcessCreate)', + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-07 03:09:51.225\nProcessGuid: {ce1d3c9b-a47f-5f55-9915-000000000b00}\nProcessId: 4328\nImage: C:\\Windows\\System32\\Speech_OneCore\\common\\SpeechModelDownload.exe\nFileVersion: 10.0.17763.1369 (WinBuild.160101.0800)\nDescription: Speech Model Download Executable\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: SpeechModelDownload.exe\nCommandLine: C:\\Windows\\system32\\speech_onecore\\common\\SpeechModelDownload.exe\nCurrentDirectory: C:\\Windows\\system32\\\nUser: NT AUTHORITY\\NETWORK SERVICE\nLogonGuid: {ce1d3c9b-b9ac-5f34-e403-000000000000}\nLogonId: 0x3E4\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=03E6E81192621DFD873814DE3787C6E7D6AF1509,MD5=3FD687E97E03D303E02BB37EC85DE962,SHA256=963FD9DC1B82C44D00EB91D61E2CB442AF7357E3A603C23D469DF53A6376F073,IMPHASH=23BD5F904494D14029D9263CEBAE088D\nParentProcessGuid: {ce1d3c9b-b9b1-5f34-1c00-000000000b00}\nParentProcessId: 1060\nParentImage: C:\\Windows\\System32\\svchost.exe\nParentCommandLine: C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-07T03:09:51.225Z', + related: { + user: 'NETWORK SERVICE', + hash: [ + '03e6e81192621dfd873814de3787c6e7d6af1509', + '3fd687e97e03d303e02bb37ec85de962', + '963fd9dc1b82c44d00eb91d61e2cb442af7357e3a603c23d469df53a6376f073', + '23bd5f904494d14029d9263cebae088d', + ], + }, + ecs: { + version: '1.5.0', + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + kind: 'event', + created: '2020-09-07T03:09:52.370Z', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + type: ['start', 'process_start'], + category: ['process'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'NETWORK SERVICE', + }, + hash: { + sha1: '03e6e81192621dfd873814de3787c6e7d6af1509', + imphash: '23bd5f904494d14029d9263cebae088d', + sha256: + '963fd9dc1b82c44d00eb91d61e2cb442af7357e3a603c23d469df53a6376f073', + md5: '3fd687e97e03d303e02bb37ec85de962', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'UsoClient.exe', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'Pi68Z3QBc39KFIJb3txa', + _score: null, + _source: { + process: { + args: ['C:\\Windows\\system32\\usoclient.exe', 'StartScan'], + name: 'UsoClient.exe', + }, + user: { + name: 'SYSTEM', + }, + }, + sort: [1599468262455], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-windows', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: 'winlogbeat-8.0.0-2020.09.02-000001', + _id: 'Pi68Z3QBc39KFIJb3txa', + _score: 0, + _source: { + process: { + args: ['C:\\Windows\\system32\\usoclient.exe', 'StartScan'], + parent: { + args: ['C:\\Windows\\system32\\svchost.exe', '-k', 'netsvcs', '-p'], + name: 'svchost.exe', + pid: 1060, + entity_id: '{ce1d3c9b-b9b1-5f34-1c00-000000000b00}', + executable: 'C:\\Windows\\System32\\svchost.exe', + command_line: 'C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + }, + pe: { + imphash: '2510e8a4554aef2caf0a913be015929f', + }, + name: 'UsoClient.exe', + pid: 3864, + working_directory: 'C:\\Windows\\system32\\', + entity_id: '{ce1d3c9b-f2e6-5f55-bc15-000000000b00}', + command_line: 'C:\\Windows\\system32\\usoclient.exe StartScan', + executable: 'C:\\Windows\\System32\\UsoClient.exe', + hash: { + sha1: 'ebf56ad89d4740359d5d3d5370b31e56614bbb79', + sha256: + 'df3900cdc3c6f023037aaf2d4407c4e8aaa909013a69539fb4688e2bd099db85', + md5: '39750d33d277617b322adbb917f7b626', + }, + }, + agent: { + build_date: '2020-07-16 09:16:27 +0000 UTC ', + commit: '4dcbde39492bdc3843034bba8db811c68cb44b97 ', + name: 'siem-windows', + id: '05e1bff7-d7a8-416a-8554-aa10288fa07d', + ephemeral_id: '655abd6c-6c33-435d-a2eb-79b2a01e6d61', + type: 'winlogbeat', + version: '8.0.0', + user: { + name: 'inside_winlogbeat_user', + }, + }, + winlog: { + computer_name: 'siem-windows', + process: { + pid: 1252, + thread: { + id: 2896, + }, + }, + channel: 'Microsoft-Windows-Sysmon/Operational', + event_data: { + Company: 'Microsoft Corporation', + Description: 'UsoClient', + LogonGuid: '{ce1d3c9b-b9a7-5f34-e703-000000000000}', + OriginalFileName: 'UsoClient', + TerminalSessionId: '0', + IntegrityLevel: 'System', + FileVersion: '10.0.17763.1007 (WinBuild.160101.0800)', + Product: 'Microsoft® Windows® Operating System', + LogonId: '0x3e7', + RuleName: '-', + }, + opcode: 'Info', + version: 5, + record_id: 222846, + event_id: 1, + task: 'Process Create (rule: ProcessCreate)', + provider_guid: '{5770385f-c22a-43e0-bf4c-06f5698ffbd9}', + api: 'wineventlog', + provider_name: 'Microsoft-Windows-Sysmon', + user: { + identifier: 'S-1-5-18', + domain: 'NT AUTHORITY', + name: 'SYSTEM', + type: 'User', + }, + }, + log: { + level: 'information', + }, + message: + 'Process Create:\nRuleName: -\nUtcTime: 2020-09-07 08:44:22.455\nProcessGuid: {ce1d3c9b-f2e6-5f55-bc15-000000000b00}\nProcessId: 3864\nImage: C:\\Windows\\System32\\UsoClient.exe\nFileVersion: 10.0.17763.1007 (WinBuild.160101.0800)\nDescription: UsoClient\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: UsoClient\nCommandLine: C:\\Windows\\system32\\usoclient.exe StartScan\nCurrentDirectory: C:\\Windows\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {ce1d3c9b-b9a7-5f34-e703-000000000000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA1=EBF56AD89D4740359D5D3D5370B31E56614BBB79,MD5=39750D33D277617B322ADBB917F7B626,SHA256=DF3900CDC3C6F023037AAF2D4407C4E8AAA909013A69539FB4688E2BD099DB85,IMPHASH=2510E8A4554AEF2CAF0A913BE015929F\nParentProcessGuid: {ce1d3c9b-b9b1-5f34-1c00-000000000b00}\nParentProcessId: 1060\nParentImage: C:\\Windows\\System32\\svchost.exe\nParentCommandLine: C:\\Windows\\system32\\svchost.exe -k netsvcs -p', + cloud: { + availability_zone: 'us-central1-c', + instance: { + name: 'siem-windows', + id: '9156726559029788564', + }, + provider: 'gcp', + machine: { + type: 'g1-small', + }, + project: { + id: 'elastic-siem', + }, + }, + '@timestamp': '2020-09-07T08:44:22.455Z', + related: { + user: 'SYSTEM', + hash: [ + 'ebf56ad89d4740359d5d3d5370b31e56614bbb79', + '39750d33d277617b322adbb917f7b626', + 'df3900cdc3c6f023037aaf2d4407c4e8aaa909013a69539fb4688e2bd099db85', + '2510e8a4554aef2caf0a913be015929f', + ], + }, + ecs: { + version: '1.5.0', + }, + host: { + hostname: 'siem-windows', + os: { + build: '17763.1397', + kernel: '10.0.17763.1397 (WinBuild.160101.0800)', + name: 'Windows Server 2019 Datacenter', + family: 'windows', + version: '10.0', + platform: 'windows', + }, + ip: ['fe80::ecf5:decc:3ec3:767e', '10.200.0.15'], + name: 'siem-windows', + id: 'ce1d3c9b-a815-4643-9641-ada0f2c00609', + mac: ['42:01:0a:c8:00:0f'], + architecture: 'x86_64', + }, + event: { + code: 1, + provider: 'Microsoft-Windows-Sysmon', + created: '2020-09-07T08:44:24.029Z', + kind: 'event', + module: 'sysmon', + action: 'Process Create (rule: ProcessCreate)', + category: ['process'], + type: ['start', 'process_start'], + }, + user: { + domain: 'NT AUTHORITY', + name: 'SYSTEM', + }, + hash: { + sha1: 'ebf56ad89d4740359d5d3d5370b31e56614bbb79', + imphash: '2510e8a4554aef2caf0a913be015929f', + sha256: + 'df3900cdc3c6f023037aaf2d4407c4e8aaa909013a69539fb4688e2bd099db85', + md5: '39750d33d277617b322adbb917f7b626', + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'apt-compat', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: '.ds-logs-endpoint.events.process-default-000001', + _id: 'Ziw-Z3QBB-gskcly0vqU', + _score: null, + _source: { + process: { + args: ['/etc/cron.daily/apt-compat'], + name: 'apt-compat', + }, + user: { + name: 'root', + id: 0, + }, + }, + sort: [1599459901154], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-kibana', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: '.ds-logs-endpoint.events.process-default-000001', + _id: 'Ziw-Z3QBB-gskcly0vqU', + _score: 0, + _source: { + agent: { + id: 'b1e3298e-10be-4032-b1ee-5a4cbb280aa1', + type: 'endpoint', + version: '7.9.1', + }, + process: { + Ext: { + ancestry: [ + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYyLTEzMjQzOTMzNTAxLjUzOTIzMzAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYxLTEzMjQzOTMzNTAxLjUzMjIzMTAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYxLTEzMjQzOTMzNTAxLjUyODg0MzAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYwLTEzMjQzOTMzNTAxLjUyMDI5ODAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYwLTEzMjQzOTMzNTAxLjUwNzM4MjAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODU5LTEzMjQzOTMzNTAxLjc3NTM1MDAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTUyNC0xMzIzNjA4NTMzMC4w', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEtMTMyMzYwODUzMjIuMA==', + ], + }, + args: ['/etc/cron.daily/apt-compat'], + parent: { + name: 'run-parts', + pid: 13861, + entity_id: + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYyLTEzMjQzOTMzNTAxLjUzOTIzMzAw', + executable: '/bin/run-parts', + }, + name: 'apt-compat', + pid: 13862, + args_count: 1, + entity_id: + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYyLTEzMjQzOTMzNTAxLjU0NDY0MDAw', + command_line: '/etc/cron.daily/apt-compat', + executable: '/etc/cron.daily/apt-compat', + hash: { + sha1: '61445721d0b5d86ac0a8386a4ceef450118f4fbb', + sha256: + '8eeae3a9df22621d51062e4dadfc5c63b49732b38a37b5d4e52c99c2237e5767', + md5: 'bc4a71cbcaeed4179f25d798257fa980', + }, + }, + message: 'Endpoint process event', + '@timestamp': '2020-09-07T06:25:01.154464000Z', + ecs: { + version: '1.5.0', + }, + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'endpoint.events.process', + }, + elastic: { + agent: { + id: 'ebee9a13-9ae3-4a55-9cb7-72ddf053055f', + }, + }, + host: { + hostname: 'siem-kibana', + os: { + Ext: { + variant: 'Debian', + }, + kernel: '4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 (2018-10-27)', + name: 'Linux', + family: 'debian', + version: '9', + platform: 'debian', + full: 'Debian 9', + }, + ip: ['127.0.0.1', '::1', '10.142.0.7', 'fe80::4001:aff:fe8e:7'], + name: 'siem-kibana', + id: 'e50acb49-820b-c60a-392d-2ef75f276301', + mac: ['42:01:0a:8e:00:07'], + architecture: 'x86_64', + }, + event: { + sequence: 197060, + ingested: '2020-09-07T06:26:44.476888Z', + created: '2020-09-07T06:25:01.154464000Z', + kind: 'event', + module: 'endpoint', + action: 'exec', + id: 'Lp6oofT0fzv0Auzq+++/kwCO', + category: ['process'], + type: ['start'], + dataset: 'endpoint.events.process', + }, + user: { + Ext: { + real: { + name: 'root', + id: 0, + }, + }, + name: 'root', + id: 0, + }, + group: { + Ext: { + real: { + name: 'root', + id: 0, + }, + }, + name: 'root', + id: 0, + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + { + key: 'bsdmainutils', + doc_count: 1, + process: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: '.ds-logs-endpoint.events.process-default-000001', + _id: 'aSw-Z3QBB-gskcly0vqU', + _score: null, + _source: { + process: { + args: ['/etc/cron.daily/bsdmainutils'], + name: 'bsdmainutils', + }, + user: { + name: 'root', + id: 0, + }, + }, + sort: [1599459901155], + }, + ], + }, + }, + hosts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'siem-kibana', + doc_count: 1, + host: { + hits: { + total: 1, + max_score: 0, + hits: [ + { + _index: '.ds-logs-endpoint.events.process-default-000001', + _id: 'aSw-Z3QBB-gskcly0vqU', + _score: 0, + _source: { + agent: { + id: 'b1e3298e-10be-4032-b1ee-5a4cbb280aa1', + type: 'endpoint', + version: '7.9.1', + }, + process: { + Ext: { + ancestry: [ + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYzLTEzMjQzOTMzNTAxLjU1MzMwMzAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYxLTEzMjQzOTMzNTAxLjUzMjIzMTAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYxLTEzMjQzOTMzNTAxLjUyODg0MzAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYwLTEzMjQzOTMzNTAxLjUyMDI5ODAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYwLTEzMjQzOTMzNTAxLjUwNzM4MjAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODU5LTEzMjQzOTMzNTAxLjc3NTM1MDAw', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTUyNC0xMzIzNjA4NTMzMC4w', + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEtMTMyMzYwODUzMjIuMA==', + ], + }, + args: ['/etc/cron.daily/bsdmainutils'], + parent: { + name: 'run-parts', + pid: 13861, + entity_id: + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYzLTEzMjQzOTMzNTAxLjU1MzMwMzAw', + executable: '/bin/run-parts', + }, + name: 'bsdmainutils', + pid: 13863, + args_count: 1, + entity_id: + 'YjFlMzI5OGUtMTBiZS00MDMyLWIxZWUtNWE0Y2JiMjgwYWExLTEzODYzLTEzMjQzOTMzNTAxLjU1ODEyMDAw', + command_line: '/etc/cron.daily/bsdmainutils', + executable: '/etc/cron.daily/bsdmainutils', + hash: { + sha1: 'fd24f1f3986e5527e804c4dccddee29ff42cb682', + sha256: + 'a68002bf1dc9f42a150087b00437448a46f7cae6755ecddca70a6d3c9d20a14b', + md5: '559387f792462a62e3efb1d573e38d11', + }, + }, + message: 'Endpoint process event', + '@timestamp': '2020-09-07T06:25:01.155812000Z', + ecs: { + version: '1.5.0', + }, + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'endpoint.events.process', + }, + elastic: { + agent: { + id: 'ebee9a13-9ae3-4a55-9cb7-72ddf053055f', + }, + }, + host: { + hostname: 'siem-kibana', + os: { + Ext: { + variant: 'Debian', + }, + kernel: '4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 (2018-10-27)', + name: 'Linux', + family: 'debian', + version: '9', + platform: 'debian', + full: 'Debian 9', + }, + ip: ['127.0.0.1', '::1', '10.142.0.7', 'fe80::4001:aff:fe8e:7'], + name: 'siem-kibana', + id: 'e50acb49-820b-c60a-392d-2ef75f276301', + mac: ['42:01:0a:8e:00:07'], + architecture: 'x86_64', + }, + event: { + sequence: 197063, + ingested: '2020-09-07T06:26:44.477164Z', + created: '2020-09-07T06:25:01.155812000Z', + kind: 'event', + module: 'endpoint', + action: 'exec', + id: 'Lp6oofT0fzv0Auzq+++/kwCZ', + category: ['process'], + type: ['start'], + dataset: 'endpoint.events.process', + }, + user: { + Ext: { + real: { + name: 'root', + id: 0, + }, + }, + name: 'root', + id: 0, + }, + group: { + Ext: { + real: { + name: 'root', + id: 0, + }, + }, + name: 'root', + id: 0, + }, + }, + }, + ], + }, + }, + }, + ], + }, + host_count: { + value: 1, + }, + }, + ], + }, + }, + }, + total: 21, + loaded: 21, + edges: [ + { + node: { + _id: 'ayrMZnQBB-gskcly0w7l', + instances: 0, + process: { + args: [ + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.631.0.exe', + 'WD', + '/q', + ], + name: ['AM_Delta_Patch_1.323.631.0.exe'], + }, + hosts: [ + { + id: ['siem-windows'], + name: ['siem-windows'], + }, + ], + user: { + id: [], + name: ['SYSTEM'], + }, + }, + cursor: { + value: '', + tiebreaker: null, + }, + }, + { + node: { + _id: 'M-GvaHQBA6bGZw2uBoYz', + instances: 0, + process: { + args: [ + 'C:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_1.323.673.0.exe', + 'WD', + '/q', + ], + name: ['AM_Delta_Patch_1.323.673.0.exe'], + }, + hosts: [ + { + id: ['siem-windows'], + name: ['siem-windows'], + }, + ], + user: { + id: [], + name: ['SYSTEM'], + }, + }, + cursor: { + value: '', + tiebreaker: null, + }, + }, + { + node: { + _id: 'cinEZnQBB-gskclyvNmU', + instances: 0, + process: { + args: ['C:\\Windows\\system32\\devicecensus.exe'], + name: ['DeviceCensus.exe'], + }, + hosts: [ + { + id: ['siem-windows'], + name: ['siem-windows'], + }, + ], + user: { + id: [], + name: ['SYSTEM'], + }, + }, + cursor: { + value: '', + tiebreaker: null, + }, + }, + { + node: { + _id: 'HNKSZHQBA6bGZw2uCtRk', + instances: 0, + process: { + args: ['C:\\Windows\\system32\\disksnapshot.exe', '-z'], + name: ['DiskSnapshot.exe'], + }, + hosts: [ + { + id: ['siem-windows'], + name: ['siem-windows'], + }, + ], + user: { + id: [], + name: ['SYSTEM'], + }, + }, + cursor: { + value: '', + tiebreaker: null, + }, + }, + { + node: { + _id: '2zncaHQBB-gskcly1QaD', + instances: 0, + process: { + args: [ + 'C:\\Windows\\TEMP\\88C4F57A-8744-4EA6-824E-88FEF8A0E9DD\\dismhost.exe', + '{6BB79B50-2038-4A10-B513-2FAC72FF213E}', + ], + name: ['DismHost.exe'], + }, + hosts: [ + { + id: ['siem-windows'], + name: ['siem-windows'], + }, + ], + user: { + id: [], + name: ['SYSTEM'], + }, + }, + cursor: { + value: '', + tiebreaker: null, + }, + }, + { + node: { + _id: 'gdVuZXQBA6bGZw2uFsPP', + instances: 0, + process: { + args: ['C:\\Windows\\System32\\sihclient.exe', '/cv', '33nfV21X50ie84HvATAt1w.0.1'], + name: ['SIHClient.exe'], + }, + hosts: [ + { + id: ['siem-windows'], + name: ['siem-windows'], + }, + ], + user: { + id: [], + name: ['SYSTEM'], + }, + }, + cursor: { + value: '', + tiebreaker: null, + }, + }, + { + node: { + _id: '6NmKZnQBA6bGZw2uma12', + instances: 0, + process: { + args: ['C:\\Windows\\system32\\speech_onecore\\common\\SpeechModelDownload.exe'], + name: ['SpeechModelDownload.exe'], + }, + hosts: [ + { + id: ['siem-windows'], + name: ['siem-windows'], + }, + ], + user: { + id: [], + name: ['NETWORK SERVICE'], + }, + }, + cursor: { + value: '', + tiebreaker: null, + }, + }, + { + node: { + _id: 'Pi68Z3QBc39KFIJb3txa', + instances: 0, + process: { + args: ['C:\\Windows\\system32\\usoclient.exe', 'StartScan'], + name: ['UsoClient.exe'], + }, + hosts: [ + { + id: ['siem-windows'], + name: ['siem-windows'], + }, + ], + user: { + id: [], + name: ['SYSTEM'], + }, + }, + cursor: { + value: '', + tiebreaker: null, + }, + }, + { + node: { + _id: 'Ziw-Z3QBB-gskcly0vqU', + instances: 0, + process: { + args: ['/etc/cron.daily/apt-compat'], + name: ['apt-compat'], + }, + hosts: [ + { + id: ['siem-kibana'], + name: ['siem-kibana'], + }, + ], + user: { + id: [0], + name: ['root'], + }, + }, + cursor: { + value: '', + tiebreaker: null, + }, + }, + { + node: { + _id: 'aSw-Z3QBB-gskcly0vqU', + instances: 0, + process: { + args: ['/etc/cron.daily/bsdmainutils'], + name: ['bsdmainutils'], + }, + hosts: [ + { + id: ['siem-kibana'], + name: ['siem-kibana'], + }, + ], + user: { + id: [0], + name: ['root'], + }, + }, + cursor: { + value: '', + tiebreaker: null, + }, + }, + ], + inspect: { + dsl: [ + '{\n "allowNoIndices": true,\n "index": [\n "apm-*-transaction*",\n "auditbeat-*",\n "endgame-*",\n "filebeat-*",\n "logs-*",\n "packetbeat-*",\n "winlogbeat-*"\n ],\n "ignoreUnavailable": true,\n "body": {\n "aggregations": {\n "process_count": {\n "cardinality": {\n "field": "process.name"\n }\n },\n "group_by_process": {\n "terms": {\n "size": 10,\n "field": "process.name",\n "order": [\n {\n "host_count": "asc"\n },\n {\n "_count": "asc"\n },\n {\n "_key": "asc"\n }\n ]\n },\n "aggregations": {\n "process": {\n "top_hits": {\n "size": 1,\n "sort": [\n {\n "@timestamp": {\n "order": "desc"\n }\n }\n ],\n "_source": [\n "process.args",\n "process.name",\n "user.id",\n "user.name"\n ]\n }\n },\n "host_count": {\n "cardinality": {\n "field": "host.name"\n }\n },\n "hosts": {\n "terms": {\n "field": "host.name"\n },\n "aggregations": {\n "host": {\n "top_hits": {\n "size": 1,\n "_source": []\n }\n }\n }\n }\n }\n }\n },\n "query": {\n "bool": {\n "should": [\n {\n "bool": {\n "filter": [\n {\n "term": {\n "agent.type": "auditbeat"\n }\n },\n {\n "term": {\n "event.module": "auditd"\n }\n },\n {\n "term": {\n "event.action": "executed"\n }\n }\n ]\n }\n },\n {\n "bool": {\n "filter": [\n {\n "term": {\n "agent.type": "auditbeat"\n }\n },\n {\n "term": {\n "event.module": "system"\n }\n },\n {\n "term": {\n "event.dataset": "process"\n }\n },\n {\n "term": {\n "event.action": "process_started"\n }\n }\n ]\n }\n },\n {\n "bool": {\n "filter": [\n {\n "term": {\n "agent.type": "winlogbeat"\n }\n },\n {\n "term": {\n "event.code": "4688"\n }\n }\n ]\n }\n },\n {\n "bool": {\n "filter": [\n {\n "term": {\n "winlog.event_id": 1\n }\n },\n {\n "term": {\n "winlog.channel": "Microsoft-Windows-Sysmon/Operational"\n }\n }\n ]\n }\n },\n {\n "bool": {\n "filter": [\n {\n "term": {\n "event.type": "process_start"\n }\n },\n {\n "term": {\n "event.category": "process"\n }\n }\n ]\n }\n },\n {\n "bool": {\n "filter": [\n {\n "term": {\n "event.category": "process"\n }\n },\n {\n "term": {\n "event.type": "start"\n }\n }\n ]\n }\n }\n ],\n "minimum_should_match": 1,\n "filter": [\n "{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"match_all\\":{}},{\\"match_phrase\\":{\\"host.name\\":{\\"query\\":\\"siem-kibana\\"}}}],\\"should\\":[],\\"must_not\\":[]}}",\n {\n "range": {\n "@timestamp": {\n "gte": "2020-09-06T15:23:52.757Z",\n "lte": "2020-09-07T15:23:52.757Z",\n "format": "strict_date_optional_time"\n }\n }\n }\n ]\n }\n }\n },\n "size": 0,\n "track_total_hits": false\n}', + ], + }, + pageInfo: { + activePage: 0, + fakeTotalCount: 50, + showMorePagesIndicator: true, + }, + totalCount: 92, +}; + +export const expectedDsl = { + allowNoIndices: true, + index: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + ignoreUnavailable: true, + body: { + aggregations: { + process_count: { cardinality: { field: 'process.name' } }, + group_by_process: { + terms: { + size: 10, + field: 'process.name', + order: [{ host_count: 'asc' }, { _count: 'asc' }, { _key: 'asc' }], + }, + aggregations: { + process: { + top_hits: { + size: 1, + sort: [{ '@timestamp': { order: 'desc' } }], + _source: ['process.args', 'process.name', 'user.id', 'user.name'], + }, + }, + host_count: { cardinality: { field: 'host.name' } }, + hosts: { + terms: { field: 'host.name' }, + aggregations: { host: { top_hits: { size: 1, _source: [] } } }, + }, + }, + }, + }, + query: { + bool: { + should: [ + { + bool: { + filter: [ + { term: { 'agent.type': 'auditbeat' } }, + { term: { 'event.module': 'auditd' } }, + { term: { 'event.action': 'executed' } }, + ], + }, + }, + { + bool: { + filter: [ + { term: { 'agent.type': 'auditbeat' } }, + { term: { 'event.module': 'system' } }, + { term: { 'event.dataset': 'process' } }, + { term: { 'event.action': 'process_started' } }, + ], + }, + }, + { + bool: { + filter: [ + { term: { 'agent.type': 'winlogbeat' } }, + { term: { 'event.code': '4688' } }, + ], + }, + }, + { + bool: { + filter: [ + { term: { 'winlog.event_id': 1 } }, + { term: { 'winlog.channel': 'Microsoft-Windows-Sysmon/Operational' } }, + ], + }, + }, + { + bool: { + filter: [ + { term: { 'event.type': 'process_start' } }, + { term: { 'event.category': 'process' } }, + ], + }, + }, + { + bool: { + filter: [ + { term: { 'event.category': 'process' } }, + { term: { 'event.type': 'start' } }, + ], + }, + }, + ], + minimum_should_match: 1, + filter: [ + '{"bool":{"must":[],"filter":[{"match_all":{}},{"match_phrase":{"host.name":{"query":"siem-kibana"}}}],"should":[],"must_not":[]}}', + { + range: { + '@timestamp': { + gte: '2020-09-06T15:23:52.757Z', + lte: '2020-09-07T15:23:52.757Z', + format: 'strict_date_optional_time', + }, + }, + }, + ], + }, + }, + }, + size: 0, + track_total_hits: false, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/dsl/query.dsl.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/dsl/query.dsl.test.ts new file mode 100644 index 0000000000000..31e4069e458be --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/dsl/query.dsl.test.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { buildQuery } from './query.dsl'; +import { mockOptions, expectedDsl } from '../__mocks__/'; + +describe('buildQuery', () => { + test('build query from options correctly', () => { + expect(buildQuery(mockOptions)).toEqual(expectedDsl); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.test.ts new file mode 100644 index 0000000000000..096ca570ae852 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.test.ts @@ -0,0 +1,269 @@ +/* + * 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 { processFieldsMap } from '../../../../../../common/ecs/ecs_fields'; + +import { + UncommonProcessesEdges, + UncommonProcessHit, +} from '../../../../../../common/search_strategy'; + +import { formatUncommonProcessesData, getHosts, UncommonProcessBucket } from './helpers'; + +describe('helpers', () => { + describe('#getHosts', () => { + const bucket1: UncommonProcessBucket = { + key: '123', + hosts: { + buckets: [ + { + key: '123', + host: { + hits: { + total: 0, + max_score: 0, + hits: [ + { + _index: 'hit-1', + _type: 'type-1', + _id: 'id-1', + _score: 0, + _source: { + host: { + name: ['host-1'], + id: ['host-id-1'], + }, + }, + }, + ], + }, + }, + }, + ], + }, + process: { + hits: { + total: { + value: 1, + relation: 'eq', + }, + max_score: 5, + hits: [], + }, + }, + }; + const bucket2: UncommonProcessBucket = { + key: '345', + hosts: { + buckets: [ + { + key: '123', + host: { + hits: { + total: 0, + max_score: 0, + hits: [ + { + _index: 'hit-1', + _type: 'type-1', + _id: 'id-1', + _score: 0, + _source: { + host: { + name: ['host-1'], + id: ['host-id-1'], + }, + }, + }, + ], + }, + }, + }, + { + key: '345', + host: { + hits: { + total: 0, + max_score: 0, + hits: [ + { + _index: 'hit-2', + _type: 'type-2', + _id: 'id-2', + _score: 0, + _source: { + host: { + name: ['host-2'], + id: ['host-id-2'], + }, + }, + }, + ], + }, + }, + }, + ], + }, + process: { + hits: { + total: { + value: 1, + relation: 'eq', + }, + max_score: 5, + hits: [], + }, + }, + }; + const bucket3: UncommonProcessBucket = { + key: '789', + hosts: { + buckets: [ + { + key: '789', + host: { + hits: { + total: 0, + max_score: 0, + hits: [ + { + _index: 'hit-9', + _type: 'type-9', + _id: 'id-9', + _score: 0, + _source: { + // @ts-expect-error ts doesn't like seeing the object written this way, but sometimes this is the data we get! + 'host.id': ['host-id-9'], + 'host.name': ['host-9'], + }, + }, + ], + }, + }, + }, + ], + }, + process: { + hits: { + total: { + value: 1, + relation: 'eq', + }, + max_score: 5, + hits: [], + }, + }, + }; + + test('will return a single host correctly', () => { + const hosts = getHosts(bucket1.hosts.buckets); + expect(hosts).toEqual([{ id: ['123'], name: ['host-1'] }]); + }); + + test('will return two hosts correctly', () => { + const hosts = getHosts(bucket2.hosts.buckets); + expect(hosts).toEqual([ + { id: ['123'], name: ['host-1'] }, + { id: ['345'], name: ['host-2'] }, + ]); + }); + + test('will return a dot notation host', () => { + const hosts = getHosts(bucket3.hosts.buckets); + expect(hosts).toEqual([{ id: ['789'], name: ['host-9'] }]); + }); + + test('will return no hosts when given an empty array', () => { + const hosts = getHosts([]); + expect(hosts).toEqual([]); + }); + }); + + describe('#formatUncommonProcessesData', () => { + const hit: UncommonProcessHit = { + _index: 'index-123', + _type: 'type-123', + _id: 'id-123', + _score: 10, + total: { + value: 100, + relation: 'eq', + }, + host: [ + { id: ['host-id-1'], name: ['host-name-1'] }, + { id: ['host-id-1'], name: ['host-name-1'] }, + ], + _source: { + '@timestamp': 'time', + process: { + name: ['process-1'], + title: ['title-1'], + }, + }, + cursor: 'cursor-1', + sort: [0], + }; + + test('it formats a uncommon process data with a source of name correctly', () => { + const fields: readonly string[] = ['process.name']; + const data = formatUncommonProcessesData(fields, hit, processFieldsMap); + const expected: UncommonProcessesEdges = { + cursor: { tiebreaker: null, value: 'cursor-1' }, + node: { + _id: 'id-123', + hosts: [ + { id: ['host-id-1'], name: ['host-name-1'] }, + { id: ['host-id-1'], name: ['host-name-1'] }, + ], + process: { + name: ['process-1'], + }, + instances: 100, + }, + }; + expect(data).toEqual(expected); + }); + + test('it formats a uncommon process data with a source of name and title correctly', () => { + const fields: readonly string[] = ['process.name', 'process.title']; + const data = formatUncommonProcessesData(fields, hit, processFieldsMap); + const expected: UncommonProcessesEdges = { + cursor: { tiebreaker: null, value: 'cursor-1' }, + node: { + _id: 'id-123', + hosts: [ + { id: ['host-id-1'], name: ['host-name-1'] }, + { id: ['host-id-1'], name: ['host-name-1'] }, + ], + instances: 100, + process: { + name: ['process-1'], + title: ['title-1'], + }, + }, + }; + expect(data).toEqual(expected); + }); + + test('it formats a uncommon process data without any data if fields is empty', () => { + const fields: readonly string[] = []; + const data = formatUncommonProcessesData(fields, hit, processFieldsMap); + const expected: UncommonProcessesEdges = { + cursor: { + tiebreaker: null, + value: '', + }, + node: { + _id: '', + hosts: [], + instances: 0, + process: {}, + }, + }; + expect(data).toEqual(expected); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.test.ts new file mode 100644 index 0000000000000..a5fa9b459d1bf --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/index.test.ts @@ -0,0 +1,52 @@ +/* + * 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 { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; + +import { HostUncommonProcessesRequestOptions } from '../../../../../../common/search_strategy/security_solution'; +import * as buildQuery from './dsl/query.dsl'; +import { uncommonProcesses } from '.'; +import { + mockOptions, + mockSearchStrategyResponse, + formattedSearchStrategyResponse, +} from './__mocks__'; + +describe('uncommonProcesses search strategy', () => { + const buildUncommonProcessesQuery = jest.spyOn(buildQuery, 'buildQuery'); + + afterEach(() => { + buildUncommonProcessesQuery.mockClear(); + }); + + describe('buildDsl', () => { + test('should build dsl query', () => { + uncommonProcesses.buildDsl(mockOptions); + expect(buildUncommonProcessesQuery).toHaveBeenCalledWith(mockOptions); + }); + + test('should throw error if query size is greater equal than DEFAULT_MAX_TABLE_QUERY_SIZE ', () => { + const overSizeOptions = { + ...mockOptions, + pagination: { + ...mockOptions.pagination, + querySize: DEFAULT_MAX_TABLE_QUERY_SIZE, + }, + } as HostUncommonProcessesRequestOptions; + + expect(() => { + uncommonProcesses.buildDsl(overSizeOptions); + }).toThrowError(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + }); + }); + + describe('parse', () => { + test('should parse data correctly', async () => { + const result = await uncommonProcesses.parse(mockOptions, mockSearchStrategyResponse); + expect(result).toMatchObject(formattedSearchStrategyResponse); + }); + }); +}); From ed5a2bd090bc8f4fffba4cab6bf37292c6a48d0c Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Tue, 8 Sep 2020 08:49:35 -0500 Subject: [PATCH 07/19] Remove useMatchedRoutes/MatchedRouteContext (#76788) We're not using them. --- .../plugins/apm/public/application/index.tsx | 17 ++++------ .../public/context/MatchedRouteContext.tsx | 34 ------------------- .../apm/public/hooks/useMatchedRoutes.tsx | 12 ------- 3 files changed, 7 insertions(+), 56 deletions(-) delete mode 100644 x-pack/plugins/apm/public/context/MatchedRouteContext.tsx delete mode 100644 x-pack/plugins/apm/public/hooks/useMatchedRoutes.tsx diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index 3f4f3116152c4..2b0b3ddd98167 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -27,7 +27,6 @@ import { ApmPluginContext } from '../context/ApmPluginContext'; import { LicenseProvider } from '../context/LicenseContext'; import { LoadingIndicatorProvider } from '../context/LoadingIndicatorContext'; import { LocationProvider } from '../context/LocationContext'; -import { MatchedRouteProvider } from '../context/MatchedRouteContext'; import { UrlParamsProvider } from '../context/UrlParamsContext'; import { ApmPluginSetupDeps } from '../plugin'; import { createCallApmApi } from '../services/rest/createCallApmApi'; @@ -100,15 +99,13 @@ export function ApmAppRoot({ - - - - - - - - - + + + + + + + diff --git a/x-pack/plugins/apm/public/context/MatchedRouteContext.tsx b/x-pack/plugins/apm/public/context/MatchedRouteContext.tsx deleted file mode 100644 index 64a26a183d8cb..0000000000000 --- a/x-pack/plugins/apm/public/context/MatchedRouteContext.tsx +++ /dev/null @@ -1,34 +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 React, { useMemo, ReactChild } from 'react'; -import { matchPath } from 'react-router-dom'; -import { useLocation } from '../hooks/useLocation'; -import { BreadcrumbRoute } from '../components/app/Main/ProvideBreadcrumbs'; - -export const MatchedRouteContext = React.createContext([]); - -interface MatchedRouteProviderProps { - children: ReactChild; - routes: BreadcrumbRoute[]; -} -export function MatchedRouteProvider({ - children, - routes, -}: MatchedRouteProviderProps) { - const { pathname } = useLocation(); - - const contextValue = useMemo(() => { - return routes.filter((route) => { - return matchPath(pathname, { - path: route.path, - }); - }); - }, [pathname, routes]); - - return ( - - ); -} diff --git a/x-pack/plugins/apm/public/hooks/useMatchedRoutes.tsx b/x-pack/plugins/apm/public/hooks/useMatchedRoutes.tsx deleted file mode 100644 index 74250096022d0..0000000000000 --- a/x-pack/plugins/apm/public/hooks/useMatchedRoutes.tsx +++ /dev/null @@ -1,12 +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 { useContext } from 'react'; -import { MatchedRouteContext } from '../context/MatchedRouteContext'; - -export function useMatchedRoutes() { - return useContext(MatchedRouteContext); -} From f351400cd8c7dae536b92743d77bf995b437efcd Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 8 Sep 2020 14:51:54 +0100 Subject: [PATCH 08/19] [Security Strategy] add unit test for lastFirstSeen Search Strategy (#76911) * add unit test for lastFirstSeen * fix types * fix unit test --- .../hosts/last_first_seen/__mocks__/index.ts | 90 +++++++++++++++++++ .../hosts/last_first_seen/index.test.ts | 35 ++++++++ .../query.last_first_seen_host.dsl.test.ts | 13 +++ 3 files changed, 138 insertions(+) create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.test.ts diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts new file mode 100644 index 0000000000000..224dcd1f8de24 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts @@ -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 { HostsQueries } from '../../../../../../../common/search_strategy'; + +export const mockOptions = { + defaultIndex: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + docValueFields: [], + factoryQueryType: HostsQueries.firstLastSeen, + hostName: 'siem-kibana', +}; + +export const mockSearchStrategyResponse = { + isPartial: false, + isRunning: false, + rawResponse: { + took: 230, + timed_out: false, + _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, + hits: { total: -1, max_score: 0, hits: [] }, + aggregations: { + lastSeen: { value: 1599554931759, value_as_string: '2020-09-08T08:48:51.759Z' }, + firstSeen: { value: 1591611722000, value_as_string: '2020-06-08T10:22:02.000Z' }, + }, + }, + total: 21, + loaded: 21, +}; + +export const formattedSearchStrategyResponse = { + isPartial: false, + isRunning: false, + rawResponse: { + took: 230, + timed_out: false, + _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, + hits: { total: -1, max_score: 0, hits: [] }, + aggregations: { + lastSeen: { value: 1599554931759, value_as_string: '2020-09-08T08:48:51.759Z' }, + firstSeen: { value: 1591611722000, value_as_string: '2020-06-08T10:22:02.000Z' }, + }, + }, + total: 21, + loaded: 21, + inspect: { + dsl: [ + '{\n "allowNoIndices": true,\n "index": [\n "apm-*-transaction*",\n "auditbeat-*",\n "endgame-*",\n "filebeat-*",\n "logs-*",\n "packetbeat-*",\n "winlogbeat-*"\n ],\n "ignoreUnavailable": true,\n "body": {\n "docvalue_fields": [],\n "aggregations": {\n "firstSeen": {\n "min": {\n "field": "@timestamp"\n }\n },\n "lastSeen": {\n "max": {\n "field": "@timestamp"\n }\n }\n },\n "query": {\n "bool": {\n "filter": [\n {\n "term": {\n "host.name": "siem-kibana"\n }\n }\n ]\n }\n },\n "size": 0,\n "track_total_hits": false\n }\n}', + ], + response: [ + '{\n "isPartial": false,\n "isRunning": false,\n "rawResponse": {\n "took": 230,\n "timed_out": false,\n "_shards": {\n "total": 21,\n "successful": 21,\n "skipped": 0,\n "failed": 0\n },\n "hits": {\n "total": -1,\n "max_score": 0,\n "hits": []\n },\n "aggregations": {\n "lastSeen": {\n "value": 1599554931759,\n "value_as_string": "2020-09-08T08:48:51.759Z"\n },\n "firstSeen": {\n "value": 1591611722000,\n "value_as_string": "2020-06-08T10:22:02.000Z"\n }\n }\n },\n "total": 21,\n "loaded": 21\n}', + ], + }, + firstSeen: '2020-06-08T10:22:02.000Z', + lastSeen: '2020-09-08T08:48:51.759Z', +}; + +export const expectedDsl = { + allowNoIndices: true, + index: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + ignoreUnavailable: true, + body: { + docvalue_fields: [], + aggregations: { + firstSeen: { min: { field: '@timestamp' } }, + lastSeen: { max: { field: '@timestamp' } }, + }, + query: { bool: { filter: [{ term: { 'host.name': 'siem-kibana' } }] } }, + size: 0, + track_total_hits: false, + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts new file mode 100644 index 0000000000000..9217a2654f1a6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.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 * as buildQuery from './query.last_first_seen_host.dsl'; +import { firstLastSeenHost } from '.'; +import { + mockOptions, + mockSearchStrategyResponse, + formattedSearchStrategyResponse, +} from './__mocks__'; + +describe('firstLastSeenHost search strategy', () => { + const buildFirstLastSeenHostQuery = jest.spyOn(buildQuery, 'buildFirstLastSeenHostQuery'); + + afterEach(() => { + buildFirstLastSeenHostQuery.mockClear(); + }); + + describe('buildDsl', () => { + test('should build dsl query', () => { + firstLastSeenHost.buildDsl(mockOptions); + expect(buildFirstLastSeenHostQuery).toHaveBeenCalledWith(mockOptions); + }); + }); + + describe('parse', () => { + test('should parse data correctly', async () => { + const result = await firstLastSeenHost.parse(mockOptions, mockSearchStrategyResponse); + expect(result).toMatchObject(formattedSearchStrategyResponse); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.test.ts new file mode 100644 index 0000000000000..b03bc3a8197f5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.last_first_seen_host.dsl.test.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { buildFirstLastSeenHostQuery as buildQuery } from './query.last_first_seen_host.dsl'; +import { mockOptions, expectedDsl } from './__mocks__'; + +describe('buildQuery', () => { + test('build query from options correctly', () => { + expect(buildQuery(mockOptions)).toEqual(expectedDsl); + }); +}); From a06ade93649509364dd022c674c98ca05518162e Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 8 Sep 2020 14:53:28 +0100 Subject: [PATCH 09/19] add unit test for hosts (#76927) --- .../factory/hosts/helpers.ts | 113 ------------------ .../factory/hosts/index.test.ts | 35 ++++++ 2 files changed, 35 insertions(+), 113 deletions(-) delete mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/helpers.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/helpers.ts deleted file mode 100644 index 56f7aec2327a5..0000000000000 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/helpers.ts +++ /dev/null @@ -1,113 +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 { set } from '@elastic/safer-lodash-set/fp'; -import { get, has, head } from 'lodash/fp'; -import { hostFieldsMap } from '../../../../../common/ecs/ecs_fields'; -import { - HostsEdges, - HostItem, -} from '../../../../../common/search_strategy/security_solution/hosts'; - -import { HostAggEsItem, HostBuckets, HostValue } from '../../../../lib/hosts/types'; - -import { toArray } from '../../../helpers/to_array'; - -const hostsFields = ['_id', 'lastSeen', 'host.id', 'host.name', 'host.os.name', 'host.os.version']; - -export const formatHostEdgesData = (bucket: HostAggEsItem): HostsEdges => - hostsFields.reduce( - (flattenedFields, fieldName) => { - const hostId = get('key', bucket); - flattenedFields.node._id = hostId || null; - flattenedFields.cursor.value = hostId || ''; - const fieldValue = getHostFieldValue(fieldName, bucket); - if (fieldValue != null) { - return set(`node.${fieldName}`, toArray(fieldValue), flattenedFields); - } - return flattenedFields; - }, - { - node: {}, - cursor: { - value: '', - tiebreaker: null, - }, - } as HostsEdges - ); - -const hostFields = [ - '_id', - 'host.architecture', - 'host.id', - 'host.ip', - 'host.id', - 'host.mac', - 'host.name', - 'host.os.family', - 'host.os.name', - 'host.os.platform', - 'host.os.version', - 'host.type', - 'cloud.instance.id', - 'cloud.machine.type', - 'cloud.provider', - 'cloud.region', - 'endpoint.endpointPolicy', - 'endpoint.policyStatus', - 'endpoint.sensorVersion', -]; - -export const formatHostItem = (bucket: HostAggEsItem): HostItem => - hostFields.reduce((flattenedFields, fieldName) => { - const fieldValue = getHostFieldValue(fieldName, bucket); - if (fieldValue != null) { - return set(fieldName, fieldValue, flattenedFields); - } - return flattenedFields; - }, {}); - -const getHostFieldValue = (fieldName: string, bucket: HostAggEsItem): string | string[] | null => { - const aggField = hostFieldsMap[fieldName] - ? hostFieldsMap[fieldName].replace(/\./g, '_') - : fieldName.replace(/\./g, '_'); - if ( - [ - 'host.ip', - 'host.mac', - 'cloud.instance.id', - 'cloud.machine.type', - 'cloud.provider', - 'cloud.region', - ].includes(fieldName) && - has(aggField, bucket) - ) { - const data: HostBuckets = get(aggField, bucket); - return data.buckets.map((obj) => obj.key); - } else if (has(`${aggField}.buckets`, bucket)) { - return getFirstItem(get(`${aggField}`, bucket)); - } else if (has(aggField, bucket)) { - const valueObj: HostValue = get(aggField, bucket); - return valueObj.value_as_string; - } else if (['host.name', 'host.os.name', 'host.os.version'].includes(fieldName)) { - switch (fieldName) { - case 'host.name': - return get('key', bucket) || null; - case 'host.os.name': - return get('os.hits.hits[0]._source.host.os.name', bucket) || null; - case 'host.os.version': - return get('os.hits.hits[0]._source.host.os.version', bucket) || null; - } - } - return null; -}; - -const getFirstItem = (data: HostBuckets): string | null => { - const firstItem = head(data.buckets); - if (firstItem == null) { - return null; - } - return firstItem.key; -}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts new file mode 100644 index 0000000000000..edcba88a0cd89 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.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 { hostsFactory } from '.'; +import { HostsQueries } from '../../../../../common/search_strategy'; +import { allHosts } from './all'; +import { hostDetails } from './details'; +import { hostOverview } from './overview'; +import { firstLastSeenHost } from './last_first_seen'; +import { uncommonProcesses } from './uncommon_processes'; +import { authentications } from './authentications'; + +jest.mock('./all'); +jest.mock('./details'); +jest.mock('./overview'); +jest.mock('./last_first_seen'); +jest.mock('./uncommon_processes'); +jest.mock('./authentications'); + +describe('hostsFactory', () => { + test('should include correct apis', () => { + const expectedHostsFactory = { + [HostsQueries.details]: hostDetails, + [HostsQueries.hosts]: allHosts, + [HostsQueries.overview]: hostOverview, + [HostsQueries.firstLastSeen]: firstLastSeenHost, + [HostsQueries.uncommonProcesses]: uncommonProcesses, + [HostsQueries.authentications]: authentications, + }; + expect(hostsFactory).toEqual(expectedHostsFactory); + }); +}); From 79d799c34c97caa574f1c88d13a4cde83f2eec02 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 8 Sep 2020 14:54:46 +0100 Subject: [PATCH 10/19] [Security Solution] add unit test for host overview search strategy (#76917) * add unit test for host overview search strategy * fix types * fix types * fix unit test --- .../factory/hosts/overview/__mocks__/index.ts | 302 ++++++++++++++++++ .../factory/hosts/overview/index.test.ts | 35 ++ .../overview/query.overview_host.dsl.test.ts | 13 + 3 files changed, 350 insertions(+) create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/__mocks__/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.test.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.test.ts diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/__mocks__/index.ts new file mode 100644 index 0000000000000..c017f39842ba1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/__mocks__/index.ts @@ -0,0 +1,302 @@ +/* + * 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 { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + HostsQueries, + HostOverviewRequestOptions, +} from '../../../../../../../common/search_strategy'; + +export const mockOptions: HostOverviewRequestOptions = { + defaultIndex: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + factoryQueryType: HostsQueries.overview, + filterQuery: + '{"bool":{"must":[],"filter":[{"match_all":{}},{"bool":{"filter":[{"bool":{"should":[{"exists":{"field":"host.name"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}', + timerange: { interval: '12h', from: '2020-09-07T09:47:28.606Z', to: '2020-09-08T09:47:28.606Z' }, +}; + +export const mockSearchStrategyResponse: IEsSearchResponse = { + isPartial: false, + isRunning: false, + rawResponse: { + took: 45, + timed_out: false, + _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, + hits: { total: -1, max_score: 0, hits: [] }, + aggregations: { + fim_count: { meta: {}, doc_count: 0 }, + endgame_module: { + meta: {}, + doc_count: 66903, + process_event_count: { meta: {}, doc_count: 52981 }, + dns_event_count: { meta: {}, doc_count: 0 }, + network_event_count: { meta: {}, doc_count: 9860 }, + security_event_count: { meta: {}, doc_count: 0 }, + image_load_event_count: { meta: {}, doc_count: 0 }, + registry_event: { meta: {}, doc_count: 0 }, + file_event_count: { meta: {}, doc_count: 4062 }, + }, + winlog_module: { + meta: {}, + doc_count: 1949, + mwsysmon_operational_event_count: { meta: {}, doc_count: 1781 }, + security_event_count: { meta: {}, doc_count: 42 }, + }, + auditd_count: { meta: {}, doc_count: 0 }, + system_module: { + meta: {}, + doc_count: 1793, + package_count: { doc_count: 0 }, + login_count: { doc_count: 0 }, + user_count: { doc_count: 0 }, + process_count: { doc_count: 0 }, + filebeat_count: { doc_count: 1793 }, + }, + }, + }, + total: 21, + loaded: 21, +}; + +export const formattedSearchStrategyResponse = { + isPartial: false, + isRunning: false, + rawResponse: { + took: 45, + timed_out: false, + _shards: { total: 21, successful: 21, skipped: 0, failed: 0 }, + hits: { total: -1, max_score: 0, hits: [] }, + aggregations: { + fim_count: { meta: {}, doc_count: 0 }, + endgame_module: { + meta: {}, + doc_count: 66903, + process_event_count: { meta: {}, doc_count: 52981 }, + dns_event_count: { meta: {}, doc_count: 0 }, + network_event_count: { meta: {}, doc_count: 9860 }, + security_event_count: { meta: {}, doc_count: 0 }, + image_load_event_count: { meta: {}, doc_count: 0 }, + registry_event: { meta: {}, doc_count: 0 }, + file_event_count: { meta: {}, doc_count: 4062 }, + }, + winlog_module: { + meta: {}, + doc_count: 1949, + mwsysmon_operational_event_count: { meta: {}, doc_count: 1781 }, + security_event_count: { meta: {}, doc_count: 42 }, + }, + auditd_count: { meta: {}, doc_count: 0 }, + system_module: { + meta: {}, + doc_count: 1793, + package_count: { doc_count: 0 }, + login_count: { doc_count: 0 }, + user_count: { doc_count: 0 }, + process_count: { doc_count: 0 }, + filebeat_count: { doc_count: 1793 }, + }, + }, + }, + total: 21, + loaded: 21, + inspect: { + dsl: [ + '{\n "allowNoIndices": true,\n "index": [\n "apm-*-transaction*",\n "auditbeat-*",\n "endgame-*",\n "filebeat-*",\n "logs-*",\n "packetbeat-*",\n "winlogbeat-*"\n ],\n "ignoreUnavailable": true,\n "body": {\n "aggregations": {\n "auditd_count": {\n "filter": {\n "term": {\n "event.module": "auditd"\n }\n }\n },\n "endgame_module": {\n "filter": {\n "bool": {\n "should": [\n {\n "term": {\n "event.module": "endpoint"\n }\n },\n {\n "term": {\n "event.module": "endgame"\n }\n }\n ]\n }\n },\n "aggs": {\n "dns_event_count": {\n "filter": {\n "bool": {\n "should": [\n {\n "bool": {\n "filter": [\n {\n "term": {\n "network.protocol": "dns"\n }\n },\n {\n "term": {\n "event.category": "network"\n }\n }\n ]\n }\n },\n {\n "term": {\n "endgame.event_type_full": "dns_event"\n }\n }\n ]\n }\n }\n },\n "file_event_count": {\n "filter": {\n "bool": {\n "should": [\n {\n "term": {\n "event.category": "file"\n }\n },\n {\n "term": {\n "endgame.event_type_full": "file_event"\n }\n }\n ]\n }\n }\n },\n "image_load_event_count": {\n "filter": {\n "bool": {\n "should": [\n {\n "bool": {\n "should": [\n {\n "term": {\n "event.category": "library"\n }\n },\n {\n "term": {\n "event.category": "driver"\n }\n }\n ]\n }\n },\n {\n "term": {\n "endgame.event_type_full": "image_load_event"\n }\n }\n ]\n }\n }\n },\n "network_event_count": {\n "filter": {\n "bool": {\n "should": [\n {\n "bool": {\n "filter": [\n {\n "bool": {\n "must_not": {\n "term": {\n "network.protocol": "dns"\n }\n }\n }\n },\n {\n "term": {\n "event.category": "network"\n }\n }\n ]\n }\n },\n {\n "term": {\n "endgame.event_type_full": "network_event"\n }\n }\n ]\n }\n }\n },\n "process_event_count": {\n "filter": {\n "bool": {\n "should": [\n {\n "term": {\n "event.category": "process"\n }\n },\n {\n "term": {\n "endgame.event_type_full": "process_event"\n }\n }\n ]\n }\n }\n },\n "registry_event": {\n "filter": {\n "bool": {\n "should": [\n {\n "term": {\n "event.category": "registry"\n }\n },\n {\n "term": {\n "endgame.event_type_full": "registry_event"\n }\n }\n ]\n }\n }\n },\n "security_event_count": {\n "filter": {\n "bool": {\n "should": [\n {\n "bool": {\n "filter": [\n {\n "term": {\n "event.category": "session"\n }\n },\n {\n "term": {\n "event.category": "authentication"\n }\n }\n ]\n }\n },\n {\n "term": {\n "endgame.event_type_full": "security_event"\n }\n }\n ]\n }\n }\n }\n }\n },\n "fim_count": {\n "filter": {\n "term": {\n "event.module": "file_integrity"\n }\n }\n },\n "winlog_module": {\n "filter": {\n "term": {\n "agent.type": "winlogbeat"\n }\n },\n "aggs": {\n "mwsysmon_operational_event_count": {\n "filter": {\n "term": {\n "winlog.channel": "Microsoft-Windows-Sysmon/Operational"\n }\n }\n },\n "security_event_count": {\n "filter": {\n "term": {\n "winlog.channel": "Security"\n }\n }\n }\n }\n },\n "system_module": {\n "filter": {\n "term": {\n "event.module": "system"\n }\n },\n "aggs": {\n "login_count": {\n "filter": {\n "term": {\n "event.dataset": "login"\n }\n }\n },\n "package_count": {\n "filter": {\n "term": {\n "event.dataset": "package"\n }\n }\n },\n "process_count": {\n "filter": {\n "term": {\n "event.dataset": "process"\n }\n }\n },\n "user_count": {\n "filter": {\n "term": {\n "event.dataset": "user"\n }\n }\n },\n "filebeat_count": {\n "filter": {\n "term": {\n "agent.type": "filebeat"\n }\n }\n }\n }\n }\n },\n "query": {\n "bool": {\n "filter": [\n "{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"match_all\\":{}},{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"exists\\":{\\"field\\":\\"host.name\\"}}],\\"minimum_should_match\\":1}}]}}],\\"should\\":[],\\"must_not\\":[]}}",\n {\n "range": {\n "@timestamp": {\n "gte": "2020-09-07T09:47:28.606Z",\n "lte": "2020-09-08T09:47:28.606Z",\n "format": "strict_date_optional_time"\n }\n }\n }\n ]\n }\n },\n "size": 0,\n "track_total_hits": false\n }\n}', + ], + }, + overviewHost: { + auditbeatAuditd: 0, + auditbeatFIM: 0, + auditbeatLogin: 0, + auditbeatPackage: 0, + auditbeatProcess: 0, + auditbeatUser: 0, + endgameDns: 0, + endgameFile: 4062, + endgameImageLoad: 0, + endgameNetwork: 9860, + endgameProcess: 52981, + endgameRegistry: 0, + endgameSecurity: 0, + filebeatSystemModule: 1793, + winlogbeatSecurity: 42, + winlogbeatMWSysmonOperational: null, + }, +}; + +export const expectedDsl = { + allowNoIndices: true, + index: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + ignoreUnavailable: true, + body: { + aggregations: { + auditd_count: { filter: { term: { 'event.module': 'auditd' } } }, + endgame_module: { + filter: { + bool: { + should: [ + { term: { 'event.module': 'endpoint' } }, + { term: { 'event.module': 'endgame' } }, + ], + }, + }, + aggs: { + dns_event_count: { + filter: { + bool: { + should: [ + { + bool: { + filter: [ + { term: { 'network.protocol': 'dns' } }, + { term: { 'event.category': 'network' } }, + ], + }, + }, + { term: { 'endgame.event_type_full': 'dns_event' } }, + ], + }, + }, + }, + file_event_count: { + filter: { + bool: { + should: [ + { term: { 'event.category': 'file' } }, + { term: { 'endgame.event_type_full': 'file_event' } }, + ], + }, + }, + }, + image_load_event_count: { + filter: { + bool: { + should: [ + { + bool: { + should: [ + { term: { 'event.category': 'library' } }, + { term: { 'event.category': 'driver' } }, + ], + }, + }, + { term: { 'endgame.event_type_full': 'image_load_event' } }, + ], + }, + }, + }, + network_event_count: { + filter: { + bool: { + should: [ + { + bool: { + filter: [ + { bool: { must_not: { term: { 'network.protocol': 'dns' } } } }, + { term: { 'event.category': 'network' } }, + ], + }, + }, + { term: { 'endgame.event_type_full': 'network_event' } }, + ], + }, + }, + }, + process_event_count: { + filter: { + bool: { + should: [ + { term: { 'event.category': 'process' } }, + { term: { 'endgame.event_type_full': 'process_event' } }, + ], + }, + }, + }, + registry_event: { + filter: { + bool: { + should: [ + { term: { 'event.category': 'registry' } }, + { term: { 'endgame.event_type_full': 'registry_event' } }, + ], + }, + }, + }, + security_event_count: { + filter: { + bool: { + should: [ + { + bool: { + filter: [ + { term: { 'event.category': 'session' } }, + { term: { 'event.category': 'authentication' } }, + ], + }, + }, + { term: { 'endgame.event_type_full': 'security_event' } }, + ], + }, + }, + }, + }, + }, + fim_count: { filter: { term: { 'event.module': 'file_integrity' } } }, + winlog_module: { + filter: { term: { 'agent.type': 'winlogbeat' } }, + aggs: { + mwsysmon_operational_event_count: { + filter: { term: { 'winlog.channel': 'Microsoft-Windows-Sysmon/Operational' } }, + }, + security_event_count: { filter: { term: { 'winlog.channel': 'Security' } } }, + }, + }, + system_module: { + filter: { term: { 'event.module': 'system' } }, + aggs: { + login_count: { filter: { term: { 'event.dataset': 'login' } } }, + package_count: { filter: { term: { 'event.dataset': 'package' } } }, + process_count: { filter: { term: { 'event.dataset': 'process' } } }, + user_count: { filter: { term: { 'event.dataset': 'user' } } }, + filebeat_count: { filter: { term: { 'agent.type': 'filebeat' } } }, + }, + }, + }, + query: { + bool: { + filter: [ + '{"bool":{"must":[],"filter":[{"match_all":{}},{"bool":{"filter":[{"bool":{"should":[{"exists":{"field":"host.name"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}', + { + range: { + '@timestamp': { + gte: '2020-09-07T09:47:28.606Z', + lte: '2020-09-08T09:47:28.606Z', + format: 'strict_date_optional_time', + }, + }, + }, + ], + }, + }, + size: 0, + track_total_hits: false, + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.test.ts new file mode 100644 index 0000000000000..e5c3f4bd2c2c3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/index.test.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 * as buildQuery from './query.overview_host.dsl'; +import { hostOverview } from '.'; +import { + mockOptions, + mockSearchStrategyResponse, + formattedSearchStrategyResponse, +} from './__mocks__'; + +describe('hostOverview search strategy', () => { + const buildOverviewHostQuery = jest.spyOn(buildQuery, 'buildOverviewHostQuery'); + + afterEach(() => { + buildOverviewHostQuery.mockClear(); + }); + + describe('buildDsl', () => { + test('should build dsl query', () => { + hostOverview.buildDsl(mockOptions); + expect(buildOverviewHostQuery).toHaveBeenCalledWith(mockOptions); + }); + }); + + describe('parse', () => { + test('should parse data correctly', async () => { + const result = await hostOverview.parse(mockOptions, mockSearchStrategyResponse); + expect(result).toMatchObject(formattedSearchStrategyResponse); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.test.ts new file mode 100644 index 0000000000000..eb4ea4f215b34 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/overview/query.overview_host.dsl.test.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { buildOverviewHostQuery as buildQuery } from './query.overview_host.dsl'; +import { mockOptions, expectedDsl } from './__mocks__/'; + +describe('buildQuery', () => { + test('build query from options correctly', () => { + expect(buildQuery(mockOptions)).toEqual(expectedDsl); + }); +}); From e8803d86f45a1d04cfab93d1bb2ceca3c86f65d6 Mon Sep 17 00:00:00 2001 From: James Rodewig <40268737+jrodewig@users.noreply.github.com> Date: Tue, 8 Sep 2020 10:01:47 -0400 Subject: [PATCH 11/19] [Ingest Pipelines] Add descriptions for ingest processors E-J (#76113) Co-authored-by: Jean-Louis Leysens --- .../manage_processor_form/processors/gsub.tsx | 14 +--- .../processors/html_strip.tsx | 12 +--- .../manage_processor_form/processors/join.tsx | 14 +--- .../manage_processor_form/processors/json.tsx | 7 +- .../shared/map_processor_type_to_form.tsx | 69 ++++++++++++++++++- 5 files changed, 72 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/gsub.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/gsub.tsx index a42df6873d57b..2f2a75853d9e9 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/gsub.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/gsub.tsx @@ -6,8 +6,6 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiCode } from '@elastic/eui'; import { FIELD_TYPES, fieldValidators, UseField, Field } from '../../../../../../shared_imports'; @@ -87,17 +85,7 @@ export const Gsub: FunctionComponent = () => { - {'field'}, - }} - /> - } - /> + diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/html_strip.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/html_strip.tsx index fb1a2d97672b0..c3f38cb021371 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/html_strip.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/html_strip.tsx @@ -6,8 +6,6 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiCode } from '@elastic/eui'; import { FieldNameField } from './common_fields/field_name_field'; import { IgnoreMissingField } from './common_fields/ignore_missing_field'; @@ -23,15 +21,7 @@ export const HtmlStrip: FunctionComponent = () => { )} /> - {'field'} }} - /> - } - /> + diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/join.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/join.tsx index ab077d3337f63..c70f48e0297e4 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/join.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/join.tsx @@ -6,8 +6,6 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiCode } from '@elastic/eui'; import { FIELD_TYPES, fieldValidators, UseField, Field } from '../../../../../../shared_imports'; @@ -55,17 +53,7 @@ export const Join: FunctionComponent = () => { - {'field'}, - }} - /> - } - /> + ); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/json.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/json.tsx index b68b398325085..f01228a26297b 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/json.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/json.tsx @@ -65,12 +65,7 @@ export const Json: FunctionComponent = () => { )} /> - + + {'enrich policy'} + + ), + }} + /> + ); + }, }, fail: { FieldsComponent: Fail, @@ -178,6 +198,10 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { label: i18n.translate('xpack.ingestPipelines.processors.label.fail', { defaultMessage: 'Fail', }), + description: i18n.translate('xpack.ingestPipelines.processors.description.fail', { + defaultMessage: + 'Returns a custom error message on failure. Often used to notify requesters of required conditions.', + }), }, foreach: { FieldsComponent: Foreach, @@ -185,6 +209,9 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { label: i18n.translate('xpack.ingestPipelines.processors.label.foreach', { defaultMessage: 'Foreach', }), + description: i18n.translate('xpack.ingestPipelines.processors.description.foreach', { + defaultMessage: 'Applies an ingest processor to each value in an array.', + }), }, geoip: { FieldsComponent: GeoIP, @@ -192,6 +219,10 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { label: i18n.translate('xpack.ingestPipelines.processors.label.geoip', { defaultMessage: 'GeoIP', }), + description: i18n.translate('xpack.ingestPipelines.processors.description.geoip', { + defaultMessage: + 'Adds geo data based on an IP address. Uses geo data from a Maxmind database file.', + }), }, grok: { FieldsComponent: Grok, @@ -199,6 +230,25 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { label: i18n.translate('xpack.ingestPipelines.processors.label.grok', { defaultMessage: 'Grok', }), + description: function Description() { + const { + services: { documentation }, + } = useKibana(); + const esDocUrl = documentation.getEsDocsBasePath(); + return ( + + {'grok'} + + ), + }} + /> + ); + }, }, gsub: { FieldsComponent: Gsub, @@ -206,6 +256,9 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { label: i18n.translate('xpack.ingestPipelines.processors.label.gsub', { defaultMessage: 'Gsub', }), + description: i18n.translate('xpack.ingestPipelines.processors.description.gsub', { + defaultMessage: 'Uses a regular expression to replace field substrings.', + }), }, html_strip: { FieldsComponent: HtmlStrip, @@ -213,6 +266,9 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { label: i18n.translate('xpack.ingestPipelines.processors.label.htmlStrip', { defaultMessage: 'HTML strip', }), + description: i18n.translate('xpack.ingestPipelines.processors.description.htmlStrip', { + defaultMessage: 'Removes HTML tags from a field.', + }), }, inference: { FieldsComponent: Inference, @@ -220,6 +276,10 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { label: i18n.translate('xpack.ingestPipelines.processors.label.inference', { defaultMessage: 'Inference', }), + description: i18n.translate('xpack.ingestPipelines.processors.description.inference', { + defaultMessage: + 'Uses a pre-trained data frame analytics model to infer against incoming data.', + }), }, join: { FieldsComponent: Join, @@ -227,6 +287,10 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { label: i18n.translate('xpack.ingestPipelines.processors.label.join', { defaultMessage: 'Join', }), + description: i18n.translate('xpack.ingestPipelines.processors.description.join', { + defaultMessage: + 'Joins array elements into a string. Inserts a separator between each element.', + }), }, json: { FieldsComponent: Json, @@ -234,6 +298,9 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { label: i18n.translate('xpack.ingestPipelines.processors.label.json', { defaultMessage: 'JSON', }), + description: i18n.translate('xpack.ingestPipelines.processors.description.json', { + defaultMessage: 'Creates a JSON object from a compatible string.', + }), }, kv: { FieldsComponent: Kv, From 0286c7f70298ad9b92b4fbc2d40ba9a2ab83b195 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Tue, 8 Sep 2020 09:15:28 -0500 Subject: [PATCH 12/19] Replace uses of useUrlParams for path params (#76459) Part of #51963. --- .../apm/dev_docs/routing_and_linking.md | 38 ++++ .../app/ErrorGroupDetails/index.tsx | 27 ++- .../List/__test__/List.test.tsx | 4 +- .../app/ErrorGroupOverview/List/index.tsx | 8 +- .../app/ErrorGroupOverview/index.tsx | 17 +- .../app/Main/route_config/index.tsx | 192 ++++++++++++------ .../Main/route_config/route_config.test.tsx | 6 +- .../route_handlers/agent_configuration.tsx | 16 +- .../app/RumDashboard/ClientMetrics/index.tsx | 6 +- .../PageLoadDistribution/index.tsx | 13 +- .../PageLoadDistribution/use_breakdowns.ts | 6 +- .../app/RumDashboard/PageViewsTrend/index.tsx | 6 +- .../RumDashboard/VisitorBreakdown/index.tsx | 6 +- .../app/ServiceDetails/ServiceDetailTabs.tsx | 21 +- .../components/app/ServiceDetails/index.tsx | 19 +- .../components/app/ServiceMetrics/index.tsx | 10 +- .../app/ServiceNodeMetrics/index.test.tsx | 8 +- .../app/ServiceNodeMetrics/index.tsx | 35 ++-- .../app/ServiceNodeOverview/index.tsx | 14 +- .../app/TraceLink/__test__/TraceLink.test.tsx | 103 ++++++---- .../public/components/app/TraceLink/index.tsx | 7 +- .../app/TransactionDetails/index.tsx | 8 +- .../TransactionOverview.test.tsx | 12 +- .../app/TransactionOverview/index.tsx | 9 +- .../shared/EnvironmentFilter/index.tsx | 5 +- .../shared/ErrorRateAlertTrigger/index.tsx | 18 +- .../shared/KueryBar/get_bool_filter.ts | 25 ++- .../components/shared/KueryBar/index.tsx | 16 +- .../shared/KueryBar/use_processor_event.ts | 47 +++++ .../shared/ServiceAlertTrigger/index.tsx | 6 +- .../TransactionDurationAlertTrigger/index.tsx | 4 +- .../index.tsx | 3 +- .../charts/TransactionCharts/ml_header.tsx | 13 +- .../apm/public/context/ChartsSyncContext.tsx | 5 +- .../__tests__/UrlParamsContext.test.tsx | 18 -- .../context/UrlParamsContext/helpers.ts | 77 ------- .../UrlParamsContext/resolveUrlParams.ts | 18 -- .../public/context/UrlParamsContext/types.ts | 6 - .../plugins/apm/public/hooks/useAgentName.ts | 5 +- .../public/hooks/useServiceMetricCharts.ts | 9 +- .../hooks/useServiceTransactionTypes.tsx | 4 +- .../apm/public/hooks/useTransactionList.ts | 8 +- x-pack/plugins/apm/readme.md | 1 + 43 files changed, 495 insertions(+), 384 deletions(-) create mode 100644 x-pack/plugins/apm/dev_docs/routing_and_linking.md create mode 100644 x-pack/plugins/apm/public/components/shared/KueryBar/use_processor_event.ts diff --git a/x-pack/plugins/apm/dev_docs/routing_and_linking.md b/x-pack/plugins/apm/dev_docs/routing_and_linking.md new file mode 100644 index 0000000000000..d27513d44935f --- /dev/null +++ b/x-pack/plugins/apm/dev_docs/routing_and_linking.md @@ -0,0 +1,38 @@ +# APM Plugin Routing and Linking + +## Routing + +This document describes routing in the APM plugin. + +### Server-side + +Route definitions for APM's server-side API are in the [server/routes directory](../server/routes). Routes are created with [the `createRoute` function](../server/routes/create_route.ts). Routes are added to the API in [the `createApmApi` function](../server/routes/create_apm_api.ts), which is initialized in the plugin `start` lifecycle method. + +The path and query string parameters are defined in the calls to `createRoute` with io-ts types, so that each route has its parameters type checked. + +### Client-side + +The client-side routing uses [React Router](https://reactrouter.com/), The [`ApmRoute` component from the Elastic RUM Agent](https://www.elastic.co/guide/en/apm/agent/rum-js/current/react-integration.html), and the `history` object provided by the Kibana Platform. + +Routes are defined in [public/components/app/Main/route_config/index.tsx](../public/components/app/Main/route_config/index.tsx). These contain route definitions as well as the breadcrumb text. + +#### Parameter handling + +Path parameters (like `serviceName` in '/services/:serviceName/transactions') are handled by the `match.params` props passed into +routes by React Router. The types of these parameters are defined in the route definitions. + +If the parameters are not available as props you can use React Router's `useParams`, but their type definitions should be delcared inline and it's a good idea to make the properties optional if you don't know where a component will be used, since those parameters might not be available at that route. + +Query string parameters can be used in any component with `useUrlParams`. All of the available parameters are defined by this hook and its context. + +## Linking + +Raw URLs should almost never be used in the APM UI. Instead, we have mechanisms for creating links and URLs that ensure links are reliable. + +### In-app linking + +Links that stay inside APM should use the [`getAPMHref` function and `APMLink` component](../public/components/shared/Links/apm/APMLink.tsx). Other components inside that directory contain other functions and components that provide the same functionality for linking to more specific sections inside the APM plugin. + +### Cross-app linking + +Other helpers and components in [the Links directory](../public/components/shared/Links) allow linking to other Kibana apps. diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx index 31f299f94bc26..e95d35142684d 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx @@ -15,11 +15,11 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { Fragment } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; import styled from 'styled-components'; import { useTrackPageview } from '../../../../../observability/public'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; import { useFetcher } from '../../../hooks/useFetcher'; -import { useLocation } from '../../../hooks/useLocation'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { fontFamilyCode, fontSizes, px, units } from '../../../style/variables'; @@ -56,19 +56,24 @@ function getShortGroupId(errorGroupId?: string) { return errorGroupId.slice(0, 5); } -export function ErrorGroupDetails() { - const location = useLocation(); +type ErrorGroupDetailsProps = RouteComponentProps<{ + groupId: string; + serviceName: string; +}>; + +export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) { + const { serviceName, groupId } = match.params; const { urlParams, uiFilters } = useUrlParams(); - const { serviceName, start, end, errorGroupId } = urlParams; + const { start, end } = urlParams; const { data: errorGroupData } = useFetcher(() => { - if (serviceName && start && end && errorGroupId) { + if (start && end) { return callApmApi({ pathname: '/api/apm/services/{serviceName}/errors/{groupId}', params: { path: { serviceName, - groupId: errorGroupId, + groupId, }, query: { start, @@ -78,10 +83,10 @@ export function ErrorGroupDetails() { }, }); } - }, [serviceName, start, end, errorGroupId, uiFilters]); + }, [serviceName, start, end, groupId, uiFilters]); const { data: errorDistributionData } = useFetcher(() => { - if (serviceName && start && end && errorGroupId) { + if (start && end) { return callApmApi({ pathname: '/api/apm/services/{serviceName}/errors/distribution', params: { @@ -91,13 +96,13 @@ export function ErrorGroupDetails() { query: { start, end, - groupId: errorGroupId, + groupId, uiFilters: JSON.stringify(uiFilters), }, }, }); } - }, [serviceName, start, end, errorGroupId, uiFilters]); + }, [serviceName, start, end, groupId, uiFilters]); useTrackPageview({ app: 'apm', path: 'error_group_details' }); useTrackPageview({ app: 'apm', path: 'error_group_details', delay: 15000 }); @@ -124,7 +129,7 @@ export function ErrorGroupDetails() { {i18n.translate('xpack.apm.errorGroupDetails.errorGroupTitle', { defaultMessage: 'Error group {errorGroupId}', values: { - errorGroupId: getShortGroupId(urlParams.errorGroupId), + errorGroupId: getShortGroupId(groupId), }, })} diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx index 5798deaf19c9c..1acfc5c49245d 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx @@ -27,7 +27,7 @@ describe('ErrorGroupOverview -> List', () => { const storeState = {}; const wrapper = mount( - + , storeState ); @@ -39,7 +39,7 @@ describe('ErrorGroupOverview -> List', () => { const wrapper = mount( - + ); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx index 5c16bf0f324be..33105189f9c3e 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx @@ -51,16 +51,12 @@ const Culprit = styled.div` interface Props { items: ErrorGroupListAPIResponse; + serviceName: string; } -function ErrorGroupList(props: Props) { - const { items } = props; +function ErrorGroupList({ items, serviceName }: Props) { const { urlParams } = useUrlParams(); - const { serviceName } = urlParams; - if (!serviceName) { - throw new Error('Service name is required'); - } const columns = useMemo( () => [ { diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx index 92ea044720531..42b0016ca8cfe 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx @@ -22,13 +22,17 @@ import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { ErrorDistribution } from '../ErrorGroupDetails/Distribution'; import { ErrorGroupList } from './List'; -function ErrorGroupOverview() { +interface ErrorGroupOverviewProps { + serviceName: string; +} + +function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { const { urlParams, uiFilters } = useUrlParams(); - const { serviceName, start, end, sortField, sortDirection } = urlParams; + const { start, end, sortField, sortDirection } = urlParams; const { data: errorDistributionData } = useFetcher(() => { - if (serviceName && start && end) { + if (start && end) { return callApmApi({ pathname: '/api/apm/services/{serviceName}/errors/distribution', params: { @@ -48,7 +52,7 @@ function ErrorGroupOverview() { const { data: errorGroupListData } = useFetcher(() => { const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc'; - if (serviceName && start && end) { + if (start && end) { return callApmApi({ pathname: '/api/apm/services/{serviceName}/errors', params: { @@ -117,7 +121,10 @@ function ErrorGroupOverview() { - + diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx index 56026dcf477ec..1fe5f17c39985 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx @@ -7,38 +7,33 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; +import { UNIDENTIFIED_SERVICE_NODES_LABEL } from '../../../../../common/i18n'; import { SERVICE_NODE_NAME_MISSING } from '../../../../../common/service_nodes'; +import { toQuery } from '../../../shared/Links/url_helpers'; import { ErrorGroupDetails } from '../../ErrorGroupDetails'; -import { ServiceDetails } from '../../ServiceDetails'; -import { TransactionDetails } from '../../TransactionDetails'; import { Home } from '../../Home'; -import { BreadcrumbRoute } from '../ProvideBreadcrumbs'; -import { RouteName } from './route_names'; +import { ServiceDetails } from '../../ServiceDetails'; +import { ServiceNodeMetrics } from '../../ServiceNodeMetrics'; import { Settings } from '../../Settings'; import { AgentConfigurations } from '../../Settings/AgentConfigurations'; +import { AnomalyDetection } from '../../Settings/anomaly_detection'; import { ApmIndices } from '../../Settings/ApmIndices'; -import { toQuery } from '../../../shared/Links/url_helpers'; -import { ServiceNodeMetrics } from '../../ServiceNodeMetrics'; -import { resolveUrlParams } from '../../../../context/UrlParamsContext/resolveUrlParams'; -import { UNIDENTIFIED_SERVICE_NODES_LABEL } from '../../../../../common/i18n'; -import { TraceLink } from '../../TraceLink'; import { CustomizeUI } from '../../Settings/CustomizeUI'; -import { AnomalyDetection } from '../../Settings/anomaly_detection'; +import { TraceLink } from '../../TraceLink'; +import { TransactionDetails } from '../../TransactionDetails'; +import { BreadcrumbRoute } from '../ProvideBreadcrumbs'; import { CreateAgentConfigurationRouteHandler, EditAgentConfigurationRouteHandler, } from './route_handlers/agent_configuration'; +import { RouteName } from './route_names'; -const metricsBreadcrumb = i18n.translate('xpack.apm.breadcrumb.metricsTitle', { - defaultMessage: 'Metrics', -}); - -interface RouteParams { - serviceName: string; -} - -export const renderAsRedirectTo = (to: string) => { - return ({ location }: RouteComponentProps) => { +/** + * Given a path, redirect to that location, preserving the search and maintaining + * backward-compatibilty with legacy (pre-7.9) hash-based URLs. + */ +export function renderAsRedirectTo(to: string) { + return ({ location }: RouteComponentProps<{}>) => { let resolvedUrl: URL | undefined; // Redirect root URLs with a hash to support backward compatibility with URLs @@ -60,20 +55,113 @@ export const renderAsRedirectTo = (to: string) => { /> ); }; -}; +} + +// These component function definitions are used below with the `component` +// property of the route definitions. +// +// If you provide an inline function to the component prop, you would create a +// new component every render. This results in the existing component unmounting +// and the new component mounting instead of just updating the existing component. +// +// This means you should use `render` if you're providing an inline function. +// However, the `ApmRoute` component from @elastic/apm-rum-react, only supports +// `component`, and will give you a large console warning if you use `render`. +// +// This warning cannot be turned off +// (see https://github.com/elastic/apm-agent-rum-js/issues/881) so while this is +// slightly more code, it provides better performance without causing console +// warnings to appear. +function HomeServices() { + return ; +} + +function HomeServiceMap() { + return ; +} + +function HomeTraces() { + return ; +} + +function ServiceDetailsErrors( + props: RouteComponentProps<{ serviceName: string }> +) { + return ; +} + +function ServiceDetailsMetrics( + props: RouteComponentProps<{ serviceName: string }> +) { + return ; +} + +function ServiceDetailsNodes( + props: RouteComponentProps<{ serviceName: string }> +) { + return ; +} + +function ServiceDetailsServiceMap( + props: RouteComponentProps<{ serviceName: string }> +) { + return ; +} + +function ServiceDetailsTransactions( + props: RouteComponentProps<{ serviceName: string }> +) { + return ; +} + +function SettingsAgentConfiguration() { + return ( + + + + ); +} + +function SettingsAnomalyDetection() { + return ( + + + + ); +} + +function SettingsApmIndices() { + return ( + + + + ); +} +function SettingsCustomizeUI() { + return ( + + + + ); +} + +/** + * The array of route definitions to be used when the application + * creates the routes. + */ export const routes: BreadcrumbRoute[] = [ { exact: true, path: '/', - render: renderAsRedirectTo('/services'), + component: renderAsRedirectTo('/services'), breadcrumb: 'APM', name: RouteName.HOME, }, { exact: true, path: '/services', - component: () => , + component: HomeServices, breadcrumb: i18n.translate('xpack.apm.breadcrumb.servicesTitle', { defaultMessage: 'Services', }), @@ -82,7 +170,7 @@ export const routes: BreadcrumbRoute[] = [ { exact: true, path: '/traces', - component: () => , + component: HomeTraces, breadcrumb: i18n.translate('xpack.apm.breadcrumb.tracesTitle', { defaultMessage: 'Traces', }), @@ -91,7 +179,7 @@ export const routes: BreadcrumbRoute[] = [ { exact: true, path: '/settings', - render: renderAsRedirectTo('/settings/agent-configuration'), + component: renderAsRedirectTo('/settings/agent-configuration'), breadcrumb: i18n.translate('xpack.apm.breadcrumb.listSettingsTitle', { defaultMessage: 'Settings', }), @@ -100,11 +188,7 @@ export const routes: BreadcrumbRoute[] = [ { exact: true, path: '/settings/apm-indices', - component: () => ( - - - - ), + component: SettingsApmIndices, breadcrumb: i18n.translate('xpack.apm.breadcrumb.settings.indicesTitle', { defaultMessage: 'Indices', }), @@ -113,18 +197,13 @@ export const routes: BreadcrumbRoute[] = [ { exact: true, path: '/settings/agent-configuration', - component: () => ( - - - - ), + component: SettingsAgentConfiguration, breadcrumb: i18n.translate( 'xpack.apm.breadcrumb.settings.agentConfigurationTitle', { defaultMessage: 'Agent Configuration' } ), name: RouteName.AGENT_CONFIGURATION, }, - { exact: true, path: '/settings/agent-configuration/create', @@ -133,7 +212,7 @@ export const routes: BreadcrumbRoute[] = [ { defaultMessage: 'Create Agent Configuration' } ), name: RouteName.AGENT_CONFIGURATION_CREATE, - component: () => , + component: CreateAgentConfigurationRouteHandler, }, { exact: true, @@ -143,13 +222,13 @@ export const routes: BreadcrumbRoute[] = [ { defaultMessage: 'Edit Agent Configuration' } ), name: RouteName.AGENT_CONFIGURATION_EDIT, - component: () => , + component: EditAgentConfigurationRouteHandler, }, { exact: true, path: '/services/:serviceName', breadcrumb: ({ match }) => match.params.serviceName, - render: (props: RouteComponentProps) => + component: (props: RouteComponentProps<{ serviceName: string }>) => renderAsRedirectTo( `/services/${props.match.params.serviceName}/transactions` )(props), @@ -166,7 +245,7 @@ export const routes: BreadcrumbRoute[] = [ { exact: true, path: '/services/:serviceName/errors', - component: () => , + component: ServiceDetailsErrors, breadcrumb: i18n.translate('xpack.apm.breadcrumb.errorsTitle', { defaultMessage: 'Errors', }), @@ -176,7 +255,7 @@ export const routes: BreadcrumbRoute[] = [ { exact: true, path: '/services/:serviceName/transactions', - component: () => , + component: ServiceDetailsTransactions, breadcrumb: i18n.translate('xpack.apm.breadcrumb.transactionsTitle', { defaultMessage: 'Transactions', }), @@ -186,15 +265,17 @@ export const routes: BreadcrumbRoute[] = [ { exact: true, path: '/services/:serviceName/metrics', - component: () => , - breadcrumb: metricsBreadcrumb, + component: ServiceDetailsMetrics, + breadcrumb: i18n.translate('xpack.apm.breadcrumb.metricsTitle', { + defaultMessage: 'Metrics', + }), name: RouteName.METRICS, }, // service nodes, only enabled for java agents for now { exact: true, path: '/services/:serviceName/nodes', - component: () => , + component: ServiceDetailsNodes, breadcrumb: i18n.translate('xpack.apm.breadcrumb.nodesTitle', { defaultMessage: 'JVMs', }), @@ -204,9 +285,9 @@ export const routes: BreadcrumbRoute[] = [ { exact: true, path: '/services/:serviceName/nodes/:serviceNodeName/metrics', - component: () => , - breadcrumb: ({ location }) => { - const { serviceNodeName } = resolveUrlParams(location, {}); + component: ServiceNodeMetrics, + breadcrumb: ({ match }) => { + const { serviceNodeName } = match.params; if (serviceNodeName === SERVICE_NODE_NAME_MISSING) { return UNIDENTIFIED_SERVICE_NODES_LABEL; @@ -233,11 +314,10 @@ export const routes: BreadcrumbRoute[] = [ breadcrumb: null, name: RouteName.LINK_TO_TRACE, }, - { exact: true, path: '/service-map', - component: () => , + component: HomeServiceMap, breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', { defaultMessage: 'Service Map', }), @@ -246,7 +326,7 @@ export const routes: BreadcrumbRoute[] = [ { exact: true, path: '/services/:serviceName/service-map', - component: () => , + component: ServiceDetailsServiceMap, breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', { defaultMessage: 'Service Map', }), @@ -255,11 +335,7 @@ export const routes: BreadcrumbRoute[] = [ { exact: true, path: '/settings/customize-ui', - component: () => ( - - - - ), + component: SettingsCustomizeUI, breadcrumb: i18n.translate('xpack.apm.breadcrumb.settings.customizeUI', { defaultMessage: 'Customize UI', }), @@ -268,11 +344,7 @@ export const routes: BreadcrumbRoute[] = [ { exact: true, path: '/settings/anomaly-detection', - component: () => ( - - - - ), + component: SettingsAnomalyDetection, breadcrumb: i18n.translate( 'xpack.apm.breadcrumb.settings.anomalyDetection', { diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx index ad12afe35fa20..21a162111bc79 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/route_config.test.tsx @@ -14,7 +14,7 @@ describe('routes', () => { it('redirects to /services', () => { const location = { hash: '', pathname: '/', search: '' }; expect( - (route as any).render({ location } as any).props.to.pathname + (route as any).component({ location } as any).props.to.pathname ).toEqual('/services'); }); }); @@ -28,7 +28,9 @@ describe('routes', () => { search: '', }; - expect(((route as any).render({ location }) as any).props.to).toEqual({ + expect( + ((route as any).component({ location }) as any).props.to + ).toEqual({ hash: '', pathname: '/services/opbeans-python/transactions/view', search: diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx index 7a00840daa3c5..cc07286457908 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx @@ -5,14 +5,17 @@ */ import React from 'react'; -import { useHistory } from 'react-router-dom'; +import { RouteComponentProps } from 'react-router-dom'; import { useFetcher } from '../../../../../hooks/useFetcher'; import { toQuery } from '../../../../shared/Links/url_helpers'; import { Settings } from '../../../Settings'; import { AgentConfigurationCreateEdit } from '../../../Settings/AgentConfigurations/AgentConfigurationCreateEdit'; -export function EditAgentConfigurationRouteHandler() { - const history = useHistory(); +type EditAgentConfigurationRouteHandler = RouteComponentProps<{}>; + +export function EditAgentConfigurationRouteHandler({ + history, +}: EditAgentConfigurationRouteHandler) { const { search } = history.location; // typescript complains because `pageStop` does not exist in `APMQueryParams` @@ -40,8 +43,11 @@ export function EditAgentConfigurationRouteHandler() { ); } -export function CreateAgentConfigurationRouteHandler() { - const history = useHistory(); +type CreateAgentConfigurationRouteHandlerProps = RouteComponentProps<{}>; + +export function CreateAgentConfigurationRouteHandler({ + history, +}: CreateAgentConfigurationRouteHandlerProps) { const { search } = history.location; // Ignoring here because we specifically DO NOT want to add the query params to the global route handler diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx index 67404ece3d2c7..e21dd0d6ff126 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx @@ -22,11 +22,11 @@ const ClFlexGroup = styled(EuiFlexGroup)` export function ClientMetrics() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, serviceName } = urlParams; + const { start, end } = urlParams; const { data, status } = useFetcher( (callApmApi) => { - if (start && end && serviceName) { + if (start && end) { return callApmApi({ pathname: '/api/apm/rum/client-metrics', params: { @@ -36,7 +36,7 @@ export function ClientMetrics() { } return Promise.resolve(null); }, - [start, end, serviceName, uiFilters] + [start, end, uiFilters] ); const STAT_STYLE = { width: '240px' }; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx index 3e35f15254937..8fd03ebb65f4c 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx @@ -27,7 +27,7 @@ export interface PercentileRange { export function PageLoadDistribution() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, serviceName } = urlParams; + const { start, end } = urlParams; const [percentileRange, setPercentileRange] = useState({ min: null, @@ -38,7 +38,7 @@ export function PageLoadDistribution() { const { data, status } = useFetcher( (callApmApi) => { - if (start && end && serviceName) { + if (start && end) { return callApmApi({ pathname: '/api/apm/rum-client/page-load-distribution', params: { @@ -58,14 +58,7 @@ export function PageLoadDistribution() { } return Promise.resolve(null); }, - [ - end, - start, - serviceName, - uiFilters, - percentileRange.min, - percentileRange.max, - ] + [end, start, uiFilters, percentileRange.min, percentileRange.max] ); const onPercentileChange = (min: number, max: number) => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts index 805d19e2321d5..814cf977c9569 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts @@ -17,13 +17,13 @@ interface Props { export const useBreakdowns = ({ percentileRange, field, value }: Props) => { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, serviceName } = urlParams; + const { start, end } = urlParams; const { min: minP, max: maxP } = percentileRange ?? {}; return useFetcher( (callApmApi) => { - if (start && end && serviceName && field && value) { + if (start && end && field && value) { return callApmApi({ pathname: '/api/apm/rum-client/page-load-distribution/breakdown', params: { @@ -43,6 +43,6 @@ export const useBreakdowns = ({ percentileRange, field, value }: Props) => { }); } }, - [end, start, serviceName, uiFilters, field, value, minP, maxP] + [end, start, uiFilters, field, value, minP, maxP] ); }; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index a67f6dd8e3cb5..62ecc4ddbaaca 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -16,13 +16,13 @@ import { BreakdownItem } from '../../../../../typings/ui_filters'; export function PageViewsTrend() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, serviceName } = urlParams; + const { start, end } = urlParams; const [breakdown, setBreakdown] = useState(null); const { data, status } = useFetcher( (callApmApi) => { - if (start && end && serviceName) { + if (start && end) { return callApmApi({ pathname: '/api/apm/rum-client/page-view-trends', params: { @@ -41,7 +41,7 @@ export function PageViewsTrend() { } return Promise.resolve(undefined); }, - [end, start, serviceName, uiFilters, breakdown] + [end, start, uiFilters, breakdown] ); return ( diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx index 5c68ebb1667ab..c19e2cd4a3742 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx @@ -14,11 +14,11 @@ import { useUrlParams } from '../../../../hooks/useUrlParams'; export function VisitorBreakdown() { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, serviceName } = urlParams; + const { start, end } = urlParams; const { data } = useFetcher( (callApmApi) => { - if (start && end && serviceName) { + if (start && end) { return callApmApi({ pathname: '/api/apm/rum-client/visitor-breakdown', params: { @@ -32,7 +32,7 @@ export function VisitorBreakdown() { } return Promise.resolve(null); }, - [end, start, serviceName, uiFilters] + [end, start, uiFilters] ); return ( diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx index 2f35e329720de..cbb6d9a8fbe41 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx @@ -10,7 +10,6 @@ import React from 'react'; import { isJavaAgentName, isRumAgentName } from '../../../../common/agent_name'; import { useAgentName } from '../../../hooks/useAgentName'; import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useUrlParams } from '../../../hooks/useUrlParams'; import { EuiTabLink } from '../../shared/EuiTabLink'; import { ErrorOverviewLink } from '../../shared/Links/apm/ErrorOverviewLink'; import { MetricOverviewLink } from '../../shared/Links/apm/MetricOverviewLink'; @@ -24,20 +23,14 @@ import { ServiceNodeOverview } from '../ServiceNodeOverview'; import { TransactionOverview } from '../TransactionOverview'; interface Props { + serviceName: string; tab: 'transactions' | 'errors' | 'metrics' | 'nodes' | 'service-map'; } -export function ServiceDetailTabs({ tab }: Props) { - const { urlParams } = useUrlParams(); - const { serviceName } = urlParams; +export function ServiceDetailTabs({ serviceName, tab }: Props) { const { agentName } = useAgentName(); const { serviceMapEnabled } = useApmPluginContext().config; - if (!serviceName) { - // this never happens, urlParams type is not accurate enough - throw new Error('Service name was not defined'); - } - const transactionsTab = { link: ( @@ -46,7 +39,7 @@ export function ServiceDetailTabs({ tab }: Props) { })} ), - render: () => , + render: () => , name: 'transactions', }; @@ -59,7 +52,7 @@ export function ServiceDetailTabs({ tab }: Props) { ), render: () => { - return ; + return ; }, name: 'errors', }; @@ -75,7 +68,7 @@ export function ServiceDetailTabs({ tab }: Props) { })} ), - render: () => , + render: () => , name: 'nodes', }; tabs.push(nodesListTab); @@ -88,7 +81,9 @@ export function ServiceDetailTabs({ tab }: Props) { })} ), - render: () => , + render: () => ( + + ), name: 'metrics', }; tabs.push(metricsTab); diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx index b5a4ca4799afd..67c4a7c4cde1b 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/index.tsx @@ -5,27 +5,26 @@ */ import { + EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiTitle, - EuiButtonEmpty, } from '@elastic/eui'; -import React from 'react'; import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { RouteComponentProps } from 'react-router-dom'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; import { ApmHeader } from '../../shared/ApmHeader'; -import { ServiceDetailTabs } from './ServiceDetailTabs'; -import { useUrlParams } from '../../../hooks/useUrlParams'; import { AlertIntegrations } from './AlertIntegrations'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { ServiceDetailTabs } from './ServiceDetailTabs'; -interface Props { +interface Props extends RouteComponentProps<{ serviceName: string }> { tab: React.ComponentProps['tab']; } -export function ServiceDetails({ tab }: Props) { +export function ServiceDetails({ match, tab }: Props) { const plugin = useApmPluginContext(); - const { urlParams } = useUrlParams(); - const { serviceName } = urlParams; + const { serviceName } = match.params; const capabilities = plugin.core.application.capabilities; const canReadAlerts = !!capabilities.apm['alerting:show']; const canSaveAlerts = !!capabilities.apm['alerting:save']; @@ -76,7 +75,7 @@ export function ServiceDetails({ tab }: Props) { - + ); } diff --git a/x-pack/plugins/apm/public/components/app/ServiceMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMetrics/index.tsx index 9b01f9ebb7e99..2fb500f3c9916 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMetrics/index.tsx @@ -21,11 +21,14 @@ import { LocalUIFilters } from '../../shared/LocalUIFilters'; interface ServiceMetricsProps { agentName: string; + serviceName: string; } -export function ServiceMetrics({ agentName }: ServiceMetricsProps) { +export function ServiceMetrics({ + agentName, + serviceName, +}: ServiceMetricsProps) { const { urlParams } = useUrlParams(); - const { serviceName, serviceNodeName } = urlParams; const { data } = useServiceMetricCharts(urlParams, agentName); const { start, end } = urlParams; @@ -34,12 +37,11 @@ export function ServiceMetrics({ agentName }: ServiceMetricsProps) { filterNames: ['host', 'containerId', 'podName', 'serviceVersion'], params: { serviceName, - serviceNodeName, }, projection: Projection.metrics, showCount: false, }), - [serviceName, serviceNodeName] + [serviceName] ); return ( diff --git a/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.test.tsx index eced7457318d8..c6f7e68e4f4d0 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.test.tsx @@ -8,14 +8,20 @@ import React from 'react'; import { shallow } from 'enzyme'; import { ServiceNodeMetrics } from '.'; import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { RouteComponentProps } from 'react-router-dom'; describe('ServiceNodeMetrics', () => { describe('render', () => { it('renders', () => { + const props = ({} as unknown) as RouteComponentProps<{ + serviceName: string; + serviceNodeName: string; + }>; + expect(() => shallow( - + ) ).not.toThrowError(); diff --git a/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx index e81968fb298fa..84a1920d17fa8 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx @@ -5,30 +5,31 @@ */ import { + EuiCallOut, + EuiFlexGrid, EuiFlexGroup, EuiFlexItem, - EuiTitle, EuiHorizontalRule, - EuiFlexGrid, EuiPanel, EuiSpacer, EuiStat, + EuiTitle, EuiToolTip, - EuiCallOut, } from '@elastic/eui'; -import React from 'react'; import { i18n } from '@kbn/i18n'; -import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; +import { RouteComponentProps } from 'react-router-dom'; +import styled from 'styled-components'; import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; -import { ApmHeader } from '../../shared/ApmHeader'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { ChartsSyncContextProvider } from '../../../context/ChartsSyncContext'; import { useAgentName } from '../../../hooks/useAgentName'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; import { useServiceMetricCharts } from '../../../hooks/useServiceMetricCharts'; -import { ChartsSyncContextProvider } from '../../../context/ChartsSyncContext'; +import { useUrlParams } from '../../../hooks/useUrlParams'; +import { px, truncate, unit } from '../../../style/variables'; +import { ApmHeader } from '../../shared/ApmHeader'; import { MetricsChart } from '../../shared/charts/MetricsChart'; -import { useFetcher, FETCH_STATUS } from '../../../hooks/useFetcher'; -import { truncate, px, unit } from '../../../style/variables'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; const INITIAL_DATA = { @@ -41,17 +42,21 @@ const Truncate = styled.span` ${truncate(px(unit * 12))} `; -export function ServiceNodeMetrics() { - const { urlParams, uiFilters } = useUrlParams(); - const { serviceName, serviceNodeName } = urlParams; +type ServiceNodeMetricsProps = RouteComponentProps<{ + serviceName: string; + serviceNodeName: string; +}>; +export function ServiceNodeMetrics({ match }: ServiceNodeMetricsProps) { + const { urlParams, uiFilters } = useUrlParams(); + const { serviceName, serviceNodeName } = match.params; const { agentName } = useAgentName(); const { data } = useServiceMetricCharts(urlParams, agentName); const { start, end } = urlParams; const { data: { host, containerId } = INITIAL_DATA, status } = useFetcher( (callApmApi) => { - if (serviceName && serviceNodeName && start && end) { + if (start && end) { return callApmApi({ pathname: '/api/apm/services/{serviceName}/node/{serviceNodeName}/metadata', @@ -167,7 +172,7 @@ export function ServiceNodeMetrics() { )} - {agentName && serviceNodeName && ( + {agentName && ( {data.charts.map((chart) => ( diff --git a/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx index 9940a7aabb219..28477d2448899 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx @@ -33,9 +33,13 @@ const ServiceNodeName = styled.div` ${truncate(px(8 * unit))} `; -function ServiceNodeOverview() { +interface ServiceNodeOverviewProps { + serviceName: string; +} + +function ServiceNodeOverview({ serviceName }: ServiceNodeOverviewProps) { const { uiFilters, urlParams } = useUrlParams(); - const { serviceName, start, end } = urlParams; + const { start, end } = urlParams; const localFiltersConfig: React.ComponentProps = useMemo( () => ({ @@ -50,7 +54,7 @@ function ServiceNodeOverview() { const { data: items = [] } = useFetcher( (callApmApi) => { - if (!serviceName || !start || !end) { + if (!start || !end) { return undefined; } return callApmApi({ @@ -70,10 +74,6 @@ function ServiceNodeOverview() { [serviceName, start, end, uiFilters] ); - if (!serviceName) { - return null; - } - const columns: Array> = [ { name: ( diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/__test__/TraceLink.test.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/__test__/TraceLink.test.tsx index bbaf6340e18f7..8d37a8e54d87c 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/__test__/TraceLink.test.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/__test__/TraceLink.test.tsx @@ -5,63 +5,84 @@ */ import { render } from '@testing-library/react'; import { shallow } from 'enzyme'; -import React from 'react'; +import React, { ReactNode } from 'react'; +import { MemoryRouter, RouteComponentProps } from 'react-router-dom'; import { TraceLink } from '../'; +import { ApmPluginContextValue } from '../../../../context/ApmPluginContext'; +import { + mockApmPluginContextValue, + MockApmPluginContextWrapper, +} from '../../../../context/ApmPluginContext/MockApmPluginContext'; import * as hooks from '../../../../hooks/useFetcher'; import * as urlParamsHooks from '../../../../hooks/useUrlParams'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; -const renderOptions = { wrapper: MockApmPluginContextWrapper }; +function Wrapper({ children }: { children?: ReactNode }) { + return ( + + + {children} + + + ); +} -jest.mock('../../Main/route_config', () => ({ - routes: [ - { - path: '/services/:serviceName/transactions/view', - name: 'transaction_name', - }, - { - path: '/traces', - name: 'traces', - }, - ], -})); +const renderOptions = { wrapper: Wrapper }; describe('TraceLink', () => { afterAll(() => { jest.clearAllMocks(); }); - it('renders transition page', () => { - const component = render(, renderOptions); + + it('renders a transition page', () => { + const props = ({ + match: { params: { traceId: 'x' } }, + } as unknown) as RouteComponentProps<{ traceId: string }>; + const component = render(, renderOptions); + expect(component.getByText('Fetching trace...')).toBeDefined(); }); - it('renders trace page when transaction is not found', () => { - jest.spyOn(urlParamsHooks, 'useUrlParams').mockReturnValue({ - urlParams: { - traceIdLink: '123', - rangeFrom: 'now-24h', - rangeTo: 'now', - }, - refreshTimeRange: jest.fn(), - uiFilters: {}, - }); - jest.spyOn(hooks, 'useFetcher').mockReturnValue({ - data: { transaction: undefined }, - status: hooks.FETCH_STATUS.SUCCESS, - refetch: jest.fn(), - }); + describe('when no transaction is found', () => { + it('renders a trace page', () => { + jest.spyOn(urlParamsHooks, 'useUrlParams').mockReturnValue({ + urlParams: { + rangeFrom: 'now-24h', + rangeTo: 'now', + }, + refreshTimeRange: jest.fn(), + uiFilters: {}, + }); + jest.spyOn(hooks, 'useFetcher').mockReturnValue({ + data: { transaction: undefined }, + status: hooks.FETCH_STATUS.SUCCESS, + refetch: jest.fn(), + }); + + const props = ({ + match: { params: { traceId: '123' } }, + } as unknown) as RouteComponentProps<{ traceId: string }>; + const component = shallow(); - const component = shallow(); - expect(component.prop('to')).toEqual( - '/traces?kuery=trace.id%2520%253A%2520%2522123%2522&rangeFrom=now-24h&rangeTo=now' - ); + expect(component.prop('to')).toEqual( + '/traces?kuery=trace.id%2520%253A%2520%2522123%2522&rangeFrom=now-24h&rangeTo=now' + ); + }); }); describe('transaction page', () => { beforeAll(() => { jest.spyOn(urlParamsHooks, 'useUrlParams').mockReturnValue({ urlParams: { - traceIdLink: '123', rangeFrom: 'now-24h', rangeTo: 'now', }, @@ -69,6 +90,7 @@ describe('TraceLink', () => { uiFilters: {}, }); }); + it('renders with date range params', () => { const transaction = { service: { name: 'foo' }, @@ -84,7 +106,12 @@ describe('TraceLink', () => { status: hooks.FETCH_STATUS.SUCCESS, refetch: jest.fn(), }); - const component = shallow(); + + const props = ({ + match: { params: { traceId: '123' } }, + } as unknown) as RouteComponentProps<{ traceId: string }>; + const component = shallow(); + expect(component.prop('to')).toEqual( '/services/foo/transactions/view?traceId=123&transactionId=456&transactionName=bar&transactionType=GET&rangeFrom=now-24h&rangeTo=now' ); diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx index 55ab275002b4e..584af956c2022 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx @@ -6,7 +6,7 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import React from 'react'; -import { Redirect } from 'react-router-dom'; +import { Redirect, RouteComponentProps } from 'react-router-dom'; import styled from 'styled-components'; import url from 'url'; import { TRACE_ID } from '../../../../common/elasticsearch_fieldnames'; @@ -58,9 +58,10 @@ const redirectToTracePage = ({ }, }); -export function TraceLink() { +export function TraceLink({ match }: RouteComponentProps<{ traceId: string }>) { + const { traceId } = match.params; const { urlParams } = useUrlParams(); - const { traceIdLink: traceId, rangeFrom, rangeTo } = urlParams; + const { rangeFrom, rangeTo } = urlParams; const { data = { transaction: null }, status } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx index 515fcbc88c901..bab31c9a460d0 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -13,6 +13,7 @@ import { EuiTitle, } from '@elastic/eui'; import React, { useMemo } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; import { ChartsSyncContextProvider } from '../../../context/ChartsSyncContext'; @@ -29,7 +30,10 @@ import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { TransactionDistribution } from './Distribution'; import { WaterfallWithSummmary } from './WaterfallWithSummmary'; -export function TransactionDetails() { +type TransactionDetailsProps = RouteComponentProps<{ serviceName: string }>; + +export function TransactionDetails({ match }: TransactionDetailsProps) { + const { serviceName } = match.params; const location = useLocation(); const { urlParams } = useUrlParams(); const { @@ -41,7 +45,7 @@ export function TransactionDetails() { const { waterfall, exceedsMax, status: waterfallStatus } = useWaterfall( urlParams ); - const { transactionName, transactionType, serviceName } = urlParams; + const { transactionName, transactionType } = urlParams; useTrackPageview({ app: 'apm', path: 'transaction_details' }); useTrackPageview({ app: 'apm', path: 'transaction_details', delay: 15000 }); diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx index 81fe9e2282667..b7d1b93600a73 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/TransactionOverview.test.tsx @@ -12,7 +12,6 @@ import { } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { CoreStart } from 'kibana/public'; -import { omit } from 'lodash'; import React from 'react'; import { Router } from 'react-router-dom'; import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; @@ -42,7 +41,7 @@ function setup({ }) { const defaultLocation = { pathname: '/services/foo/transactions', - search: fromQuery(omit(urlParams, 'serviceName')), + search: fromQuery(urlParams), } as any; history.replace({ @@ -60,7 +59,7 @@ function setup({ - + @@ -87,9 +86,7 @@ describe('TransactionOverview', () => { it('should redirect to first type', () => { setup({ serviceTransactionTypes: ['firstType', 'secondType'], - urlParams: { - serviceName: 'MyServiceName', - }, + urlParams: {}, }); expect(history.replace).toHaveBeenCalledWith( expect.objectContaining({ @@ -107,7 +104,6 @@ describe('TransactionOverview', () => { serviceTransactionTypes: ['firstType', 'secondType'], urlParams: { transactionType: 'secondType', - serviceName: 'MyServiceName', }, }); @@ -122,7 +118,6 @@ describe('TransactionOverview', () => { serviceTransactionTypes: ['firstType', 'secondType'], urlParams: { transactionType: 'secondType', - serviceName: 'MyServiceName', }, }); @@ -143,7 +138,6 @@ describe('TransactionOverview', () => { serviceTransactionTypes: ['firstType'], urlParams: { transactionType: 'firstType', - serviceName: 'MyServiceName', }, }); diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx index 5999988abe848..544e2450fe5d9 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx @@ -59,11 +59,14 @@ function getRedirectLocation({ } } -export function TransactionOverview() { +interface TransactionOverviewProps { + serviceName: string; +} + +export function TransactionOverview({ serviceName }: TransactionOverviewProps) { const location = useLocation(); const { urlParams } = useUrlParams(); - - const { serviceName, transactionType } = urlParams; + const { transactionType } = urlParams; // TODO: fetching of transaction types should perhaps be lifted since it is needed in several places. Context? const serviceTransactionTypes = useServiceTransactionTypes(urlParams); diff --git a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx index 9a61e773d73bf..7e5c789507e07 100644 --- a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx @@ -8,7 +8,7 @@ import { EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { History } from 'history'; import React from 'react'; -import { useHistory } from 'react-router-dom'; +import { useHistory, useParams } from 'react-router-dom'; import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, @@ -63,10 +63,11 @@ function getOptions(environments: string[]) { export function EnvironmentFilter() { const history = useHistory(); const location = useLocation(); + const { serviceName } = useParams<{ serviceName?: string }>(); const { uiFilters, urlParams } = useUrlParams(); const { environment } = uiFilters; - const { serviceName, start, end } = urlParams; + const { start, end } = urlParams; const { environments, status = 'loading' } = useEnvironments({ serviceName, start, diff --git a/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx index 7344839795955..7b284696477f3 100644 --- a/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx @@ -3,21 +3,21 @@ * 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 { EuiFieldNumber } from '@elastic/eui'; +import { EuiFieldNumber, EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isFinite } from 'lodash'; -import { EuiSelect } from '@elastic/eui'; +import React from 'react'; +import { useParams } from 'react-router-dom'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; -import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; -import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; import { ENVIRONMENT_ALL, getEnvironmentLabel, } from '../../../../common/environment_filter_values'; +import { useEnvironments } from '../../../hooks/useEnvironments'; +import { useUrlParams } from '../../../hooks/useUrlParams'; +import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; +import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; export interface ErrorRateAlertTriggerParams { windowSize: number; @@ -34,9 +34,9 @@ interface Props { export function ErrorRateAlertTrigger(props: Props) { const { setAlertParams, setAlertProperty, alertParams } = props; - + const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); - const { serviceName, start, end } = urlParams; + const { start, end } = urlParams; const { environmentOptions } = useEnvironments({ serviceName, start, end }); const defaults = { diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts index 5bac01cfaf55d..74d7ace20dae0 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts @@ -4,18 +4,29 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter } from '../../../../typings/elasticsearch'; import { - TRANSACTION_TYPE, ERROR_GROUP_ID, PROCESSOR_EVENT, - TRANSACTION_NAME, SERVICE_NAME, + TRANSACTION_NAME, + TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; +import { UIProcessorEvent } from '../../../../common/processor_event'; +import { ESFilter } from '../../../../typings/elasticsearch'; import { IUrlParams } from '../../../context/UrlParamsContext/types'; -export function getBoolFilter(urlParams: IUrlParams) { - const { start, end, serviceName, processorEvent } = urlParams; +export function getBoolFilter({ + groupId, + processorEvent, + serviceName, + urlParams, +}: { + groupId?: string; + processorEvent?: UIProcessorEvent; + serviceName?: string; + urlParams: IUrlParams; +}) { + const { start, end } = urlParams; if (!start || !end) { throw new Error('Date range was not defined'); @@ -63,9 +74,9 @@ export function getBoolFilter(urlParams: IUrlParams) { term: { [PROCESSOR_EVENT]: 'error' }, }); - if (urlParams.errorGroupId) { + if (groupId) { boolFilter.push({ - term: { [ERROR_GROUP_ID]: urlParams.errorGroupId }, + term: { [ERROR_GROUP_ID]: groupId }, }); } break; diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx index a52676ee89590..efd1446f21b21 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { startsWith, uniqueId } from 'lodash'; import React, { useState } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useHistory, useParams } from 'react-router-dom'; import styled from 'styled-components'; import { esKuery, @@ -22,6 +22,7 @@ import { fromQuery, toQuery } from '../Links/url_helpers'; import { getBoolFilter } from './get_bool_filter'; // @ts-expect-error import { Typeahead } from './Typeahead'; +import { useProcessorEvent } from './use_processor_event'; const Container = styled.div` margin-bottom: 10px; @@ -38,6 +39,10 @@ function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) { } export function KueryBar() { + const { groupId, serviceName } = useParams<{ + groupId?: string; + serviceName?: string; + }>(); const history = useHistory(); const [state, setState] = useState({ suggestions: [], @@ -49,7 +54,7 @@ export function KueryBar() { let currentRequestCheck; - const { processorEvent } = urlParams; + const processorEvent = useProcessorEvent(); const examples = { transaction: 'transaction.duration.us > 300000', @@ -98,7 +103,12 @@ export function KueryBar() { (await data.autocomplete.getQuerySuggestions({ language: 'kuery', indexPatterns: [indexPattern], - boolFilter: getBoolFilter(urlParams), + boolFilter: getBoolFilter({ + groupId, + processorEvent, + serviceName, + urlParams, + }), query: inputValue, selectionStart, selectionEnd: selectionStart, diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/use_processor_event.ts b/x-pack/plugins/apm/public/components/shared/KueryBar/use_processor_event.ts new file mode 100644 index 0000000000000..1e8686f0fe5ee --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/use_processor_event.ts @@ -0,0 +1,47 @@ +/* + * 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 { useLocation } from 'react-router-dom'; +import { + ProcessorEvent, + UIProcessorEvent, +} from '../../../../common/processor_event'; + +/** + * Infer the processor.event to used based on the route path + */ +export function useProcessorEvent(): UIProcessorEvent | undefined { + const { pathname } = useLocation(); + const paths = pathname.split('/').slice(1); + const pageName = paths[0]; + + switch (pageName) { + case 'services': + let servicePageName = paths[2]; + + if (servicePageName === 'nodes' && paths.length > 3) { + servicePageName = 'metrics'; + } + + switch (servicePageName) { + case 'transactions': + return ProcessorEvent.transaction; + case 'errors': + return ProcessorEvent.error; + case 'metrics': + return ProcessorEvent.metric; + case 'nodes': + return ProcessorEvent.metric; + + default: + return undefined; + } + case 'traces': + return ProcessorEvent.transaction; + default: + return undefined; + } +} diff --git a/x-pack/plugins/apm/public/components/shared/ServiceAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/ServiceAlertTrigger/index.tsx index 6d90a10891c21..86dc7f5a90475 100644 --- a/x-pack/plugins/apm/public/components/shared/ServiceAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ServiceAlertTrigger/index.tsx @@ -6,7 +6,7 @@ import React, { useEffect } from 'react'; import { EuiSpacer, EuiFlexGrid, EuiFlexItem } from '@elastic/eui'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useParams } from 'react-router-dom'; interface Props { alertTypeName: string; @@ -17,7 +17,7 @@ interface Props { } export function ServiceAlertTrigger(props: Props) { - const { urlParams } = useUrlParams(); + const { serviceName } = useParams<{ serviceName?: string }>(); const { fields, @@ -29,7 +29,7 @@ export function ServiceAlertTrigger(props: Props) { const params: Record = { ...defaults, - serviceName: urlParams.serviceName!, + serviceName, }; useEffect(() => { diff --git a/x-pack/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx index ba12b11c9527d..3c1669c39ac4c 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx @@ -40,12 +40,12 @@ interface Props { export function TransactionDurationAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; - + const { serviceName } = alertParams; const { urlParams } = useUrlParams(); const transactionTypes = useServiceTransactionTypes(urlParams); - const { serviceName, start, end } = urlParams; + const { start, end } = urlParams; const { environmentOptions } = useEnvironments({ serviceName, start, end }); if (!transactionTypes.length) { diff --git a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx index 911c51013a844..20e0a3f27c4a4 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionDurationAnomalyAlertTrigger/index.tsx @@ -42,9 +42,10 @@ interface Props { export function TransactionDurationAnomalyAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; + const { serviceName } = alertParams; const { urlParams } = useUrlParams(); const transactionTypes = useServiceTransactionTypes(urlParams); - const { serviceName, start, end } = urlParams; + const { start, end } = urlParams; const { environmentOptions } = useEnvironments({ serviceName, start, end }); const supportedTransactionTypes = transactionTypes.filter((transactionType) => [TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST].includes(transactionType) diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ml_header.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ml_header.tsx index f829b5841efa9..52b0470d31552 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ml_header.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ml_header.tsx @@ -4,13 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiIconTip } from '@elastic/eui'; +import { EuiFlexItem, EuiIconTip, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React from 'react'; -import { EuiFlexItem } from '@elastic/eui'; +import { useParams } from 'react-router-dom'; import styled from 'styled-components'; -import { i18n } from '@kbn/i18n'; -import { EuiText } from '@elastic/eui'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { MLJobLink } from '../../Links/MachineLearningLinks/MLJobLink'; @@ -32,16 +31,14 @@ const ShiftedEuiText = styled(EuiText)` `; export function MLHeader({ hasValidMlLicense, mlJobId }: Props) { + const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); if (!hasValidMlLicense || !mlJobId) { return null; } - const { serviceName, kuery, transactionType } = urlParams; - if (!serviceName) { - return null; - } + const { kuery, transactionType } = urlParams; const hasKuery = !isEmpty(kuery); const icon = hasKuery ? ( diff --git a/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx b/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx index 801c1d7e53f2e..7df35bc443226 100644 --- a/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx +++ b/x-pack/plugins/apm/public/context/ChartsSyncContext.tsx @@ -5,7 +5,7 @@ */ import React, { ReactNode, useMemo, useState } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useHistory, useParams } from 'react-router-dom'; import { fromQuery, toQuery } from '../components/shared/Links/url_helpers'; import { useFetcher } from '../hooks/useFetcher'; import { useUrlParams } from '../hooks/useUrlParams'; @@ -20,9 +20,10 @@ const ChartsSyncContext = React.createContext<{ function ChartsSyncContextProvider({ children }: { children: ReactNode }) { const history = useHistory(); const [time, setTime] = useState(null); + const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); - const { start, end, serviceName } = urlParams; + const { start, end } = urlParams; const { environment } = uiFilters; const { data = { annotations: [] } } = useFetcher( diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/__tests__/UrlParamsContext.test.tsx b/x-pack/plugins/apm/public/context/UrlParamsContext/__tests__/UrlParamsContext.test.tsx index fbb79eae6a136..9989e568953f5 100644 --- a/x-pack/plugins/apm/public/context/UrlParamsContext/__tests__/UrlParamsContext.test.tsx +++ b/x-pack/plugins/apm/public/context/UrlParamsContext/__tests__/UrlParamsContext.test.tsx @@ -41,24 +41,6 @@ describe('UrlParamsContext', () => { moment.tz.setDefault(''); }); - it('should have default params', () => { - const location = { - pathname: '/services/opbeans-node/transactions', - } as Location; - - jest - .spyOn(Date, 'now') - .mockImplementation(() => new Date('2000-06-15T12:00:00Z').getTime()); - const wrapper = mountParams(location); - const params = getDataFromOutput(wrapper); - - expect(params).toEqual({ - serviceName: 'opbeans-node', - page: 0, - processorEvent: 'transaction', - }); - }); - it('should read values in from location', () => { const location = { pathname: '/test/pathname', diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts b/x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts index 65514ff71d02b..45db4dcc94cce 100644 --- a/x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts +++ b/x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts @@ -7,18 +7,6 @@ import { compact, pickBy } from 'lodash'; import datemath from '@elastic/datemath'; import { IUrlParams } from './types'; -import { - ProcessorEvent, - UIProcessorEvent, -} from '../../../common/processor_event'; - -interface PathParams { - processorEvent?: UIProcessorEvent; - serviceName?: string; - errorGroupId?: string; - serviceNodeName?: string; - traceId?: string; -} export function getParsedDate(rawDate?: string, opts = {}) { if (rawDate) { @@ -67,68 +55,3 @@ export function getPathAsArray(pathname: string = '') { export function removeUndefinedProps(obj: T): Partial { return pickBy(obj, (value) => value !== undefined); } - -export function getPathParams(pathname: string = ''): PathParams { - const paths = getPathAsArray(pathname); - const pageName = paths[0]; - // TODO: use react router's real match params instead of guessing the path order - - switch (pageName) { - case 'services': - let servicePageName = paths[2]; - const serviceName = paths[1]; - const serviceNodeName = paths[3]; - - if (servicePageName === 'nodes' && paths.length > 3) { - servicePageName = 'metrics'; - } - - switch (servicePageName) { - case 'transactions': - return { - processorEvent: ProcessorEvent.transaction, - serviceName, - }; - case 'errors': - return { - processorEvent: ProcessorEvent.error, - serviceName, - errorGroupId: paths[3], - }; - case 'metrics': - return { - processorEvent: ProcessorEvent.metric, - serviceName, - serviceNodeName, - }; - case 'nodes': - return { - processorEvent: ProcessorEvent.metric, - serviceName, - }; - case 'service-map': - return { - serviceName, - }; - default: - return {}; - } - - case 'traces': - return { - processorEvent: ProcessorEvent.transaction, - }; - case 'link-to': - const link = paths[1]; - switch (link) { - case 'trace': - return { - traceId: paths[2], - }; - default: - return {}; - } - default: - return {}; - } -} diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts b/x-pack/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts index 2201e162904a2..8feb4ac1858d1 100644 --- a/x-pack/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts +++ b/x-pack/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts @@ -7,7 +7,6 @@ import { Location } from 'history'; import { IUrlParams } from './types'; import { - getPathParams, removeUndefinedProps, getStart, getEnd, @@ -26,14 +25,6 @@ type TimeUrlParams = Pick< >; export function resolveUrlParams(location: Location, state: TimeUrlParams) { - const { - processorEvent, - serviceName, - serviceNodeName, - errorGroupId, - traceId: traceIdLink, - } = getPathParams(location.pathname); - const query = toQuery(location.search); const { @@ -85,15 +76,6 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) { transactionType, searchTerm: toString(searchTerm), - // path params - processorEvent, - serviceName, - traceIdLink, - errorGroupId, - serviceNodeName: serviceNodeName - ? decodeURIComponent(serviceNodeName) - : serviceNodeName, - // ui filters environment, ...localUIFilters, diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/types.ts b/x-pack/plugins/apm/public/context/UrlParamsContext/types.ts index 7b50a705afa33..574eca3b74f70 100644 --- a/x-pack/plugins/apm/public/context/UrlParamsContext/types.ts +++ b/x-pack/plugins/apm/public/context/UrlParamsContext/types.ts @@ -6,12 +6,10 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { LocalUIFilterName } from '../../../server/lib/ui_filters/local_ui_filters/config'; -import { UIProcessorEvent } from '../../../common/processor_event'; export type IUrlParams = { detailTab?: string; end?: string; - errorGroupId?: string; flyoutDetailTab?: string; kuery?: string; environment?: string; @@ -19,7 +17,6 @@ export type IUrlParams = { rangeTo?: string; refreshInterval?: number; refreshPaused?: boolean; - serviceName?: string; sortDirection?: string; sortField?: string; start?: string; @@ -30,8 +27,5 @@ export type IUrlParams = { waterfallItemId?: string; page?: number; pageSize?: number; - serviceNodeName?: string; searchTerm?: string; - processorEvent?: UIProcessorEvent; - traceIdLink?: string; } & Partial>; diff --git a/x-pack/plugins/apm/public/hooks/useAgentName.ts b/x-pack/plugins/apm/public/hooks/useAgentName.ts index 7a11b662f06f0..1f8a3b916ecd0 100644 --- a/x-pack/plugins/apm/public/hooks/useAgentName.ts +++ b/x-pack/plugins/apm/public/hooks/useAgentName.ts @@ -3,13 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { useParams } from 'react-router-dom'; import { useFetcher } from './useFetcher'; import { useUrlParams } from './useUrlParams'; export function useAgentName() { + const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); - const { start, end, serviceName } = urlParams; + const { start, end } = urlParams; const { data: agentName, error, status } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts b/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts index 78f022ec6b8b5..f4a981ff0975b 100644 --- a/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts +++ b/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import { useParams } from 'react-router-dom'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { MetricsChartsByAgentAPIResponse } from '../../server/lib/metrics/get_metrics_chart_data_by_agent'; -import { IUrlParams } from '../context/UrlParamsContext/types'; import { useUiFilters } from '../context/UrlParamsContext'; +import { IUrlParams } from '../context/UrlParamsContext/types'; import { useFetcher } from './useFetcher'; const INITIAL_DATA: MetricsChartsByAgentAPIResponse = { @@ -18,7 +19,8 @@ export function useServiceMetricCharts( urlParams: IUrlParams, agentName?: string ) { - const { serviceName, start, end, serviceNodeName } = urlParams; + const { serviceName } = useParams<{ serviceName?: string }>(); + const { start, end } = urlParams; const uiFilters = useUiFilters(urlParams); const { data = INITIAL_DATA, error, status } = useFetcher( (callApmApi) => { @@ -31,14 +33,13 @@ export function useServiceMetricCharts( start, end, agentName, - serviceNodeName, uiFilters: JSON.stringify(uiFilters), }, }, }); } }, - [serviceName, start, end, agentName, serviceNodeName, uiFilters] + [serviceName, start, end, agentName, uiFilters] ); return { diff --git a/x-pack/plugins/apm/public/hooks/useServiceTransactionTypes.tsx b/x-pack/plugins/apm/public/hooks/useServiceTransactionTypes.tsx index 227cd849d6c7c..4e110ac2d4380 100644 --- a/x-pack/plugins/apm/public/hooks/useServiceTransactionTypes.tsx +++ b/x-pack/plugins/apm/public/hooks/useServiceTransactionTypes.tsx @@ -4,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import { useParams } from 'react-router-dom'; import { IUrlParams } from '../context/UrlParamsContext/types'; import { useFetcher } from './useFetcher'; const INITIAL_DATA = { transactionTypes: [] }; export function useServiceTransactionTypes(urlParams: IUrlParams) { - const { serviceName, start, end } = urlParams; + const { serviceName } = useParams<{ serviceName?: string }>(); + const { start, end } = urlParams; const { data = INITIAL_DATA } = useFetcher( (callApmApi) => { if (serviceName && start && end) { diff --git a/x-pack/plugins/apm/public/hooks/useTransactionList.ts b/x-pack/plugins/apm/public/hooks/useTransactionList.ts index 0ad221b95b4ff..9c3a18b9c0d0d 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionList.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionList.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IUrlParams } from '../context/UrlParamsContext/types'; +import { useParams } from 'react-router-dom'; import { useUiFilters } from '../context/UrlParamsContext'; -import { useFetcher } from './useFetcher'; +import { IUrlParams } from '../context/UrlParamsContext/types'; import { APIReturnType } from '../services/rest/createCallApmApi'; +import { useFetcher } from './useFetcher'; type TransactionsAPIResponse = APIReturnType< '/api/apm/services/{serviceName}/transaction_groups' @@ -20,7 +21,8 @@ const DEFAULT_RESPONSE: TransactionsAPIResponse = { }; export function useTransactionList(urlParams: IUrlParams) { - const { serviceName, transactionType, start, end } = urlParams; + const { serviceName } = useParams<{ serviceName?: string }>(); + const { transactionType, start, end } = urlParams; const uiFilters = useUiFilters(urlParams); const { data = DEFAULT_RESPONSE, error, status } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/readme.md b/x-pack/plugins/apm/readme.md index 9b02972d35302..d6fdb5f52291c 100644 --- a/x-pack/plugins/apm/readme.md +++ b/x-pack/plugins/apm/readme.md @@ -162,4 +162,5 @@ You can access the development environment at http://localhost:9001. - [Cypress integration tests](./e2e/README.md) - [VSCode setup instructions](./dev_docs/vscode_setup.md) - [Github PR commands](./dev_docs/github_commands.md) +- [Routing and Linking](./dev_docs/routing_and_linking.md) - [Telemetry](./dev_docs/telemetry.md) From caa4e0c11b430719cff97a42ecac5548c8d3cf4d Mon Sep 17 00:00:00 2001 From: Daniil Suleiman <31325372+sulemanof@users.noreply.github.com> Date: Tue, 8 Sep 2020 17:20:51 +0300 Subject: [PATCH 13/19] [TSVB/Markdown] Introduce formatted date field label (#75555) * Introduce formatted date field label * Apply changes * Use default format if can't parse, add comments Co-authored-by: Elastic Machine --- .../components/lib/convert_series_to_vars.js | 20 +++++++++++++++++++ .../server/lib/vis_data/helpers/get_splits.js | 1 + .../lib/vis_data/helpers/get_splits.test.js | 4 ++++ .../response_processors/series/std_metric.js | 1 + 4 files changed, 26 insertions(+) diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js index f969778bbc615..34f339ce24c21 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js @@ -54,6 +54,26 @@ export const convertSeriesToVars = (series, model, dateFormat = 'lll', getConfig }; set(variables, varName, data); set(variables, `${_.snakeCase(row.label)}.label`, row.label); + + /** + * Handle the case when a field has "key_as_string" value. + * Common case is the value is a date string (e.x. "2020-08-21T20:36:58.000Z") or a boolean stringified value ("true"/"false"). + * Try to convert the value into a moment object and format it with "dateFormat" from UI settings, + * if the "key_as_string" value is recognized by a known format in Moments.js https://momentjs.com/docs/#/parsing/string/ . + * If not, return a formatted value from elasticsearch + */ + if (row.labelFormatted) { + const momemntObj = moment(row.labelFormatted); + let val; + + if (momemntObj.isValid()) { + val = momemntObj.format(dateFormat); + } else { + val = row.labelFormatted; + } + + set(variables, `${_.snakeCase(row.label)}.formatted`, val); + } }); }); return variables; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js index 54139a7c27e3f..37cc7fd3380d0 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js @@ -42,6 +42,7 @@ export function getSplits(resp, panel, series, meta) { return buckets.map((bucket) => { bucket.id = `${series.id}:${bucket.key}`; bucket.label = formatKey(bucket.key, series); + bucket.labelFormatted = bucket.key_as_string || ''; bucket.color = panel.type === 'top_n' ? color.string() : colors.shift(); bucket.meta = meta; return bucket; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js index 376d32d0da13f..28f056613b082 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js @@ -89,6 +89,7 @@ describe('getSplits(resp, panel, series)', () => { id: 'SERIES:example-01', key: 'example-01', label: 'example-01', + labelFormatted: '', meta: { bucketSize: 10 }, color: 'rgb(255, 0, 0)', timeseries: { buckets: [] }, @@ -98,6 +99,7 @@ describe('getSplits(resp, panel, series)', () => { id: 'SERIES:example-02', key: 'example-02', label: 'example-02', + labelFormatted: '', meta: { bucketSize: 10 }, color: 'rgb(255, 0, 0)', timeseries: { buckets: [] }, @@ -145,6 +147,7 @@ describe('getSplits(resp, panel, series)', () => { id: 'SERIES:example-01', key: 'example-01', label: 'example-01', + labelFormatted: '', meta: { bucketSize: 10 }, color: undefined, timeseries: { buckets: [] }, @@ -154,6 +157,7 @@ describe('getSplits(resp, panel, series)', () => { id: 'SERIES:example-02', key: 'example-02', label: 'example-02', + labelFormatted: '', meta: { bucketSize: 10 }, color: undefined, timeseries: { buckets: [] }, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.js index 0d567b7fd4154..e04c3a93e81bb 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.js @@ -40,6 +40,7 @@ export function stdMetric(resp, panel, series, meta) { results.push({ id: `${split.id}`, label: split.label, + labelFormatted: split.labelFormatted, color: split.color, data, ...decoration, From e827a6761e1667ea8b9ff1f10603849bc7219f91 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 8 Sep 2020 17:19:48 +0200 Subject: [PATCH 14/19] [RUM Dashboard] Added rum core web vitals (#75685) Co-authored-by: Elastic Machine --- .../elasticsearch_fieldnames.test.ts.snap | 30 +++++ .../apm/common/elasticsearch_fieldnames.ts | 6 + .../app/RumDashboard/ClientMetrics/index.tsx | 2 +- .../CoreVitals/ColorPaletteFlexItem.tsx | 72 ++++++++++ .../RumDashboard/CoreVitals/CoreVitalItem.tsx | 124 ++++++++++++++++++ .../CoreVitals/PaletteLegends.tsx | 69 ++++++++++ .../__stories__/CoreVitals.stories.tsx | 93 +++++++++++++ .../app/RumDashboard/CoreVitals/index.tsx | 73 +++++++++++ .../RumDashboard/CoreVitals/translations.ts | 50 +++++++ .../ResetPercentileZoom.tsx | 53 ++++++++ .../PageLoadDistribution/index.tsx | 25 +--- .../app/RumDashboard/RumDashboard.tsx | 16 ++- .../app/RumDashboard/translations.ts | 9 ++ .../lib/rum_client/get_web_core_vitals.ts | 123 +++++++++++++++++ .../apm/server/routes/create_apm_api.ts | 2 + .../plugins/apm/server/routes/rum_client.ts | 13 ++ .../apm/typings/elasticsearch/aggregations.ts | 2 +- 17 files changed, 740 insertions(+), 22 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/ColorPaletteFlexItem.tsx create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/CoreVitalItem.tsx create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/PaletteLegends.tsx create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/__stories__/CoreVitals.stories.tsx create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/index.tsx create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/translations.ts create mode 100644 x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/ResetPercentileZoom.tsx create mode 100644 x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts diff --git a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index 6238fbfdaa1ab..f93df9a01dea2 100644 --- a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -14,6 +14,8 @@ exports[`Error CLOUD_PROVIDER 1`] = `"gcp"`; exports[`Error CLOUD_REGION 1`] = `"europe-west1"`; +exports[`Error CLS_FIELD 1`] = `undefined`; + exports[`Error CONTAINER_ID 1`] = `undefined`; exports[`Error DESTINATION_ADDRESS 1`] = `undefined`; @@ -34,6 +36,10 @@ exports[`Error ERROR_LOG_MESSAGE 1`] = `undefined`; exports[`Error ERROR_PAGE_URL 1`] = `undefined`; +exports[`Error FCP_FIELD 1`] = `undefined`; + +exports[`Error FID_FIELD 1`] = `undefined`; + exports[`Error EVENT_OUTCOME 1`] = `undefined`; exports[`Error HOST_NAME 1`] = `"my hostname"`; @@ -44,6 +50,8 @@ exports[`Error HTTP_RESPONSE_STATUS_CODE 1`] = `undefined`; exports[`Error LABEL_NAME 1`] = `undefined`; +exports[`Error LCP_FIELD 1`] = `undefined`; + exports[`Error METRIC_JAVA_GC_COUNT 1`] = `undefined`; exports[`Error METRIC_JAVA_GC_TIME 1`] = `undefined`; @@ -118,6 +126,8 @@ exports[`Error SPAN_SUBTYPE 1`] = `undefined`; exports[`Error SPAN_TYPE 1`] = `undefined`; +exports[`Error TBT_FIELD 1`] = `undefined`; + exports[`Error TRACE_ID 1`] = `"trace id"`; exports[`Error TRANSACTION_BREAKDOWN_COUNT 1`] = `undefined`; @@ -168,6 +178,8 @@ exports[`Span CLOUD_PROVIDER 1`] = `"gcp"`; exports[`Span CLOUD_REGION 1`] = `"europe-west1"`; +exports[`Span CLS_FIELD 1`] = `undefined`; + exports[`Span CONTAINER_ID 1`] = `undefined`; exports[`Span DESTINATION_ADDRESS 1`] = `undefined`; @@ -188,6 +200,10 @@ exports[`Span ERROR_LOG_MESSAGE 1`] = `undefined`; exports[`Span ERROR_PAGE_URL 1`] = `undefined`; +exports[`Span FCP_FIELD 1`] = `undefined`; + +exports[`Span FID_FIELD 1`] = `undefined`; + exports[`Span EVENT_OUTCOME 1`] = `undefined`; exports[`Span HOST_NAME 1`] = `undefined`; @@ -198,6 +214,8 @@ exports[`Span HTTP_RESPONSE_STATUS_CODE 1`] = `undefined`; exports[`Span LABEL_NAME 1`] = `undefined`; +exports[`Span LCP_FIELD 1`] = `undefined`; + exports[`Span METRIC_JAVA_GC_COUNT 1`] = `undefined`; exports[`Span METRIC_JAVA_GC_TIME 1`] = `undefined`; @@ -272,6 +290,8 @@ exports[`Span SPAN_SUBTYPE 1`] = `"my subtype"`; exports[`Span SPAN_TYPE 1`] = `"span type"`; +exports[`Span TBT_FIELD 1`] = `undefined`; + exports[`Span TRACE_ID 1`] = `"trace id"`; exports[`Span TRANSACTION_BREAKDOWN_COUNT 1`] = `undefined`; @@ -322,6 +342,8 @@ exports[`Transaction CLOUD_PROVIDER 1`] = `"gcp"`; exports[`Transaction CLOUD_REGION 1`] = `"europe-west1"`; +exports[`Transaction CLS_FIELD 1`] = `undefined`; + exports[`Transaction CONTAINER_ID 1`] = `"container1234567890abcdef"`; exports[`Transaction DESTINATION_ADDRESS 1`] = `undefined`; @@ -342,6 +364,10 @@ exports[`Transaction ERROR_LOG_MESSAGE 1`] = `undefined`; exports[`Transaction ERROR_PAGE_URL 1`] = `undefined`; +exports[`Transaction FCP_FIELD 1`] = `undefined`; + +exports[`Transaction FID_FIELD 1`] = `undefined`; + exports[`Transaction EVENT_OUTCOME 1`] = `undefined`; exports[`Transaction HOST_NAME 1`] = `"my hostname"`; @@ -352,6 +378,8 @@ exports[`Transaction HTTP_RESPONSE_STATUS_CODE 1`] = `200`; exports[`Transaction LABEL_NAME 1`] = `undefined`; +exports[`Transaction LCP_FIELD 1`] = `undefined`; + exports[`Transaction METRIC_JAVA_GC_COUNT 1`] = `undefined`; exports[`Transaction METRIC_JAVA_GC_TIME 1`] = `undefined`; @@ -426,6 +454,8 @@ exports[`Transaction SPAN_SUBTYPE 1`] = `undefined`; exports[`Transaction SPAN_TYPE 1`] = `undefined`; +exports[`Transaction TBT_FIELD 1`] = `undefined`; + exports[`Transaction TRACE_ID 1`] = `"trace id"`; exports[`Transaction TRANSACTION_BREAKDOWN_COUNT 1`] = `undefined`; diff --git a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts index c13169549a566..b322abeb3d597 100644 --- a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts +++ b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts @@ -106,3 +106,9 @@ export const TRANSACTION_TIME_TO_FIRST_BYTE = 'transaction.marks.agent.timeToFirstByte'; export const TRANSACTION_DOM_INTERACTIVE = 'transaction.marks.agent.domInteractive'; + +export const FCP_FIELD = 'transaction.marks.agent.firstContentfulPaint'; +export const LCP_FIELD = 'transaction.marks.agent.largestContentfulPaint'; +export const TBT_FIELD = 'transaction.experience.tbt'; +export const FID_FIELD = 'transaction.experience.fid'; +export const CLS_FIELD = 'transaction.experience.cls'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx index e21dd0d6ff126..b2132c50dc6bc 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx @@ -56,7 +56,7 @@ export function ClientMetrics() { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/ColorPaletteFlexItem.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/ColorPaletteFlexItem.tsx new file mode 100644 index 0000000000000..fc2390acde0be --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/ColorPaletteFlexItem.tsx @@ -0,0 +1,72 @@ +/* + * 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 { EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import styled from 'styled-components'; + +const ColoredSpan = styled.div` + height: 16px; + width: 100%; + cursor: pointer; +`; + +const getSpanStyle = ( + position: number, + inFocus: boolean, + hexCode: string, + percentage: number +) => { + let first = position === 0 || percentage === 100; + let last = position === 2 || percentage === 100; + if (percentage === 100) { + first = true; + last = true; + } + + const spanStyle: any = { + backgroundColor: hexCode, + opacity: !inFocus ? 1 : 0.3, + }; + let borderRadius = ''; + + if (first) { + borderRadius = '4px 0 0 4px'; + } + if (last) { + borderRadius = '0 4px 4px 0'; + } + if (first && last) { + borderRadius = '4px'; + } + spanStyle.borderRadius = borderRadius; + + return spanStyle; +}; + +export function ColorPaletteFlexItem({ + hexCode, + inFocus, + percentage, + tooltip, + position, +}: { + hexCode: string; + position: number; + inFocus: boolean; + percentage: number; + tooltip: string; +}) { + const spanStyle = getSpanStyle(position, inFocus, hexCode, percentage); + + return ( + + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/CoreVitalItem.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/CoreVitalItem.tsx new file mode 100644 index 0000000000000..a4cbebf20b54c --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/CoreVitalItem.tsx @@ -0,0 +1,124 @@ +/* + * 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, + euiPaletteForStatus, + EuiSpacer, + EuiStat, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { PaletteLegends } from './PaletteLegends'; +import { ColorPaletteFlexItem } from './ColorPaletteFlexItem'; +import { + AVERAGE_LABEL, + GOOD_LABEL, + LESS_LABEL, + MORE_LABEL, + POOR_LABEL, +} from './translations'; + +export interface Thresholds { + good: string; + bad: string; +} + +interface Props { + title: string; + value: string; + ranks?: number[]; + loading: boolean; + thresholds: Thresholds; +} + +export function getCoreVitalTooltipMessage( + thresholds: Thresholds, + position: number, + title: string, + percentage: number +) { + const good = position === 0; + const bad = position === 2; + const average = !good && !bad; + + return i18n.translate('xpack.apm.csm.dashboard.webVitals.palette.tooltip', { + defaultMessage: + '{percentage} % of users have {exp} experience because the {title} takes {moreOrLess} than {value}{averageMessage}.', + values: { + percentage, + title: title?.toLowerCase(), + exp: good ? GOOD_LABEL : bad ? POOR_LABEL : AVERAGE_LABEL, + moreOrLess: bad || average ? MORE_LABEL : LESS_LABEL, + value: good || average ? thresholds.good : thresholds.bad, + averageMessage: average + ? i18n.translate('xpack.apm.rum.coreVitals.averageMessage', { + defaultMessage: ' and less than {bad}', + values: { bad: thresholds.bad }, + }) + : '', + }, + }); +} + +export function CoreVitalItem({ + loading, + title, + value, + thresholds, + ranks = [100, 0, 0], +}: Props) { + const palette = euiPaletteForStatus(3); + + const [inFocusInd, setInFocusInd] = useState(null); + + const biggestValIndex = ranks.indexOf(Math.max(...ranks)); + + return ( + <> + + + + {palette.map((hexCode, ind) => ( + + ))} + + + { + setInFocusInd(ind); + }} + /> + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/PaletteLegends.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/PaletteLegends.tsx new file mode 100644 index 0000000000000..84cc5f1ddb230 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/PaletteLegends.tsx @@ -0,0 +1,69 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiHealth, + euiPaletteForStatus, + EuiToolTip, +} from '@elastic/eui'; +import styled from 'styled-components'; +import { getCoreVitalTooltipMessage, Thresholds } from './CoreVitalItem'; + +const PaletteLegend = styled(EuiHealth)` + &:hover { + cursor: pointer; + text-decoration: underline; + background-color: #e7f0f7; + } +`; + +interface Props { + onItemHover: (ind: number | null) => void; + ranks: number[]; + thresholds: Thresholds; + title: string; +} + +export function PaletteLegends({ + ranks, + title, + onItemHover, + thresholds, +}: Props) { + const palette = euiPaletteForStatus(3); + + return ( + + {palette.map((color, ind) => ( + { + onItemHover(ind); + }} + onMouseLeave={() => { + onItemHover(null); + }} + > + + {ranks?.[ind]}% + + + ))} + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/__stories__/CoreVitals.stories.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/__stories__/CoreVitals.stories.tsx new file mode 100644 index 0000000000000..a611df00f1e65 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/__stories__/CoreVitals.stories.tsx @@ -0,0 +1,93 @@ +/* + * 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 React from 'react'; +import { EuiThemeProvider } from '../../../../../../../observability/public'; +import { CoreVitalItem } from '../CoreVitalItem'; +import { LCP_LABEL } from '../translations'; + +storiesOf('app/RumDashboard/WebCoreVitals', module) + .addDecorator((storyFn) => {storyFn()}) + .add( + 'Basic', + () => { + return ( + + ); + }, + { + info: { + propTables: false, + source: false, + }, + } + ) + .add( + '50% Good', + () => { + return ( + + ); + }, + { + info: { + propTables: false, + source: false, + }, + } + ) + .add( + '100% Bad', + () => { + return ( + + ); + }, + { + info: { + propTables: false, + source: false, + }, + } + ) + .add( + '100% Average', + () => { + return ( + + ); + }, + { + info: { + propTables: false, + source: false, + }, + } + ); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/index.tsx new file mode 100644 index 0000000000000..e8305a6aef0d4 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/index.tsx @@ -0,0 +1,73 @@ +/* + * 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 * as React from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { useFetcher } from '../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { CLS_LABEL, FID_LABEL, LCP_LABEL } from './translations'; +import { CoreVitalItem } from './CoreVitalItem'; + +const CoreVitalsThresholds = { + LCP: { good: '2.5s', bad: '4.0s' }, + FID: { good: '100ms', bad: '300ms' }, + CLS: { good: '0.1', bad: '0.25' }, +}; + +export function CoreVitals() { + const { urlParams, uiFilters } = useUrlParams(); + + const { start, end, serviceName } = urlParams; + + const { data, status } = useFetcher( + (callApmApi) => { + if (start && end && serviceName) { + return callApmApi({ + pathname: '/api/apm/rum-client/web-core-vitals', + params: { + query: { start, end, uiFilters: JSON.stringify(uiFilters) }, + }, + }); + } + return Promise.resolve(null); + }, + [start, end, serviceName, uiFilters] + ); + + const { lcp, lcpRanks, fid, fidRanks, cls, clsRanks } = data || {}; + + return ( + + + + + + + + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/translations.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/translations.ts new file mode 100644 index 0000000000000..136dfb279e336 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/CoreVitals/translations.ts @@ -0,0 +1,50 @@ +/* + * 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'; + +export const LCP_LABEL = i18n.translate('xpack.apm.rum.coreVitals.lcp', { + defaultMessage: 'Largest contentful paint', +}); + +export const FID_LABEL = i18n.translate('xpack.apm.rum.coreVitals.fip', { + defaultMessage: 'First input delay', +}); + +export const CLS_LABEL = i18n.translate('xpack.apm.rum.coreVitals.cls', { + defaultMessage: 'Cumulative layout shift', +}); + +export const FCP_LABEL = i18n.translate('xpack.apm.rum.coreVitals.fcp', { + defaultMessage: 'First contentful paint', +}); + +export const TBT_LABEL = i18n.translate('xpack.apm.rum.coreVitals.tbt', { + defaultMessage: 'Total blocking time', +}); + +export const POOR_LABEL = i18n.translate('xpack.apm.rum.coreVitals.poor', { + defaultMessage: 'a poor', +}); + +export const GOOD_LABEL = i18n.translate('xpack.apm.rum.coreVitals.good', { + defaultMessage: 'a good', +}); + +export const AVERAGE_LABEL = i18n.translate( + 'xpack.apm.rum.coreVitals.average', + { + defaultMessage: 'an average', + } +); + +export const MORE_LABEL = i18n.translate('xpack.apm.rum.coreVitals.more', { + defaultMessage: 'more', +}); + +export const LESS_LABEL = i18n.translate('xpack.apm.rum.coreVitals.less', { + defaultMessage: 'less', +}); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/ResetPercentileZoom.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/ResetPercentileZoom.tsx new file mode 100644 index 0000000000000..deaeed70e572b --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/ResetPercentileZoom.tsx @@ -0,0 +1,53 @@ +/* + * 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 { + EuiButtonEmpty, + EuiHideFor, + EuiShowFor, + EuiButtonIcon, +} from '@elastic/eui'; +import { I18LABELS } from '../translations'; +import { PercentileRange } from './index'; + +interface Props { + percentileRange: PercentileRange; + setPercentileRange: (value: PercentileRange) => void; +} +export function ResetPercentileZoom({ + percentileRange, + setPercentileRange, +}: Props) { + const isDisabled = + percentileRange.min === null && percentileRange.max === null; + const onClick = () => { + setPercentileRange({ min: null, max: null }); + }; + return ( + <> + + + + + + {I18LABELS.resetZoom} + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx index 8fd03ebb65f4c..f63b914c73398 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx @@ -5,19 +5,14 @@ */ import React, { useState } from 'react'; -import { - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { useFetcher } from '../../../../hooks/useFetcher'; import { I18LABELS } from '../translations'; import { BreakdownFilter } from '../Breakdowns/BreakdownFilter'; import { PageLoadDistChart } from '../Charts/PageLoadDistChart'; import { BreakdownItem } from '../../../../../typings/ui_filters'; +import { ResetPercentileZoom } from './ResetPercentileZoom'; export interface PercentileRange { min?: number | null; @@ -74,18 +69,10 @@ export function PageLoadDistribution() { - { - setPercentileRange({ min: null, max: null }); - }} - disabled={ - percentileRange.min === null && percentileRange.max === null - } - > - {I18LABELS.resetZoom} - + -

{I18LABELS.pageLoadTimes}

+

{I18LABELS.pageLoadDuration}

@@ -34,6 +35,19 @@ export function RumDashboard() {
+ + + + + +

{I18LABELS.coreWebVitals}

+
+ + +
+
+
+
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts index 66eeaf433d2a1..042e138793f11 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts @@ -25,6 +25,12 @@ export const I18LABELS = { pageLoadTimes: i18n.translate('xpack.apm.rum.dashboard.pageLoadTimes.label', { defaultMessage: 'Page load times', }), + pageLoadDuration: i18n.translate( + 'xpack.apm.rum.dashboard.pageLoadDuration.label', + { + defaultMessage: 'Page load duration', + } + ), pageLoadDistribution: i18n.translate( 'xpack.apm.rum.dashboard.pageLoadDistribution.label', { @@ -46,6 +52,9 @@ export const I18LABELS = { seconds: i18n.translate('xpack.apm.rum.filterGroup.seconds', { defaultMessage: 'seconds', }), + coreWebVitals: i18n.translate('xpack.apm.rum.filterGroup.coreWebVitals', { + defaultMessage: 'Core web vitals', + }), }; export const VisitorBreakdownLabel = i18n.translate( diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts new file mode 100644 index 0000000000000..9395e5fe14336 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts @@ -0,0 +1,123 @@ +/* + * 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 { getRumOverviewProjection } from '../../projections/rum_overview'; +import { mergeProjection } from '../../projections/util/merge_projection'; +import { + Setup, + SetupTimeRange, + SetupUIFilters, +} from '../helpers/setup_request'; +import { + CLS_FIELD, + FID_FIELD, + LCP_FIELD, +} from '../../../common/elasticsearch_fieldnames'; + +export async function getWebCoreVitals({ + setup, +}: { + setup: Setup & SetupTimeRange & SetupUIFilters; +}) { + const projection = getRumOverviewProjection({ + setup, + }); + + const params = mergeProjection(projection, { + body: { + size: 0, + query: { + bool: { + filter: [ + ...projection.body.query.bool.filter, + { + term: { + 'user_agent.name': 'Chrome', + }, + }, + ], + }, + }, + aggs: { + lcp: { + percentiles: { + field: LCP_FIELD, + percents: [50], + }, + }, + fid: { + percentiles: { + field: FID_FIELD, + percents: [50], + }, + }, + cls: { + percentiles: { + field: CLS_FIELD, + percents: [50], + }, + }, + lcpRanks: { + percentile_ranks: { + field: LCP_FIELD, + values: [2500, 4000], + keyed: false, + }, + }, + fidRanks: { + percentile_ranks: { + field: FID_FIELD, + values: [100, 300], + keyed: false, + }, + }, + clsRanks: { + percentile_ranks: { + field: CLS_FIELD, + values: [0.1, 0.25], + keyed: false, + }, + }, + }, + }, + }); + + const { apmEventClient } = setup; + + const response = await apmEventClient.search(params); + const { + lcp, + cls, + fid, + lcpRanks, + fidRanks, + clsRanks, + } = response.aggregations!; + + const getRanksPercentages = ( + ranks: Array<{ key: number; value: number }> + ) => { + const ranksVal = (ranks ?? [0, 0]).map( + ({ value }) => value?.toFixed(0) ?? 0 + ); + return [ + Number(ranksVal?.[0]), + Number(ranksVal?.[1]) - Number(ranksVal?.[0]), + 100 - Number(ranksVal?.[1]), + ]; + }; + + // Divide by 1000 to convert ms into seconds + return { + cls: String(cls.values['50.0'] || 0), + fid: ((fid.values['50.0'] || 0) / 1000).toFixed(2), + lcp: ((lcp.values['50.0'] || 0) / 1000).toFixed(2), + + lcpRanks: getRanksPercentages(lcpRanks.values), + fidRanks: getRanksPercentages(fidRanks.values), + clsRanks: getRanksPercentages(clsRanks.values), + }; +} diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index 5dff13e5b37e0..cf7a02cde975c 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -77,6 +77,7 @@ import { rumPageLoadDistBreakdownRoute, rumServicesRoute, rumVisitorsBreakdownRoute, + rumWebCoreVitals, } from './rum_client'; import { observabilityOverviewHasDataRoute, @@ -172,6 +173,7 @@ const createApmApi = () => { .add(rumClientMetricsRoute) .add(rumServicesRoute) .add(rumVisitorsBreakdownRoute) + .add(rumWebCoreVitals) // Observability dashboard .add(observabilityOverviewHasDataRoute) diff --git a/x-pack/plugins/apm/server/routes/rum_client.ts b/x-pack/plugins/apm/server/routes/rum_client.ts index 0781512c6f7a0..e17791f56eef2 100644 --- a/x-pack/plugins/apm/server/routes/rum_client.ts +++ b/x-pack/plugins/apm/server/routes/rum_client.ts @@ -14,6 +14,7 @@ import { getPageLoadDistribution } from '../lib/rum_client/get_page_load_distrib import { getPageLoadDistBreakdown } from '../lib/rum_client/get_pl_dist_breakdown'; import { getRumServices } from '../lib/rum_client/get_rum_services'; import { getVisitorBreakdown } from '../lib/rum_client/get_visitor_breakdown'; +import { getWebCoreVitals } from '../lib/rum_client/get_web_core_vitals'; export const percentileRangeRt = t.partial({ minPercentile: t.string, @@ -117,3 +118,15 @@ export const rumVisitorsBreakdownRoute = createRoute(() => ({ return getVisitorBreakdown({ setup }); }, })); + +export const rumWebCoreVitals = createRoute(() => ({ + path: '/api/apm/rum-client/web-core-vitals', + params: { + query: t.intersection([uiFiltersRt, rangeRt]), + }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + + return getWebCoreVitals({ setup }); + }, +})); diff --git a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts index f957614122547..7a7592b248960 100644 --- a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts +++ b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts @@ -146,7 +146,7 @@ export interface AggregationOptionsByType { buckets: number; } & AggregationSourceOptions; percentile_ranks: { - values: string[]; + values: Array; keyed?: boolean; hdr?: { number_of_significant_value_digits: number }; } & AggregationSourceOptions; From 728dfb4b6bd6fd9ee1c736132b4b7f96fcccb70e Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Tue, 8 Sep 2020 17:40:06 +0200 Subject: [PATCH 15/19] [Lens] Update grouping editor to indicate the expected result of a grouping change (#76904) --- .../dimension_panel/bucket_nesting_editor.tsx | 5 +++-- x-pack/plugins/translations/translations/ja-JP.json | 2 -- x-pack/plugins/translations/translations/zh-CN.json | 2 -- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx index 198be7085f5fc..e5d63f1f92e19 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx @@ -80,7 +80,8 @@ export function BucketNestingEditor({ values: { field: fieldName }, }) : i18n.translate('xpack.lens.indexPattern.groupingOverallDateHistogram', { - defaultMessage: 'Dates overall', + defaultMessage: 'Top values for each {field}', + values: { field: fieldName }, }) } checked={!prevColumn} @@ -96,7 +97,7 @@ export function BucketNestingEditor({ values: { target: target.fieldName }, }) : i18n.translate('xpack.lens.indexPattern.groupingSecondDateHistogram', { - defaultMessage: 'Dates for each {target}', + defaultMessage: 'Overall top {target}', values: { target: target.fieldName }, }) } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index eacb1febd20ff..ac23d1e62bcdf 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9643,9 +9643,7 @@ "xpack.lens.indexPattern.fieldTopValuesLabel": "トップの値", "xpack.lens.indexPattern.groupByDropdown": "グループ分けの条件", "xpack.lens.indexPattern.groupingControlLabel": "グループ分け", - "xpack.lens.indexPattern.groupingOverallDateHistogram": "全体の日付", "xpack.lens.indexPattern.groupingOverallTerms": "全体のトップ {field}", - "xpack.lens.indexPattern.groupingSecondDateHistogram": "各 {target} の日付", "xpack.lens.indexPattern.groupingSecondTerms": "各 {target} のトップの値", "xpack.lens.indexPattern.indexPatternLoadError": "インデックスパターンの読み込み中にエラーが発生", "xpack.lens.indexPattern.invalidInterval": "無効な間隔値", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index bd30703dd5bd6..4d5e5c05cb795 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9649,9 +9649,7 @@ "xpack.lens.indexPattern.fieldTopValuesLabel": "排名最前值", "xpack.lens.indexPattern.groupByDropdown": "分组依据", "xpack.lens.indexPattern.groupingControlLabel": "分组", - "xpack.lens.indexPattern.groupingOverallDateHistogram": "日期 - 总体", "xpack.lens.indexPattern.groupingOverallTerms": "总体排名最前 {field}", - "xpack.lens.indexPattern.groupingSecondDateHistogram": "每个 {target} 的日期", "xpack.lens.indexPattern.groupingSecondTerms": "每个 {target} 的排名最前值", "xpack.lens.indexPattern.indexPatternLoadError": "加载索引模式时出错", "xpack.lens.indexPattern.invalidInterval": "时间间隔值无效", From 4afa2d633f2e7b002ebb556becaad176d259c5f4 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 8 Sep 2020 11:52:05 -0400 Subject: [PATCH 16/19] [Uptime] Modify router to use `ScopedHistory` (#76421) * Remove hashbang and modify router to use ScopedHistory. * Update test to conform to refactored API. * Update test snapshots. * Fix broken type check. * Remove unneeded prop. * Prevent full page reload for breadcrumbs. * Fix outdated test. * Fix type errors. * Add default value for focusConnectorField url param. * Make stringify function support focusFieldConnector empty values. * Avoid unnecessary text in breadcrumb href. * Refresh test snapshot. Co-authored-by: Elastic Machine --- .../common/constants/client_defaults.ts | 1 + .../plugins/uptime/common/constants/plugin.ts | 1 - x-pack/plugins/uptime/public/apps/plugin.ts | 3 +- .../plugins/uptime/public/apps/render_app.tsx | 5 ++-- .../plugins/uptime/public/apps/uptime_app.tsx | 8 ++--- .../public/apps/uptime_overview_fetcher.ts | 2 +- .../data_or_index_missing.test.tsx.snap | 2 +- .../__snapshots__/empty_state.test.tsx.snap | 24 +++++++-------- .../empty_state/data_or_index_missing.tsx | 2 +- .../most_recent_error.test.tsx.snap | 2 +- .../hooks/__tests__/use_breadcrumbs.test.tsx | 10 ++++++- .../uptime/public/hooks/use_breadcrumbs.ts | 29 +++++++++++++++---- .../stringify_url_params.test.ts.snap | 5 ---- .../__tests__/stringify_url_params.test.ts | 10 +++++-- .../public/lib/helper/stringify_url_params.ts | 4 +++ 15 files changed, 68 insertions(+), 40 deletions(-) delete mode 100644 x-pack/plugins/uptime/public/lib/helper/__tests__/__snapshots__/stringify_url_params.test.ts.snap diff --git a/x-pack/plugins/uptime/common/constants/client_defaults.ts b/x-pack/plugins/uptime/common/constants/client_defaults.ts index d8a3ef8d7cbbb..a5db67ae3b58f 100644 --- a/x-pack/plugins/uptime/common/constants/client_defaults.ts +++ b/x-pack/plugins/uptime/common/constants/client_defaults.ts @@ -31,6 +31,7 @@ export const CLIENT_DEFAULTS = { * The end of the default date range is now. */ DATE_RANGE_END: 'now', + FOCUS_CONNECTOR_FIELD: false, FILTERS: '', MONITOR_LIST_PAGE_INDEX: 0, MONITOR_LIST_PAGE_SIZE: 20, diff --git a/x-pack/plugins/uptime/common/constants/plugin.ts b/x-pack/plugins/uptime/common/constants/plugin.ts index 6064524872a0a..71bae9d8dafcd 100644 --- a/x-pack/plugins/uptime/common/constants/plugin.ts +++ b/x-pack/plugins/uptime/common/constants/plugin.ts @@ -17,7 +17,6 @@ export const PLUGIN = { NAME: i18n.translate('xpack.uptime.featureRegistry.uptimeFeatureName', { defaultMessage: 'Uptime', }), - ROUTER_BASE_NAME: '/app/uptime#', TITLE: i18n.translate('xpack.uptime.uptimeFeatureCatalogueTitle', { defaultMessage: 'Uptime', }), diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index 9f7907ec39187..8a6699c16269e 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -59,7 +59,7 @@ export class UptimePlugin title: PLUGIN.TITLE, description: PLUGIN.DESCRIPTION, icon: 'uptimeApp', - path: '/app/uptime#/', + path: '/app/uptime', showOnHomePage: false, category: FeatureCatalogueCategory.DATA, }); @@ -84,7 +84,6 @@ export class UptimePlugin }); core.application.register({ - appRoute: '/app/uptime#/', id: PLUGIN.ID, euiIconType: 'uptimeApp', order: 8400, diff --git a/x-pack/plugins/uptime/public/apps/render_app.tsx b/x-pack/plugins/uptime/public/apps/render_app.tsx index f834f8b5cdd3c..c0567ff956ce4 100644 --- a/x-pack/plugins/uptime/public/apps/render_app.tsx +++ b/x-pack/plugins/uptime/public/apps/render_app.tsx @@ -16,13 +16,12 @@ import { } from '../../common/constants'; import { UptimeApp, UptimeAppProps } from './uptime_app'; import { ClientPluginsSetup, ClientPluginsStart } from './plugin'; -import { PLUGIN } from '../../common/constants/plugin'; export function renderApp( core: CoreStart, plugins: ClientPluginsSetup, startPlugins: ClientPluginsStart, - { element }: AppMountParameters + { element, history }: AppMountParameters ) { const { application: { capabilities }, @@ -48,6 +47,7 @@ export function renderApp( basePath: basePath.get(), darkMode: core.uiSettings.get(DEFAULT_DARK_MODE), commonlyUsedRanges: core.uiSettings.get(DEFAULT_TIMEPICKER_QUICK_RANGES), + history, isApmAvailable: apm, isInfraAvailable: infrastructure, isLogsAvailable: logs, @@ -67,7 +67,6 @@ export function renderApp( }, ], }), - routerBasename: basePath.prepend(PLUGIN.ROUTER_BASE_NAME), setBadge, setBreadcrumbs: core.chrome.setBreadcrumbs, }; diff --git a/x-pack/plugins/uptime/public/apps/uptime_app.tsx b/x-pack/plugins/uptime/public/apps/uptime_app.tsx index 1dc34b44b7c64..4b58ba104314f 100644 --- a/x-pack/plugins/uptime/public/apps/uptime_app.tsx +++ b/x-pack/plugins/uptime/public/apps/uptime_app.tsx @@ -8,7 +8,7 @@ import { EuiPage, EuiErrorBoundary } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect } from 'react'; import { Provider as ReduxProvider } from 'react-redux'; -import { BrowserRouter as Router } from 'react-router-dom'; +import { Router } from 'react-router-dom'; import { I18nStart, ChromeBreadcrumb, CoreStart } from 'kibana/public'; import { KibanaContextProvider, @@ -31,6 +31,7 @@ import { } from '../components/overview/alerts'; import { store } from '../state'; import { kibanaService } from '../state/kibana_service'; +import { ScopedHistory } from '../../../../../src/core/public'; export interface UptimeAppColors { danger: string; @@ -46,13 +47,13 @@ export interface UptimeAppProps { canSave: boolean; core: CoreStart; darkMode: boolean; + history: ScopedHistory; i18n: I18nStart; isApmAvailable: boolean; isInfraAvailable: boolean; isLogsAvailable: boolean; plugins: ClientPluginsSetup; startPlugins: ClientPluginsStart; - routerBasename: string; setBadge: UMUpdateBadge; renderGlobalHelpControls(): void; commonlyUsedRanges: CommonlyUsedRange[]; @@ -68,7 +69,6 @@ const Application = (props: UptimeAppProps) => { i18n: i18nCore, plugins, renderGlobalHelpControls, - routerBasename, setBadge, startPlugins, } = props; @@ -99,7 +99,7 @@ const Application = (props: UptimeAppProps) => { - + diff --git a/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts b/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts index 7e5c18f13b29e..b077f622c1dee 100644 --- a/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts +++ b/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts @@ -24,7 +24,7 @@ async function fetchUptimeOverviewData({ const pings = await fetchPingHistogram({ dateStart: start, dateEnd: end, bucketSize }); const response: UptimeFetchDataResponse = { - appLink: `/app/uptime#/?dateRangeStart=${relativeTime.start}&dateRangeEnd=${relativeTime.end}`, + appLink: `/app/uptime?dateRangeStart=${relativeTime.start}&dateRangeEnd=${relativeTime.end}`, stats: { monitors: { type: 'number', diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/data_or_index_missing.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/data_or_index_missing.test.tsx.snap index 0429d36bf8741..41e46259715ee 100644 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/data_or_index_missing.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/data_or_index_missing.test.tsx.snap @@ -36,7 +36,7 @@ exports[`DataOrIndexMissing component renders headingMessage 1`] = ` - + Get https://expired.badssl.com: x509: certificate has expired or is not yet valid diff --git a/x-pack/plugins/uptime/public/hooks/__tests__/use_breadcrumbs.test.tsx b/x-pack/plugins/uptime/public/hooks/__tests__/use_breadcrumbs.test.tsx index d688660f564ca..9b9af20285304 100644 --- a/x-pack/plugins/uptime/public/hooks/__tests__/use_breadcrumbs.test.tsx +++ b/x-pack/plugins/uptime/public/hooks/__tests__/use_breadcrumbs.test.tsx @@ -44,7 +44,11 @@ describe('useBreadcrumbs', () => { ); const urlParams: UptimeUrlParams = getSupportedUrlParams({}); - expect(getBreadcrumbs()).toStrictEqual([makeBaseBreadcrumb(urlParams)].concat(expectedCrumbs)); + expect(JSON.stringify(getBreadcrumbs())).toEqual( + JSON.stringify( + [makeBaseBreadcrumb('/app/uptime', jest.fn(), urlParams)].concat(expectedCrumbs) + ) + ); }); }); @@ -54,6 +58,10 @@ const mockCore: () => [() => ChromeBreadcrumb[], any] = () => { return breadcrumbObj; }; const core = { + application: { + getUrlForApp: () => '/app/uptime', + navigateToUrl: jest.fn(), + }, chrome: { setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => { breadcrumbObj = newBreadcrumbs; diff --git a/x-pack/plugins/uptime/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/uptime/public/hooks/use_breadcrumbs.ts index 182c6b0114128..ddd3ca7c4f528 100644 --- a/x-pack/plugins/uptime/public/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/uptime/public/hooks/use_breadcrumbs.ts @@ -7,35 +7,52 @@ import { ChromeBreadcrumb } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { useEffect } from 'react'; +import { EuiBreadcrumb } from '@elastic/eui'; import { UptimeUrlParams } from '../lib/helper'; import { stringifyUrlParams } from '../lib/helper/stringify_url_params'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { useUrlParams } from '.'; +import { PLUGIN } from '../../common/constants/plugin'; -export const makeBaseBreadcrumb = (params?: UptimeUrlParams): ChromeBreadcrumb => { - let href = '#/'; +const EMPTY_QUERY = '?'; + +export const makeBaseBreadcrumb = ( + href: string, + navigateToHref?: (url: string) => Promise, + params?: UptimeUrlParams +): EuiBreadcrumb => { if (params) { const crumbParams: Partial = { ...params }; // We don't want to encode this values because they are often set to Date.now(), the relative // values in dateRangeStart are better for a URL. delete crumbParams.absoluteDateRangeStart; delete crumbParams.absoluteDateRangeEnd; - href += stringifyUrlParams(crumbParams, true); + const query = stringifyUrlParams(crumbParams, true); + href += query === EMPTY_QUERY ? '' : query; } return { text: i18n.translate('xpack.uptime.breadcrumbs.overviewBreadcrumbText', { defaultMessage: 'Uptime', }), href, + onClick: (event) => { + if (href && navigateToHref) { + event.preventDefault(); + navigateToHref(href); + } + }, }; }; export const useBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => { const params = useUrlParams()[0](); - const setBreadcrumbs = useKibana().services.chrome?.setBreadcrumbs; + const kibana = useKibana(); + const setBreadcrumbs = kibana.services.chrome?.setBreadcrumbs; + const appPath = kibana.services.application?.getUrlForApp(PLUGIN.ID) ?? ''; + const navigate = kibana.services.application?.navigateToUrl; useEffect(() => { if (setBreadcrumbs) { - setBreadcrumbs([makeBaseBreadcrumb(params)].concat(extraCrumbs)); + setBreadcrumbs([makeBaseBreadcrumb(appPath, navigate, params)].concat(extraCrumbs)); } - }, [extraCrumbs, params, setBreadcrumbs]); + }, [appPath, extraCrumbs, navigate, params, setBreadcrumbs]); }; diff --git a/x-pack/plugins/uptime/public/lib/helper/__tests__/__snapshots__/stringify_url_params.test.ts.snap b/x-pack/plugins/uptime/public/lib/helper/__tests__/__snapshots__/stringify_url_params.test.ts.snap deleted file mode 100644 index 31f5ceff7d046..0000000000000 --- a/x-pack/plugins/uptime/public/lib/helper/__tests__/__snapshots__/stringify_url_params.test.ts.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`stringifyUrlParams creates expected string value 1`] = `"?autorefreshInterval=50000&autorefreshIsPaused=false&dateRangeStart=now-15m&dateRangeEnd=now&filters=monitor.id%3A%20bar&search=monitor.id%3A%20foo&selectedPingStatus=down&statusFilter=up"`; - -exports[`stringifyUrlParams creates expected string value when ignore empty is true 1`] = `"?autorefreshInterval=50000&filters=monitor.id%3A%20bar"`; diff --git a/x-pack/plugins/uptime/public/lib/helper/__tests__/stringify_url_params.test.ts b/x-pack/plugins/uptime/public/lib/helper/__tests__/stringify_url_params.test.ts index a2f9b29c4ff58..8cf35c728fc04 100644 --- a/x-pack/plugins/uptime/public/lib/helper/__tests__/stringify_url_params.test.ts +++ b/x-pack/plugins/uptime/public/lib/helper/__tests__/stringify_url_params.test.ts @@ -14,11 +14,14 @@ describe('stringifyUrlParams', () => { dateRangeStart: 'now-15m', dateRangeEnd: 'now', filters: 'monitor.id: bar', + focusConnectorField: true, search: 'monitor.id: foo', selectedPingStatus: 'down', statusFilter: 'up', }); - expect(result).toMatchSnapshot(); + expect(result).toMatchInlineSnapshot( + `"?autorefreshInterval=50000&autorefreshIsPaused=false&dateRangeStart=now-15m&dateRangeEnd=now&filters=monitor.id%3A%20bar&focusConnectorField=true&search=monitor.id%3A%20foo&selectedPingStatus=down&statusFilter=up"` + ); }); it('creates expected string value when ignore empty is true', () => { @@ -29,6 +32,7 @@ describe('stringifyUrlParams', () => { dateRangeStart: 'now-15m', dateRangeEnd: 'now', filters: 'monitor.id: bar', + focusConnectorField: false, search: undefined, selectedPingStatus: undefined, statusFilter: '', @@ -36,7 +40,9 @@ describe('stringifyUrlParams', () => { }, true ); - expect(result).toMatchSnapshot(); + expect(result).toMatchInlineSnapshot( + `"?autorefreshInterval=50000&filters=monitor.id%3A%20bar"` + ); expect(result.includes('pagination')).toBeFalsy(); expect(result.includes('search')).toBeFalsy(); diff --git a/x-pack/plugins/uptime/public/lib/helper/stringify_url_params.ts b/x-pack/plugins/uptime/public/lib/helper/stringify_url_params.ts index a8ce86c4399e2..b10af15961401 100644 --- a/x-pack/plugins/uptime/public/lib/helper/stringify_url_params.ts +++ b/x-pack/plugins/uptime/public/lib/helper/stringify_url_params.ts @@ -13,6 +13,7 @@ const { AUTOREFRESH_IS_PAUSED, DATE_RANGE_START, DATE_RANGE_END, + FOCUS_CONNECTOR_FIELD, } = CLIENT_DEFAULTS; export const stringifyUrlParams = (params: Partial, ignoreEmpty = false) => { @@ -36,6 +37,9 @@ export const stringifyUrlParams = (params: Partial, ignoreEmpty if (key === 'autorefreshInterval' && val === AUTOREFRESH_INTERVAL) { delete params[key]; } + if (key === 'focusConnectorField' && val === FOCUS_CONNECTOR_FIELD) { + delete params[key]; + } }); } return `?${stringify(params, { sort: false })}`; From 075e75e2f7bcd0791cd5db88e865d745ec39e153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Tue, 8 Sep 2020 11:53:21 -0400 Subject: [PATCH 17/19] Rename status API to instance summary (#76541) * Rename status API to instance summary * Remove unused translations * Fix typos Co-authored-by: Elastic Machine --- x-pack/plugins/alerts/README.md | 6 +- ...rt_status.ts => alert_instance_summary.ts} | 2 +- x-pack/plugins/alerts/common/index.ts | 2 +- .../alerts/server/alerts_client.mock.ts | 2 +- .../alerts/server/alerts_client.test.ts | 86 +++++++++------ x-pack/plugins/alerts/server/alerts_client.ts | 25 +++-- .../authorization/alerts_authorization.ts | 2 +- ...t_instance_summary_from_event_log.test.ts} | 100 +++++++++++++----- ... alert_instance_summary_from_event_log.ts} | 28 ++--- x-pack/plugins/alerts/server/plugin.ts | 4 +- ....ts => get_alert_instance_summary.test.ts} | 24 ++--- ...tatus.ts => get_alert_instance_summary.ts} | 8 +- x-pack/plugins/alerts/server/routes/index.ts | 2 +- .../alerting.test.ts | 8 +- .../feature_privilege_builder/alerting.ts | 2 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../public/application/lib/alert_api.ts | 14 ++- .../components/alert_instances.test.tsx | 31 +++--- .../components/alert_instances.tsx | 10 +- .../components/alert_instances_route.test.tsx | 54 ++++++---- .../components/alert_instances_route.tsx | 39 ++++--- .../with_bulk_alert_api_operations.tsx | 10 +- .../triggers_actions_ui/public/types.ts | 4 +- ...tatus.ts => get_alert_instance_summary.ts} | 20 ++-- .../tests/alerting/index.ts | 2 +- ...tatus.ts => get_alert_instance_summary.ts} | 20 ++-- .../spaces_only/tests/alerting/index.ts | 2 +- .../apps/triggers_actions_ui/details.ts | 20 ++-- .../services/alerting/alerts.ts | 6 +- 30 files changed, 325 insertions(+), 210 deletions(-) rename x-pack/plugins/alerts/common/{alert_status.ts => alert_instance_summary.ts} (95%) rename x-pack/plugins/alerts/server/lib/{alert_status_from_event_log.test.ts => alert_instance_summary_from_event_log.test.ts} (83%) rename x-pack/plugins/alerts/server/lib/{alert_status_from_event_log.ts => alert_instance_summary_from_event_log.ts} (79%) rename x-pack/plugins/alerts/server/routes/{get_alert_status.test.ts => get_alert_instance_summary.test.ts} (75%) rename x-pack/plugins/alerts/server/routes/{get_alert_status.ts => get_alert_instance_summary.ts} (83%) rename x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/{get_alert_status.ts => get_alert_instance_summary.ts} (90%) rename x-pack/test/alerting_api_integration/spaces_only/tests/alerting/{get_alert_status.ts => get_alert_instance_summary.ts} (95%) diff --git a/x-pack/plugins/alerts/README.md b/x-pack/plugins/alerts/README.md index aab05cb0a7cfd..6307e463af853 100644 --- a/x-pack/plugins/alerts/README.md +++ b/x-pack/plugins/alerts/README.md @@ -26,7 +26,7 @@ Table of Contents - [`GET /api/alerts/_find`: Find alerts](#get-apialertfind-find-alerts) - [`GET /api/alerts/alert/{id}`: Get alert](#get-apialertid-get-alert) - [`GET /api/alerts/alert/{id}/state`: Get alert state](#get-apialertidstate-get-alert-state) - - [`GET /api/alerts/alert/{id}/status`: Get alert status](#get-apialertidstate-get-alert-status) + - [`GET /api/alerts/alert/{id}/_instance_summary`: Get alert instance summary](#get-apialertidstate-get-alert-instance-summary) - [`GET /api/alerts/list_alert_types`: List alert types](#get-apialerttypes-list-alert-types) - [`PUT /api/alerts/alert/{id}`: Update alert](#put-apialertid-update-alert) - [`POST /api/alerts/alert/{id}/_enable`: Enable an alert](#post-apialertidenable-enable-an-alert) @@ -505,7 +505,7 @@ Params: |---|---|---| |id|The id of the alert whose state you're trying to get.|string| -### `GET /api/alerts/alert/{id}/status`: Get alert status +### `GET /api/alerts/alert/{id}/_instance_summary`: Get alert instance summary Similar to the `GET state` call, but collects additional information from the event log. @@ -514,7 +514,7 @@ Params: |Property|Description|Type| |---|---|---| -|id|The id of the alert whose status you're trying to get.|string| +|id|The id of the alert whose instance summary you're trying to get.|string| Query: diff --git a/x-pack/plugins/alerts/common/alert_status.ts b/x-pack/plugins/alerts/common/alert_instance_summary.ts similarity index 95% rename from x-pack/plugins/alerts/common/alert_status.ts rename to x-pack/plugins/alerts/common/alert_instance_summary.ts index 517db6d6cb243..333db3ccda963 100644 --- a/x-pack/plugins/alerts/common/alert_status.ts +++ b/x-pack/plugins/alerts/common/alert_instance_summary.ts @@ -7,7 +7,7 @@ type AlertStatusValues = 'OK' | 'Active' | 'Error'; type AlertInstanceStatusValues = 'OK' | 'Active'; -export interface AlertStatus { +export interface AlertInstanceSummary { id: string; name: string; tags: string[]; diff --git a/x-pack/plugins/alerts/common/index.ts b/x-pack/plugins/alerts/common/index.ts index 0922e164a3aa3..ab71f77a049f6 100644 --- a/x-pack/plugins/alerts/common/index.ts +++ b/x-pack/plugins/alerts/common/index.ts @@ -9,7 +9,7 @@ export * from './alert_type'; export * from './alert_instance'; export * from './alert_task_instance'; export * from './alert_navigation'; -export * from './alert_status'; +export * from './alert_instance_summary'; export interface ActionGroup { id: string; diff --git a/x-pack/plugins/alerts/server/alerts_client.mock.ts b/x-pack/plugins/alerts/server/alerts_client.mock.ts index b61139ae72c99..b28e9f805f725 100644 --- a/x-pack/plugins/alerts/server/alerts_client.mock.ts +++ b/x-pack/plugins/alerts/server/alerts_client.mock.ts @@ -25,7 +25,7 @@ const createAlertsClientMock = () => { muteInstance: jest.fn(), unmuteInstance: jest.fn(), listAlertTypes: jest.fn(), - getAlertStatus: jest.fn(), + getAlertInstanceSummary: jest.fn(), }; return mocked; }; diff --git a/x-pack/plugins/alerts/server/alerts_client.test.ts b/x-pack/plugins/alerts/server/alerts_client.test.ts index f4aef62657abc..801c2c8775361 100644 --- a/x-pack/plugins/alerts/server/alerts_client.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client.test.ts @@ -20,7 +20,7 @@ import { ActionsAuthorization } from '../../actions/server'; import { eventLogClientMock } from '../../event_log/server/mocks'; import { QueryEventsBySavedObjectResult } from '../../event_log/server'; import { SavedObject } from 'kibana/server'; -import { EventsFactory } from './lib/alert_status_from_event_log.test'; +import { EventsFactory } from './lib/alert_instance_summary_from_event_log.test'; const taskManager = taskManagerMock.start(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -2382,16 +2382,16 @@ describe('getAlertState()', () => { }); }); -const AlertStatusFindEventsResult: QueryEventsBySavedObjectResult = { +const AlertInstanceSummaryFindEventsResult: QueryEventsBySavedObjectResult = { page: 1, per_page: 10000, total: 0, data: [], }; -const AlertStatusIntervalSeconds = 1; +const AlertInstanceSummaryIntervalSeconds = 1; -const BaseAlertStatusSavedObject: SavedObject = { +const BaseAlertInstanceSummarySavedObject: SavedObject = { id: '1', type: 'alert', attributes: { @@ -2400,7 +2400,7 @@ const BaseAlertStatusSavedObject: SavedObject = { tags: ['tag-1', 'tag-2'], alertTypeId: '123', consumer: 'alert-consumer', - schedule: { interval: `${AlertStatusIntervalSeconds}s` }, + schedule: { interval: `${AlertInstanceSummaryIntervalSeconds}s` }, actions: [], params: {}, createdBy: null, @@ -2415,14 +2415,16 @@ const BaseAlertStatusSavedObject: SavedObject = { references: [], }; -function getAlertStatusSavedObject(attributes: Partial = {}): SavedObject { +function getAlertInstanceSummarySavedObject( + attributes: Partial = {} +): SavedObject { return { - ...BaseAlertStatusSavedObject, - attributes: { ...BaseAlertStatusSavedObject.attributes, ...attributes }, + ...BaseAlertInstanceSummarySavedObject, + attributes: { ...BaseAlertInstanceSummarySavedObject.attributes, ...attributes }, }; } -describe('getAlertStatus()', () => { +describe('getAlertInstanceSummary()', () => { let alertsClient: AlertsClient; beforeEach(() => { @@ -2430,7 +2432,9 @@ describe('getAlertStatus()', () => { }); test('runs as expected with some event log data', async () => { - const alertSO = getAlertStatusSavedObject({ mutedInstanceIds: ['instance-muted-no-activity'] }); + const alertSO = getAlertInstanceSummarySavedObject({ + mutedInstanceIds: ['instance-muted-no-activity'], + }); unsecuredSavedObjectsClient.get.mockResolvedValueOnce(alertSO); const eventsFactory = new EventsFactory(mockedDateString); @@ -2446,7 +2450,7 @@ describe('getAlertStatus()', () => { .addActiveInstance('instance-currently-active') .getEvents(); const eventsResult = { - ...AlertStatusFindEventsResult, + ...AlertInstanceSummaryFindEventsResult, total: events.length, data: events, }; @@ -2454,7 +2458,7 @@ describe('getAlertStatus()', () => { const dateStart = new Date(Date.now() - 60 * 1000).toISOString(); - const result = await alertsClient.getAlertStatus({ id: '1', dateStart }); + const result = await alertsClient.getAlertInstanceSummary({ id: '1', dateStart }); expect(result).toMatchInlineSnapshot(` Object { "alertTypeId": "123", @@ -2494,16 +2498,18 @@ describe('getAlertStatus()', () => { `); }); - // Further tests don't check the result of `getAlertStatus()`, as the result - // is just the result from the `alertStatusFromEventLog()`, which itself + // Further tests don't check the result of `getAlertInstanceSummary()`, as the result + // is just the result from the `alertInstanceSummaryFromEventLog()`, which itself // has a complete set of tests. These tests just make sure the data gets - // sent into `getAlertStatus()` as appropriate. + // sent into `getAlertInstanceSummary()` as appropriate. test('calls saved objects and event log client with default params', async () => { - unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertStatusSavedObject()); - eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(AlertStatusFindEventsResult); + unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertInstanceSummarySavedObject()); + eventLogClient.findEventsBySavedObject.mockResolvedValueOnce( + AlertInstanceSummaryFindEventsResult + ); - await alertsClient.getAlertStatus({ id: '1' }); + await alertsClient.getAlertInstanceSummary({ id: '1' }); expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1); expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1); @@ -2526,17 +2532,21 @@ describe('getAlertStatus()', () => { const startMillis = Date.parse(start!); const endMillis = Date.parse(end!); - const expectedDuration = 60 * AlertStatusIntervalSeconds * 1000; + const expectedDuration = 60 * AlertInstanceSummaryIntervalSeconds * 1000; expect(endMillis - startMillis).toBeGreaterThan(expectedDuration - 2); expect(endMillis - startMillis).toBeLessThan(expectedDuration + 2); }); test('calls event log client with start date', async () => { - unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertStatusSavedObject()); - eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(AlertStatusFindEventsResult); + unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertInstanceSummarySavedObject()); + eventLogClient.findEventsBySavedObject.mockResolvedValueOnce( + AlertInstanceSummaryFindEventsResult + ); - const dateStart = new Date(Date.now() - 60 * AlertStatusIntervalSeconds * 1000).toISOString(); - await alertsClient.getAlertStatus({ id: '1', dateStart }); + const dateStart = new Date( + Date.now() - 60 * AlertInstanceSummaryIntervalSeconds * 1000 + ).toISOString(); + await alertsClient.getAlertInstanceSummary({ id: '1', dateStart }); expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1); expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1); @@ -2551,11 +2561,13 @@ describe('getAlertStatus()', () => { }); test('calls event log client with relative start date', async () => { - unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertStatusSavedObject()); - eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(AlertStatusFindEventsResult); + unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertInstanceSummarySavedObject()); + eventLogClient.findEventsBySavedObject.mockResolvedValueOnce( + AlertInstanceSummaryFindEventsResult + ); const dateStart = '2m'; - await alertsClient.getAlertStatus({ id: '1', dateStart }); + await alertsClient.getAlertInstanceSummary({ id: '1', dateStart }); expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledTimes(1); expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1); @@ -2570,28 +2582,36 @@ describe('getAlertStatus()', () => { }); test('invalid start date throws an error', async () => { - unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertStatusSavedObject()); - eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(AlertStatusFindEventsResult); + unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertInstanceSummarySavedObject()); + eventLogClient.findEventsBySavedObject.mockResolvedValueOnce( + AlertInstanceSummaryFindEventsResult + ); const dateStart = 'ain"t no way this will get parsed as a date'; - expect(alertsClient.getAlertStatus({ id: '1', dateStart })).rejects.toMatchInlineSnapshot( + expect( + alertsClient.getAlertInstanceSummary({ id: '1', dateStart }) + ).rejects.toMatchInlineSnapshot( `[Error: Invalid date for parameter dateStart: "ain"t no way this will get parsed as a date"]` ); }); test('saved object get throws an error', async () => { unsecuredSavedObjectsClient.get.mockRejectedValueOnce(new Error('OMG!')); - eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(AlertStatusFindEventsResult); + eventLogClient.findEventsBySavedObject.mockResolvedValueOnce( + AlertInstanceSummaryFindEventsResult + ); - expect(alertsClient.getAlertStatus({ id: '1' })).rejects.toMatchInlineSnapshot(`[Error: OMG!]`); + expect(alertsClient.getAlertInstanceSummary({ id: '1' })).rejects.toMatchInlineSnapshot( + `[Error: OMG!]` + ); }); test('findEvents throws an error', async () => { - unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertStatusSavedObject()); + unsecuredSavedObjectsClient.get.mockResolvedValueOnce(getAlertInstanceSummarySavedObject()); eventLogClient.findEventsBySavedObject.mockRejectedValueOnce(new Error('OMG 2!')); // error eaten but logged - await alertsClient.getAlertStatus({ id: '1' }); + await alertsClient.getAlertInstanceSummary({ id: '1' }); }); }); diff --git a/x-pack/plugins/alerts/server/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client.ts index 74aef644d58ca..0703a1e13937c 100644 --- a/x-pack/plugins/alerts/server/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client.ts @@ -24,7 +24,7 @@ import { IntervalSchedule, SanitizedAlert, AlertTaskState, - AlertStatus, + AlertInstanceSummary, } from './types'; import { validateAlertTypeParams } from './lib'; import { @@ -44,7 +44,7 @@ import { } from './authorization/alerts_authorization'; import { IEventLogClient } from '../../../plugins/event_log/server'; import { parseIsoOrRelativeDate } from './lib/iso_or_relative_date'; -import { alertStatusFromEventLog } from './lib/alert_status_from_event_log'; +import { alertInstanceSummaryFromEventLog } from './lib/alert_instance_summary_from_event_log'; import { IEvent } from '../../event_log/server'; import { parseDuration } from '../common/parse_duration'; @@ -139,7 +139,7 @@ interface UpdateOptions { }; } -interface GetAlertStatusParams { +interface GetAlertInstanceSummaryParams { id: string; dateStart?: string; } @@ -284,16 +284,19 @@ export class AlertsClient { } } - public async getAlertStatus({ id, dateStart }: GetAlertStatusParams): Promise { - this.logger.debug(`getAlertStatus(): getting alert ${id}`); + public async getAlertInstanceSummary({ + id, + dateStart, + }: GetAlertInstanceSummaryParams): Promise { + this.logger.debug(`getAlertInstanceSummary(): getting alert ${id}`); const alert = await this.get({ id }); await this.authorization.ensureAuthorized( alert.alertTypeId, alert.consumer, - ReadOperations.GetAlertStatus + ReadOperations.GetAlertInstanceSummary ); - // default duration of status is 60 * alert interval + // default duration of instance summary is 60 * alert interval const dateNow = new Date(); const durationMillis = parseDuration(alert.schedule.interval) * 60; const defaultDateStart = new Date(dateNow.valueOf() - durationMillis); @@ -301,7 +304,7 @@ export class AlertsClient { const eventLogClient = await this.getEventLogClient(); - this.logger.debug(`getAlertStatus(): search the event log for alert ${id}`); + this.logger.debug(`getAlertInstanceSummary(): search the event log for alert ${id}`); let events: IEvent[]; try { const queryResults = await eventLogClient.findEventsBySavedObject('alert', id, { @@ -314,12 +317,12 @@ export class AlertsClient { events = queryResults.data; } catch (err) { this.logger.debug( - `alertsClient.getAlertStatus(): error searching event log for alert ${id}: ${err.message}` + `alertsClient.getAlertInstanceSummary(): error searching event log for alert ${id}: ${err.message}` ); events = []; } - return alertStatusFromEventLog({ + return alertInstanceSummaryFromEventLog({ alert, events, dateStart: parsedDateStart.toISOString(), @@ -952,7 +955,7 @@ function parseDate(dateString: string | undefined, propertyName: string, default const parsedDate = parseIsoOrRelativeDate(dateString); if (parsedDate === undefined) { throw Boom.badRequest( - i18n.translate('xpack.alerts.alertsClient.getAlertStatus.invalidDate', { + i18n.translate('xpack.alerts.alertsClient.invalidDate', { defaultMessage: 'Invalid date for parameter {field}: "{dateValue}"', values: { field: propertyName, diff --git a/x-pack/plugins/alerts/server/authorization/alerts_authorization.ts b/x-pack/plugins/alerts/server/authorization/alerts_authorization.ts index b2a214eae9316..b362a50c9f10b 100644 --- a/x-pack/plugins/alerts/server/authorization/alerts_authorization.ts +++ b/x-pack/plugins/alerts/server/authorization/alerts_authorization.ts @@ -18,7 +18,7 @@ import { Space } from '../../../spaces/server'; export enum ReadOperations { Get = 'get', GetAlertState = 'getAlertState', - GetAlertStatus = 'getAlertStatus', + GetAlertInstanceSummary = 'getAlertInstanceSummary', Find = 'find', } diff --git a/x-pack/plugins/alerts/server/lib/alert_status_from_event_log.test.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts similarity index 83% rename from x-pack/plugins/alerts/server/lib/alert_status_from_event_log.test.ts rename to x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts index 15570d3032f24..b5936cf3577b3 100644 --- a/x-pack/plugins/alerts/server/lib/alert_status_from_event_log.test.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts @@ -4,22 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SanitizedAlert, AlertStatus } from '../types'; +import { SanitizedAlert, AlertInstanceSummary } from '../types'; import { IValidatedEvent } from '../../../event_log/server'; import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER } from '../plugin'; -import { alertStatusFromEventLog } from './alert_status_from_event_log'; +import { alertInstanceSummaryFromEventLog } from './alert_instance_summary_from_event_log'; const ONE_HOUR_IN_MILLIS = 60 * 60 * 1000; const dateStart = '2020-06-18T00:00:00.000Z'; const dateEnd = dateString(dateStart, ONE_HOUR_IN_MILLIS); -describe('alertStatusFromEventLog', () => { +describe('alertInstanceSummaryFromEventLog', () => { test('no events and muted ids', async () => { const alert = createAlert({}); const events: IValidatedEvent[] = []; - const status: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd }); + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); - expect(status).toMatchInlineSnapshot(` + expect(summary).toMatchInlineSnapshot(` Object { "alertTypeId": "123", "consumer": "alert-consumer", @@ -52,14 +57,14 @@ describe('alertStatusFromEventLog', () => { muteAll: true, }); const events: IValidatedEvent[] = []; - const status: AlertStatus = alertStatusFromEventLog({ + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ alert, events, dateStart: dateString(dateEnd, ONE_HOUR_IN_MILLIS), dateEnd: dateString(dateEnd, ONE_HOUR_IN_MILLIS * 2), }); - expect(status).toMatchInlineSnapshot(` + expect(summary).toMatchInlineSnapshot(` Object { "alertTypeId": "456", "consumer": "alert-consumer-2", @@ -87,9 +92,14 @@ describe('alertStatusFromEventLog', () => { mutedInstanceIds: ['instance-1', 'instance-2'], }); const events: IValidatedEvent[] = []; - const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd }); + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); - const { lastRun, status, instances } = alertStatus; + const { lastRun, status, instances } = summary; expect({ lastRun, status, instances }).toMatchInlineSnapshot(` Object { "instances": Object { @@ -115,9 +125,14 @@ describe('alertStatusFromEventLog', () => { const eventsFactory = new EventsFactory(); const events = eventsFactory.addExecute().advanceTime(10000).addExecute().getEvents(); - const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd }); + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); - const { lastRun, status, instances } = alertStatus; + const { lastRun, status, instances } = summary; expect({ lastRun, status, instances }).toMatchInlineSnapshot(` Object { "instances": Object {}, @@ -136,9 +151,14 @@ describe('alertStatusFromEventLog', () => { .addExecute('rut roh!') .getEvents(); - const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd }); + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); - const { lastRun, status, errorMessages, instances } = alertStatus; + const { lastRun, status, errorMessages, instances } = summary; expect({ lastRun, status, errorMessages, instances }).toMatchInlineSnapshot(` Object { "errorMessages": Array [ @@ -170,9 +190,14 @@ describe('alertStatusFromEventLog', () => { .addResolvedInstance('instance-1') .getEvents(); - const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd }); + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); - const { lastRun, status, instances } = alertStatus; + const { lastRun, status, instances } = summary; expect({ lastRun, status, instances }).toMatchInlineSnapshot(` Object { "instances": Object { @@ -199,9 +224,14 @@ describe('alertStatusFromEventLog', () => { .addResolvedInstance('instance-1') .getEvents(); - const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd }); + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); - const { lastRun, status, instances } = alertStatus; + const { lastRun, status, instances } = summary; expect({ lastRun, status, instances }).toMatchInlineSnapshot(` Object { "instances": Object { @@ -229,9 +259,14 @@ describe('alertStatusFromEventLog', () => { .addActiveInstance('instance-1') .getEvents(); - const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd }); + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); - const { lastRun, status, instances } = alertStatus; + const { lastRun, status, instances } = summary; expect({ lastRun, status, instances }).toMatchInlineSnapshot(` Object { "instances": Object { @@ -258,9 +293,14 @@ describe('alertStatusFromEventLog', () => { .addActiveInstance('instance-1') .getEvents(); - const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd }); + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); - const { lastRun, status, instances } = alertStatus; + const { lastRun, status, instances } = summary; expect({ lastRun, status, instances }).toMatchInlineSnapshot(` Object { "instances": Object { @@ -291,9 +331,14 @@ describe('alertStatusFromEventLog', () => { .addResolvedInstance('instance-2') .getEvents(); - const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd }); + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); - const { lastRun, status, instances } = alertStatus; + const { lastRun, status, instances } = summary; expect({ lastRun, status, instances }).toMatchInlineSnapshot(` Object { "instances": Object { @@ -335,9 +380,14 @@ describe('alertStatusFromEventLog', () => { .addActiveInstance('instance-1') .getEvents(); - const alertStatus: AlertStatus = alertStatusFromEventLog({ alert, events, dateStart, dateEnd }); + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); - const { lastRun, status, instances } = alertStatus; + const { lastRun, status, instances } = summary; expect({ lastRun, status, instances }).toMatchInlineSnapshot(` Object { "instances": Object { diff --git a/x-pack/plugins/alerts/server/lib/alert_status_from_event_log.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts similarity index 79% rename from x-pack/plugins/alerts/server/lib/alert_status_from_event_log.ts rename to x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts index 606bd44c6990c..9a5e870c8199a 100644 --- a/x-pack/plugins/alerts/server/lib/alert_status_from_event_log.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts @@ -4,21 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SanitizedAlert, AlertStatus, AlertInstanceStatus } from '../types'; +import { SanitizedAlert, AlertInstanceSummary, AlertInstanceStatus } from '../types'; import { IEvent } from '../../../event_log/server'; import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER } from '../plugin'; -export interface AlertStatusFromEventLogParams { +export interface AlertInstanceSummaryFromEventLogParams { alert: SanitizedAlert; events: IEvent[]; dateStart: string; dateEnd: string; } -export function alertStatusFromEventLog(params: AlertStatusFromEventLogParams): AlertStatus { +export function alertInstanceSummaryFromEventLog( + params: AlertInstanceSummaryFromEventLogParams +): AlertInstanceSummary { // initialize the result const { alert, events, dateStart, dateEnd } = params; - const alertStatus: AlertStatus = { + const alertInstanceSummary: AlertInstanceSummary = { id: alert.id, name: alert.name, tags: alert.tags, @@ -50,17 +52,17 @@ export function alertStatusFromEventLog(params: AlertStatusFromEventLogParams): if (action === undefined) continue; if (action === EVENT_LOG_ACTIONS.execute) { - alertStatus.lastRun = timeStamp; + alertInstanceSummary.lastRun = timeStamp; const errorMessage = event?.error?.message; if (errorMessage !== undefined) { - alertStatus.status = 'Error'; - alertStatus.errorMessages.push({ + alertInstanceSummary.status = 'Error'; + alertInstanceSummary.errorMessages.push({ date: timeStamp, message: errorMessage, }); } else { - alertStatus.status = 'OK'; + alertInstanceSummary.status = 'OK'; } continue; @@ -91,19 +93,19 @@ export function alertStatusFromEventLog(params: AlertStatusFromEventLogParams): // convert the instances map to object form const instanceIds = Array.from(instances.keys()).sort(); for (const instanceId of instanceIds) { - alertStatus.instances[instanceId] = instances.get(instanceId)!; + alertInstanceSummary.instances[instanceId] = instances.get(instanceId)!; } // set the overall alert status to Active if appropriate - if (alertStatus.status !== 'Error') { + if (alertInstanceSummary.status !== 'Error') { if (Array.from(instances.values()).some((instance) => instance.status === 'Active')) { - alertStatus.status = 'Active'; + alertInstanceSummary.status = 'Active'; } } - alertStatus.errorMessages.sort((a, b) => a.date.localeCompare(b.date)); + alertInstanceSummary.errorMessages.sort((a, b) => a.date.localeCompare(b.date)); - return alertStatus; + return alertInstanceSummary; } // return an instance status object, creating and adding to the map if needed diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts index b16ded9fb5c91..4f9b1f7c22e6d 100644 --- a/x-pack/plugins/alerts/server/plugin.ts +++ b/x-pack/plugins/alerts/server/plugin.ts @@ -38,7 +38,7 @@ import { findAlertRoute, getAlertRoute, getAlertStateRoute, - getAlertStatusRoute, + getAlertInstanceSummaryRoute, listAlertTypesRoute, updateAlertRoute, enableAlertRoute, @@ -193,7 +193,7 @@ export class AlertingPlugin { findAlertRoute(router, this.licenseState); getAlertRoute(router, this.licenseState); getAlertStateRoute(router, this.licenseState); - getAlertStatusRoute(router, this.licenseState); + getAlertInstanceSummaryRoute(router, this.licenseState); listAlertTypesRoute(router, this.licenseState); updateAlertRoute(router, this.licenseState); enableAlertRoute(router, this.licenseState); diff --git a/x-pack/plugins/alerts/server/routes/get_alert_status.test.ts b/x-pack/plugins/alerts/server/routes/get_alert_instance_summary.test.ts similarity index 75% rename from x-pack/plugins/alerts/server/routes/get_alert_status.test.ts rename to x-pack/plugins/alerts/server/routes/get_alert_instance_summary.test.ts index 1b4cb1941018b..8957a3d7c091e 100644 --- a/x-pack/plugins/alerts/server/routes/get_alert_status.test.ts +++ b/x-pack/plugins/alerts/server/routes/get_alert_instance_summary.test.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getAlertStatusRoute } from './get_alert_status'; +import { getAlertInstanceSummaryRoute } from './get_alert_instance_summary'; import { httpServiceMock } from 'src/core/server/mocks'; import { mockLicenseState } from '../lib/license_state.mock'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { SavedObjectsErrorHelpers } from 'src/core/server'; import { alertsClientMock } from '../alerts_client.mock'; -import { AlertStatus } from '../types'; +import { AlertInstanceSummary } from '../types'; const alertsClient = alertsClientMock.create(); jest.mock('../lib/license_api_access.ts', () => ({ @@ -21,9 +21,9 @@ beforeEach(() => { jest.resetAllMocks(); }); -describe('getAlertStatusRoute', () => { +describe('getAlertInstanceSummaryRoute', () => { const dateString = new Date().toISOString(); - const mockedAlertStatus: AlertStatus = { + const mockedAlertInstanceSummary: AlertInstanceSummary = { id: '', name: '', tags: [], @@ -39,17 +39,17 @@ describe('getAlertStatusRoute', () => { instances: {}, }; - it('gets alert status', async () => { + it('gets alert instance summary', async () => { const licenseState = mockLicenseState(); const router = httpServiceMock.createRouter(); - getAlertStatusRoute(router, licenseState); + getAlertInstanceSummaryRoute(router, licenseState); const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/status"`); + expect(config.path).toMatchInlineSnapshot(`"/api/alerts/alert/{id}/_instance_summary"`); - alertsClient.getAlertStatus.mockResolvedValueOnce(mockedAlertStatus); + alertsClient.getAlertInstanceSummary.mockResolvedValueOnce(mockedAlertInstanceSummary); const [context, req, res] = mockHandlerArguments( { alertsClient }, @@ -64,8 +64,8 @@ describe('getAlertStatusRoute', () => { await handler(context, req, res); - expect(alertsClient.getAlertStatus).toHaveBeenCalledTimes(1); - expect(alertsClient.getAlertStatus.mock.calls[0]).toMatchInlineSnapshot(` + expect(alertsClient.getAlertInstanceSummary).toHaveBeenCalledTimes(1); + expect(alertsClient.getAlertInstanceSummary.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { "dateStart": undefined, @@ -81,11 +81,11 @@ describe('getAlertStatusRoute', () => { const licenseState = mockLicenseState(); const router = httpServiceMock.createRouter(); - getAlertStatusRoute(router, licenseState); + getAlertInstanceSummaryRoute(router, licenseState); const [, handler] = router.get.mock.calls[0]; - alertsClient.getAlertStatus = jest + alertsClient.getAlertInstanceSummary = jest .fn() .mockResolvedValueOnce(SavedObjectsErrorHelpers.createGenericNotFoundError('alert', '1')); diff --git a/x-pack/plugins/alerts/server/routes/get_alert_status.ts b/x-pack/plugins/alerts/server/routes/get_alert_instance_summary.ts similarity index 83% rename from x-pack/plugins/alerts/server/routes/get_alert_status.ts rename to x-pack/plugins/alerts/server/routes/get_alert_instance_summary.ts index eab18c50189f4..11a10c2967a58 100644 --- a/x-pack/plugins/alerts/server/routes/get_alert_status.ts +++ b/x-pack/plugins/alerts/server/routes/get_alert_instance_summary.ts @@ -24,10 +24,10 @@ const querySchema = schema.object({ dateStart: schema.maybe(schema.string()), }); -export const getAlertStatusRoute = (router: IRouter, licenseState: LicenseState) => { +export const getAlertInstanceSummaryRoute = (router: IRouter, licenseState: LicenseState) => { router.get( { - path: `${BASE_ALERT_API_PATH}/alert/{id}/status`, + path: `${BASE_ALERT_API_PATH}/alert/{id}/_instance_summary`, validate: { params: paramSchema, query: querySchema, @@ -45,8 +45,8 @@ export const getAlertStatusRoute = (router: IRouter, licenseState: LicenseState) const alertsClient = context.alerting.getAlertsClient(); const { id } = req.params; const { dateStart } = req.query; - const status = await alertsClient.getAlertStatus({ id, dateStart }); - return res.ok({ body: status }); + const summary = await alertsClient.getAlertInstanceSummary({ id, dateStart }); + return res.ok({ body: summary }); }) ); }; diff --git a/x-pack/plugins/alerts/server/routes/index.ts b/x-pack/plugins/alerts/server/routes/index.ts index 4c6b1eb8e9b58..aed66e82d11f8 100644 --- a/x-pack/plugins/alerts/server/routes/index.ts +++ b/x-pack/plugins/alerts/server/routes/index.ts @@ -9,7 +9,7 @@ export { deleteAlertRoute } from './delete'; export { findAlertRoute } from './find'; export { getAlertRoute } from './get'; export { getAlertStateRoute } from './get_alert_state'; -export { getAlertStatusRoute } from './get_alert_status'; +export { getAlertInstanceSummaryRoute } from './get_alert_instance_summary'; export { listAlertTypesRoute } from './list_alert_types'; export { updateAlertRoute } from './update'; export { enableAlertRoute } from './enable'; diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts index 636082656f1a4..5e9c1818cad2b 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts @@ -74,7 +74,7 @@ describe(`feature_privilege_builder`, () => { Array [ "alerting:1.0.0-zeta1:alert-type/my-feature/get", "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertState", - "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertStatus", + "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertInstanceSummary", "alerting:1.0.0-zeta1:alert-type/my-feature/find", ] `); @@ -111,7 +111,7 @@ describe(`feature_privilege_builder`, () => { Array [ "alerting:1.0.0-zeta1:alert-type/my-feature/get", "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertState", - "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertStatus", + "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertInstanceSummary", "alerting:1.0.0-zeta1:alert-type/my-feature/find", "alerting:1.0.0-zeta1:alert-type/my-feature/create", "alerting:1.0.0-zeta1:alert-type/my-feature/delete", @@ -158,7 +158,7 @@ describe(`feature_privilege_builder`, () => { Array [ "alerting:1.0.0-zeta1:alert-type/my-feature/get", "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertState", - "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertStatus", + "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertInstanceSummary", "alerting:1.0.0-zeta1:alert-type/my-feature/find", "alerting:1.0.0-zeta1:alert-type/my-feature/create", "alerting:1.0.0-zeta1:alert-type/my-feature/delete", @@ -172,7 +172,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/unmuteInstance", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/get", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/getAlertState", - "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/getAlertStatus", + "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/getAlertInstanceSummary", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/find", ] `); diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts index 540b9e5c1e56e..eb278a5755204 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts @@ -8,7 +8,7 @@ import { uniq } from 'lodash'; import { Feature, FeatureKibanaPrivileges } from '../../../../../features/server'; import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder'; -const readOperations: string[] = ['get', 'getAlertState', 'getAlertStatus', 'find']; +const readOperations: string[] = ['get', 'getAlertState', 'getAlertInstanceSummary', 'find']; const writeOperations: string[] = [ 'create', 'delete', diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index ac23d1e62bcdf..8b9409f01087c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -18251,7 +18251,6 @@ "xpack.triggersActionsUI.sections.alertDetails.collapsedItemActons.muteTitle": "ミュート", "xpack.triggersActionsUI.sections.alertDetails.editAlertButtonLabel": "編集", "xpack.triggersActionsUI.sections.alertDetails.unableToLoadAlertMessage": "アラートを読み込めません: {message}", - "xpack.triggersActionsUI.sections.alertDetails.unableToLoadAlertStateMessage": "アラートステートを読み込めません: {message}", "xpack.triggersActionsUI.sections.alertDetails.viewAlertInAppButtonLabel": "アプリで表示", "xpack.triggersActionsUI.sections.alertEdit.betaBadgeTooltipContent": "{pluginName} はベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません。", "xpack.triggersActionsUI.sections.alertEdit.cancelButtonLabel": "キャンセル", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4d5e5c05cb795..b9fb6340e38cf 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18262,7 +18262,6 @@ "xpack.triggersActionsUI.sections.alertDetails.collapsedItemActons.muteTitle": "静音", "xpack.triggersActionsUI.sections.alertDetails.editAlertButtonLabel": "编辑", "xpack.triggersActionsUI.sections.alertDetails.unableToLoadAlertMessage": "无法加载告警:{message}", - "xpack.triggersActionsUI.sections.alertDetails.unableToLoadAlertStateMessage": "无法加载告警状态:{message}", "xpack.triggersActionsUI.sections.alertDetails.viewAlertInAppButtonLabel": "在应用中查看", "xpack.triggersActionsUI.sections.alertEdit.betaBadgeTooltipContent": "{pluginName} 为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束。", "xpack.triggersActionsUI.sections.alertEdit.cancelButtonLabel": "取消", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts index 7dde344d06fb5..97feea6ba8a0f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.ts @@ -11,7 +11,13 @@ import { fold } from 'fp-ts/lib/Either'; import { pick } from 'lodash'; import { alertStateSchema, AlertingFrameworkHealth } from '../../../../alerts/common'; import { BASE_ALERT_API_PATH } from '../constants'; -import { Alert, AlertType, AlertWithoutId, AlertTaskState, AlertStatus } from '../../types'; +import { + Alert, + AlertType, + AlertWithoutId, + AlertTaskState, + AlertInstanceSummary, +} from '../../types'; export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise { return await http.get(`${BASE_ALERT_API_PATH}/list_alert_types`); @@ -48,14 +54,14 @@ export async function loadAlertState({ }); } -export async function loadAlertStatus({ +export async function loadAlertInstanceSummary({ http, alertId, }: { http: HttpSetup; alertId: string; -}): Promise { - return await http.get(`${BASE_ALERT_API_PATH}/alert/${alertId}/status`); +}): Promise { + return await http.get(`${BASE_ALERT_API_PATH}/alert/${alertId}/_instance_summary`); } export async function loadAlerts({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx index ff9b518a9f5b1..f59b836a7936e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.test.tsx @@ -7,7 +7,7 @@ import * as React from 'react'; import uuid from 'uuid'; import { shallow } from 'enzyme'; import { AlertInstances, AlertInstanceListItem, alertInstanceToListItem } from './alert_instances'; -import { Alert, AlertStatus, AlertInstanceStatus } from '../../../../types'; +import { Alert, AlertInstanceSummary, AlertInstanceStatus } from '../../../../types'; import { EuiBasicTable } from '@elastic/eui'; const fakeNow = new Date('2020-02-09T23:15:41.941Z'); @@ -34,7 +34,7 @@ jest.mock('../../../app_context', () => { describe('alert_instances', () => { it('render a list of alert instances', () => { const alert = mockAlert(); - const alertStatus = mockAlertStatus({ + const alertInstanceSummary = mockAlertInstanceSummary({ instances: { first_instance: { status: 'OK', @@ -52,19 +52,24 @@ describe('alert_instances', () => { fakeNow.getTime(), alert, 'first_instance', - alertStatus.instances.first_instance + alertInstanceSummary.instances.first_instance ), alertInstanceToListItem( fakeNow.getTime(), alert, 'second_instance', - alertStatus.instances.second_instance + alertInstanceSummary.instances.second_instance ), ]; expect( shallow( - + ) .find(EuiBasicTable) .prop('items') @@ -73,7 +78,7 @@ describe('alert_instances', () => { it('render a hidden field with duration epoch', () => { const alert = mockAlert(); - const alertStatus = mockAlertStatus(); + const alertInstanceSummary = mockAlertInstanceSummary(); expect( shallow( @@ -82,7 +87,7 @@ describe('alert_instances', () => { {...mockAPIs} alert={alert} readOnly={false} - alertStatus={alertStatus} + alertInstanceSummary={alertInstanceSummary} /> ) .find('[name="alertInstancesDurationEpoch"]') @@ -108,7 +113,7 @@ describe('alert_instances', () => { {...mockAPIs} alert={alert} readOnly={false} - alertStatus={mockAlertStatus({ + alertInstanceSummary={mockAlertInstanceSummary({ instances, })} /> @@ -134,7 +139,7 @@ describe('alert_instances', () => { {...mockAPIs} alert={alert} readOnly={false} - alertStatus={mockAlertStatus({ + alertInstanceSummary={mockAlertInstanceSummary({ instances: { 'us-west': { status: 'OK', @@ -253,8 +258,10 @@ function mockAlert(overloads: Partial = {}): Alert { }; } -function mockAlertStatus(overloads: Partial = {}): AlertStatus { - const status: AlertStatus = { +function mockAlertInstanceSummary( + overloads: Partial = {} +): AlertInstanceSummary { + const summary: AlertInstanceSummary = { id: 'alert-id', name: 'alert-name', tags: ['tag-1', 'tag-2'], @@ -274,5 +281,5 @@ function mockAlertStatus(overloads: Partial = {}): AlertStatus { }, }, }; - return { ...status, ...overloads }; + return { ...summary, ...overloads }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx index 77a3b454a1820..44d65eafc2412 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx @@ -11,7 +11,7 @@ import { EuiBasicTable, EuiHealth, EuiSpacer, EuiSwitch } from '@elastic/eui'; // @ts-ignore import { RIGHT_ALIGNMENT, CENTER_ALIGNMENT } from '@elastic/eui/lib/services'; import { padStart, chunk } from 'lodash'; -import { Alert, AlertStatus, AlertInstanceStatus, Pagination } from '../../../../types'; +import { Alert, AlertInstanceSummary, AlertInstanceStatus, Pagination } from '../../../../types'; import { ComponentOpts as AlertApis, withBulkAlertOperations, @@ -21,7 +21,7 @@ import { DEFAULT_SEARCH_PAGE_SIZE } from '../../../constants'; type AlertInstancesProps = { alert: Alert; readOnly: boolean; - alertStatus: AlertStatus; + alertInstanceSummary: AlertInstanceSummary; requestRefresh: () => Promise; durationEpoch?: number; } & Pick; @@ -113,7 +113,7 @@ function durationAsString(duration: Duration): string { export function AlertInstances({ alert, readOnly, - alertStatus, + alertInstanceSummary, muteAlertInstance, unmuteAlertInstance, requestRefresh, @@ -124,7 +124,9 @@ export function AlertInstances({ size: DEFAULT_SEARCH_PAGE_SIZE, }); - const alertInstances = Object.entries(alertStatus.instances).map(([instanceId, instance]) => + const alertInstances = Object.entries( + alertInstanceSummary.instances + ).map(([instanceId, instance]) => alertInstanceToListItem(durationEpoch, alert, instanceId, instance) ); const pageOfAlertInstances = getPage(alertInstances, pagination); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx index 61af8f5478521..d92148a8fea53 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx @@ -7,8 +7,8 @@ import * as React from 'react'; import uuid from 'uuid'; import { shallow } from 'enzyme'; import { ToastsApi } from 'kibana/public'; -import { AlertInstancesRoute, getAlertStatus } from './alert_instances_route'; -import { Alert, AlertStatus } from '../../../../types'; +import { AlertInstancesRoute, getAlertInstanceSummary } from './alert_instances_route'; +import { Alert, AlertInstanceSummary } from '../../../../types'; import { EuiLoadingSpinner } from '@elastic/eui'; const fakeNow = new Date('2020-02-09T23:15:41.941Z'); @@ -20,7 +20,7 @@ jest.mock('../../../app_context', () => { useAppDependencies: jest.fn(() => ({ toastNotifications })), }; }); -describe('alert_status_route', () => { +describe('alert_instance_summary_route', () => { it('render a loader while fetching data', () => { const alert = mockAlert(); @@ -37,25 +37,30 @@ describe('getAlertState useEffect handler', () => { jest.clearAllMocks(); }); - it('fetches alert status', async () => { + it('fetches alert instance summary', async () => { const alert = mockAlert(); - const alertStatus = mockAlertStatus(); - const { loadAlertStatus } = mockApis(); - const { setAlertStatus } = mockStateSetter(); + const alertInstanceSummary = mockAlertInstanceSummary(); + const { loadAlertInstanceSummary } = mockApis(); + const { setAlertInstanceSummary } = mockStateSetter(); - loadAlertStatus.mockImplementationOnce(async () => alertStatus); + loadAlertInstanceSummary.mockImplementationOnce(async () => alertInstanceSummary); const toastNotifications = ({ addDanger: jest.fn(), } as unknown) as ToastsApi; - await getAlertStatus(alert.id, loadAlertStatus, setAlertStatus, toastNotifications); + await getAlertInstanceSummary( + alert.id, + loadAlertInstanceSummary, + setAlertInstanceSummary, + toastNotifications + ); - expect(loadAlertStatus).toHaveBeenCalledWith(alert.id); - expect(setAlertStatus).toHaveBeenCalledWith(alertStatus); + expect(loadAlertInstanceSummary).toHaveBeenCalledWith(alert.id); + expect(setAlertInstanceSummary).toHaveBeenCalledWith(alertInstanceSummary); }); - it('displays an error if the alert status isnt found', async () => { + it('displays an error if the alert instance summary isnt found', async () => { const actionType = { id: '.server-log', name: 'Server log', @@ -72,34 +77,39 @@ describe('getAlertState useEffect handler', () => { ], }); - const { loadAlertStatus } = mockApis(); - const { setAlertStatus } = mockStateSetter(); + const { loadAlertInstanceSummary } = mockApis(); + const { setAlertInstanceSummary } = mockStateSetter(); - loadAlertStatus.mockImplementation(async () => { + loadAlertInstanceSummary.mockImplementation(async () => { throw new Error('OMG'); }); const toastNotifications = ({ addDanger: jest.fn(), } as unknown) as ToastsApi; - await getAlertStatus(alert.id, loadAlertStatus, setAlertStatus, toastNotifications); + await getAlertInstanceSummary( + alert.id, + loadAlertInstanceSummary, + setAlertInstanceSummary, + toastNotifications + ); expect(toastNotifications.addDanger).toHaveBeenCalledTimes(1); expect(toastNotifications.addDanger).toHaveBeenCalledWith({ - title: 'Unable to load alert status: OMG', + title: 'Unable to load alert instance summary: OMG', }); }); }); function mockApis() { return { - loadAlertStatus: jest.fn(), + loadAlertInstanceSummary: jest.fn(), requestRefresh: jest.fn(), }; } function mockStateSetter() { return { - setAlertStatus: jest.fn(), + setAlertInstanceSummary: jest.fn(), }; } @@ -126,8 +136,8 @@ function mockAlert(overloads: Partial = {}): Alert { }; } -function mockAlertStatus(overloads: Partial = {}): any { - const status: AlertStatus = { +function mockAlertInstanceSummary(overloads: Partial = {}): any { + const summary: AlertInstanceSummary = { id: 'alert-id', name: 'alert-name', tags: ['tag-1', 'tag-2'], @@ -147,5 +157,5 @@ function mockAlertStatus(overloads: Partial = {}): any { }, }, }; - return status; + return summary; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx index 3afec45bcad64..9137a26a32dd4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { ToastsApi } from 'kibana/public'; import React, { useState, useEffect } from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; -import { Alert, AlertStatus } from '../../../../types'; +import { Alert, AlertInstanceSummary } from '../../../../types'; import { useAppDependencies } from '../../../app_context'; import { ComponentOpts as AlertApis, @@ -16,33 +16,40 @@ import { } from '../../common/components/with_bulk_alert_api_operations'; import { AlertInstancesWithApi as AlertInstances } from './alert_instances'; -type WithAlertStatusProps = { +type WithAlertInstanceSummaryProps = { alert: Alert; readOnly: boolean; requestRefresh: () => Promise; -} & Pick; +} & Pick; -export const AlertInstancesRoute: React.FunctionComponent = ({ +export const AlertInstancesRoute: React.FunctionComponent = ({ alert, readOnly, requestRefresh, - loadAlertStatus: loadAlertStatus, + loadAlertInstanceSummary: loadAlertInstanceSummary, }) => { const { toastNotifications } = useAppDependencies(); - const [alertStatus, setAlertStatus] = useState(null); + const [alertInstanceSummary, setAlertInstanceSummary] = useState( + null + ); useEffect(() => { - getAlertStatus(alert.id, loadAlertStatus, setAlertStatus, toastNotifications); + getAlertInstanceSummary( + alert.id, + loadAlertInstanceSummary, + setAlertInstanceSummary, + toastNotifications + ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [alert]); - return alertStatus ? ( + return alertInstanceSummary ? ( ) : (
); }; -export async function getAlertStatus( +export async function getAlertInstanceSummary( alertId: string, - loadAlertStatus: AlertApis['loadAlertStatus'], - setAlertStatus: React.Dispatch>, + loadAlertInstanceSummary: AlertApis['loadAlertInstanceSummary'], + setAlertInstanceSummary: React.Dispatch>, toastNotifications: Pick ) { try { - const loadedStatus = await loadAlertStatus(alertId); - setAlertStatus(loadedStatus); + const loadedInstanceSummary = await loadAlertInstanceSummary(alertId); + setAlertInstanceSummary(loadedInstanceSummary); } catch (e) { toastNotifications.addDanger({ title: i18n.translate( - 'xpack.triggersActionsUI.sections.alertDetails.unableToLoadAlertStateMessage', + 'xpack.triggersActionsUI.sections.alertDetails.unableToLoadAlertInstanceSummaryMessage', { - defaultMessage: 'Unable to load alert status: {message}', + defaultMessage: 'Unable to load alert instance summary: {message}', values: { message: e.message, }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.tsx index fd8b35a96bdf0..dc961482f182d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.tsx @@ -10,7 +10,7 @@ import { Alert, AlertType, AlertTaskState, - AlertStatus, + AlertInstanceSummary, AlertingFrameworkHealth, } from '../../../../types'; import { useAppDependencies } from '../../../app_context'; @@ -28,7 +28,7 @@ import { unmuteAlertInstance, loadAlert, loadAlertState, - loadAlertStatus, + loadAlertInstanceSummary, loadAlertTypes, health, } from '../../../lib/alert_api'; @@ -58,7 +58,7 @@ export interface ComponentOpts { }>; loadAlert: (id: Alert['id']) => Promise; loadAlertState: (id: Alert['id']) => Promise; - loadAlertStatus: (id: Alert['id']) => Promise; + loadAlertInstanceSummary: (id: Alert['id']) => Promise; loadAlertTypes: () => Promise; getHealth: () => Promise; } @@ -127,7 +127,9 @@ export function withBulkAlertOperations( deleteAlert={async (alert: Alert) => deleteAlerts({ http, ids: [alert.id] })} loadAlert={async (alertId: Alert['id']) => loadAlert({ http, alertId })} loadAlertState={async (alertId: Alert['id']) => loadAlertState({ http, alertId })} - loadAlertStatus={async (alertId: Alert['id']) => loadAlertStatus({ http, alertId })} + loadAlertInstanceSummary={async (alertId: Alert['id']) => + loadAlertInstanceSummary({ http, alertId }) + } loadAlertTypes={async () => loadAlertTypes({ http })} getHealth={async () => health({ http })} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 0c0d99eed4e7b..762f41ba3691c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -12,7 +12,7 @@ import { SanitizedAlert as Alert, AlertAction, AlertTaskState, - AlertStatus, + AlertInstanceSummary, AlertInstanceStatus, RawAlertInstance, AlertingFrameworkHealth, @@ -21,7 +21,7 @@ export { Alert, AlertAction, AlertTaskState, - AlertStatus, + AlertInstanceSummary, AlertInstanceStatus, RawAlertInstance, AlertingFrameworkHealth, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_status.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_instance_summary.ts similarity index 90% rename from x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_status.ts rename to x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_instance_summary.ts index b700b5fb40b63..c8148f0c7a871 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_status.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get_alert_instance_summary.ts @@ -17,11 +17,11 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { UserAtSpaceScenarios } from '../../scenarios'; // eslint-disable-next-line import/no-default-export -export default function createGetAlertStatusTests({ getService }: FtrProviderContext) { +export default function createGetAlertInstanceSummaryTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - describe('getAlertStatus', () => { + describe('getAlertInstanceSummary', () => { const objectRemover = new ObjectRemover(supertest); afterEach(() => objectRemover.removeAll()); @@ -29,7 +29,7 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon for (const scenario of UserAtSpaceScenarios) { const { user, space } = scenario; describe(scenario.id, () => { - it('should handle getAlertStatus alert request appropriately', async () => { + it('should handle getAlertInstanceSummary alert request appropriately', async () => { const { body: createdAlert } = await supertest .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') @@ -38,7 +38,7 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/status`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary`) .auth(user.username, user.password); switch (scenario.id) { @@ -85,7 +85,7 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon } }); - it('should handle getAlertStatus alert request appropriately when unauthorized', async () => { + it('should handle getAlertInstanceSummary alert request appropriately when unauthorized', async () => { const { body: createdAlert } = await supertest .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') @@ -99,7 +99,7 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/status`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary`) .auth(user.username, user.password); switch (scenario.id) { @@ -140,7 +140,7 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon } }); - it(`shouldn't getAlertStatus for an alert from another space`, async () => { + it(`shouldn't getAlertInstanceSummary for an alert from another space`, async () => { const { body: createdAlert } = await supertest .post(`${getUrlPrefix(space.id)}/api/alerts/alert`) .set('kbn-xsrf', 'foo') @@ -149,7 +149,7 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts'); const response = await supertestWithoutAuth - .get(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}/status`) + .get(`${getUrlPrefix('other')}/api/alerts/alert/${createdAlert.id}/_instance_summary`) .auth(user.username, user.password); expect(response.statusCode).to.eql(404); @@ -172,9 +172,9 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon } }); - it(`should handle getAlertStatus request appropriately when alert doesn't exist`, async () => { + it(`should handle getAlertInstanceSummary request appropriately when alert doesn't exist`, async () => { const response = await supertestWithoutAuth - .get(`${getUrlPrefix(space.id)}/api/alerts/alert/1/status`) + .get(`${getUrlPrefix(space.id)}/api/alerts/alert/1/_instance_summary`) .auth(user.username, user.password); switch (scenario.id) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts index 45fa075a65978..b03a3c8ccf6af 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts @@ -16,7 +16,7 @@ export default function alertingTests({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./enable')); loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./get_alert_state')); - loadTestFile(require.resolve('./get_alert_status')); + loadTestFile(require.resolve('./get_alert_instance_summary')); loadTestFile(require.resolve('./list_alert_types')); loadTestFile(require.resolve('./mute_all')); loadTestFile(require.resolve('./mute_instance')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_status.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts similarity index 95% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_status.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts index 341313ce55c60..563127e028a62 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_status.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_instance_summary.ts @@ -18,20 +18,20 @@ import { import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export -export default function createGetAlertStatusTests({ getService }: FtrProviderContext) { +export default function createGetAlertInstanceSummaryTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const retry = getService('retry'); const alertUtils = new AlertUtils({ space: Spaces.space1, supertestWithoutAuth }); - describe('getAlertStatus', () => { + describe('getAlertInstanceSummary', () => { const objectRemover = new ObjectRemover(supertest); afterEach(() => objectRemover.removeAll()); it(`handles non-existant alert`, async () => { await supertest - .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/1/status`) + .get(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/1/_instance_summary`) .expect(404, { statusCode: 404, error: 'Not Found', @@ -49,7 +49,7 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon await waitForEvents(createdAlert.id, ['execute']); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/status` + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary` ); expect(response.status).to.eql(200); @@ -82,7 +82,7 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts'); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/status` + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary` ); expect(response.status).to.eql(200); @@ -119,7 +119,7 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon const response = await supertest.get( `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${ createdAlert.id - }/status?dateStart=${dateStart}` + }/_instance_summary?dateStart=${dateStart}` ); expect(response.status).to.eql(200); const { statusStartDate, statusEndDate } = response.body; @@ -140,7 +140,7 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon const response = await supertest.get( `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${ createdAlert.id - }/status?dateStart=${dateStart}` + }/_instance_summary?dateStart=${dateStart}` ); expect(response.status).to.eql(400); expect(response.body).to.eql({ @@ -161,7 +161,7 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon await alertUtils.muteInstance(createdAlert.id, '1'); await waitForEvents(createdAlert.id, ['execute']); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/status` + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary` ); expect(response.status).to.eql(200); @@ -184,7 +184,7 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon await waitForEvents(createdAlert.id, ['execute']); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/status` + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary` ); const { errorMessages } = response.body; expect(errorMessages.length).to.be.greaterThan(0); @@ -218,7 +218,7 @@ export default function createGetAlertStatusTests({ getService }: FtrProviderCon await alertUtils.muteInstance(createdAlert.id, 'instanceD'); await waitForEvents(createdAlert.id, ['new-instance', 'resolved-instance']); const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/status` + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${createdAlert.id}/_instance_summary` ); const actualInstances = response.body.instances; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts index 78ca2af12ec3f..3a3fed22f0206 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts @@ -16,7 +16,7 @@ export default function alertingTests({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./find')); loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./get_alert_state')); - loadTestFile(require.resolve('./get_alert_status')); + loadTestFile(require.resolve('./get_alert_instance_summary')); loadTestFile(require.resolve('./list_alert_types')); loadTestFile(require.resolve('./event_log')); loadTestFile(require.resolve('./mute_all')); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 1579d041c9f58..4c97c8556d7df 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -361,7 +361,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // await first run to complete so we have an initial state await retry.try(async () => { - const { instances: alertInstances } = await alerting.alerts.getAlertStatus(alert.id); + const { instances: alertInstances } = await alerting.alerts.getAlertInstanceSummary( + alert.id + ); expect(Object.keys(alertInstances).length).to.eql(instances.length); }); }); @@ -373,10 +375,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // Verify content await testSubjects.existOrFail('alertInstancesList'); - const status = await alerting.alerts.getAlertStatus(alert.id); + const summary = await alerting.alerts.getAlertInstanceSummary(alert.id); const dateOnAllInstancesFromApiResponse = mapValues( - status.instances, + summary.instances, (instance) => instance.activeStartDate ); @@ -570,7 +572,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // await first run to complete so we have an initial state await retry.try(async () => { - const { instances: alertInstances } = await alerting.alerts.getAlertStatus(alert.id); + const { instances: alertInstances } = await alerting.alerts.getAlertInstanceSummary( + alert.id + ); expect(Object.keys(alertInstances).length).to.eql(instances.length); }); @@ -591,7 +595,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // Verify content await testSubjects.existOrFail('alertInstancesList'); - const { instances: alertInstances } = await alerting.alerts.getAlertStatus(alert.id); + const { instances: alertInstances } = await alerting.alerts.getAlertInstanceSummary( + alert.id + ); const items = await pageObjects.alertDetailsUI.getAlertInstancesList(); expect(items.length).to.eql(PAGE_SIZE); @@ -604,7 +610,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // Verify content await testSubjects.existOrFail('alertInstancesList'); - const { instances: alertInstances } = await alerting.alerts.getAlertStatus(alert.id); + const { instances: alertInstances } = await alerting.alerts.getAlertInstanceSummary( + alert.id + ); await pageObjects.alertDetailsUI.clickPaginationNextPage(); diff --git a/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts b/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts index c6fbdecf77f16..942b352b4afd3 100644 --- a/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/services/alerting/alerts.ts @@ -8,7 +8,7 @@ import axios, { AxiosInstance } from 'axios'; import util from 'util'; import { ToolingLog } from '@kbn/dev-utils'; -export interface AlertStatus { +export interface AlertInstanceSummary { status: string; muted: boolean; enabled: boolean; @@ -156,10 +156,10 @@ export class Alerts { this.log.debug(`deleted alert ${alert.id}`); } - public async getAlertStatus(id: string): Promise { + public async getAlertInstanceSummary(id: string): Promise { this.log.debug(`getting alert ${id} state`); - const { data } = await this.axios.get(`/api/alerts/alert/${id}/status`); + const { data } = await this.axios.get(`/api/alerts/alert/${id}/_instance_summary`); return data; } From 11e7d825ade2ee22169eb08d25fd5a6182511812 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Tue, 8 Sep 2020 11:24:12 -0500 Subject: [PATCH 18/19] Hook for breadcrumbs (#76736) Remove `UpdateBreadcrumbs` and `ProvideBreadcrumbs` components and replace with `useBreadcrumbs` hook. The logic and tests stay pretty much the same, but it's much clearer to see what's going on. Also: * Put some of the shared route-related interfaces in /public/application/routes instead of /public/components/app/Main. I plan to move more of the routing-related code here in the future. * Remove the `name` property for routes, since it wasn't being used. * Rename `Breadcrumbroute` to `APMRouteDefinition`. Part of #51963. --- .../plugins/apm/public/application/csmApp.tsx | 34 ++- .../plugins/apm/public/application/index.tsx | 5 +- .../apm/public/application/routes/index.tsx | 16 ++ .../app/Home/__snapshots__/Home.test.tsx.snap | 2 + .../app/Main/ProvideBreadcrumbs.test.tsx | 109 --------- .../app/Main/ProvideBreadcrumbs.tsx | 135 ----------- .../components/app/Main/UpdateBreadcrumbs.tsx | 90 -------- .../app/Main/route_config/index.tsx | 30 +-- .../app/Main/route_config/route_names.tsx | 31 --- .../ApmPluginContext/MockApmPluginContext.tsx | 1 + .../use_breadcrumbs.test.tsx} | 102 ++++----- .../apm/public/hooks/use_breadcrumbs.ts | 214 ++++++++++++++++++ 12 files changed, 303 insertions(+), 466 deletions(-) create mode 100644 x-pack/plugins/apm/public/application/routes/index.tsx delete mode 100644 x-pack/plugins/apm/public/components/app/Main/ProvideBreadcrumbs.test.tsx delete mode 100644 x-pack/plugins/apm/public/components/app/Main/ProvideBreadcrumbs.tsx delete mode 100644 x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx delete mode 100644 x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx rename x-pack/plugins/apm/public/{components/app/Main/UpdateBreadcrumbs.test.tsx => hooks/use_breadcrumbs.test.tsx} (65%) create mode 100644 x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts diff --git a/x-pack/plugins/apm/public/application/csmApp.tsx b/x-pack/plugins/apm/public/application/csmApp.tsx index d76ed5c2100b2..cdfe42bd628cc 100644 --- a/x-pack/plugins/apm/public/application/csmApp.tsx +++ b/x-pack/plugins/apm/public/application/csmApp.tsx @@ -4,52 +4,51 @@ * you may not use this file except in compliance with the Elastic License. */ +import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { AppMountParameters, CoreStart } from 'kibana/public'; import React from 'react'; import ReactDOM from 'react-dom'; import { Route, Router } from 'react-router-dom'; -import styled, { ThemeProvider, DefaultTheme } from 'styled-components'; -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; -import { CoreStart, AppMountParameters } from 'kibana/public'; -import { ApmPluginSetupDeps } from '../plugin'; - +import 'react-vis/dist/style.css'; +import styled, { DefaultTheme, ThemeProvider } from 'styled-components'; import { KibanaContextProvider, - useUiSetting$, RedirectAppLinks, + useUiSetting$, } from '../../../../../src/plugins/kibana_react/public'; -import { px, units } from '../style/variables'; -import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs'; +import { APMRouteDefinition } from '../application/routes'; +import { renderAsRedirectTo } from '../components/app/Main/route_config'; import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange'; -import 'react-vis/dist/style.css'; import { RumHome } from '../components/app/RumDashboard/RumHome'; -import { ConfigSchema } from '../index'; -import { BreadcrumbRoute } from '../components/app/Main/ProvideBreadcrumbs'; -import { RouteName } from '../components/app/Main/route_config/route_names'; -import { renderAsRedirectTo } from '../components/app/Main/route_config'; import { ApmPluginContext } from '../context/ApmPluginContext'; -import { UrlParamsProvider } from '../context/UrlParamsContext'; import { LoadingIndicatorProvider } from '../context/LoadingIndicatorContext'; +import { UrlParamsProvider } from '../context/UrlParamsContext'; +import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; +import { ConfigSchema } from '../index'; +import { ApmPluginSetupDeps } from '../plugin'; import { createCallApmApi } from '../services/rest/createCallApmApi'; +import { px, units } from '../style/variables'; const CsmMainContainer = styled.div` padding: ${px(units.plus)}; height: 100%; `; -export const rumRoutes: BreadcrumbRoute[] = [ +export const rumRoutes: APMRouteDefinition[] = [ { exact: true, path: '/', render: renderAsRedirectTo('/csm'), breadcrumb: 'Client Side Monitoring', - name: RouteName.CSM, }, ]; function CsmApp() { const [darkMode] = useUiSetting$('theme:darkMode'); + useBreadcrumbs(rumRoutes); + return ( ({ @@ -59,7 +58,6 @@ function CsmApp() { })} > - diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index 2b0b3ddd98167..536d70b053f76 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -22,12 +22,12 @@ import { import { AlertsContextProvider } from '../../../triggers_actions_ui/public'; import { routes } from '../components/app/Main/route_config'; import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange'; -import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs'; import { ApmPluginContext } from '../context/ApmPluginContext'; import { LicenseProvider } from '../context/LicenseContext'; import { LoadingIndicatorProvider } from '../context/LoadingIndicatorContext'; import { LocationProvider } from '../context/LocationContext'; import { UrlParamsProvider } from '../context/UrlParamsContext'; +import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { ApmPluginSetupDeps } from '../plugin'; import { createCallApmApi } from '../services/rest/createCallApmApi'; import { createStaticIndexPattern } from '../services/rest/index_pattern'; @@ -43,6 +43,8 @@ const MainContainer = styled.div` function App() { const [darkMode] = useUiSetting$('theme:darkMode'); + useBreadcrumbs(routes); + return ( ({ @@ -52,7 +54,6 @@ function App() { })} > - {routes.map((route, i) => ( diff --git a/x-pack/plugins/apm/public/application/routes/index.tsx b/x-pack/plugins/apm/public/application/routes/index.tsx new file mode 100644 index 0000000000000..d1bb8ae8fc8a3 --- /dev/null +++ b/x-pack/plugins/apm/public/application/routes/index.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RouteComponentProps, RouteProps } from 'react-router-dom'; + +export type BreadcrumbTitle = + | string + | ((props: RouteComponentProps) => string) + | null; + +export interface APMRouteDefinition extends RouteProps { + breadcrumb: BreadcrumbTitle; +} diff --git a/x-pack/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap b/x-pack/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap index 24b51e3fba917..9706895b164a6 100644 --- a/x-pack/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap @@ -18,6 +18,7 @@ exports[`Home component should render services 1`] = ` "currentAppId$": Observable { "_isScalar": false, }, + "navigateToUrl": [Function], }, "chrome": Object { "docTitle": Object { @@ -78,6 +79,7 @@ exports[`Home component should render traces 1`] = ` "currentAppId$": Observable { "_isScalar": false, }, + "navigateToUrl": [Function], }, "chrome": Object { "docTitle": Object { diff --git a/x-pack/plugins/apm/public/components/app/Main/ProvideBreadcrumbs.test.tsx b/x-pack/plugins/apm/public/components/app/Main/ProvideBreadcrumbs.test.tsx deleted file mode 100644 index bf1cd75432ff5..0000000000000 --- a/x-pack/plugins/apm/public/components/app/Main/ProvideBreadcrumbs.test.tsx +++ /dev/null @@ -1,109 +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 { Location } from 'history'; -import { BreadcrumbRoute, getBreadcrumbs } from './ProvideBreadcrumbs'; -import { RouteName } from './route_config/route_names'; - -describe('getBreadcrumbs', () => { - const getTestRoutes = (): BreadcrumbRoute[] => [ - { path: '/a', exact: true, breadcrumb: 'A', name: RouteName.HOME }, - { - path: '/a/ignored', - exact: true, - breadcrumb: 'Ignored Route', - name: RouteName.METRICS, - }, - { - path: '/a/:letter', - exact: true, - name: RouteName.SERVICE, - breadcrumb: ({ match }) => `Second level: ${match.params.letter}`, - }, - { - path: '/a/:letter/c', - exact: true, - name: RouteName.ERRORS, - breadcrumb: ({ match }) => `Third level: ${match.params.letter}`, - }, - ]; - - const getLocation = () => - ({ - pathname: '/a/b/c/', - } as Location); - - it('should return a set of matching breadcrumbs for a given path', () => { - const breadcrumbs = getBreadcrumbs({ - location: getLocation(), - routes: getTestRoutes(), - }); - - expect(breadcrumbs.map((b) => b.value)).toMatchInlineSnapshot(` -Array [ - "A", - "Second level: b", - "Third level: b", -] -`); - }); - - it('should skip breadcrumbs if breadcrumb is null', () => { - const location = getLocation(); - const routes = getTestRoutes(); - - routes[2].breadcrumb = null; - - const breadcrumbs = getBreadcrumbs({ - location, - routes, - }); - - expect(breadcrumbs.map((b) => b.value)).toMatchInlineSnapshot(` -Array [ - "A", - "Third level: b", -] -`); - }); - - it('should skip breadcrumbs if breadcrumb key is missing', () => { - const location = getLocation(); - const routes = getTestRoutes(); - - // @ts-expect-error - delete routes[2].breadcrumb; - - const breadcrumbs = getBreadcrumbs({ location, routes }); - - expect(breadcrumbs.map((b) => b.value)).toMatchInlineSnapshot(` -Array [ - "A", - "Third level: b", -] -`); - }); - - it('should produce matching breadcrumbs even if the pathname has a query string appended', () => { - const location = getLocation(); - const routes = getTestRoutes(); - - location.pathname += '?some=thing'; - - const breadcrumbs = getBreadcrumbs({ - location, - routes, - }); - - expect(breadcrumbs.map((b) => b.value)).toMatchInlineSnapshot(` -Array [ - "A", - "Second level: b", - "Third level: b", -] -`); - }); -}); diff --git a/x-pack/plugins/apm/public/components/app/Main/ProvideBreadcrumbs.tsx b/x-pack/plugins/apm/public/components/app/Main/ProvideBreadcrumbs.tsx deleted file mode 100644 index f2505b64fb1e3..0000000000000 --- a/x-pack/plugins/apm/public/components/app/Main/ProvideBreadcrumbs.tsx +++ /dev/null @@ -1,135 +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 { Location } from 'history'; -import React from 'react'; -import { - matchPath, - RouteComponentProps, - RouteProps, - withRouter, -} from 'react-router-dom'; -import { RouteName } from './route_config/route_names'; - -type LocationMatch = Pick< - RouteComponentProps>, - 'location' | 'match' ->; - -type BreadcrumbFunction = (props: LocationMatch) => string; - -export interface BreadcrumbRoute extends RouteProps { - breadcrumb: string | BreadcrumbFunction | null; - name: RouteName; -} - -export interface Breadcrumb extends LocationMatch { - value: string; -} - -interface RenderProps extends RouteComponentProps { - breadcrumbs: Breadcrumb[]; -} - -interface ProvideBreadcrumbsProps extends RouteComponentProps { - routes: BreadcrumbRoute[]; - render: (props: RenderProps) => React.ReactElement | null; -} - -interface ParseOptions extends LocationMatch { - breadcrumb: string | BreadcrumbFunction; -} - -const parse = (options: ParseOptions) => { - const { breadcrumb, match, location } = options; - let value; - - if (typeof breadcrumb === 'function') { - value = breadcrumb({ match, location }); - } else { - value = breadcrumb; - } - - return { value, match, location }; -}; - -export function getBreadcrumb({ - location, - currentPath, - routes, -}: { - location: Location; - currentPath: string; - routes: BreadcrumbRoute[]; -}) { - return routes.reduce((found, { breadcrumb, ...route }) => { - if (found) { - return found; - } - - if (!breadcrumb) { - return null; - } - - const match = matchPath>(currentPath, route); - - if (match) { - return parse({ - breadcrumb, - match, - location, - }); - } - - return null; - }, null); -} - -export function getBreadcrumbs({ - routes, - location, -}: { - routes: BreadcrumbRoute[]; - location: Location; -}) { - const breadcrumbs: Breadcrumb[] = []; - const { pathname } = location; - - pathname - .split('?')[0] - .replace(/\/$/, '') - .split('/') - .reduce((acc, next) => { - // `/1/2/3` results in match checks for `/1`, `/1/2`, `/1/2/3`. - const currentPath = !next ? '/' : `${acc}/${next}`; - const breadcrumb = getBreadcrumb({ - location, - currentPath, - routes, - }); - - if (breadcrumb) { - breadcrumbs.push(breadcrumb); - } - - return currentPath === '/' ? '' : currentPath; - }, ''); - - return breadcrumbs; -} - -function ProvideBreadcrumbsComponent({ - routes = [], - render, - location, - match, - history, -}: ProvideBreadcrumbsProps) { - const breadcrumbs = getBreadcrumbs({ routes, location }); - return render({ breadcrumbs, location, match, history }); -} - -export const ProvideBreadcrumbs = withRouter(ProvideBreadcrumbsComponent); diff --git a/x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx b/x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx deleted file mode 100644 index 5bf5cea587f93..0000000000000 --- a/x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx +++ /dev/null @@ -1,90 +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 { Location } from 'history'; -import React, { MouseEvent } from 'react'; -import { CoreStart } from 'src/core/public'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { getAPMHref } from '../../shared/Links/apm/APMLink'; -import { - Breadcrumb, - BreadcrumbRoute, - ProvideBreadcrumbs, -} from './ProvideBreadcrumbs'; - -interface Props { - location: Location; - breadcrumbs: Breadcrumb[]; - core: CoreStart; -} - -function getTitleFromBreadCrumbs(breadcrumbs: Breadcrumb[]) { - return breadcrumbs.map(({ value }) => value).reverse(); -} - -class UpdateBreadcrumbsComponent extends React.Component { - public updateHeaderBreadcrumbs() { - const { basePath } = this.props.core.http; - const breadcrumbs = this.props.breadcrumbs.map( - ({ value, match }, index) => { - const { search } = this.props.location; - const isLastBreadcrumbItem = - index === this.props.breadcrumbs.length - 1; - const href = isLastBreadcrumbItem - ? undefined // makes the breadcrumb item not clickable - : getAPMHref({ basePath, path: match.url, search }); - return { - text: value, - href, - onClick: (event: MouseEvent) => { - if (href) { - event.preventDefault(); - this.props.core.application.navigateToUrl(href); - } - }, - }; - } - ); - - this.props.core.chrome.docTitle.change( - getTitleFromBreadCrumbs(this.props.breadcrumbs) - ); - this.props.core.chrome.setBreadcrumbs(breadcrumbs); - } - - public componentDidMount() { - this.updateHeaderBreadcrumbs(); - } - - public componentDidUpdate() { - this.updateHeaderBreadcrumbs(); - } - - public render() { - return null; - } -} - -interface UpdateBreadcrumbsProps { - routes: BreadcrumbRoute[]; -} - -export function UpdateBreadcrumbs({ routes }: UpdateBreadcrumbsProps) { - const { core } = useApmPluginContext(); - - return ( - ( - - )} - /> - ); -} diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx index 1fe5f17c39985..0cefcbdc54228 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; import { UNIDENTIFIED_SERVICE_NODES_LABEL } from '../../../../../common/i18n'; import { SERVICE_NODE_NAME_MISSING } from '../../../../../common/service_nodes'; +import { APMRouteDefinition } from '../../../../application/routes'; import { toQuery } from '../../../shared/Links/url_helpers'; import { ErrorGroupDetails } from '../../ErrorGroupDetails'; import { Home } from '../../Home'; @@ -21,12 +22,10 @@ import { ApmIndices } from '../../Settings/ApmIndices'; import { CustomizeUI } from '../../Settings/CustomizeUI'; import { TraceLink } from '../../TraceLink'; import { TransactionDetails } from '../../TransactionDetails'; -import { BreadcrumbRoute } from '../ProvideBreadcrumbs'; import { CreateAgentConfigurationRouteHandler, EditAgentConfigurationRouteHandler, } from './route_handlers/agent_configuration'; -import { RouteName } from './route_names'; /** * Given a path, redirect to that location, preserving the search and maintaining @@ -150,13 +149,12 @@ function SettingsCustomizeUI() { * The array of route definitions to be used when the application * creates the routes. */ -export const routes: BreadcrumbRoute[] = [ +export const routes: APMRouteDefinition[] = [ { exact: true, path: '/', component: renderAsRedirectTo('/services'), breadcrumb: 'APM', - name: RouteName.HOME, }, { exact: true, @@ -165,7 +163,6 @@ export const routes: BreadcrumbRoute[] = [ breadcrumb: i18n.translate('xpack.apm.breadcrumb.servicesTitle', { defaultMessage: 'Services', }), - name: RouteName.SERVICES, }, { exact: true, @@ -174,7 +171,6 @@ export const routes: BreadcrumbRoute[] = [ breadcrumb: i18n.translate('xpack.apm.breadcrumb.tracesTitle', { defaultMessage: 'Traces', }), - name: RouteName.TRACES, }, { exact: true, @@ -183,7 +179,6 @@ export const routes: BreadcrumbRoute[] = [ breadcrumb: i18n.translate('xpack.apm.breadcrumb.listSettingsTitle', { defaultMessage: 'Settings', }), - name: RouteName.SETTINGS, }, { exact: true, @@ -192,7 +187,6 @@ export const routes: BreadcrumbRoute[] = [ breadcrumb: i18n.translate('xpack.apm.breadcrumb.settings.indicesTitle', { defaultMessage: 'Indices', }), - name: RouteName.INDICES, }, { exact: true, @@ -202,7 +196,6 @@ export const routes: BreadcrumbRoute[] = [ 'xpack.apm.breadcrumb.settings.agentConfigurationTitle', { defaultMessage: 'Agent Configuration' } ), - name: RouteName.AGENT_CONFIGURATION, }, { exact: true, @@ -211,7 +204,6 @@ export const routes: BreadcrumbRoute[] = [ 'xpack.apm.breadcrumb.settings.createAgentConfigurationTitle', { defaultMessage: 'Create Agent Configuration' } ), - name: RouteName.AGENT_CONFIGURATION_CREATE, component: CreateAgentConfigurationRouteHandler, }, { @@ -221,7 +213,6 @@ export const routes: BreadcrumbRoute[] = [ 'xpack.apm.breadcrumb.settings.editAgentConfigurationTitle', { defaultMessage: 'Edit Agent Configuration' } ), - name: RouteName.AGENT_CONFIGURATION_EDIT, component: EditAgentConfigurationRouteHandler, }, { @@ -232,16 +223,14 @@ export const routes: BreadcrumbRoute[] = [ renderAsRedirectTo( `/services/${props.match.params.serviceName}/transactions` )(props), - name: RouteName.SERVICE, - }, + } as APMRouteDefinition<{ serviceName: string }>, // errors { exact: true, path: '/services/:serviceName/errors/:groupId', component: ErrorGroupDetails, breadcrumb: ({ match }) => match.params.groupId, - name: RouteName.ERROR, - }, + } as APMRouteDefinition<{ groupId: string; serviceName: string }>, { exact: true, path: '/services/:serviceName/errors', @@ -249,7 +238,6 @@ export const routes: BreadcrumbRoute[] = [ breadcrumb: i18n.translate('xpack.apm.breadcrumb.errorsTitle', { defaultMessage: 'Errors', }), - name: RouteName.ERRORS, }, // transactions { @@ -259,7 +247,6 @@ export const routes: BreadcrumbRoute[] = [ breadcrumb: i18n.translate('xpack.apm.breadcrumb.transactionsTitle', { defaultMessage: 'Transactions', }), - name: RouteName.TRANSACTIONS, }, // metrics { @@ -269,7 +256,6 @@ export const routes: BreadcrumbRoute[] = [ breadcrumb: i18n.translate('xpack.apm.breadcrumb.metricsTitle', { defaultMessage: 'Metrics', }), - name: RouteName.METRICS, }, // service nodes, only enabled for java agents for now { @@ -279,7 +265,6 @@ export const routes: BreadcrumbRoute[] = [ breadcrumb: i18n.translate('xpack.apm.breadcrumb.nodesTitle', { defaultMessage: 'JVMs', }), - name: RouteName.SERVICE_NODES, }, // node metrics { @@ -295,7 +280,6 @@ export const routes: BreadcrumbRoute[] = [ return serviceNodeName || ''; }, - name: RouteName.SERVICE_NODE_METRICS, }, { exact: true, @@ -305,14 +289,12 @@ export const routes: BreadcrumbRoute[] = [ const query = toQuery(location.search); return query.transactionName as string; }, - name: RouteName.TRANSACTION_NAME, }, { exact: true, path: '/link-to/trace/:traceId', component: TraceLink, breadcrumb: null, - name: RouteName.LINK_TO_TRACE, }, { exact: true, @@ -321,7 +303,6 @@ export const routes: BreadcrumbRoute[] = [ breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', { defaultMessage: 'Service Map', }), - name: RouteName.SERVICE_MAP, }, { exact: true, @@ -330,7 +311,6 @@ export const routes: BreadcrumbRoute[] = [ breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', { defaultMessage: 'Service Map', }), - name: RouteName.SINGLE_SERVICE_MAP, }, { exact: true, @@ -339,7 +319,6 @@ export const routes: BreadcrumbRoute[] = [ breadcrumb: i18n.translate('xpack.apm.breadcrumb.settings.customizeUI', { defaultMessage: 'Customize UI', }), - name: RouteName.CUSTOMIZE_UI, }, { exact: true, @@ -351,6 +330,5 @@ export const routes: BreadcrumbRoute[] = [ defaultMessage: 'Anomaly detection', } ), - name: RouteName.ANOMALY_DETECTION, }, ]; diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx deleted file mode 100644 index 1bf798e3b26d7..0000000000000 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/route_names.tsx +++ /dev/null @@ -1,31 +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 RouteName { - HOME = 'home', - SERVICES = 'services', - SERVICE_MAP = 'service-map', - SINGLE_SERVICE_MAP = 'single-service-map', - TRACES = 'traces', - SERVICE = 'service', - TRANSACTIONS = 'transactions', - ERRORS = 'errors', - ERROR = 'error', - METRICS = 'metrics', - SERVICE_NODE_METRICS = 'node_metrics', - TRANSACTION_TYPE = 'transaction_type', - TRANSACTION_NAME = 'transaction_name', - SETTINGS = 'settings', - AGENT_CONFIGURATION = 'agent_configuration', - AGENT_CONFIGURATION_CREATE = 'agent_configuration_create', - AGENT_CONFIGURATION_EDIT = 'agent_configuration_edit', - INDICES = 'indices', - SERVICE_NODES = 'nodes', - LINK_TO_TRACE = 'link_to_trace', - CUSTOMIZE_UI = 'customize_ui', - ANOMALY_DETECTION = 'anomaly_detection', - CSM = 'csm', -} diff --git a/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx b/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx index 8334efffbd511..48206572932b1 100644 --- a/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx +++ b/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx @@ -39,6 +39,7 @@ const mockCore = { apm: {}, }, currentAppId$: new Observable(), + navigateToUrl: (url: string) => {}, }, chrome: { docTitle: { change: () => {} }, diff --git a/x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.test.tsx b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx similarity index 65% rename from x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.test.tsx rename to x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx index 102a3d91e4a91..dcd6ed0ba4934 100644 --- a/x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx @@ -4,63 +4,56 @@ * you may not use this file except in compliance with the Elastic License. */ -import { mount } from 'enzyme'; -import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import produce from 'immer'; +import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; -import { routes } from './route_config'; -import { UpdateBreadcrumbs } from './UpdateBreadcrumbs'; +import { routes } from '../components/app/Main/route_config'; +import { ApmPluginContextValue } from '../context/ApmPluginContext'; import { - MockApmPluginContextWrapper, mockApmPluginContextValue, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; + MockApmPluginContextWrapper, +} from '../context/ApmPluginContext/MockApmPluginContext'; +import { useBreadcrumbs } from './use_breadcrumbs'; -const setBreadcrumbs = jest.fn(); -const changeTitle = jest.fn(); +function createWrapper(path: string) { + return ({ children }: { children?: ReactNode }) => { + const value = (produce(mockApmPluginContextValue, (draft) => { + draft.core.application.navigateToUrl = (url: string) => Promise.resolve(); + draft.core.chrome.docTitle.change = changeTitle; + draft.core.chrome.setBreadcrumbs = setBreadcrumbs; + }) as unknown) as ApmPluginContextValue; -function mountBreadcrumb(route: string, params = '') { - mount( - - - + return ( + + + {children} + - - ); - expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + ); + }; } -describe('UpdateBreadcrumbs', () => { - beforeEach(() => { - setBreadcrumbs.mockReset(); - changeTitle.mockReset(); - }); +function mountBreadcrumb(path: string) { + renderHook(() => useBreadcrumbs(routes), { wrapper: createWrapper(path) }); +} - it('Changes the homepage title', () => { +const changeTitle = jest.fn(); +const setBreadcrumbs = jest.fn(); + +describe('useBreadcrumbs', () => { + it('changes the page title', () => { mountBreadcrumb('/'); + expect(changeTitle).toHaveBeenCalledWith(['APM']); }); - it('/services/:serviceName/errors/:groupId', () => { + test('/services/:serviceName/errors/:groupId', () => { mountBreadcrumb( - '/services/opbeans-node/errors/myGroupId', - 'rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0' + '/services/opbeans-node/errors/myGroupId?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0' ); - const breadcrumbs = setBreadcrumbs.mock.calls[0][0]; - expect(breadcrumbs).toEqual( + + expect(setBreadcrumbs).toHaveBeenCalledWith( expect.arrayContaining([ expect.objectContaining({ text: 'APM', @@ -95,10 +88,10 @@ describe('UpdateBreadcrumbs', () => { ]); }); - it('/services/:serviceName/errors', () => { - mountBreadcrumb('/services/opbeans-node/errors'); - const breadcrumbs = setBreadcrumbs.mock.calls[0][0]; - expect(breadcrumbs).toEqual( + test('/services/:serviceName/errors', () => { + mountBreadcrumb('/services/opbeans-node/errors?kuery=myKuery'); + + expect(setBreadcrumbs).toHaveBeenCalledWith( expect.arrayContaining([ expect.objectContaining({ text: 'APM', @@ -115,6 +108,7 @@ describe('UpdateBreadcrumbs', () => { expect.objectContaining({ text: 'Errors', href: undefined }), ]) ); + expect(changeTitle).toHaveBeenCalledWith([ 'Errors', 'opbeans-node', @@ -123,10 +117,10 @@ describe('UpdateBreadcrumbs', () => { ]); }); - it('/services/:serviceName/transactions', () => { - mountBreadcrumb('/services/opbeans-node/transactions'); - const breadcrumbs = setBreadcrumbs.mock.calls[0][0]; - expect(breadcrumbs).toEqual( + test('/services/:serviceName/transactions', () => { + mountBreadcrumb('/services/opbeans-node/transactions?kuery=myKuery'); + + expect(setBreadcrumbs).toHaveBeenCalledWith( expect.arrayContaining([ expect.objectContaining({ text: 'APM', @@ -152,14 +146,12 @@ describe('UpdateBreadcrumbs', () => { ]); }); - it('/services/:serviceName/transactions/view?transactionName=my-transaction-name', () => { + test('/services/:serviceName/transactions/view?transactionName=my-transaction-name', () => { mountBreadcrumb( - '/services/opbeans-node/transactions/view', - 'transactionName=my-transaction-name' + '/services/opbeans-node/transactions/view?kuery=myKuery&transactionName=my-transaction-name' ); - const breadcrumbs = setBreadcrumbs.mock.calls[0][0]; - expect(breadcrumbs).toEqual( + expect(setBreadcrumbs).toHaveBeenCalledWith( expect.arrayContaining([ expect.objectContaining({ text: 'APM', diff --git a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts new file mode 100644 index 0000000000000..640170bf3bff2 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts @@ -0,0 +1,214 @@ +/* + * 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 { History, Location } from 'history'; +import { ChromeBreadcrumb } from 'kibana/public'; +import { MouseEvent, ReactNode, useEffect } from 'react'; +import { + matchPath, + RouteComponentProps, + useHistory, + match as Match, + useLocation, +} from 'react-router-dom'; +import { APMRouteDefinition, BreadcrumbTitle } from '../application/routes'; +import { getAPMHref } from '../components/shared/Links/apm/APMLink'; +import { useApmPluginContext } from './useApmPluginContext'; + +interface BreadcrumbWithoutLink extends ChromeBreadcrumb { + match: Match>; +} + +interface BreadcrumbFunctionArgs extends RouteComponentProps { + breadcrumbTitle: BreadcrumbTitle; +} + +/** + * Call the breadcrumb function if there is one, otherwise return it as a string + */ +function getBreadcrumbText({ + breadcrumbTitle, + history, + location, + match, +}: BreadcrumbFunctionArgs) { + return typeof breadcrumbTitle === 'function' + ? breadcrumbTitle({ history, location, match }) + : breadcrumbTitle; +} + +/** + * Get a breadcrumb from the current path and route definitions. + */ +function getBreadcrumb({ + currentPath, + history, + location, + routes, +}: { + currentPath: string; + history: History; + location: Location; + routes: APMRouteDefinition[]; +}) { + return routes.reduce( + (found, { breadcrumb, ...routeDefinition }) => { + if (found) { + return found; + } + + if (!breadcrumb) { + return null; + } + + const match = matchPath>( + currentPath, + routeDefinition + ); + + if (match) { + return { + match, + text: getBreadcrumbText({ + breadcrumbTitle: breadcrumb, + history, + location, + match, + }), + }; + } + + return null; + }, + null + ); +} + +/** + * Once we have the breadcrumbs, we need to iterate through the list again to + * add the href and onClick, since we need to know which one is the final + * breadcrumb + */ +function addLinksToBreadcrumbs({ + breadcrumbs, + navigateToUrl, + wrappedGetAPMHref, +}: { + breadcrumbs: BreadcrumbWithoutLink[]; + navigateToUrl: (url: string) => Promise; + wrappedGetAPMHref: (path: string) => string; +}) { + return breadcrumbs.map((breadcrumb, index) => { + const isLastBreadcrumbItem = index === breadcrumbs.length - 1; + + // Make the link not clickable if it's the last item + const href = isLastBreadcrumbItem + ? undefined + : wrappedGetAPMHref(breadcrumb.match.url); + const onClick = !href + ? undefined + : (event: MouseEvent) => { + event.preventDefault(); + navigateToUrl(href); + }; + + return { + ...breadcrumb, + match: undefined, + href, + onClick, + }; + }); +} + +/** + * Convert a list of route definitions to a list of breadcrumbs + */ +function routeDefinitionsToBreadcrumbs({ + history, + location, + routes, +}: { + history: History; + location: Location; + routes: APMRouteDefinition[]; +}) { + const breadcrumbs: BreadcrumbWithoutLink[] = []; + const { pathname } = location; + + pathname + .split('?')[0] + .replace(/\/$/, '') + .split('/') + .reduce((acc, next) => { + // `/1/2/3` results in match checks for `/1`, `/1/2`, `/1/2/3`. + const currentPath = !next ? '/' : `${acc}/${next}`; + const breadcrumb = getBreadcrumb({ + currentPath, + history, + location, + routes, + }); + + if (breadcrumb) { + breadcrumbs.push(breadcrumb); + } + + return currentPath === '/' ? '' : currentPath; + }, ''); + + return breadcrumbs; +} + +/** + * Get an array for a page title from a list of breadcrumbs + */ +function getTitleFromBreadcrumbs(breadcrumbs: ChromeBreadcrumb[]): string[] { + function removeNonStrings(item: ReactNode): item is string { + return typeof item === 'string'; + } + + return breadcrumbs + .map(({ text }) => text) + .reverse() + .filter(removeNonStrings); +} + +/** + * Determine the breadcrumbs from the routes, set them, and update the page + * title when the route changes. + */ +export function useBreadcrumbs(routes: APMRouteDefinition[]) { + const history = useHistory(); + const location = useLocation(); + const { search } = location; + const { core } = useApmPluginContext(); + const { basePath } = core.http; + const { navigateToUrl } = core.application; + const { docTitle, setBreadcrumbs } = core.chrome; + const changeTitle = docTitle.change; + + function wrappedGetAPMHref(path: string) { + return getAPMHref({ basePath, path, search }); + } + + const breadcrumbsWithoutLinks = routeDefinitionsToBreadcrumbs({ + history, + location, + routes, + }); + const breadcrumbs = addLinksToBreadcrumbs({ + breadcrumbs: breadcrumbsWithoutLinks, + wrappedGetAPMHref, + navigateToUrl, + }); + const title = getTitleFromBreadcrumbs(breadcrumbs); + + useEffect(() => { + changeTitle(title); + setBreadcrumbs(breadcrumbs); + }, [breadcrumbs, changeTitle, location, title, setBreadcrumbs]); +} From d2ad7ce15108933ede9b7ffe7fdefa6199e17bf6 Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Tue, 8 Sep 2020 13:10:52 -0400 Subject: [PATCH 19/19] [Monitoring] Read from metricbeat-* for ES node_stats (#76015) * Read from metricbeat-* for node_stats * Fix tests Co-authored-by: Elastic Machine --- .../plugins/monitoring/server/config.test.ts | 3 ++ x-pack/plugins/monitoring/server/config.ts | 3 ++ .../monitoring/server/lib/ccs_utils.js | 21 +++++++++++-- .../monitoring/server/lib/create_query.js | 2 +- .../elasticsearch/nodes/get_node_summary.js | 31 ++++++++++++++----- .../nodes/get_nodes/get_node_ids.js | 1 + .../nodes/get_nodes/get_nodes.js | 1 + .../nodes/get_nodes/map_nodes_info.js | 12 ++++--- 8 files changed, 58 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/monitoring/server/config.test.ts b/x-pack/plugins/monitoring/server/config.test.ts index 32b8691bd6049..2efc325a3edec 100644 --- a/x-pack/plugins/monitoring/server/config.test.ts +++ b/x-pack/plugins/monitoring/server/config.test.ts @@ -86,6 +86,9 @@ describe('config schema', () => { "index": "filebeat-*", }, "max_bucket_size": 10000, + "metricbeat": Object { + "index": "metricbeat-*", + }, "min_interval_seconds": 10, "show_license_expiration": true, }, diff --git a/x-pack/plugins/monitoring/server/config.ts b/x-pack/plugins/monitoring/server/config.ts index 789211c43db31..6ae99e3d16d64 100644 --- a/x-pack/plugins/monitoring/server/config.ts +++ b/x-pack/plugins/monitoring/server/config.ts @@ -29,6 +29,9 @@ export const configSchema = schema.object({ logs: schema.object({ index: schema.string({ defaultValue: 'filebeat-*' }), }), + metricbeat: schema.object({ + index: schema.string({ defaultValue: 'metricbeat-*' }), + }), max_bucket_size: schema.number({ defaultValue: 10000 }), elasticsearch: monitoringElasticsearchConfigSchema, container: schema.object({ diff --git a/x-pack/plugins/monitoring/server/lib/ccs_utils.js b/x-pack/plugins/monitoring/server/lib/ccs_utils.js index dab1e87435c86..bef07124fb430 100644 --- a/x-pack/plugins/monitoring/server/lib/ccs_utils.js +++ b/x-pack/plugins/monitoring/server/lib/ccs_utils.js @@ -5,6 +5,21 @@ */ import { isFunction, get } from 'lodash'; +export function appendMetricbeatIndex(config, indexPattern) { + // Leverage this function to also append the dynamic metricbeat index too + let mbIndex = null; + // TODO: NP + // This function is called with both NP config and LP config + if (isFunction(config.get)) { + mbIndex = config.get('monitoring.ui.metricbeat.index'); + } else { + mbIndex = get(config, 'monitoring.ui.metricbeat.index'); + } + + const newIndexPattern = `${indexPattern},${mbIndex}`; + return newIndexPattern; +} + /** * Prefix all comma separated index patterns within the original {@code indexPattern}. * @@ -27,7 +42,7 @@ export function prefixIndexPattern(config, indexPattern, ccs) { } if (!ccsEnabled || !ccs) { - return indexPattern; + return appendMetricbeatIndex(config, indexPattern); } const patterns = indexPattern.split(','); @@ -35,10 +50,10 @@ export function prefixIndexPattern(config, indexPattern, ccs) { // if a wildcard is used, then we also want to search the local indices if (ccs === '*') { - return `${prefixedPattern},${indexPattern}`; + return appendMetricbeatIndex(config, `${prefixedPattern},${indexPattern}`); } - return prefixedPattern; + return appendMetricbeatIndex(config, prefixedPattern); } /** diff --git a/x-pack/plugins/monitoring/server/lib/create_query.js b/x-pack/plugins/monitoring/server/lib/create_query.js index 04e0d7642ec58..1983dc3dcf9af 100644 --- a/x-pack/plugins/monitoring/server/lib/create_query.js +++ b/x-pack/plugins/monitoring/server/lib/create_query.js @@ -57,7 +57,7 @@ export function createQuery(options) { let typeFilter; if (type) { - typeFilter = { term: { type } }; + typeFilter = { bool: { should: [{ term: { type } }, { term: { 'metricset.name': type } }] } }; } let clusterUuidFilter; diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.js index 6abb392e58818..84384021a3593 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.js @@ -17,15 +17,23 @@ export function handleResponse(clusterState, shardStats, nodeUuid) { return (response) => { let nodeSummary = {}; const nodeStatsHits = get(response, 'hits.hits', []); - const nodes = nodeStatsHits.map((hit) => hit._source.source_node); // using [0] value because query results are sorted desc per timestamp + const nodes = nodeStatsHits.map((hit) => + get(hit, '_source.elasticsearch.node', hit._source.source_node) + ); // using [0] value because query results are sorted desc per timestamp const node = nodes[0] || getDefaultNodeFromId(nodeUuid); - const sourceStats = get(response, 'hits.hits[0]._source.node_stats'); + const sourceStats = + get(response, 'hits.hits[0]._source.elasticsearch.node.stats') || + get(response, 'hits.hits[0]._source.node_stats'); const clusterNode = get(clusterState, ['nodes', nodeUuid]); const stats = { resolver: nodeUuid, - node_ids: nodes.map((node) => node.uuid), + node_ids: nodes.map((node) => node.id || node.uuid), attributes: node.attributes, - transport_address: node.transport_address, + transport_address: get( + response, + 'hits.hits[0]._source.service.address', + node.transport_address + ), name: node.name, type: node.type, }; @@ -45,10 +53,17 @@ export function handleResponse(clusterState, shardStats, nodeUuid) { totalShards: _shardStats.shardCount, indexCount: _shardStats.indexCount, documents: get(sourceStats, 'indices.docs.count'), - dataSize: get(sourceStats, 'indices.store.size_in_bytes'), - freeSpace: get(sourceStats, 'fs.total.available_in_bytes'), - totalSpace: get(sourceStats, 'fs.total.total_in_bytes'), - usedHeap: get(sourceStats, 'jvm.mem.heap_used_percent'), + dataSize: + get(sourceStats, 'indices.store.size_in_bytes') || + get(sourceStats, 'indices.store.size.bytes'), + freeSpace: + get(sourceStats, 'fs.total.available_in_bytes') || + get(sourceStats, 'fs.summary.available.bytes'), + totalSpace: + get(sourceStats, 'fs.total.total_in_bytes') || get(sourceStats, 'fs.summary.total.bytes'), + usedHeap: + get(sourceStats, 'jvm.mem.heap_used_percent') || + get(sourceStats, 'jvm.mem.heap.used.pct'), status: i18n.translate('xpack.monitoring.es.nodes.onlineStatusLabel', { defaultMessage: 'Online', }), diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_node_ids.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_node_ids.js index 573f1792e5f8a..68bca96e2911b 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_node_ids.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_node_ids.js @@ -19,6 +19,7 @@ export async function getNodeIds(req, indexPattern, { clusterUuid }, size) { filterPath: ['aggregations.composite_data.buckets'], body: { query: createQuery({ + type: 'node_stats', start, end, metric: ElasticsearchMetric.getMetricFields(), diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js index 682da324ee72f..c2794b7e7fa44 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js @@ -96,6 +96,7 @@ export async function getNodes(req, esIndexPattern, pageOfNodes, clusterStats, n }, filterPath: [ 'hits.hits._source.source_node', + 'hits.hits._source.elasticsearch.node', 'aggregations.nodes.buckets.key', ...LISTING_METRICS_PATHS, ], diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.js index 3c719c2ddfbf8..317c1cddf57ae 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.js @@ -17,25 +17,29 @@ export function mapNodesInfo(nodeHits, clusterStats, nodesShardCount) { const clusterState = get(clusterStats, 'cluster_state', { nodes: {} }); return nodeHits.reduce((prev, node) => { - const sourceNode = get(node, '_source.source_node'); + const sourceNode = get(node, '_source.source_node') || get(node, '_source.elasticsearch.node'); const calculatedNodeType = calculateNodeType(sourceNode, get(clusterState, 'master_node')); const { nodeType, nodeTypeLabel, nodeTypeClass } = getNodeTypeClassLabel( sourceNode, calculatedNodeType ); - const isOnline = !isUndefined(get(clusterState, ['nodes', sourceNode.uuid])); + const isOnline = !isUndefined(get(clusterState, ['nodes', sourceNode.uuid || sourceNode.id])); return { ...prev, - [sourceNode.uuid]: { + [sourceNode.uuid || sourceNode.id]: { name: sourceNode.name, transport_address: sourceNode.transport_address, type: nodeType, isOnline, nodeTypeLabel: nodeTypeLabel, nodeTypeClass: nodeTypeClass, - shardCount: get(nodesShardCount, `nodes[${sourceNode.uuid}].shardCount`, 0), + shardCount: get( + nodesShardCount, + `nodes[${sourceNode.uuid || sourceNode.id}].shardCount`, + 0 + ), }, }; }, {});