diff --git a/.ci/Jenkinsfile_baseline_capture b/.ci/Jenkinsfile_baseline_capture index 33ecfcd84fd3e6..791cacf7abb4c6 100644 --- a/.ci/Jenkinsfile_baseline_capture +++ b/.ci/Jenkinsfile_baseline_capture @@ -12,12 +12,12 @@ kibanaPipeline(timeoutMinutes: 120) { ]) { parallel([ 'oss-baseline': { - workers.ci(name: 'oss-baseline', size: 's-highmem', ramDisk: true, runErrorReporter: false) { + workers.ci(name: 'oss-baseline', size: 'l', ramDisk: true, runErrorReporter: false) { kibanaPipeline.functionalTestProcess('oss-baseline', './test/scripts/jenkins_baseline.sh')() } }, 'xpack-baseline': { - workers.ci(name: 'xpack-baseline', size: 's-highmem', ramDisk: true, runErrorReporter: false) { + workers.ci(name: 'xpack-baseline', size: 'l', ramDisk: true, runErrorReporter: false) { kibanaPipeline.functionalTestProcess('xpack-baseline', './test/scripts/jenkins_xpack_baseline.sh')() } }, diff --git a/docs/apm/service-maps.asciidoc b/docs/apm/service-maps.asciidoc index db2f85c54c7624..d629a95073a745 100644 --- a/docs/apm/service-maps.asciidoc +++ b/docs/apm/service-maps.asciidoc @@ -2,11 +2,6 @@ [[service-maps]] === Service maps -beta::[] - -WARNING: Service map support for Internet Explorer 11 is extremely limited. -Please use Chrome or Firefox if available. - A service map is a real-time visual representation of the instrumented services in your application's architecture. It shows you how these services are connected, along with high-level metrics like average transaction duration, requests per minute, and errors per minute. diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md index 903462ac3039d5..470a41f30afbfe 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.md @@ -29,4 +29,5 @@ export interface SavedObjectsFindOptions | [sortField](./kibana-plugin-core-public.savedobjectsfindoptions.sortfield.md) | string | | | [sortOrder](./kibana-plugin-core-public.savedobjectsfindoptions.sortorder.md) | string | | | [type](./kibana-plugin-core-public.savedobjectsfindoptions.type.md) | string | string[] | | +| [typeToNamespacesMap](./kibana-plugin-core-public.savedobjectsfindoptions.typetonamespacesmap.md) | Map<string, string[] | undefined> | This map defines each type to search for, and the namespace(s) to search for the type in; this is only intended to be used by a saved object client wrapper. If this is defined, it supersedes the type and namespaces fields when building the Elasticsearch query. Any types that are not included in this map will be excluded entirely. If a type is included but its value is undefined, the operation will search for that type in the Default namespace. | diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.typetonamespacesmap.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.typetonamespacesmap.md new file mode 100644 index 00000000000000..4af8c9ddeaff4f --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsfindoptions.typetonamespacesmap.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsFindOptions](./kibana-plugin-core-public.savedobjectsfindoptions.md) > [typeToNamespacesMap](./kibana-plugin-core-public.savedobjectsfindoptions.typetonamespacesmap.md) + +## SavedObjectsFindOptions.typeToNamespacesMap property + +This map defines each type to search for, and the namespace(s) to search for the type in; this is only intended to be used by a saved object client wrapper. If this is defined, it supersedes the `type` and `namespaces` fields when building the Elasticsearch query. Any types that are not included in this map will be excluded entirely. If a type is included but its value is undefined, the operation will search for that type in the Default namespace. + +Signature: + +```typescript +typeToNamespacesMap?: Map; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md index 05e408ab499952..0a49ee6e63d6c7 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md @@ -33,4 +33,5 @@ export declare class KibanaRequestRecursiveReadonly<KibanaRequestRoute<Method>> | matched route details | | [socket](./kibana-plugin-core-server.kibanarequest.socket.md) | | IKibanaSocket | [IKibanaSocket](./kibana-plugin-core-server.ikibanasocket.md) | | [url](./kibana-plugin-core-server.kibanarequest.url.md) | | Url | a WHATWG URL standard object. | +| [uuid](./kibana-plugin-core-server.kibanarequest.uuid.md) | | string | A UUID to identify this request. | diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.uuid.md b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.uuid.md new file mode 100644 index 00000000000000..8b980b82d0adbe --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.uuid.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaRequest](./kibana-plugin-core-server.kibanarequest.md) > [uuid](./kibana-plugin-core-server.kibanarequest.uuid.md) + +## KibanaRequest.uuid property + +A UUID to identify this request. + +Signature: + +```typescript +readonly uuid: string; +``` + +## Remarks + +This value is NOT sourced from the incoming request's `X-Opaque-Id` header. it is always a UUID uniquely identifying the request. + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md index 804c83f7c1b48f..ce5c20e60ca118 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.md @@ -29,4 +29,5 @@ export interface SavedObjectsFindOptions | [sortField](./kibana-plugin-core-server.savedobjectsfindoptions.sortfield.md) | string | | | [sortOrder](./kibana-plugin-core-server.savedobjectsfindoptions.sortorder.md) | string | | | [type](./kibana-plugin-core-server.savedobjectsfindoptions.type.md) | string | string[] | | +| [typeToNamespacesMap](./kibana-plugin-core-server.savedobjectsfindoptions.typetonamespacesmap.md) | Map<string, string[] | undefined> | This map defines each type to search for, and the namespace(s) to search for the type in; this is only intended to be used by a saved object client wrapper. If this is defined, it supersedes the type and namespaces fields when building the Elasticsearch query. Any types that are not included in this map will be excluded entirely. If a type is included but its value is undefined, the operation will search for that type in the Default namespace. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.typetonamespacesmap.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.typetonamespacesmap.md new file mode 100644 index 00000000000000..8bec759f055804 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsfindoptions.typetonamespacesmap.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsFindOptions](./kibana-plugin-core-server.savedobjectsfindoptions.md) > [typeToNamespacesMap](./kibana-plugin-core-server.savedobjectsfindoptions.typetonamespacesmap.md) + +## SavedObjectsFindOptions.typeToNamespacesMap property + +This map defines each type to search for, and the namespace(s) to search for the type in; this is only intended to be used by a saved object client wrapper. If this is defined, it supersedes the `type` and `namespaces` fields when building the Elasticsearch query. Any types that are not included in this map will be excluded entirely. If a type is included but its value is undefined, the operation will search for that type in the Default namespace. + +Signature: + +```typescript +typeToNamespacesMap?: Map; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.find.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.find.md index 1b562263145daf..d3e93e7af2aa07 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.find.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.find.md @@ -7,14 +7,14 @@ Signature: ```typescript -find({ search, defaultSearchOperator, searchFields, rootSearchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, }: SavedObjectsFindOptions): Promise>; +find(options: SavedObjectsFindOptions): Promise>; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| { search, defaultSearchOperator, searchFields, rootSearchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, } | SavedObjectsFindOptions | | +| options | SavedObjectsFindOptions | | Returns: diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md index 14d3741425987f..1d11d5262a9c42 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md @@ -24,7 +24,7 @@ export declare class SavedObjectsRepository | [delete(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.delete.md) | | Deletes an object | | [deleteByNamespace(namespace, options)](./kibana-plugin-core-server.savedobjectsrepository.deletebynamespace.md) | | Deletes all objects from the provided namespace. | | [deleteFromNamespaces(type, id, namespaces, options)](./kibana-plugin-core-server.savedobjectsrepository.deletefromnamespaces.md) | | Removes one or more namespaces from a given multi-namespace saved object. If no namespaces remain, the saved object is deleted entirely. This method and \[addToNamespaces\][SavedObjectsRepository.addToNamespaces()](./kibana-plugin-core-server.savedobjectsrepository.addtonamespaces.md) are the only ways to change which Spaces a multi-namespace saved object is shared to. | -| [find({ search, defaultSearchOperator, searchFields, rootSearchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, })](./kibana-plugin-core-server.savedobjectsrepository.find.md) | | | +| [find(options)](./kibana-plugin-core-server.savedobjectsrepository.find.md) | | | | [get(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.get.md) | | Gets a single object | | [incrementCounter(type, id, counterFieldName, options)](./kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md) | | Increases a counter field by one. Creates the document if one doesn't exist for the given id. | | [update(type, id, attributes, options)](./kibana-plugin-core-server.savedobjectsrepository.update.md) | | Updates an object | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.createemptyfindresponse.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.createemptyfindresponse.md new file mode 100644 index 00000000000000..40e865cb02ce8e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.createemptyfindresponse.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsUtils](./kibana-plugin-core-server.savedobjectsutils.md) > [createEmptyFindResponse](./kibana-plugin-core-server.savedobjectsutils.createemptyfindresponse.md) + +## SavedObjectsUtils.createEmptyFindResponse property + +Creates an empty response for a find operation. This is only intended to be used by saved objects client wrappers. + +Signature: + +```typescript +static createEmptyFindResponse: ({ page, perPage, }: SavedObjectsFindOptions) => SavedObjectsFindResponse; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.md index e365dfbcb51426..83831f65bd41a7 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsutils.md @@ -15,6 +15,7 @@ export declare class SavedObjectsUtils | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [createEmptyFindResponse](./kibana-plugin-core-server.savedobjectsutils.createemptyfindresponse.md) | static | <T>({ page, perPage, }: SavedObjectsFindOptions) => SavedObjectsFindResponse<T> | Creates an empty response for a find operation. This is only intended to be used by saved objects client wrappers. | | [namespaceIdToString](./kibana-plugin-core-server.savedobjectsutils.namespaceidtostring.md) | static | (namespace?: string | undefined) => string | Converts a given saved object namespace ID to its string representation. All namespace IDs have an identical string representation, with the exception of the undefined namespace ID (which has a namespace string of 'default'). | | [namespaceStringToId](./kibana-plugin-core-server.savedobjectsutils.namespacestringtoid.md) | static | (namespace: string) => string | undefined | Converts a given saved object namespace string to its ID representation. All namespace strings have an identical ID representation, with the exception of the 'default' namespace string (which has a namespace ID of undefined). | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsforwildcard.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsforwildcard.md index 6bd3bbf2433cda..52382372d6d967 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsforwildcard.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher.getfieldsforwildcard.md @@ -12,6 +12,9 @@ Get a list of field objects for an index pattern that may contain wildcards getFieldsForWildcard(options: { pattern: string | string[]; metaFields?: string[]; + fieldCapsOptions?: { + allowNoIndices: boolean; + }; }): Promise; ``` @@ -19,7 +22,7 @@ getFieldsForWildcard(options: { | Parameter | Type | Description | | --- | --- | --- | -| options | {
pattern: string | string[];
metaFields?: string[];
} | | +| options | {
pattern: string | string[];
metaFields?: string[];
fieldCapsOptions?: {
allowNoIndices: boolean;
};
} | | Returns: diff --git a/docs/user/monitoring/viewing-metrics.asciidoc b/docs/user/monitoring/viewing-metrics.asciidoc index f35caea025cdd5..0c48e3b7d011d7 100644 --- a/docs/user/monitoring/viewing-metrics.asciidoc +++ b/docs/user/monitoring/viewing-metrics.asciidoc @@ -13,13 +13,19 @@ At a minimum, you must have monitoring data for the {es} production cluster. Once that data exists, {kib} can display monitoring data for other products in the cluster. +TIP: If you use a separate monitoring cluster to store the monitoring data, it +is strongly recommended that you use a separate {kib} instance to view it. If +you log in to {kib} using SAML, Kerberos, PKI, OpenID Connect, or token +authentication providers, a dedicated {kib} instance is *required*. The security +tokens that are used in these contexts are cluster-specific, therefore you +cannot use a single {kib} instance to connect to both production and monitoring +clusters. For more information about the recommended configuration, see +{ref}/monitoring-overview.html[Monitoring overview]. + . Identify where to retrieve monitoring data from. + -- -The cluster that contains the monitoring data is referred to -as the _monitoring cluster_. - -TIP: If the monitoring data is stored on a *dedicated* monitoring cluster, it is +If the monitoring data is stored on a dedicated monitoring cluster, it is accessible even when the cluster you're monitoring is not. If you have at least a gold license, you can send data from multiple clusters to the same monitoring cluster and view them all through the same instance of {kib}. diff --git a/package.json b/package.json index 1f2749ea44a90e..57f5ac16059c92 100644 --- a/package.json +++ b/package.json @@ -350,7 +350,7 @@ "babel-eslint": "^10.0.3", "babel-jest": "^25.5.1", "babel-plugin-istanbul": "^6.0.0", - "backport": "5.5.1", + "backport": "5.6.0", "brace": "0.11.1", "chai": "3.5.0", "chance": "1.0.18", diff --git a/packages/kbn-es-archiver/src/cli.ts b/packages/kbn-es-archiver/src/cli.ts index 41abe83c148cdc..87df07fe865bdc 100644 --- a/packages/kbn-es-archiver/src/cli.ts +++ b/packages/kbn-es-archiver/src/cli.ts @@ -144,7 +144,7 @@ export function runCli() { const query = flags.query; let parsedQuery; - if (typeof query === 'string') { + if (typeof query === 'string' && query.length > 0) { try { parsedQuery = JSON.parse(query); } catch (err) { diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 1c17be50454c55..7179c6cf8b1332 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -1079,6 +1079,7 @@ export interface SavedObjectsFindOptions { sortOrder?: string; // (undocumented) type: string | string[]; + typeToNamespacesMap?: Map; } // @public diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index 5a8949ca2f55ff..6a10eb44d9ca49 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -34,7 +34,7 @@ import { HttpFetchOptions, HttpSetup } from '../http'; type SavedObjectsFindOptions = Omit< SavedObjectFindOptionsServer, - 'namespace' | 'sortOrder' | 'rootSearchFields' + 'sortOrder' | 'rootSearchFields' | 'typeToNamespacesMap' >; type PromiseType> = T extends Promise ? U : never; diff --git a/src/core/server/elasticsearch/client/cluster_client.test.ts b/src/core/server/elasticsearch/client/cluster_client.test.ts index 121ef3aa42d513..e35d9962e9e7ee 100644 --- a/src/core/server/elasticsearch/client/cluster_client.test.ts +++ b/src/core/server/elasticsearch/client/cluster_client.test.ts @@ -206,7 +206,7 @@ describe('ClusterClient', () => { const clusterClient = new ClusterClient(config, logger, getAuthHeaders); const request = httpServerMock.createKibanaRequest({ - kibanaRequestState: { requestId: 'my-fake-id' }, + kibanaRequestState: { requestId: 'my-fake-id', requestUuid: 'ignore-this-id' }, }); clusterClient.asScoped(request); @@ -284,7 +284,7 @@ describe('ClusterClient', () => { const clusterClient = new ClusterClient(config, logger, getAuthHeaders); const request = httpServerMock.createKibanaRequest({ headers: { foo: 'request' }, - kibanaRequestState: { requestId: 'from request' }, + kibanaRequestState: { requestId: 'from request', requestUuid: 'ignore-this-id' }, }); clusterClient.asScoped(request); diff --git a/src/core/server/elasticsearch/legacy/cluster_client.test.ts b/src/core/server/elasticsearch/legacy/cluster_client.test.ts index 73d941053e84b7..745ef4304d0b1c 100644 --- a/src/core/server/elasticsearch/legacy/cluster_client.test.ts +++ b/src/core/server/elasticsearch/legacy/cluster_client.test.ts @@ -351,7 +351,9 @@ describe('#asScoped', () => { test('passes x-opaque-id header with request id', () => { clusterClient.asScoped( - httpServerMock.createKibanaRequest({ kibanaRequestState: { requestId: 'alpha' } }) + httpServerMock.createKibanaRequest({ + kibanaRequestState: { requestId: 'alpha', requestUuid: 'ignore-this-id' }, + }) ); expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts index 6d096b76263b53..9deaa73d8aacfa 100644 --- a/src/core/server/http/http_server.mocks.ts +++ b/src/core/server/http/http_server.mocks.ts @@ -68,7 +68,7 @@ function createKibanaRequestMock

({ routeAuthRequired, validation = {}, kibanaRouteOptions = { xsrfRequired: true }, - kibanaRequestState = { requestId: '123' }, + kibanaRequestState = { requestId: '123', requestUuid: '123e4567-e89b-12d3-a456-426614174000' }, auth = { isAuthenticated: true }, }: RequestFixtureOptions = {}) { const queryString = stringify(query, { sort: false }); diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 7609f23fe0c51d..2440f2b1da0bd2 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -19,6 +19,7 @@ import { Server } from 'hapi'; import HapiStaticFiles from 'inert'; import url from 'url'; +import uuid from 'uuid'; import { Logger, LoggerFactory } from '../logging'; import { HttpConfig } from './http_config'; @@ -315,6 +316,7 @@ export class HttpServer { request.app = { ...(request.app ?? {}), requestId: getRequestId(request, config.requestId), + requestUuid: uuid.v4(), } as KibanaRequestState; return responseToolkit.continue; }); diff --git a/src/core/server/http/integration_tests/request.test.ts b/src/core/server/http/integration_tests/request.test.ts index 0727ff848c189f..0170e94867c064 100644 --- a/src/core/server/http/integration_tests/request.test.ts +++ b/src/core/server/http/integration_tests/request.test.ts @@ -16,6 +16,11 @@ * specific language governing permissions and limitations * under the License. */ + +jest.mock('uuid', () => ({ + v4: jest.fn().mockReturnValue('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'), +})); + import supertest from 'supertest'; import { HttpService } from '../http_service'; @@ -308,4 +313,20 @@ describe('KibanaRequest', () => { expect(resp3.body).toEqual({ requestId: 'gamma' }); }); }); + + describe('request uuid', () => { + it('generates a UUID', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + router.get({ path: '/', validate: false }, async (context, req, res) => { + return res.ok({ body: { requestUuid: req.uuid } }); + }); + await server.start(); + + const st = supertest(innerServer.listener); + + const resp1 = await st.get('/').expect(200); + expect(resp1.body.requestUuid).toBe('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'); + }); + }); }); diff --git a/src/core/server/http/router/request.test.ts b/src/core/server/http/router/request.test.ts index e741121f3d70c2..0bf81a7aca8520 100644 --- a/src/core/server/http/router/request.test.ts +++ b/src/core/server/http/router/request.test.ts @@ -55,6 +55,34 @@ describe('KibanaRequest', () => { }); }); + describe('uuid property', () => { + it('uses the request.app.requestUuid property if present', () => { + const request = httpServerMock.createRawRequest({ + app: { requestUuid: '123e4567-e89b-12d3-a456-426614174000' }, + }); + const kibanaRequest = KibanaRequest.from(request); + expect(kibanaRequest.uuid).toEqual('123e4567-e89b-12d3-a456-426614174000'); + }); + + it('generates a new UUID if request.app property is not present', () => { + // Undefined app property + const request = httpServerMock.createRawRequest({ + app: undefined, + }); + const kibanaRequest = KibanaRequest.from(request); + expect(kibanaRequest.uuid).toEqual('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'); + }); + + it('generates a new UUID if request.app.requestUuid property is not present', () => { + // Undefined app.requestUuid property + const request = httpServerMock.createRawRequest({ + app: {}, + }); + const kibanaRequest = KibanaRequest.from(request); + expect(kibanaRequest.uuid).toEqual('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'); + }); + }); + describe('get all headers', () => { it('returns all headers', () => { const request = httpServerMock.createRawRequest({ diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index e04f8585981b52..903eb75022df35 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -44,6 +44,7 @@ export interface KibanaRouteOptions extends RouteOptionsApp { */ export interface KibanaRequestState extends ApplicationState { requestId: string; + requestUuid: string; } /** @@ -152,6 +153,14 @@ export class KibanaRequest< * per request. */ public readonly id: string; + /** + * A UUID to identify this request. + * + * @remarks + * This value is NOT sourced from the incoming request's `X-Opaque-Id` header. it + * is always a UUID uniquely identifying the request. + */ + public readonly uuid: string; /** a WHATWG URL standard object. */ public readonly url: Url; /** matched route details */ @@ -189,10 +198,11 @@ export class KibanaRequest< // until that time we have to expose all the headers private readonly withoutSecretHeaders: boolean ) { - // The `requestId` property will not be populated for requests that are 'faked' by internal systems that leverage + // The `requestId` and `requestUuid` properties will not be populated for requests that are 'faked' by internal systems that leverage // KibanaRequest in conjunction with scoped Elaticcsearch and SavedObjectsClient in order to pass credentials. - // In these cases, the id defaults to a newly generated UUID. + // In these cases, the ids default to a newly generated UUID. this.id = (request.app as KibanaRequestState | undefined)?.requestId ?? uuid.v4(); + this.uuid = (request.app as KibanaRequestState | undefined)?.requestUuid ?? uuid.v4(); this.url = request.url; this.headers = deepFreeze({ ...request.headers }); diff --git a/src/core/server/saved_objects/routes/integration_tests/import.test.ts b/src/core/server/saved_objects/routes/integration_tests/import.test.ts index 0bc03fbcf80381..67be2b56b4447e 100644 --- a/src/core/server/saved_objects/routes/integration_tests/import.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/import.test.ts @@ -471,7 +471,11 @@ describe(`POST ${URL}`, () => { describe('createNewCopies enabled', () => { it('imports objects, regenerating all IDs/reference IDs present, and resetting all origin IDs', async () => { - mockUuidv4.mockReturnValueOnce('new-id-1').mockReturnValueOnce('new-id-2'); + mockUuidv4 + .mockReturnValueOnce('foo') // a uuid.v4() is generated for the request.id + .mockReturnValueOnce('foo') // another uuid.v4() is used for the request.uuid + .mockReturnValueOnce('new-id-1') + .mockReturnValueOnce('new-id-2'); savedObjectsClient.bulkGet.mockResolvedValueOnce({ saved_objects: [mockIndexPattern] }); const obj1 = { type: 'visualization', @@ -490,7 +494,6 @@ describe(`POST ${URL}`, () => { const result = await supertest(httpSetup.server.listener) .post(`${URL}?createNewCopies=true`) .set('content-Type', 'multipart/form-data; boundary=EXAMPLE') - .set('x-opaque-id', uuidv4()) // prevents src/core/server/http/http_tools.ts from using our mocked uuidv4 to generate a unique ID for this request .send( [ '--EXAMPLE', diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index 352ce4c1c16eb2..0e72ad2fec06cd 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -2477,6 +2477,33 @@ describe('SavedObjectsRepository', () => { expect(client.search).not.toHaveBeenCalled(); }); + it(`throws when namespaces is an empty array`, async () => { + await expect( + savedObjectsRepository.find({ type: 'foo', namespaces: [] }) + ).rejects.toThrowError('options.namespaces cannot be an empty array'); + expect(client.search).not.toHaveBeenCalled(); + }); + + it(`throws when type is not falsy and typeToNamespacesMap is defined`, async () => { + await expect( + savedObjectsRepository.find({ type: 'foo', typeToNamespacesMap: new Map() }) + ).rejects.toThrowError( + 'options.type must be an empty string when options.typeToNamespacesMap is used' + ); + expect(client.search).not.toHaveBeenCalled(); + }); + + it(`throws when type is not an empty array and typeToNamespacesMap is defined`, async () => { + const test = async (args) => { + await expect(savedObjectsRepository.find(args)).rejects.toThrowError( + 'options.namespaces must be an empty array when options.typeToNamespacesMap is used' + ); + expect(client.search).not.toHaveBeenCalled(); + }; + await test({ type: '', typeToNamespacesMap: new Map() }); + await test({ type: '', namespaces: ['some-ns'], typeToNamespacesMap: new Map() }); + }); + it(`throws when searchFields is defined but not an array`, async () => { await expect( savedObjectsRepository.find({ type, searchFields: 'string' }) @@ -2493,7 +2520,7 @@ describe('SavedObjectsRepository', () => { it(`throws when KQL filter syntax is invalid`, async () => { const findOpts = { - namespace, + namespaces: [namespace], search: 'foo*', searchFields: ['foo'], type: ['dashboard'], @@ -2577,38 +2604,70 @@ describe('SavedObjectsRepository', () => { const test = async (types) => { const result = await savedObjectsRepository.find({ type: types }); expect(result).toEqual(expect.objectContaining({ saved_objects: [] })); + expect(client.search).not.toHaveBeenCalled(); }; await test('unknownType'); await test(HIDDEN_TYPE); await test(['unknownType', HIDDEN_TYPE]); }); + + it(`should return empty results when attempting to find only invalid or hidden types using typeToNamespacesMap`, async () => { + const test = async (types) => { + const result = await savedObjectsRepository.find({ + typeToNamespacesMap: new Map(types.map((x) => [x, undefined])), + type: '', + namespaces: [], + }); + expect(result).toEqual(expect.objectContaining({ saved_objects: [] })); + expect(client.search).not.toHaveBeenCalled(); + }; + + await test(['unknownType']); + await test([HIDDEN_TYPE]); + await test(['unknownType', HIDDEN_TYPE]); + }); }); describe('search dsl', () => { - it(`passes mappings, registry, search, defaultSearchOperator, searchFields, type, sortField, sortOrder and hasReference to getSearchDsl`, async () => { + const commonOptions = { + type: [type], // cannot be used when `typeToNamespacesMap` is present + namespaces: [namespace], // cannot be used when `typeToNamespacesMap` is present + search: 'foo*', + searchFields: ['foo'], + sortField: 'name', + sortOrder: 'desc', + defaultSearchOperator: 'AND', + hasReference: { + type: 'foo', + id: '1', + }, + kueryNode: undefined, + }; + + it(`passes mappings, registry, and search options to getSearchDsl`, async () => { + await findSuccess(commonOptions, namespace); + expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, registry, commonOptions); + }); + + it(`accepts typeToNamespacesMap`, async () => { const relevantOpts = { - namespaces: [namespace], - search: 'foo*', - searchFields: ['foo'], - type: [type], - sortField: 'name', - sortOrder: 'desc', - defaultSearchOperator: 'AND', - hasReference: { - type: 'foo', - id: '1', - }, - kueryNode: undefined, + ...commonOptions, + type: '', + namespaces: [], + typeToNamespacesMap: new Map([[type, [namespace]]]), // can only be used when `type` is falsy and `namespaces` is an empty array }; await findSuccess(relevantOpts, namespace); - expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, registry, relevantOpts); + expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, registry, { + ...relevantOpts, + type: [type], + }); }); it(`accepts KQL expression filter and passes KueryNode to getSearchDsl`, async () => { const findOpts = { - namespace, + namespaces: [namespace], search: 'foo*', searchFields: ['foo'], type: ['dashboard'], @@ -2649,7 +2708,7 @@ describe('SavedObjectsRepository', () => { it(`accepts KQL KueryNode filter and passes KueryNode to getSearchDsl`, async () => { const findOpts = { - namespace, + namespaces: [namespace], search: 'foo*', searchFields: ['foo'], type: ['dashboard'], diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 125f97e7feb116..a83c86e5856289 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -67,7 +67,7 @@ import { } from '../../types'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { validateConvertFilterToKueryNode } from './filter_utils'; -import { SavedObjectsUtils } from './utils'; +import { FIND_DEFAULT_PAGE, FIND_DEFAULT_PER_PAGE, SavedObjectsUtils } from './utils'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. @@ -693,37 +693,51 @@ export class SavedObjectsRepository { * @property {string} [options.preference] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ - async find({ - search, - defaultSearchOperator = 'OR', - searchFields, - rootSearchFields, - hasReference, - page = 1, - perPage = 20, - sortField, - sortOrder, - fields, - namespaces, - type, - filter, - preference, - }: SavedObjectsFindOptions): Promise> { - if (!type) { + async find(options: SavedObjectsFindOptions): Promise> { + const { + search, + defaultSearchOperator = 'OR', + searchFields, + rootSearchFields, + hasReference, + page = FIND_DEFAULT_PAGE, + perPage = FIND_DEFAULT_PER_PAGE, + sortField, + sortOrder, + fields, + namespaces, + type, + typeToNamespacesMap, + filter, + preference, + } = options; + + if (!type && !typeToNamespacesMap) { throw SavedObjectsErrorHelpers.createBadRequestError( 'options.type must be a string or an array of strings' ); + } else if (namespaces?.length === 0 && !typeToNamespacesMap) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'options.namespaces cannot be an empty array' + ); + } else if (type && typeToNamespacesMap) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'options.type must be an empty string when options.typeToNamespacesMap is used' + ); + } else if ((!namespaces || namespaces?.length) && typeToNamespacesMap) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'options.namespaces must be an empty array when options.typeToNamespacesMap is used' + ); } - const types = Array.isArray(type) ? type : [type]; + const types = type + ? Array.isArray(type) + ? type + : [type] + : Array.from(typeToNamespacesMap!.keys()); const allowedTypes = types.filter((t) => this._allowedTypes.includes(t)); if (allowedTypes.length === 0) { - return { - page, - per_page: perPage, - total: 0, - saved_objects: [], - }; + return SavedObjectsUtils.createEmptyFindResponse(options); } if (searchFields && !Array.isArray(searchFields)) { @@ -766,6 +780,7 @@ export class SavedObjectsRepository { sortField, sortOrder, namespaces, + typeToNamespacesMap, hasReference, kueryNode, }), diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts index 4adc92df318058..e13c67a7204000 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts @@ -50,6 +50,40 @@ const ALL_TYPE_SUBSETS = ALL_TYPES.reduce( .filter((x) => x.length) // exclude empty set .map((x) => (x.length === 1 ? x[0] : x)); // if a subset is a single string, destructure it +const createTypeClause = (type: string, namespaces?: string[]) => { + if (registry.isMultiNamespace(type)) { + return { + bool: { + must: expect.arrayContaining([{ terms: { namespaces: namespaces ?? ['default'] } }]), + must_not: [{ exists: { field: 'namespace' } }], + }, + }; + } else if (registry.isSingleNamespace(type)) { + const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? []; + const should: any = []; + if (nonDefaultNamespaces.length > 0) { + should.push({ terms: { namespace: nonDefaultNamespaces } }); + } + if (namespaces?.includes('default')) { + should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); + } + return { + bool: { + must: [{ term: { type } }], + should: expect.arrayContaining(should), + minimum_should_match: 1, + must_not: [{ exists: { field: 'namespaces' } }], + }, + }; + } + // isNamespaceAgnostic + return { + bool: expect.objectContaining({ + must_not: [{ exists: { field: 'namespace' } }, { exists: { field: 'namespaces' } }], + }), + }; +}; + /** * Note: these tests cases are defined in the order they appear in the source code, for readability's sake */ @@ -198,40 +232,6 @@ describe('#getQueryParams', () => { }); describe('`namespaces` parameter', () => { - const createTypeClause = (type: string, namespaces?: string[]) => { - if (registry.isMultiNamespace(type)) { - return { - bool: { - must: expect.arrayContaining([{ terms: { namespaces: namespaces ?? ['default'] } }]), - must_not: [{ exists: { field: 'namespace' } }], - }, - }; - } else if (registry.isSingleNamespace(type)) { - const nonDefaultNamespaces = namespaces?.filter((n) => n !== 'default') ?? []; - const should: any = []; - if (nonDefaultNamespaces.length > 0) { - should.push({ terms: { namespace: nonDefaultNamespaces } }); - } - if (namespaces?.includes('default')) { - should.push({ bool: { must_not: [{ exists: { field: 'namespace' } }] } }); - } - return { - bool: { - must: [{ term: { type } }], - should: expect.arrayContaining(should), - minimum_should_match: 1, - must_not: [{ exists: { field: 'namespaces' } }], - }, - }; - } - // isNamespaceAgnostic - return { - bool: expect.objectContaining({ - must_not: [{ exists: { field: 'namespace' } }, { exists: { field: 'namespaces' } }], - }), - }; - }; - const expectResult = (result: Result, ...typeClauses: any) => { expect(result.query.bool.filter).toEqual( expect.arrayContaining([ @@ -281,6 +281,37 @@ describe('#getQueryParams', () => { test(['default']); }); }); + + describe('`typeToNamespacesMap` parameter', () => { + const expectResult = (result: Result, ...typeClauses: any) => { + expect(result.query.bool.filter).toEqual( + expect.arrayContaining([ + { bool: expect.objectContaining({ should: typeClauses, minimum_should_match: 1 }) }, + ]) + ); + }; + + it('supersedes `type` and `namespaces` parameters', () => { + const result = getQueryParams({ + mappings, + registry, + type: ['pending', 'saved', 'shared', 'global'], + namespaces: ['foo', 'bar', 'default'], + typeToNamespacesMap: new Map([ + ['pending', ['foo']], // 'pending' is only authorized in the 'foo' namespace + // 'saved' is not authorized in any namespaces + ['shared', ['bar', 'default']], // 'shared' is only authorized in the 'bar' and 'default' namespaces + ['global', ['foo', 'bar', 'default']], // 'global' is authorized in all namespaces (which are ignored anyway) + ]), + }); + expectResult( + result, + createTypeClause('pending', ['foo']), + createTypeClause('shared', ['bar', 'default']), + createTypeClause('global') + ); + }); + }); }); describe('search clause (query.bool.must.simple_query_string)', () => { diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index 642d51c70766e4..eaddc05fa921c1 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -129,6 +129,7 @@ interface QueryParams { registry: ISavedObjectTypeRegistry; namespaces?: string[]; type?: string | string[]; + typeToNamespacesMap?: Map; search?: string; searchFields?: string[]; rootSearchFields?: string[]; @@ -145,6 +146,7 @@ export function getQueryParams({ registry, namespaces, type, + typeToNamespacesMap, search, searchFields, rootSearchFields, @@ -152,7 +154,10 @@ export function getQueryParams({ hasReference, kueryNode, }: QueryParams) { - const types = getTypes(mappings, type); + const types = getTypes( + mappings, + typeToNamespacesMap ? Array.from(typeToNamespacesMap.keys()) : type + ); // A de-duplicated set of namespaces makes for a more effecient query. // @@ -163,9 +168,12 @@ export function getQueryParams({ // since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place // would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard. // We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716 - const normalizedNamespaces = namespaces - ? Array.from(new Set(namespaces.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x)))) - : undefined; + const normalizeNamespaces = (namespacesToNormalize?: string[]) => + namespacesToNormalize + ? Array.from( + new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x))) + ) + : undefined; const bool: any = { filter: [ @@ -197,9 +205,12 @@ export function getQueryParams({ }, ] : undefined, - should: types.map((shouldType) => - getClauseForType(registry, normalizedNamespaces, shouldType) - ), + should: types.map((shouldType) => { + const normalizedNamespaces = normalizeNamespaces( + typeToNamespacesMap ? typeToNamespacesMap.get(shouldType) : namespaces + ); + return getClauseForType(registry, normalizedNamespaces, shouldType); + }), minimum_should_match: 1, }, }, diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts index 62e629ad33cc88..7276e505bce7d6 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.test.ts @@ -57,10 +57,11 @@ describe('getSearchDsl', () => { }); describe('passes control', () => { - it('passes (mappings, schema, namespaces, type, search, searchFields, rootSearchFields, hasReference) to getQueryParams', () => { + it('passes (mappings, schema, namespaces, type, typeToNamespacesMap, search, searchFields, rootSearchFields, hasReference) to getQueryParams', () => { const opts = { namespaces: ['foo-namespace'], type: 'foo', + typeToNamespacesMap: new Map(), search: 'bar', searchFields: ['baz'], rootSearchFields: ['qux'], @@ -78,6 +79,7 @@ describe('getSearchDsl', () => { registry, namespaces: opts.namespaces, type: opts.type, + typeToNamespacesMap: opts.typeToNamespacesMap, search: opts.search, searchFields: opts.searchFields, rootSearchFields: opts.rootSearchFields, diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts index aa79a10b2a9bef..858770579fb9e6 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts @@ -35,6 +35,7 @@ interface GetSearchDslOptions { sortField?: string; sortOrder?: string; namespaces?: string[]; + typeToNamespacesMap?: Map; hasReference?: { type: string; id: string; @@ -56,6 +57,7 @@ export function getSearchDsl( sortField, sortOrder, namespaces, + typeToNamespacesMap, hasReference, kueryNode, } = options; @@ -74,6 +76,7 @@ export function getSearchDsl( registry, namespaces, type, + typeToNamespacesMap, search, searchFields, rootSearchFields, diff --git a/src/core/server/saved_objects/service/lib/utils.test.ts b/src/core/server/saved_objects/service/lib/utils.test.ts index ea4fa68242beaf..ac06ca92757831 100644 --- a/src/core/server/saved_objects/service/lib/utils.test.ts +++ b/src/core/server/saved_objects/service/lib/utils.test.ts @@ -17,10 +17,11 @@ * under the License. */ +import { SavedObjectsFindOptions } from '../../types'; import { SavedObjectsUtils } from './utils'; describe('SavedObjectsUtils', () => { - const { namespaceIdToString, namespaceStringToId } = SavedObjectsUtils; + const { namespaceIdToString, namespaceStringToId, createEmptyFindResponse } = SavedObjectsUtils; describe('#namespaceIdToString', () => { it('converts `undefined` to default namespace string', () => { @@ -54,4 +55,26 @@ describe('SavedObjectsUtils', () => { test(''); }); }); + + describe('#createEmptyFindResponse', () => { + it('returns expected result', () => { + const options = {} as SavedObjectsFindOptions; + expect(createEmptyFindResponse(options)).toEqual({ + page: 1, + per_page: 20, + total: 0, + saved_objects: [], + }); + }); + + it('handles `page` field', () => { + const options = { page: 42 } as SavedObjectsFindOptions; + expect(createEmptyFindResponse(options).page).toEqual(42); + }); + + it('handles `perPage` field', () => { + const options = { perPage: 42 } as SavedObjectsFindOptions; + expect(createEmptyFindResponse(options).per_page).toEqual(42); + }); + }); }); diff --git a/src/core/server/saved_objects/service/lib/utils.ts b/src/core/server/saved_objects/service/lib/utils.ts index 6101ad57cc4010..3efe8614da1d79 100644 --- a/src/core/server/saved_objects/service/lib/utils.ts +++ b/src/core/server/saved_objects/service/lib/utils.ts @@ -17,7 +17,12 @@ * under the License. */ +import { SavedObjectsFindOptions } from '../../types'; +import { SavedObjectsFindResponse } from '..'; + export const DEFAULT_NAMESPACE_STRING = 'default'; +export const FIND_DEFAULT_PAGE = 1; +export const FIND_DEFAULT_PER_PAGE = 20; /** * @public @@ -50,4 +55,17 @@ export class SavedObjectsUtils { return namespace !== DEFAULT_NAMESPACE_STRING ? namespace : undefined; }; + + /** + * Creates an empty response for a find operation. This is only intended to be used by saved objects client wrappers. + */ + public static createEmptyFindResponse = ({ + page = FIND_DEFAULT_PAGE, + perPage = FIND_DEFAULT_PER_PAGE, + }: SavedObjectsFindOptions): SavedObjectsFindResponse => ({ + page, + per_page: perPage, + total: 0, + saved_objects: [], + }); } diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index 1885f5ec50139b..01128e4f8cf517 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -89,6 +89,14 @@ export interface SavedObjectsFindOptions { defaultSearchOperator?: 'AND' | 'OR'; filter?: string | KueryNode; namespaces?: string[]; + /** + * This map defines each type to search for, and the namespace(s) to search for the type in; this is only intended to be used by a saved + * object client wrapper. + * If this is defined, it supersedes the `type` and `namespaces` fields when building the Elasticsearch query. + * Any types that are not included in this map will be excluded entirely. + * If a type is included but its value is undefined, the operation will search for that type in the Default namespace. + */ + typeToNamespacesMap?: Map; /** An optional ES preference value to be used for the query **/ preference?: string; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 11a14457784fd1..8a764d9bd2f661 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -943,6 +943,7 @@ export class KibanaRequest; } // @public @@ -2387,7 +2389,7 @@ export class SavedObjectsRepository { deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise; // (undocumented) - find({ search, defaultSearchOperator, searchFields, rootSearchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespaces, type, filter, preference, }: SavedObjectsFindOptions): Promise>; + find(options: SavedObjectsFindOptions): Promise>; get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise; update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; @@ -2495,6 +2497,7 @@ export interface SavedObjectsUpdateResponse extends Omit({ page, perPage, }: SavedObjectsFindOptions) => SavedObjectsFindResponse; static namespaceIdToString: (namespace?: string | undefined) => string; static namespaceStringToId: (namespace: string) => string | undefined; } diff --git a/src/core/tsconfig.json b/src/core/tsconfig.json index b8780321e11dd8..a3531057767d4e 100644 --- a/src/core/tsconfig.json +++ b/src/core/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "composite": true, - "outDir": "./target", + "outDir": "./target/types", "emitDeclarationOnly": true, "declaration": true, "declarationMap": true diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index fc88b31711b23c..abef8afcc39857 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -182,6 +182,9 @@ function EditorUI({ initialTextValue }: EditorProps) { unsubscribeResizer(); clearSubscriptions(); window.removeEventListener('hashchange', onHashChange); + if (editorInstanceRef.current) { + editorInstanceRef.current.getCoreEditor().destroy(); + } }; }, [saveCurrentTextObject, initialTextValue, history, setInputEditor, settingsService]); diff --git a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts index 469ef6d79fae57..393b7eee346f5f 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts +++ b/src/plugins/console/public/application/models/legacy_core_editor/legacy_core_editor.ts @@ -408,4 +408,8 @@ export class LegacyCoreEditor implements CoreEditor { }, ]); } + + destroy() { + this.editor.destroy(); + } } diff --git a/src/plugins/console/public/types/core_editor.ts b/src/plugins/console/public/types/core_editor.ts index b71f4fff44ca5f..d88d8f86b874cb 100644 --- a/src/plugins/console/public/types/core_editor.ts +++ b/src/plugins/console/public/types/core_editor.ts @@ -268,4 +268,9 @@ export interface CoreEditor { * detects a change */ registerAutocompleter(autocompleter: AutoCompleterFunction): void; + + /** + * Release any resources in use by the editor. + */ + destroy(): void; } diff --git a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts index ff9d67152e268d..57c636a9e3c69c 100644 --- a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts +++ b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts @@ -55,9 +55,10 @@ export class IndexPatternsFetcher { async getFieldsForWildcard(options: { pattern: string | string[]; metaFields?: string[]; + fieldCapsOptions?: { allowNoIndices: boolean }; }): Promise { - const { pattern, metaFields } = options; - return await getFieldCapabilities(this._callDataCluster, pattern, metaFields); + const { pattern, metaFields, fieldCapsOptions } = options; + return await getFieldCapabilities(this._callDataCluster, pattern, metaFields, fieldCapsOptions); } /** diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts b/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts index 0738a16034d467..27ce14f9a3597c 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts @@ -69,15 +69,20 @@ export async function callIndexAliasApi( * * @param {Function} callCluster bound function for accessing an es client * @param {Array|String} indices + * @param {Object} fieldCapsOptions * @return {Promise} */ -export async function callFieldCapsApi(callCluster: LegacyAPICaller, indices: string[] | string) { +export async function callFieldCapsApi( + callCluster: LegacyAPICaller, + indices: string[] | string, + fieldCapsOptions: { allowNoIndices: boolean } = { allowNoIndices: false } +) { try { return (await callCluster('fieldCaps', { index: indices, fields: '*', ignoreUnavailable: true, - allowNoIndices: false, + ...fieldCapsOptions, })) as FieldCapsResponse; } catch (error) { throw convertEsError(indices, error); diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js index a0af7582ac6f3e..0e5757b7b782b5 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.test.js @@ -61,7 +61,7 @@ describe('index_patterns/field_capabilities/field_capabilities', () => { await getFieldCapabilities(footballs[0], footballs[1]); sinon.assert.calledOnce(callFieldCapsApi); - calledWithExactly(callFieldCapsApi, [footballs[0], footballs[1]]); + calledWithExactly(callFieldCapsApi, [footballs[0], footballs[1], undefined]); }); }); diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts index 6b26c82dc95e75..62e77e0adad66a 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts @@ -32,14 +32,20 @@ import { FieldDescriptor } from '../../index_patterns_fetcher'; * @param {Function} callCluster bound function for accessing an es client * @param {Array} [indices=[]] the list of indexes to check * @param {Array} [metaFields=[]] the list of internal fields to include + * @param {Object} fieldCapsOptions * @return {Promise>} */ export async function getFieldCapabilities( callCluster: LegacyAPICaller, indices: string | string[] = [], - metaFields: string[] = [] + metaFields: string[] = [], + fieldCapsOptions?: { allowNoIndices: boolean } ) { - const esFieldCaps: FieldCapsResponse = await callFieldCapsApi(callCluster, indices); + const esFieldCaps: FieldCapsResponse = await callFieldCapsApi( + callCluster, + indices, + fieldCapsOptions + ); const fieldsFromFieldCapsByName = keyBy(readFieldCapsResponse(esFieldCaps), 'name'); const allFieldsUnsorted = Object.keys(fieldsFromFieldCapsByName) diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index c48aa8397dc83a..2024e9e7f29743 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -687,6 +687,9 @@ export class IndexPatternsFetcher { getFieldsForWildcard(options: { pattern: string | string[]; metaFields?: string[]; + fieldCapsOptions?: { + allowNoIndices: boolean; + }; }): Promise; } diff --git a/src/plugins/discover/public/application/angular/get_painless_error.ts b/src/plugins/discover/public/application/angular/get_painless_error.ts index e1e98d9df27b19..162dacd3ac3b7c 100644 --- a/src/plugins/discover/public/application/angular/get_painless_error.ts +++ b/src/plugins/discover/public/application/angular/get_painless_error.ts @@ -18,20 +18,77 @@ */ import { i18n } from '@kbn/i18n'; -import { get } from 'lodash'; -export function getPainlessError(error: Error) { - const rootCause: Array<{ lang: string; script: string }> | undefined = get( - error, - 'body.attributes.error.root_cause' - ); - const message: string = get(error, 'body.message'); +interface FailedShards { + shard: number; + index: string; + node: string; + reason: { + type: string; + reason: string; + script_stack: string[]; + script: string; + lang: string; + position: { + offset: number; + start: number; + end: number; + }; + caused_by: { + type: string; + reason: string; + }; + }; +} + +interface EsError { + body: { + statusCode: number; + error: string; + message: string; + attributes?: { + error?: { + root_cause?: [ + { + lang: string; + script: string; + } + ]; + type: string; + reason: string; + caused_by: { + type: string; + reason: string; + phase: string; + grouped: boolean; + failed_shards: FailedShards[]; + }; + }; + }; + }; +} + +export function getCause(error: EsError) { + const cause = error.body?.attributes?.error?.root_cause; + if (cause) { + return cause[0]; + } + + const failedShards = error.body?.attributes?.error?.caused_by?.failed_shards; + + if (failedShards && failedShards[0] && failedShards[0].reason) { + return error.body?.attributes?.error?.caused_by?.failed_shards[0].reason; + } +} + +export function getPainlessError(error: EsError) { + const cause = getCause(error); - if (!rootCause) { + if (!cause) { return; } - const [{ lang, script }] = rootCause; + const { lang, script } = cause; if (lang !== 'painless') { return; @@ -44,6 +101,6 @@ export function getPainlessError(error: Error) { defaultMessage: "Error with Painless scripted field '{script}'.", values: { script }, }), - error: message, + error: error.body?.message, }; } diff --git a/src/plugins/embeddable/public/bootstrap.ts b/src/plugins/embeddable/public/bootstrap.ts index 33cf210763b100..5c95214ef591bf 100644 --- a/src/plugins/embeddable/public/bootstrap.ts +++ b/src/plugins/embeddable/public/bootstrap.ts @@ -19,7 +19,6 @@ import { UiActionsSetup } from '../../ui_actions/public'; import { contextMenuTrigger, - createFilterAction, panelBadgeTrigger, EmbeddableContext, CONTEXT_MENU_TRIGGER, @@ -29,8 +28,6 @@ import { ACTION_INSPECT_PANEL, REMOVE_PANEL_ACTION, ACTION_EDIT_PANEL, - FilterActionContext, - ACTION_APPLY_FILTER, panelNotificationTrigger, PANEL_NOTIFICATION_TRIGGER, } from './lib'; @@ -48,7 +45,6 @@ declare module '../../ui_actions/public' { [ACTION_INSPECT_PANEL]: EmbeddableContext; [REMOVE_PANEL_ACTION]: EmbeddableContext; [ACTION_EDIT_PANEL]: EmbeddableContext; - [ACTION_APPLY_FILTER]: FilterActionContext; } } @@ -60,8 +56,4 @@ export const bootstrap = (uiActions: UiActionsSetup) => { uiActions.registerTrigger(contextMenuTrigger); uiActions.registerTrigger(panelBadgeTrigger); uiActions.registerTrigger(panelNotificationTrigger); - - const actionApplyFilter = createFilterAction(); - - uiActions.registerAction(actionApplyFilter); }; diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index c5d8853ada5e8d..7609f07d660bcd 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -24,7 +24,6 @@ import { EmbeddablePublicPlugin } from './plugin'; export { ACTION_ADD_PANEL, - ACTION_APPLY_FILTER, ACTION_EDIT_PANEL, Adapters, AddPanelAction, diff --git a/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts b/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts deleted file mode 100644 index 88c1a5917e609c..00000000000000 --- a/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { createFilterAction } from './apply_filter_action'; -import { expectErrorAsync } from '../../tests/helpers'; -import { defaultTrigger } from '../../../../ui_actions/public/triggers'; - -test('has ACTION_APPLY_FILTER type and id', () => { - const action = createFilterAction(); - expect(action.id).toBe('ACTION_APPLY_FILTER'); - expect(action.type).toBe('ACTION_APPLY_FILTER'); -}); - -test('has expected display name', () => { - const action = createFilterAction(); - expect(action.getDisplayName({} as any)).toMatchInlineSnapshot(`"Apply filter to current view"`); -}); - -describe('getIconType()', () => { - test('returns "filter" icon', async () => { - const action = createFilterAction(); - const result = action.getIconType({} as any); - expect(result).toBe('filter'); - }); -}); - -describe('isCompatible()', () => { - test('when embeddable filters and filters exist, returns true', async () => { - const action = createFilterAction(); - const result = await action.isCompatible({ - embeddable: { - getRoot: () => ({ - getInput: () => ({ - filters: [], - }), - }), - } as any, - filters: [], - trigger: defaultTrigger, - }); - expect(result).toBe(true); - }); - - test('when embeddable filters not set, returns false', async () => { - const action = createFilterAction(); - const result = await action.isCompatible({ - embeddable: { - getRoot: () => ({ - getInput: () => ({ - // filters: [], - }), - }), - } as any, - filters: [], - trigger: defaultTrigger, - }); - expect(result).toBe(false); - }); - - test('when triggerContext or filters are not set, returns false', async () => { - const action = createFilterAction(); - - const result1 = await action.isCompatible({ - embeddable: { - getRoot: () => ({ - getInput: () => ({ - filters: [], - }), - }), - } as any, - } as any); - expect(result1).toBe(false); - }); -}); - -const getEmbeddable = () => { - const root = { - getInput: jest.fn(() => ({ - filters: [], - })), - updateInput: jest.fn(), - }; - const embeddable = { - getRoot: () => root, - } as any; - return [embeddable, root]; -}; - -describe('execute()', () => { - describe('when no filters are given', () => { - test('throws an error', async () => { - const action = createFilterAction(); - const error = await expectErrorAsync(() => - action.execute({ - embeddable: getEmbeddable(), - } as any) - ); - expect(error).toBeInstanceOf(Error); - expect(error.message).toBe('Applying a filter requires a filter and embeddable as context'); - }); - - test('updates filter input on success', async () => { - const action = createFilterAction(); - const [embeddable, root] = getEmbeddable(); - - await action.execute({ - embeddable, - filters: ['FILTER' as any], - trigger: defaultTrigger, - }); - - expect(root.updateInput).toHaveBeenCalledTimes(1); - expect(root.updateInput.mock.calls[0][0]).toMatchObject({ - filters: ['FILTER'], - }); - }); - }); -}); diff --git a/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts b/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts deleted file mode 100644 index 3460203aac29c6..00000000000000 --- a/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import { ActionByType, createAction, IncompatibleActionError } from '../ui_actions'; -import { IEmbeddable, EmbeddableInput } from '../embeddables'; -import { Filter } from '../../../../../plugins/data/public'; - -export const ACTION_APPLY_FILTER = 'ACTION_APPLY_FILTER'; - -type RootEmbeddable = IEmbeddable; -export interface FilterActionContext { - embeddable: IEmbeddable; - filters: Filter[]; -} - -async function isCompatible(context: FilterActionContext) { - if (context.embeddable === undefined) { - return false; - } - const root = context.embeddable.getRoot() as RootEmbeddable; - return Boolean(root.getInput().filters !== undefined && context.filters !== undefined); -} - -export function createFilterAction(): ActionByType { - return createAction({ - type: ACTION_APPLY_FILTER, - id: ACTION_APPLY_FILTER, - order: 100, - getIconType: () => 'filter', - getDisplayName: () => { - return i18n.translate('embeddableApi.actions.applyFilterActionTitle', { - defaultMessage: 'Apply filter to current view', - }); - }, - isCompatible, - execute: async ({ embeddable, filters }) => { - if (!filters || !embeddable) { - throw new Error('Applying a filter requires a filter and embeddable as context'); - } - - if (!(await isCompatible({ embeddable, filters }))) { - throw new IncompatibleActionError(); - } - - const root = embeddable.getRoot() as RootEmbeddable; - - root.updateInput({ - filters, - }); - }, - }); -} diff --git a/src/plugins/embeddable/public/lib/actions/index.ts b/src/plugins/embeddable/public/lib/actions/index.ts index ea32c6aa2d4550..8be2c3f5df4504 100644 --- a/src/plugins/embeddable/public/lib/actions/index.ts +++ b/src/plugins/embeddable/public/lib/actions/index.ts @@ -17,5 +17,4 @@ * under the License. */ -export * from './apply_filter_action'; export * from './edit_panel_action'; diff --git a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts b/src/plugins/embeddable/public/tests/apply_filter_action.test.ts deleted file mode 100644 index f8c4a4a7e4b72f..00000000000000 --- a/src/plugins/embeddable/public/tests/apply_filter_action.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { testPlugin } from './test_plugin'; -import { EmbeddableOutput, isErrorEmbeddable, createFilterAction } from '../lib'; -import { - FilterableContainer, - FilterableContainerInput, - FILTERABLE_CONTAINER, - FilterableEmbeddableFactory, - HelloWorldContainer, - FILTERABLE_EMBEDDABLE, - FilterableEmbeddable, - FilterableContainerFactory, - FilterableEmbeddableInput, -} from '../lib/test_samples'; -import { esFilters } from '../../../data/public'; -import { applyFilterTrigger } from '../../../ui_actions/public'; - -test('ApplyFilterAction applies the filter to the root of the container tree', async () => { - const { doStart, setup } = testPlugin(); - - const factory2 = new FilterableEmbeddableFactory(); - const factory1 = new FilterableContainerFactory(async () => await api.getEmbeddableFactory); - setup.registerEmbeddableFactory(factory2.type, factory2); - setup.registerEmbeddableFactory(factory1.type, factory1); - - const api = doStart(); - - const applyFilterAction = createFilterAction(); - - const root = new FilterableContainer( - { id: 'root', panels: {}, filters: [] }, - api.getEmbeddableFactory - ); - - const node1 = await root.addNewEmbeddable< - FilterableContainerInput, - EmbeddableOutput, - FilterableContainer - >(FILTERABLE_CONTAINER, { panels: {}, id: 'node1' }); - - const node2 = await root.addNewEmbeddable< - FilterableContainerInput, - EmbeddableOutput, - FilterableContainer - >(FILTERABLE_CONTAINER, { panels: {}, id: 'Node2' }); - - if (isErrorEmbeddable(node1) || isErrorEmbeddable(node2)) { - throw new Error(); - } - - const embeddable = await node2.addNewEmbeddable< - FilterableEmbeddableInput, - EmbeddableOutput, - FilterableEmbeddable - >(FILTERABLE_EMBEDDABLE, { id: 'leaf' }); - - if (isErrorEmbeddable(embeddable)) { - throw new Error(); - } - - const filter: any = { - $state: { store: esFilters.FilterStateStore.APP_STATE }, - meta: { - disabled: false, - negate: false, - alias: '', - }, - query: { match: { extension: { query: 'foo' } } }, - }; - - await applyFilterAction.execute({ embeddable, filters: [filter], trigger: applyFilterTrigger }); - expect(root.getInput().filters.length).toBe(1); - expect(node1.getInput().filters.length).toBe(1); - expect(embeddable.getInput().filters.length).toBe(1); - expect(node2.getInput().filters.length).toBe(1); -}); - -test('ApplyFilterAction is incompatible if the root container does not accept a filter as input', async () => { - const { doStart, setup } = testPlugin(); - - const factory = new FilterableEmbeddableFactory(); - setup.registerEmbeddableFactory(factory.type, factory); - const api = doStart(); - const applyFilterAction = createFilterAction(); - - const parent = new HelloWorldContainer({ id: 'root', panels: {} }, { - getEmbeddableFactory: api.getEmbeddableFactory, - } as any); - const embeddable = await parent.addNewEmbeddable< - FilterableContainerInput, - EmbeddableOutput, - FilterableContainer - >(FILTERABLE_EMBEDDABLE, { id: 'leaf' }); - - if (isErrorEmbeddable(embeddable)) { - throw new Error(); - } - - // @ts-ignore - expect(await applyFilterAction.isCompatible({ embeddable })).toBe(false); -}); - -test('trying to execute on incompatible context throws an error ', async () => { - const { doStart, setup } = testPlugin(); - - const factory = new FilterableEmbeddableFactory(); - setup.registerEmbeddableFactory(factory.type, factory); - - const api = doStart(); - const applyFilterAction = createFilterAction(); - - const parent = new HelloWorldContainer({ id: 'root', panels: {} }, { - getEmbeddableFactory: api.getEmbeddableFactory, - } as any); - - const embeddable = await parent.addNewEmbeddable< - FilterableContainerInput, - EmbeddableOutput, - FilterableContainer - >(FILTERABLE_EMBEDDABLE, { id: 'leaf' }); - - if (isErrorEmbeddable(embeddable)) { - throw new Error(); - } - - async function check() { - await applyFilterAction.execute({ embeddable } as any); - } - await expect(check()).rejects.toThrow(Error); -}); - -test('gets title', async () => { - const applyFilterAction = createFilterAction(); - expect(applyFilterAction.getDisplayName({} as any)).toBeDefined(); -}); diff --git a/src/plugins/newsfeed/public/components/flyout_list.tsx b/src/plugins/newsfeed/public/components/flyout_list.tsx index 6e9444c9501072..d4cdf72fee6135 100644 --- a/src/plugins/newsfeed/public/components/flyout_list.tsx +++ b/src/plugins/newsfeed/public/components/flyout_list.tsx @@ -30,6 +30,7 @@ import { EuiText, EuiBadge, EuiHeaderAlert, + EuiPortal, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { NewsfeedContext } from './newsfeed_header_nav_button'; @@ -42,70 +43,75 @@ export const NewsfeedFlyout = () => { const closeFlyout = useCallback(() => setFlyoutVisible(false), [setFlyoutVisible]); return ( - - - -

- -

- - - - {!newsFetchResult ? ( - - ) : newsFetchResult.feedItems.length > 0 ? ( - newsFetchResult.feedItems.map((item: NewsfeedItem) => { - return ( - - {item.linkText} - - } - date={item.publishOn.format('DD MMMM YYYY')} - badge={item.badge ? {item.badge} : undefined} + + + + +

+ - ); - }) - ) : ( - - )} - - - - - - - - - - {newsFetchResult ? ( - -

- -

-
- ) : null} -
-
-
- +

+
+
+ + {!newsFetchResult ? ( + + ) : newsFetchResult.feedItems.length > 0 ? ( + newsFetchResult.feedItems.map((item: NewsfeedItem) => { + return ( + + {item.linkText} + + } + date={item.publishOn.format('DD MMMM YYYY')} + badge={item.badge ? {item.badge} : undefined} + /> + ); + }) + ) : ( + + )} + + + + + + + + + + {newsFetchResult ? ( + +

+ +

+
+ ) : null} +
+
+
+
+
); }; diff --git a/src/plugins/newsfeed/public/components/newsfeed_header_nav_button.tsx b/src/plugins/newsfeed/public/components/newsfeed_header_nav_button.tsx index 628cfde18b0d54..79245446400865 100644 --- a/src/plugins/newsfeed/public/components/newsfeed_header_nav_button.tsx +++ b/src/plugins/newsfeed/public/components/newsfeed_header_nav_button.tsx @@ -19,7 +19,8 @@ import React, { useState, Fragment, useEffect } from 'react'; import * as Rx from 'rxjs'; -import { EuiHeaderSectionItemButton, EuiIcon, EuiNotificationBadge } from '@elastic/eui'; +import { EuiHeaderSectionItemButton, EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { NewsfeedFlyout } from './flyout_list'; import { FetchResult } from '../types'; @@ -65,15 +66,19 @@ export const NewsfeedNavButton = ({ apiFetchResult }: Props) => { aria-controls="keyPadMenu" aria-expanded={flyoutVisible} aria-haspopup="true" - aria-label="Newsfeed menu" + aria-label={ + showBadge + ? i18n.translate('newsfeed.headerButton.unreadAriaLabel', { + defaultMessage: 'Newsfeed menu - unread items available', + }) + : i18n.translate('newsfeed.headerButton.readAriaLabel', { + defaultMessage: 'Newsfeed menu - all items read', + }) + } + notification={showBadge ? true : null} onClick={showFlyout} > - {showBadge ? ( - - ▪ - - ) : null} {flyoutVisible ? : null} diff --git a/src/plugins/timelion/public/plugin.ts b/src/plugins/timelion/public/plugin.ts index 24d1b9eb3fb656..7656a808dfb00f 100644 --- a/src/plugins/timelion/public/plugin.ts +++ b/src/plugins/timelion/public/plugin.ts @@ -28,6 +28,7 @@ import { AppMountParameters, AppUpdater, ScopedHistory, + AppNavLinkStatus, } from '../../../core/public'; import { Panel } from './panels/panel'; import { initAngularBootstrap, KibanaLegacyStart } from '../../kibana_legacy/public'; @@ -35,7 +36,10 @@ import { createKbnUrlTracker } from '../../kibana_utils/public'; import { DataPublicPluginStart, esFilters, DataPublicPluginSetup } from '../../data/public'; import { NavigationPublicPluginStart } from '../../navigation/public'; import { VisualizationsStart } from '../../visualizations/public'; -import { VisTypeTimelionPluginStart } from '../../vis_type_timelion/public'; +import { + VisTypeTimelionPluginStart, + VisTypeTimelionPluginSetup, +} from '../../vis_type_timelion/public'; export interface TimelionPluginDependencies { data: DataPublicPluginStart; @@ -55,7 +59,13 @@ export class TimelionPlugin implements Plugin { this.initializerContext = initializerContext; } - public setup(core: CoreSetup, { data }: { data: DataPublicPluginSetup }) { + public setup( + core: CoreSetup, + { + data, + visTypeTimelion, + }: { data: DataPublicPluginSetup; visTypeTimelion: VisTypeTimelionPluginSetup } + ) { const timelionPanels: Map = new Map(); const { appMounted, appUnMounted, stop: stopUrlTracker } = createKbnUrlTracker({ @@ -93,7 +103,8 @@ export class TimelionPlugin implements Plugin { defaultPath: '#/', euiIconType: 'logoKibana', category: DEFAULT_APP_CATEGORIES.kibana, - updater$: this.appStateUpdater.asObservable(), + navLinkStatus: + visTypeTimelion.isUiEnabled === false ? AppNavLinkStatus.hidden : AppNavLinkStatus.default, mount: async (params: AppMountParameters) => { const [coreStart, pluginsStart] = await core.getStartServices(); this.currentHistory = params.history; diff --git a/src/plugins/vis_type_timelion/public/index.ts b/src/plugins/vis_type_timelion/public/index.ts index abfe345d8c6729..f5e781b95f2e25 100644 --- a/src/plugins/vis_type_timelion/public/index.ts +++ b/src/plugins/vis_type_timelion/public/index.ts @@ -31,4 +31,4 @@ export { generateTicksProvider } from './helpers/tick_generator'; export { DEFAULT_TIME_FORMAT, calculateInterval } from '../common/lib'; -export { VisTypeTimelionPluginStart } from './plugin'; +export { VisTypeTimelionPluginStart, VisTypeTimelionPluginSetup } from './plugin'; diff --git a/src/plugins/vis_type_timelion/public/plugin.ts b/src/plugins/vis_type_timelion/public/plugin.ts index 060fec04deb3f9..e2c7efec34c7f3 100644 --- a/src/plugins/vis_type_timelion/public/plugin.ts +++ b/src/plugins/vis_type_timelion/public/plugin.ts @@ -66,11 +66,16 @@ export interface VisTypeTimelionPluginStart { getArgValueSuggestions: typeof getArgValueSuggestions; } +/** @public */ +export interface VisTypeTimelionPluginSetup { + isUiEnabled: boolean; +} + /** @internal */ export class TimelionVisPlugin implements Plugin< - void, + VisTypeTimelionPluginSetup, VisTypeTimelionPluginStart, TimelionVisSetupDependencies, TimelionVisStartDependencies @@ -89,14 +94,15 @@ export class TimelionVisPlugin expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); visualizations.createReactVisualization(getTimelionVisDefinition(dependencies)); + + return { + isUiEnabled: this.initializerContext.config.get().ui.enabled, + }; } public start(core: CoreStart, plugins: TimelionVisStartDependencies) { setIndexPatterns(plugins.data.indexPatterns); setSavedObjectsClient(core.savedObjects.client); - if (this.initializerContext.config.get().ui.enabled === false) { - core.chrome.navLinks.update('timelion', { hidden: true }); - } return { getArgValueSuggestions, diff --git a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts index 777de89672bbe0..26a1792e3ec70e 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts @@ -16,15 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import { uniqBy } from 'lodash'; +import { uniqBy, get } from 'lodash'; import { first, map } from 'rxjs/operators'; import { KibanaRequest, RequestHandlerContext } from 'kibana/server'; -// @ts-ignore -import { getIndexPatternObject } from './vis_data/helpers/get_index_pattern'; -import { indexPatterns } from '../../../data/server'; import { Framework } from '../plugin'; -import { IndexPatternFieldDescriptor, IndexPatternsFetcher } from '../../../data/server'; +import { + indexPatterns, + IndexPatternFieldDescriptor, + IndexPatternsFetcher, +} from '../../../data/server'; import { ReqFacade } from './search_strategies/strategies/abstract_search_strategy'; export async function getFields( @@ -58,7 +59,15 @@ export async function getFields( .toPromise(); }, }; - const { indexPatternString } = await getIndexPatternObject(reqFacade, indexPattern); + let indexPatternString = indexPattern; + + if (!indexPatternString) { + const [, { data }] = await framework.core.getStartServices(); + const indexPatternsService = await data.indexPatterns.indexPatternsServiceFactory(request); + const defaultIndexPattern = await indexPatternsService.getDefault(); + indexPatternString = get(defaultIndexPattern, 'title', ''); + } + const { searchStrategy, capabilities, diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js index 6773ee482b098d..4dcc67dc46976f 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js @@ -49,6 +49,7 @@ describe('AbstractSearchStrategy', () => { expect(fields).toBe(mockedFields); expect(req.pre.indexPatternsService.getFieldsForWildcard).toHaveBeenCalledWith({ pattern: indexPattern, + fieldCapsOptions: { allowNoIndices: true }, }); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts index 92b7e6976962e8..2eb92b2b777e8b 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts @@ -84,6 +84,7 @@ export class AbstractSearchStrategy { return await indexPatternsService!.getFieldsForWildcard({ pattern: indexPattern, + fieldCapsOptions: { allowNoIndices: true }, }); } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js index e4bda194299df2..82a2ef66cb1c0d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js @@ -17,12 +17,10 @@ * under the License. */ -import { get } from 'lodash'; - const DEFAULT_TIME_FIELD = '@timestamp'; export function getIntervalAndTimefield(panel, series = {}, indexPatternObject) { - const getDefaultTimeField = () => get(indexPatternObject, 'timeFieldName', DEFAULT_TIME_FIELD); + const getDefaultTimeField = () => indexPatternObject?.timeFieldName ?? DEFAULT_TIME_FIELD; const timeField = (series.override_index_pattern && series.series_time_field) || diff --git a/test/functional/apps/discover/_errors.js b/test/functional/apps/discover/_errors.ts similarity index 92% rename from test/functional/apps/discover/_errors.js rename to test/functional/apps/discover/_errors.ts index 614059dc8ac942..9520d652a65d56 100644 --- a/test/functional/apps/discover/_errors.js +++ b/test/functional/apps/discover/_errors.ts @@ -18,8 +18,9 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService, getPageObjects }) { +export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common', 'discover', 'timePicker']); diff --git a/test/functional/page_objects/newsfeed_page.ts b/test/functional/page_objects/newsfeed_page.ts index 52267138819abd..6e5e8015892955 100644 --- a/test/functional/page_objects/newsfeed_page.ts +++ b/test/functional/page_objects/newsfeed_page.ts @@ -21,6 +21,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function NewsfeedPageProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); + const find = getService('find'); const retry = getService('retry'); const flyout = getService('flyout'); const testSubjects = getService('testSubjects'); @@ -49,7 +50,7 @@ export function NewsfeedPageProvider({ getService, getPageObjects }: FtrProvider } async getRedButtonSign() { - return await testSubjects.exists('showBadgeNews'); + return await find.existsByCssSelector('.euiHeaderSectionItemButton__notification--dot'); } async getNewsfeedList() { diff --git a/x-pack/plugins/actions/common/types.ts b/x-pack/plugins/actions/common/types.ts index 49e8f3e80b14a0..41ec4d2a88e9f4 100644 --- a/x-pack/plugins/actions/common/types.ts +++ b/x-pack/plugins/actions/common/types.ts @@ -24,3 +24,13 @@ export interface ActionResult { config: Record; isPreconfigured: boolean; } + +// the result returned from an action type executor function +export interface ActionTypeExecutorResult { + actionId: string; + status: 'ok' | 'error'; + message?: string; + serviceMessage?: string; + data?: Data; + retry?: null | boolean | Date; +} diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts index 7a0e24521a1c6e..3d92d5ebf33fc2 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts @@ -284,4 +284,47 @@ describe('execute()', () => { ] `); }); + + test('resolves with an error when an error occurs in the indexing operation', async () => { + const secrets = {}; + // minimal params + const config = { index: 'index-value', refresh: false, executionTimeField: null }; + const params = { + documents: [{ '': 'bob' }], + }; + + const actionId = 'some-id'; + + services.callCluster.mockResolvedValue({ + took: 0, + errors: true, + items: [ + { + index: { + _index: 'indexme', + _id: '7buTjHQB0SuNSiS9Hayt', + status: 400, + error: { + type: 'mapper_parsing_exception', + reason: 'failed to parse', + caused_by: { + type: 'illegal_argument_exception', + reason: 'field name cannot be an empty string', + }, + }, + }, + }, + ], + }); + + expect(await actionType.executor({ actionId, config, secrets, params, services })) + .toMatchInlineSnapshot(` + Object { + "actionId": "some-id", + "message": "error indexing documents", + "serviceMessage": "failed to parse (field name cannot be an empty string)", + "status": "error", + } + `); + }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts index 53bf75651b1e5b..868c07b775c78f 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { curry } from 'lodash'; +import { curry, find } from 'lodash'; import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; @@ -85,21 +85,39 @@ async function executor( refresh: config.refresh, }; - let result; try { - result = await services.callCluster('bulk', bulkParams); + const result = await services.callCluster('bulk', bulkParams); + + const err = find(result.items, 'index.error.reason'); + if (err) { + return wrapErr( + `${err.index.error!.reason}${ + err.index.error?.caused_by ? ` (${err.index.error?.caused_by?.reason})` : '' + }`, + actionId, + logger + ); + } + + return { status: 'ok', data: result, actionId }; } catch (err) { - const message = i18n.translate('xpack.actions.builtin.esIndex.errorIndexingErrorMessage', { - defaultMessage: 'error indexing documents', - }); - logger.error(`error indexing documents: ${err.message}`); - return { - status: 'error', - actionId, - message, - serviceMessage: err.message, - }; + return wrapErr(err.message, actionId, logger); } +} - return { status: 'ok', data: result, actionId }; +function wrapErr( + errMessage: string, + actionId: string, + logger: Logger +): ActionTypeExecutorResult { + const message = i18n.translate('xpack.actions.builtin.esIndex.errorIndexingErrorMessage', { + defaultMessage: 'error indexing documents', + }); + logger.error(`error indexing documents: ${errMessage}`); + return { + status: 'error', + actionId, + message, + serviceMessage: errMessage, + }; } diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index 3e92ca331bb93e..a23a2b08932613 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -15,6 +15,8 @@ import { SavedObjectsClientContract, SavedObjectAttributes, } from '../../../../src/core/server'; +import { ActionTypeExecutorResult } from '../common'; +export { ActionTypeExecutorResult } from '../common'; export type WithoutQueryAndParams = Pick>; export type GetServicesFunction = (request: KibanaRequest) => Services; @@ -80,16 +82,6 @@ export interface FindActionResult extends ActionResult { referencedByCount: number; } -// the result returned from an action type executor function -export interface ActionTypeExecutorResult { - actionId: string; - status: 'ok' | 'error'; - message?: string; - serviceMessage?: string; - data?: Data; - retry?: null | boolean | Date; -} - // signature of the action type executor function export type ExecutorType = ( options: ActionTypeExecutorOptions diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 5be684eca4651a..7ea3f83d747c0c 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pickBy, mapValues, omit, without } from 'lodash'; +import { pickBy, mapValues, without } from 'lodash'; import { Logger, KibanaRequest } from '../../../../../src/core/server'; import { TaskRunnerContext } from './task_runner_factory'; import { ConcreteTaskInstance } from '../../../task_manager/server'; @@ -228,12 +228,13 @@ export class TaskRunner { }); if (!muteAll) { - const enabledAlertInstances = omit(instancesWithScheduledActions, ...mutedInstanceIds); + const mutedInstanceIdsSet = new Set(mutedInstanceIds); await Promise.all( - Object.entries(enabledAlertInstances) + Object.entries(instancesWithScheduledActions) .filter( - ([, alertInstance]: [string, AlertInstance]) => !alertInstance.isThrottled(throttle) + ([alertInstanceName, alertInstance]: [string, AlertInstance]) => + !alertInstance.isThrottled(throttle) && !mutedInstanceIdsSet.has(alertInstanceName) ) .map(([id, alertInstance]: [string, AlertInstance]) => this.executeAlertInstance(id, alertInstance, executionHandler) diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap index 2044053e049f16..2962a5fd2df3b1 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap @@ -172,7 +172,7 @@ Array [ }, Object { "key": "transaction_max_spans", - "max": 32000, + "max": undefined, "min": 0, "type": "integer", "validationName": "integerRt", diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts index 95892f435e8f9c..e777e1fd09d0bc 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts @@ -177,7 +177,6 @@ export const generalSettings: RawSettingDefinition[] = [ key: 'transaction_max_spans', type: 'integer', min: 0, - max: 32000, defaultValue: '500', label: i18n.translate('xpack.apm.agentConfig.transactionMaxSpans.label', { defaultMessage: 'Transaction max spans', diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx index c76be19edfe473..904144dec6de93 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx @@ -33,7 +33,10 @@ import { ChartWrapper } from '../ChartWrapper'; import { I18LABELS } from '../translations'; interface Props { - data?: Array>; + data?: { + topItems: string[]; + items: Array>; + }; loading: boolean; } @@ -68,15 +71,9 @@ export function PageViewsChart({ data, loading }: Props) { }); }; - let breakdownAccessors: Set = new Set(); - if (data && data.length > 0) { - data.forEach((item) => { - breakdownAccessors = new Set([ - ...Array.from(breakdownAccessors), - ...Object.keys(item).filter((key) => key !== 'x'), - ]); - }); - } + const breakdownAccessors = data?.topItems?.length ? data?.topItems : ['y']; + + const [darkMode] = useUiSetting$('theme:darkMode'); const customSeriesNaming: SeriesNameFn = ({ yAccessor }) => { if (yAccessor === 'y') { @@ -86,8 +83,6 @@ export function PageViewsChart({ data, loading }: Props) { return yAccessor; }; - const [darkMode] = useUiSetting$('theme:darkMode'); - return ( {(!loading || data) && ( @@ -115,7 +110,8 @@ export function PageViewsChart({ data, loading }: Props) { id="page_views" title={I18LABELS.pageViews} position={Position.Left} - tickFormat={(d) => numeral(d).format('0a')} + tickFormat={(d) => numeral(d).format('0')} + labelFormat={(d) => numeral(d).format('0a')} /> diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx deleted file mode 100644 index b468470e3a17d8..00000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/BetaBadge.tsx +++ /dev/null @@ -1,36 +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 { EuiBetaBadge } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import styled from 'styled-components'; - -const BetaBadgeContainer = styled.div` - right: ${({ theme }) => theme.eui.gutterTypes.gutterMedium}; - position: absolute; - top: ${({ theme }) => theme.eui.gutterTypes.gutterSmall}; - z-index: 1; /* The element containing the cytoscape canvas has z-index = 0. */ -`; - -export function BetaBadge() { - return ( - - - - ); -} diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx index cb5a57e9ab9fba..bb450131bdfb88 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { useTheme } from '../../../hooks/useTheme'; +import React from 'react'; +import { useTrackPageview } from '../../../../../observability/public'; import { invalidLicenseMessage, isActivePlatinumLicense, } from '../../../../common/service_map'; import { useFetcher } from '../../../hooks/useFetcher'; import { useLicense } from '../../../hooks/useLicense'; +import { useTheme } from '../../../hooks/useTheme'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { LicensePrompt } from '../../shared/LicensePrompt'; @@ -22,8 +23,6 @@ import { getCytoscapeDivStyle } from './cytoscapeOptions'; import { EmptyBanner } from './EmptyBanner'; import { Popover } from './Popover'; import { useRefDimensions } from './useRefDimensions'; -import { BetaBadge } from './BetaBadge'; -import { useTrackPageview } from '../../../../../observability/public'; interface ServiceMapProps { serviceName?: string; @@ -80,7 +79,6 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { style={getCytoscapeDivStyle(theme)} > - {serviceName && } @@ -96,7 +94,7 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { grow={false} style={{ width: 600, textAlign: 'center' as const }} > - + ); diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/ClientSideMonitoringCallout.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/ClientSideMonitoringCallout.tsx new file mode 100644 index 00000000000000..b6938b211994d6 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/ClientSideMonitoringCallout.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiButton, EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; + +export function ClientSideMonitoringCallout() { + const { core } = useApmPluginContext(); + const clientSideMonitoringHref = core.http.basePath.prepend(`/app/csm`); + + return ( + + + {i18n.translate( + 'xpack.apm.transactionOverview.clientSideMonitoring.calloutText', + { + defaultMessage: + 'We are beyond excited to introduce a new experience for analyzing the user experience metrics specifically for your RUM services. It provides insights into the core vitals and visitor breakdown by browser and location. The app is always available in the left sidebar among the other Observability views.', + } + )} + + + + {i18n.translate( + 'xpack.apm.transactionOverview.clientSideMonitoring.linkLabel', + { defaultMessage: 'Take me there' } + )} + + + ); +} 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 3e32b0ec23b13d..7c887da6dc5e6a 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx @@ -35,6 +35,8 @@ import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { TransactionTypeFilter } from '../../shared/LocalUIFilters/TransactionTypeFilter'; import { TransactionList } from './TransactionList'; import { useRedirect } from './useRedirect'; +import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; +import { ClientSideMonitoringCallout } from './ClientSideMonitoringCallout'; function getRedirectLocation({ urlParams, @@ -125,6 +127,12 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { + {transactionType === TRANSACTION_PAGE_LOAD && ( + <> + + + + )} { ); expect(href).toMatchInlineSnapshot( - `"/basepath/app/ml#/timeseriesexplorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now-4h))&_a=(mlTimeSeriesExplorer:(entities:(service.name:opbeans-test,transaction.type:request),zoom:(from:now/w,to:now-4h)))"` + `"/basepath/app/ml#/timeseriesexplorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:0),time:(from:now/w,to:now-4h))&_a=(mlTimeSeriesExplorer:(entities:(service.name:opbeans-test,transaction.type:request)))"` ); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.test.ts b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.test.ts index 66f3903ba849b0..d84f55af993aa2 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.test.ts +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.test.ts @@ -28,7 +28,7 @@ describe('useTimeSeriesExplorerHref', () => { }); expect(href).toMatchInlineSnapshot( - `"/app/ml#/timeseriesexplorer?_g=(ml:(jobIds:!(apm-production-485b-high_mean_transaction_duration)),refreshInterval:(pause:!t,value:10000),time:(from:'2020-07-29T17:27:29.000Z',to:'2020-07-29T18:45:00.000Z'))&_a=(mlTimeSeriesExplorer:(entities:(service.name:opbeans-java,transaction.type:request),zoom:(from:'2020-07-29T17:27:29.000Z',to:'2020-07-29T18:45:00.000Z')))"` + `"/app/ml#/timeseriesexplorer?_g=(ml:(jobIds:!(apm-production-485b-high_mean_transaction_duration)),refreshInterval:(pause:!t,value:10000),time:(from:'2020-07-29T17:27:29.000Z',to:'2020-07-29T18:45:00.000Z'))&_a=(mlTimeSeriesExplorer:(entities:(service.name:opbeans-java,transaction.type:request)))"` ); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts index 3b60962d797edc..0cb87a4f515b6e 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts @@ -39,7 +39,6 @@ export function useTimeSeriesExplorerHref({ 'service.name': serviceName, 'transaction.type': transactionType, }, - zoom: time, }, }), } diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.test.tsx deleted file mode 100644 index 9f112475a4a78f..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.test.tsx +++ /dev/null @@ -1,24 +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 from 'react'; -import { shallow } from 'enzyme'; -import { BrowserLineChart } from './BrowserLineChart'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; - -describe('BrowserLineChart', () => { - describe('render', () => { - it('renders', () => { - expect(() => - shallow( - - - - ) - ).not.toThrowError(); - }); - }); -}); diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.tsx deleted file mode 100644 index 40caf351559183..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.tsx +++ /dev/null @@ -1,45 +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 { EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { useAvgDurationByBrowser } from '../../../../hooks/useAvgDurationByBrowser'; -import { getDurationFormatter } from '../../../../utils/formatters'; -import { - getResponseTimeTickFormatter, - getResponseTimeTooltipFormatter, - getMaxY, -} from './helper'; -import { TransactionLineChart } from './TransactionLineChart'; - -export function BrowserLineChart() { - const { data } = useAvgDurationByBrowser(); - const maxY = getMaxY(data); - const formatter = getDurationFormatter(maxY); - const formatTooltipValue = getResponseTimeTooltipFormatter(formatter); - const tickFormatY = getResponseTimeTickFormatter(formatter); - - return ( - <> - - - {i18n.translate( - 'xpack.apm.metrics.pageLoadCharts.avgPageLoadByBrowser', - { - defaultMessage: 'Avg. page load duration distribution by browser', - } - )} - - - - - ); -} diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/ChoroplethToolTip.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/ChoroplethToolTip.tsx deleted file mode 100644 index 69d4e8109dfbff..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/ChoroplethToolTip.tsx +++ /dev/null @@ -1,48 +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 from 'react'; -import { i18n } from '@kbn/i18n'; -import { asDuration, asInteger } from '../../../../../utils/formatters'; -import { fontSizes } from '../../../../../style/variables'; - -export function ChoroplethToolTip({ - name, - value, - docCount, -}: { - name: string; - value: number; - docCount: number; -}) { - return ( -
-
{name}
-
- {i18n.translate( - 'xpack.apm.metrics.durationByCountryMap.RegionMapChart.ToolTip.avgPageLoadDuration', - { - defaultMessage: 'Avg. page load duration:', - } - )} -
-
- {asDuration(value)} -
-
- ( - {i18n.translate( - 'xpack.apm.metrics.durationByCountryMap.RegionMapChart.ToolTip.countPageLoads', - { - values: { docCount: asInteger(docCount) }, - defaultMessage: '{docCount} page loads', - } - )} - ) -
-
- ); -} diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/index.tsx deleted file mode 100644 index 965cb2ae4f50ad..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/index.tsx +++ /dev/null @@ -1,270 +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, { - useState, - useEffect, - useRef, - useCallback, - useMemo, -} from 'react'; -import { Map, NavigationControl, Popup } from 'mapbox-gl'; -import 'mapbox-gl/dist/mapbox-gl.css'; -import { shade, tint } from 'polished'; -import { EuiTheme } from '../../../../../../../observability/public'; -import { useTheme } from '../../../../../hooks/useTheme'; -import { ChoroplethToolTip } from './ChoroplethToolTip'; - -interface ChoroplethItem { - key: string; - value: number; - docCount: number; -} - -interface Tooltip { - name: string; - value: number; - docCount: number; -} - -interface WorldCountryFeatureProperties { - name: string; - iso2: string; - iso3: string; -} - -interface Props { - items: ChoroplethItem[]; -} - -const CHOROPLETH_LAYER_ID = 'choropleth_layer'; -const CHOROPLETH_POLYGONS_SOURCE_ID = 'choropleth_polygons'; -const GEOJSON_KEY_PROPERTY = 'iso2'; -const MAPBOX_STYLE = - 'https://tiles.maps.elastic.co/styles/osm-bright-desaturated/style.json'; -const GEOJSON_SOURCE = - 'https://vector.maps.elastic.co/files/world_countries_v1.geo.json?elastic_tile_service_tos=agree&my_app_name=ems-landing&my_app_version=7.2.0'; - -export function getProgressionColor(scale: number, theme: EuiTheme) { - const baseColor = theme.eui.euiColorPrimary; - const adjustedScale = 0.75 * scale + 0.05; // prevents pure black & white as min/max colors. - if (adjustedScale < 0.5) { - return tint(adjustedScale * 2, baseColor); - } - if (adjustedScale > 0.5) { - return shade(1 - (adjustedScale - 0.5) * 2, baseColor); - } - return baseColor; -} - -const getMin = (items: ChoroplethItem[]) => - Math.min(...items.map((item) => item.value)); - -const getMax = (items: ChoroplethItem[]) => - Math.max(...items.map((item) => item.value)); - -export function ChoroplethMap(props: Props) { - const theme = useTheme(); - const { items } = props; - const containerRef = useRef(null); - const [map, setMap] = useState(null); - const popupRef = useRef(null); - const popupContainerRef = useRef(null); - const [tooltipState, setTooltipState] = useState(null); - const [min, max] = useMemo(() => [getMin(items), getMax(items)], [items]); - - // converts an item value to a scaled value between 0 and 1 - const getValueScale = useCallback( - (value: number) => (value - min) / (max - min), - [max, min] - ); - - const controlScrollZoomOnWheel = useCallback((event: WheelEvent) => { - if (event.ctrlKey || event.metaKey) { - event.preventDefault(); - } else { - event.stopPropagation(); - } - }, []); - - // side effect creates a new mouseover handler referencing new component state - // and replaces the old one stored in `updateTooltipStateOnMousemoveRef` - useEffect(() => { - const updateTooltipStateOnMousemove = (event: mapboxgl.MapMouseEvent) => { - const isMapQueryable = - map && - popupRef.current && - items.length && - map.getLayer(CHOROPLETH_LAYER_ID); - - if (!isMapQueryable) { - return; - } - (popupRef.current as Popup).setLngLat(event.lngLat); - const hoverFeatures = (map as Map).queryRenderedFeatures(event.point, { - layers: [CHOROPLETH_LAYER_ID], - }); - - if (tooltipState && hoverFeatures.length === 0) { - return setTooltipState(null); - } - - const featureProperties = hoverFeatures[0] - .properties as WorldCountryFeatureProperties; - - if (tooltipState && tooltipState.name === featureProperties.name) { - return; - } - - const item = items.find( - ({ key }) => - featureProperties && key === featureProperties[GEOJSON_KEY_PROPERTY] - ); - - if (item) { - return setTooltipState({ - name: featureProperties.name, - value: item.value, - docCount: item.docCount, - }); - } - - setTooltipState(null); - }; - updateTooltipStateOnMousemoveRef.current = updateTooltipStateOnMousemove; - }, [map, items, tooltipState]); - - const updateTooltipStateOnMousemoveRef = useRef( - (_event: mapboxgl.MapMouseEvent & mapboxgl.EventData) => {} - ); - - // initialization side effect, only runs once - useEffect(() => { - if (containerRef.current === null) { - return; - } - - // set up Map object - const mapboxMap = new Map({ - attributionControl: false, - container: containerRef.current, - dragRotate: false, - touchZoomRotate: false, - zoom: 0.85, - center: { lng: 0, lat: 30 }, - style: MAPBOX_STYLE, - }); - - mapboxMap.addControl( - new NavigationControl({ showCompass: false }), - 'top-left' - ); - - // set up Popup object - popupRef.current = new Popup({ - closeButton: false, - closeOnClick: false, - }); - - // always use the current handler which changes with component state - mapboxMap.on('mousemove', (...args) => - updateTooltipStateOnMousemoveRef.current(...args) - ); - mapboxMap.on('mouseout', () => { - setTooltipState(null); - }); - - // only scroll zoom when key is pressed - const canvasElement = mapboxMap.getCanvas(); - canvasElement.addEventListener('wheel', controlScrollZoomOnWheel); - - mapboxMap.on('load', () => { - mapboxMap.addSource(CHOROPLETH_POLYGONS_SOURCE_ID, { - type: 'geojson', - data: GEOJSON_SOURCE, - }); - setMap(mapboxMap); - }); - - // cleanup function called when component unmounts - return () => { - canvasElement.removeEventListener('wheel', controlScrollZoomOnWheel); - }; - }, [controlScrollZoomOnWheel]); - - // side effect replaces choropleth layer with new one on items changes - useEffect(() => { - if (!map) { - return; - } - - // find first symbol layer to place new layer in correct order - const symbolLayer = (map.getStyle().layers || []).find( - ({ type }) => type === 'symbol' - ); - - if (map.getLayer(CHOROPLETH_LAYER_ID)) { - map.removeLayer(CHOROPLETH_LAYER_ID); - } - - if (items.length === 0) { - return; - } - - const stops = items.map(({ key, value }) => [ - key, - getProgressionColor(getValueScale(value), theme), - ]); - - const fillColor: mapboxgl.FillPaint['fill-color'] = { - property: GEOJSON_KEY_PROPERTY, - stops, - type: 'categorical', - default: 'transparent', - }; - - map.addLayer( - { - id: CHOROPLETH_LAYER_ID, - type: 'fill', - source: CHOROPLETH_POLYGONS_SOURCE_ID, - layout: {}, - paint: { - 'fill-opacity': 0.75, - 'fill-color': fillColor, - }, - }, - symbolLayer ? symbolLayer.id : undefined - ); - }, [map, items, theme, getValueScale]); - - // side effect to only render the Popup when hovering a region with a matching item - useEffect(() => { - if (!(popupContainerRef.current && map && popupRef.current)) { - return; - } - if (tooltipState) { - popupRef.current.setDOMContent(popupContainerRef.current).addTo(map); - if (popupContainerRef.current.parentElement) { - popupContainerRef.current.parentElement.style.pointerEvents = 'none'; - } - } else { - popupRef.current.remove(); - } - }, [map, tooltipState]); - - // render map container and tooltip in a hidden container - return ( -
-
-
-
- {tooltipState ? : null} -
-
-
- ); -} diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/DurationByCountryMap/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/DurationByCountryMap/index.tsx deleted file mode 100644 index 2dd3d058e98b8a..00000000000000 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/DurationByCountryMap/index.tsx +++ /dev/null @@ -1,33 +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 { EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { useAvgDurationByCountry } from '../../../../../hooks/useAvgDurationByCountry'; - -import { ChoroplethMap } from '../ChoroplethMap'; - -export function DurationByCountryMap() { - const { data } = useAvgDurationByCountry(); - - return ( - <> - {' '} - - - {i18n.translate( - 'xpack.apm.metrics.durationByCountryMap.avgPageLoadByCountryLabel', - { - defaultMessage: 'Avg. page load duration distribution by country', - } - )} - - - - - ); -} diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index 6ba080a07b9d38..30ee0ba3eaa1f0 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -26,8 +26,6 @@ import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { ITransactionChartData } from '../../../../selectors/chartSelectors'; import { asDecimal, tpmUnit } from '../../../../utils/formatters'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; -import { BrowserLineChart } from './BrowserLineChart'; -import { DurationByCountryMap } from './DurationByCountryMap'; import { ErroneousTransactionsRateChart } from '../ErroneousTransactionsRateChart'; import { TransactionBreakdown } from '../../TransactionBreakdown'; import { @@ -120,24 +118,6 @@ export function TransactionCharts({ - - {transactionType === TRANSACTION_PAGE_LOAD && ( - <> - - - - - - - - - - - - - - - )} ); } diff --git a/x-pack/plugins/apm/public/hooks/useAvgDurationByBrowser.test.tsx b/x-pack/plugins/apm/public/hooks/useAvgDurationByBrowser.test.tsx deleted file mode 100644 index bb947e307437eb..00000000000000 --- a/x-pack/plugins/apm/public/hooks/useAvgDurationByBrowser.test.tsx +++ /dev/null @@ -1,41 +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 { renderHook } from '@testing-library/react-hooks'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; -import * as useFetcherModule from './useFetcher'; -import { useAvgDurationByBrowser } from './useAvgDurationByBrowser'; -import React, { ReactNode } from 'react'; -import { MemoryRouter } from 'react-router-dom'; - -function Wrapper({ children }: { children?: ReactNode }) { - return {children}; -} - -describe('useAvgDurationByBrowser', () => { - it('returns data', () => { - const data = [ - { title: 'Other', data: [{ x: 1572530100000, y: 130010.8947368421 }] }, - ]; - jest.spyOn(useFetcherModule, 'useFetcher').mockReturnValueOnce({ - data, - refetch: () => {}, - status: 'success' as useFetcherModule.FETCH_STATUS, - }); - const { result } = renderHook(() => useAvgDurationByBrowser(), { - wrapper: Wrapper, - }); - - expect(result.current.data).toEqual([ - { - color: theme.euiColorVis0, - data: [{ x: 1572530100000, y: 130010.8947368421 }], - title: 'Other', - type: 'linemark', - }, - ]); - }); -}); diff --git a/x-pack/plugins/apm/public/hooks/useAvgDurationByBrowser.ts b/x-pack/plugins/apm/public/hooks/useAvgDurationByBrowser.ts deleted file mode 100644 index 78dc4210711ef6..00000000000000 --- a/x-pack/plugins/apm/public/hooks/useAvgDurationByBrowser.ts +++ /dev/null @@ -1,63 +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 theme from '@elastic/eui/dist/eui_theme_light.json'; -import { useParams } from 'react-router-dom'; -import { getVizColorForIndex } from '../../common/viz_colors'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { AvgDurationByBrowserAPIResponse } from '../../server/lib/transactions/avg_duration_by_browser'; -import { TimeSeries } from '../../typings/timeseries'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; - -function toTimeSeries(data?: AvgDurationByBrowserAPIResponse): TimeSeries[] { - if (!data) { - return []; - } - - return data.map((item, index) => { - return { - ...item, - color: getVizColorForIndex(index, theme), - type: 'linemark', - }; - }); -} - -export function useAvgDurationByBrowser() { - const { serviceName } = useParams<{ serviceName?: string }>(); - const { - urlParams: { start, end, transactionName }, - uiFilters, - } = useUrlParams(); - - const { data, error, status } = useFetcher( - (callApmApi) => { - if (serviceName && start && end) { - return callApmApi({ - pathname: - '/api/apm/services/{serviceName}/transaction_groups/avg_duration_by_browser', - params: { - path: { serviceName }, - query: { - start, - end, - transactionName, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, - [serviceName, start, end, transactionName, uiFilters] - ); - - return { - data: toTimeSeries(data), - status, - error, - }; -} diff --git a/x-pack/plugins/apm/public/hooks/useAvgDurationByCountry.ts b/x-pack/plugins/apm/public/hooks/useAvgDurationByCountry.ts deleted file mode 100644 index 983f949b72961b..00000000000000 --- a/x-pack/plugins/apm/public/hooks/useAvgDurationByCountry.ts +++ /dev/null @@ -1,43 +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 { useParams } from 'react-router-dom'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; - -export function useAvgDurationByCountry() { - const { serviceName } = useParams<{ serviceName?: string }>(); - const { - urlParams: { start, end, transactionName }, - uiFilters, - } = useUrlParams(); - - const { data = [], error, status } = useFetcher( - (callApmApi) => { - if (serviceName && start && end) { - return callApmApi({ - pathname: - '/api/apm/services/{serviceName}/transaction_groups/avg_duration_by_country', - params: { - path: { serviceName }, - query: { - start, - end, - uiFilters: JSON.stringify(uiFilters), - transactionName, - }, - }, - }); - } - }, - [serviceName, start, end, uiFilters, transactionName] - ); - - return { - data, - status, - error, - }; -} diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts index f25062c67f87ad..543aa911b0b1fd 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts @@ -51,6 +51,16 @@ export async function getPageViewTrends({ } : undefined, }, + ...(breakdownItem + ? { + topBreakdowns: { + terms: { + field: breakdownItem.fieldName, + size: 9, + }, + }, + } + : {}), }, }, }); @@ -59,25 +69,44 @@ export async function getPageViewTrends({ const response = await apmEventClient.search(params); + const { topBreakdowns } = response.aggregations ?? {}; + + // we are only displaying top 9 + const topItems: string[] = (topBreakdowns?.buckets ?? []).map( + ({ key }) => key as string + ); + const result = response.aggregations?.pageViews.buckets ?? []; - return result.map((bucket) => { - const { key: xVal, doc_count: bCount } = bucket; - const res: Record = { - x: xVal, - y: bCount, - }; - if ('breakdown' in bucket) { - const categoryBuckets = bucket.breakdown.buckets; - categoryBuckets.forEach(({ key, doc_count: docCount }) => { - if (key === 'Other') { - res[key + `(${breakdownItem?.name})`] = docCount; - } else { - res[key] = docCount; + return { + topItems, + items: result.map((bucket) => { + const { key: xVal, doc_count: bCount } = bucket; + const res: Record = { + x: xVal, + y: bCount, + }; + if ('breakdown' in bucket) { + let top9Count = 0; + const categoryBuckets = bucket.breakdown.buckets; + categoryBuckets.forEach(({ key, doc_count: docCount }) => { + if (topItems.includes(key as string)) { + if (res[key]) { + // if term is already in object, just add it to it + res[key] += docCount; + } else { + res[key] = docCount; + } + top9Count += docCount; + } + }); + // Top 9 plus others, get a diff from parent bucket total + if (bCount > top9Count) { + res.Other = bCount - top9Count; } - }); - } + } - return res; - }); + return res; + }), + }; } diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts index 5c183fd9150dd0..7c2137ce65d839 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts @@ -96,10 +96,12 @@ async function getErrorStats({ setup, serviceName, environment, + searchAggregatedTransactions, }: { setup: Options['setup']; serviceName: string; environment?: string; + searchAggregatedTransactions: boolean; }) { const setupWithBlankUiFilters = { ...setup, @@ -108,6 +110,7 @@ async function getErrorStats({ const { noHits, average } = await getErrorRate({ setup: setupWithBlankUiFilters, serviceName, + searchAggregatedTransactions, }); return { avgErrorRate: noHits ? null : average }; } diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index a65536df37bc85..431f11066aaff0 100644 --- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -277,6 +277,13 @@ Array [ "services": Object { "aggs": Object { "outcomes": Object { + "aggs": Object { + "count": Object { + "value_count": Object { + "field": "transaction.duration.us", + }, + }, + }, "terms": Object { "field": "event.outcome", }, @@ -284,6 +291,13 @@ Array [ "timeseries": Object { "aggs": Object { "outcomes": Object { + "aggs": Object { + "count": Object { + "value_count": Object { + "field": "transaction.duration.us", + }, + }, + }, "terms": Object { "field": "event.outcome", }, diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts index 17799203fe73b9..65bc3f7e47171e 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts @@ -14,7 +14,6 @@ import { EVENT_OUTCOME, } from '../../../../common/elasticsearch_fieldnames'; import { mergeProjection } from '../../../projections/util/merge_projection'; -import { ProcessorEvent } from '../../../../common/processor_event'; import { ServicesItemsSetup, ServicesItemsProjection, @@ -258,6 +257,7 @@ export const getTransactionRates = async ({ export const getTransactionErrorRates = async ({ setup, projection, + searchAggregatedTransactions, }: AggregationParams) => { const { apmEventClient, start, end } = setup; @@ -265,12 +265,25 @@ export const getTransactionErrorRates = async ({ terms: { field: EVENT_OUTCOME, }, + aggs: { + count: { + value_count: { + field: getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ), + }, + }, + }, }; const response = await apmEventClient.search( mergeProjection(projection, { apm: { - events: [ProcessorEvent.transaction], + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], }, body: { size: 0, @@ -319,11 +332,11 @@ export const getTransactionErrorRates = async ({ const successfulTransactions = outcomeResponse.buckets.find( (bucket) => bucket.key === EventOutcome.success - )?.doc_count ?? 0; + )?.count.value ?? 0; const failedTransactions = outcomeResponse.buckets.find( (bucket) => bucket.key === EventOutcome.failure - )?.doc_count ?? 0; + )?.count.value ?? 0; return failedTransactions / (successfulTransactions + failedTransactions); } diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts index 82595317342f15..3dc126c45d328a 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts @@ -11,7 +11,6 @@ import { SERVICE_NAME, EVENT_OUTCOME, } from '../../../common/elasticsearch_fieldnames'; -import { ProcessorEvent } from '../../../common/processor_event'; import { rangeFilter } from '../../../common/utils/range_filter'; import { Setup, @@ -19,17 +18,23 @@ import { SetupUIFilters, } from '../helpers/setup_request'; import { getBucketSize } from '../helpers/get_bucket_size'; +import { + getProcessorEventForAggregatedTransactions, + getTransactionDurationFieldForAggregatedTransactions, +} from '../helpers/aggregated_transactions'; export async function getErrorRate({ serviceName, transactionType, transactionName, setup, + searchAggregatedTransactions, }: { serviceName: string; transactionType?: string; transactionName?: string; setup: Setup & SetupTimeRange & SetupUIFilters; + searchAggregatedTransactions: boolean; }) { const { start, end, uiFiltersES, apmEventClient } = setup; @@ -53,7 +58,11 @@ export async function getErrorRate({ const params = { apm: { - events: [ProcessorEvent.transaction], + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], }, body: { size: 0, @@ -67,8 +76,19 @@ export async function getErrorRate({ extended_bounds: { min: start, max: end }, }, aggs: { - erroneous_transactions: { - filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, + [EVENT_OUTCOME]: { + terms: { + field: EVENT_OUTCOME, + }, + aggs: { + count: { + value_count: { + field: getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ), + }, + }, + }, }, }, }, @@ -81,18 +101,24 @@ export async function getErrorRate({ const noHits = resp.hits.total.value === 0; const erroneousTransactionsRate = - resp.aggregations?.total_transactions.buckets.map( - ({ - key, - doc_count: totalTransactions, - erroneous_transactions: erroneousTransactions, - }) => { - return { - x: key, - y: erroneousTransactions.doc_count / totalTransactions, - }; - } - ) || []; + resp.aggregations?.total_transactions.buckets.map((bucket) => { + const successful = + bucket[EVENT_OUTCOME].buckets.find( + (eventOutcomeBucket) => + eventOutcomeBucket.key === EventOutcome.success + )?.count.value ?? 0; + + const failed = + bucket[EVENT_OUTCOME].buckets.find( + (eventOutcomeBucket) => + eventOutcomeBucket.key === EventOutcome.failure + )?.count.value ?? 0; + + return { + x: bucket.key, + y: failed / (successful + failed), + }; + }) || []; const average = mean( erroneousTransactionsRate diff --git a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/__fixtures__/responses.ts b/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/__fixtures__/responses.ts deleted file mode 100644 index 44878aa6c1f2e6..00000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/__fixtures__/responses.ts +++ /dev/null @@ -1,76 +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 { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { APMBaseDoc } from '../../../../../typings/es_schemas/raw/apm_base_doc'; -import { - ESSearchResponse, - ESSearchRequest, -} from '../../../../../typings/elasticsearch'; - -export const response = ({ - hits: { - total: 599, - max_score: 0, - hits: [], - }, - took: 4, - timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - aggregations: { - user_agent_keys: { - buckets: [{ key: 'Firefox' }, { key: 'Other' }], - }, - browsers: { - buckets: [ - { - key_as_string: '2019-10-21T04:38:20.000-05:00', - key: 1571650700000, - doc_count: 0, - user_agent: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [], - }, - }, - { - key_as_string: '2019-10-21T04:40:00.000-05:00', - key: 1571650800000, - doc_count: 1, - user_agent: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { - key: 'Other', - doc_count: 1, - avg_duration: { - value: 860425.0, - }, - }, - { - key: 'Firefox', - doc_count: 10, - avg_duration: { - value: 86425.1, - }, - }, - ], - }, - }, - ], - }, - }, -} as unknown) as ESSearchResponse< - APMBaseDoc | Transaction, - ESSearchRequest, - { restTotalHitsAsInt: false } ->; diff --git a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.test.ts b/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.test.ts deleted file mode 100644 index aec124e4f46232..00000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.test.ts +++ /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. - */ - -import { - Setup, - SetupTimeRange, - SetupUIFilters, -} from '../../helpers/setup_request'; -import { fetcher } from './fetcher'; - -describe('fetcher', () => { - it('performs a search', async () => { - const search = jest.fn(); - const setup = ({ - apmEventClient: { search }, - indices: {}, - uiFiltersES: [], - } as unknown) as Setup & SetupTimeRange & SetupUIFilters; - - await fetcher({ - serviceName: 'testServiceName', - setup, - searchAggregatedTransactions: false, - }); - - expect(search).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.ts deleted file mode 100644 index d40fcaaa02f608..00000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.ts +++ /dev/null @@ -1,100 +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 { ESFilter } from '../../../../typings/elasticsearch'; -import { PromiseReturnType } from '../../../../../observability/typings/common'; -import { - SERVICE_NAME, - TRANSACTION_TYPE, - USER_AGENT_NAME, - TRANSACTION_NAME, -} from '../../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../../../../common/utils/range_filter'; -import { getBucketSize } from '../../helpers/get_bucket_size'; -import { Options } from '.'; -import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; -import { - getDocumentTypeFilterForAggregatedTransactions, - getTransactionDurationFieldForAggregatedTransactions, - getProcessorEventForAggregatedTransactions, -} from '../../helpers/aggregated_transactions'; - -export type ESResponse = PromiseReturnType; - -export function fetcher(options: Options) { - const { end, apmEventClient, start, uiFiltersES } = options.setup; - const { - serviceName, - searchAggregatedTransactions, - transactionName, - } = options; - const { intervalString } = getBucketSize(start, end); - - const transactionNameFilter = transactionName - ? [{ term: { [TRANSACTION_NAME]: transactionName } }] - : []; - - const filter: ESFilter[] = [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } }, - { range: rangeFilter(start, end) }, - ...getDocumentTypeFilterForAggregatedTransactions( - searchAggregatedTransactions - ), - ...uiFiltersES, - ...transactionNameFilter, - ]; - - const params = { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { bool: { filter } }, - aggs: { - user_agent_keys: { - terms: { - field: USER_AGENT_NAME, - }, - }, - browsers: { - date_histogram: { - extended_bounds: { - max: end, - min: start, - }, - field: '@timestamp', - fixed_interval: intervalString, - min_doc_count: 0, - }, - aggs: { - user_agent: { - terms: { - field: USER_AGENT_NAME, - }, - aggs: { - avg_duration: { - avg: { - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), - }, - }, - }, - }, - }, - }, - }, - }, - }; - - return apmEventClient.search(params); -} diff --git a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.test.ts b/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.test.ts deleted file mode 100644 index b8cea3a0322689..00000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.test.ts +++ /dev/null @@ -1,33 +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 { - getTransactionAvgDurationByBrowser, - Options, - AvgDurationByBrowserAPIResponse, -} from '.'; -import * as transformerModule from './transformer'; -import * as fetcherModule from './fetcher'; -import { response } from './__fixtures__/responses'; - -describe('getAvgDurationByBrowser', () => { - it('returns a transformed response', async () => { - const transformer = jest - .spyOn(transformerModule, 'transformer') - .mockReturnValueOnce(({} as unknown) as AvgDurationByBrowserAPIResponse); - const search = () => {}; - const options = ({ - setup: { client: { search }, indices: {}, uiFiltersES: [] }, - } as unknown) as Options; - jest - .spyOn<{ fetcher: any }, 'fetcher'>(fetcherModule, 'fetcher') - .mockResolvedValueOnce(response); - - await getTransactionAvgDurationByBrowser(options); - - expect(transformer).toHaveBeenCalledWith({ response }); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.ts b/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.ts deleted file mode 100644 index 2c259edaa26ab3..00000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.ts +++ /dev/null @@ -1,30 +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 { Coordinate } from '../../../../typings/timeseries'; -import { - Setup, - SetupTimeRange, - SetupUIFilters, -} from '../../helpers/setup_request'; -import { fetcher } from './fetcher'; -import { transformer } from './transformer'; - -export interface Options { - serviceName: string; - setup: Setup & SetupTimeRange & SetupUIFilters; - searchAggregatedTransactions: boolean; - transactionName?: string; -} - -export type AvgDurationByBrowserAPIResponse = Array<{ - data: Coordinate[]; - title: string; -}>; - -export async function getTransactionAvgDurationByBrowser(options: Options) { - return transformer({ response: await fetcher(options) }); -} diff --git a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.test.ts b/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.test.ts deleted file mode 100644 index 91ff2698ea5549..00000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.test.ts +++ /dev/null @@ -1,29 +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 { transformer } from './transformer'; -import { response } from './__fixtures__/responses'; - -describe('transformer', () => { - it('transforms', () => { - expect(transformer({ response })).toEqual([ - { - data: [ - { x: 1571650700000, y: undefined }, - { x: 1571650800000, y: 86425.1 }, - ], - title: 'Firefox', - }, - { - data: [ - { x: 1571650700000, y: undefined }, - { x: 1571650800000, y: 860425.0 }, - ], - title: 'Other', - }, - ]); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.ts b/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.ts deleted file mode 100644 index 5234af8cede667..00000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.ts +++ /dev/null @@ -1,53 +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 { ESResponse } from './fetcher'; -import { AvgDurationByBrowserAPIResponse } from '.'; -import { Coordinate } from '../../../../typings/timeseries'; - -export function transformer({ - response, -}: { - response: ESResponse; -}): AvgDurationByBrowserAPIResponse { - const allUserAgentKeys = new Set( - (response.aggregations?.user_agent_keys?.buckets ?? []).map(({ key }) => - key.toString() - ) - ); - const buckets = response.aggregations?.browsers?.buckets ?? []; - - const series = buckets.reduce<{ [key: string]: Coordinate[] }>( - (acc, next) => { - const userAgentBuckets = next.user_agent?.buckets ?? []; - const x = next.key; - const seenUserAgentKeys = new Set(); - - userAgentBuckets.map((userAgentBucket) => { - const key = userAgentBucket.key; - const y = userAgentBucket.avg_duration?.value; - - seenUserAgentKeys.add(key.toString()); - acc[key] = (acc[key] || []).concat({ x, y }); - }); - - const emptyUserAgents = new Set( - [...allUserAgentKeys].filter((key) => !seenUserAgentKeys.has(key)) - ); - - // If no user agent requests exist for this bucked, fill in the data with - // undefined - [...emptyUserAgents].map((key) => { - acc[key] = (acc[key] || []).concat({ x, y: undefined }); - }); - - return acc; - }, - {} - ); - - return Object.entries(series).map(([title, data]) => ({ title, data })); -} diff --git a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts b/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts deleted file mode 100644 index bc1e0af051ace7..00000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts +++ /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 { - CLIENT_GEO_COUNTRY_ISO_CODE, - SERVICE_NAME, - TRANSACTION_TYPE, - TRANSACTION_NAME, -} from '../../../../common/elasticsearch_fieldnames'; -import { - Setup, - SetupTimeRange, - SetupUIFilters, -} from '../../helpers/setup_request'; -import { rangeFilter } from '../../../../common/utils/range_filter'; -import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; -import { - getProcessorEventForAggregatedTransactions, - getTransactionDurationFieldForAggregatedTransactions, - getDocumentTypeFilterForAggregatedTransactions, -} from '../../helpers/aggregated_transactions'; - -export async function getTransactionAvgDurationByCountry({ - setup, - serviceName, - transactionName, - searchAggregatedTransactions, -}: { - setup: Setup & SetupTimeRange & SetupUIFilters; - serviceName: string; - transactionName?: string; - searchAggregatedTransactions: boolean; -}) { - const { uiFiltersES, apmEventClient, start, end } = setup; - const transactionNameFilter = transactionName - ? [{ term: { [TRANSACTION_NAME]: transactionName } }] - : []; - const params = { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - ...transactionNameFilter, - { term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } }, - { exists: { field: CLIENT_GEO_COUNTRY_ISO_CODE } }, - { range: rangeFilter(start, end) }, - ...uiFiltersES, - ...getDocumentTypeFilterForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - }, - aggs: { - country_code: { - terms: { - field: CLIENT_GEO_COUNTRY_ISO_CODE, - size: 500, - }, - aggs: { - count: { - value_count: { - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), - }, - }, - avg_duration: { - avg: { - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), - }, - }, - }, - }, - }, - }, - }; - - const resp = await apmEventClient.search(params); - - if (!resp.aggregations) { - return []; - } - - const buckets = resp.aggregations.country_code.buckets; - const avgDurationsByCountry = buckets.map( - ({ key, count, avg_duration: { value } }) => ({ - key: key as string, - docCount: count.value, - value: value === null ? 0 : value, - }) - ); - - return avgDurationsByCountry; -} 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 1230e8aa05c9f7..7d9a9ccc167e0a 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -46,8 +46,6 @@ import { transactionGroupsChartsRoute, transactionGroupsDistributionRoute, transactionGroupsRoute, - transactionGroupsAvgDurationByCountry, - transactionGroupsAvgDurationByBrowser, transactionSampleForGroupRoute, transactionGroupsErrorRateRoute, } from './transaction_groups'; @@ -139,8 +137,6 @@ const createApmApi = () => { .add(transactionGroupsChartsRoute) .add(transactionGroupsDistributionRoute) .add(transactionGroupsRoute) - .add(transactionGroupsAvgDurationByBrowser) - .add(transactionGroupsAvgDurationByCountry) .add(transactionSampleForGroupRoute) .add(transactionGroupsErrorRateRoute) diff --git a/x-pack/plugins/apm/server/routes/transaction_groups.ts b/x-pack/plugins/apm/server/routes/transaction_groups.ts index 3c512c1fe52780..10e917f385e719 100644 --- a/x-pack/plugins/apm/server/routes/transaction_groups.ts +++ b/x-pack/plugins/apm/server/routes/transaction_groups.ts @@ -12,8 +12,6 @@ import { getTransactionBreakdown } from '../lib/transactions/breakdown'; import { getTransactionGroupList } from '../lib/transaction_groups'; import { createRoute } from './create_route'; import { uiFiltersRt, rangeRt } from './default_api_types'; -import { getTransactionAvgDurationByBrowser } from '../lib/transactions/avg_duration_by_browser'; -import { getTransactionAvgDurationByCountry } from '../lib/transactions/avg_duration_by_country'; import { getTransactionSampleForGroup } from '../lib/transaction_groups/get_transaction_sample_for_group'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; import { getErrorRate } from '../lib/transaction_groups/get_error_rate'; @@ -168,68 +166,6 @@ export const transactionGroupsBreakdownRoute = createRoute(() => ({ }, })); -export const transactionGroupsAvgDurationByBrowser = createRoute(() => ({ - path: `/api/apm/services/{serviceName}/transaction_groups/avg_duration_by_browser`, - params: { - path: t.type({ - serviceName: t.string, - }), - query: t.intersection([ - t.partial({ - transactionName: t.string, - }), - uiFiltersRt, - rangeRt, - ]), - }, - handler: async ({ context, request }) => { - const setup = await setupRequest(context, request); - const { serviceName } = context.params.path; - const { transactionName } = context.params.query; - - const searchAggregatedTransactions = await getSearchAggregatedTransactions( - setup - ); - - return getTransactionAvgDurationByBrowser({ - serviceName, - setup, - searchAggregatedTransactions, - transactionName, - }); - }, -})); - -export const transactionGroupsAvgDurationByCountry = createRoute(() => ({ - path: `/api/apm/services/{serviceName}/transaction_groups/avg_duration_by_country`, - params: { - path: t.type({ - serviceName: t.string, - }), - query: t.intersection([ - uiFiltersRt, - rangeRt, - t.partial({ transactionName: t.string }), - ]), - }, - handler: async ({ context, request }) => { - const setup = await setupRequest(context, request); - const { serviceName } = context.params.path; - const { transactionName } = context.params.query; - - const searchAggregatedTransactions = await getSearchAggregatedTransactions( - setup - ); - - return getTransactionAvgDurationByCountry({ - serviceName, - transactionName, - setup, - searchAggregatedTransactions, - }); - }, -})); - export const transactionSampleForGroupRoute = createRoute(() => ({ path: `/api/apm/transaction_sample`, params: { @@ -274,11 +210,17 @@ export const transactionGroupsErrorRateRoute = createRoute(() => ({ const { params } = context; const { serviceName } = params.path; const { transactionType, transactionName } = params.query; + + const searchAggregatedTransactions = await getSearchAggregatedTransactions( + setup + ); + return getErrorRate({ serviceName, transactionType, transactionName, setup, + searchAggregatedTransactions, }); }, })); diff --git a/x-pack/plugins/audit_trail/server/client/audit_trail_client.test.ts b/x-pack/plugins/audit_trail/server/client/audit_trail_client.test.ts index 45a192e40c87bf..76ca3e56fe837d 100644 --- a/x-pack/plugins/audit_trail/server/client/audit_trail_client.test.ts +++ b/x-pack/plugins/audit_trail/server/client/audit_trail_client.test.ts @@ -24,7 +24,9 @@ describe('AuditTrailClient', () => { beforeEach(() => { event$ = new Subject(); client = new AuditTrailClient( - httpServerMock.createKibanaRequest({ kibanaRequestState: { requestId: 'request id alpha' } }), + httpServerMock.createKibanaRequest({ + kibanaRequestState: { requestId: 'request id alpha', requestUuid: 'ignore-me' }, + }), event$, deps ); diff --git a/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts b/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts index 4533383ebd80ea..939fc2c9c2ec2c 100644 --- a/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts +++ b/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts @@ -24,13 +24,12 @@ export const DEFAULT_INITIAL_APP_DATA = { }, appSearch: { accountId: 'some-id-string', - onBoardingComplete: true, + onboardingComplete: true, role: { id: 'account_id:somestring|user_oid:somestring', roleType: 'owner', ability: { accessAllEngines: true, - destroy: ['session'], manage: ['account_credentials', 'account_engines'], // etc edit: ['LocoMoco::Account'], // etc view: ['Engine'], // etc diff --git a/x-pack/plugins/enterprise_search/common/types/app_search.ts b/x-pack/plugins/enterprise_search/common/types/app_search.ts index 72259ecd2343d2..203b77834bc15d 100644 --- a/x-pack/plugins/enterprise_search/common/types/app_search.ts +++ b/x-pack/plugins/enterprise_search/common/types/app_search.ts @@ -6,21 +6,18 @@ export interface IAccount { accountId: string; - onBoardingComplete: boolean; - role: IRole; -} - -export interface IRole { - id: string; - roleType: string; - ability: { - accessAllEngines: boolean; - destroy: string[]; - manage: string[]; - edit: string[]; - view: string[]; - credentialTypes: string[]; - availableRoleTypes: string[]; + onboardingComplete: boolean; + role: { + id: string; + roleType: string; + ability: { + accessAllEngines: boolean; + manage: string[]; + edit: string[]; + view: string[]; + credentialTypes: string[]; + availableRoleTypes: string[]; + }; }; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts index bc31b7df5d971d..9410b9ef7cb03a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts @@ -17,6 +17,10 @@ describe('AppLogic', () => { const DEFAULT_VALUES = { hasInitialized: false, + account: {}, + configuredLimits: {}, + ilmEnabled: false, + myRole: {}, }; it('has expected default values', () => { @@ -29,7 +33,45 @@ describe('AppLogic', () => { expect(AppLogic.values).toEqual({ hasInitialized: true, + ilmEnabled: true, + configuredLimits: { + engine: { + maxDocumentByteSize: 102400, + maxEnginesPerMetaEngine: 15, + }, + }, + account: { + accountId: 'some-id-string', + onboardingComplete: true, + role: DEFAULT_INITIAL_APP_DATA.appSearch.role, + }, + myRole: expect.objectContaining({ + id: 'account_id:somestring|user_oid:somestring', + roleType: 'owner', + availableRoleTypes: ['owner', 'admin'], + credentialTypes: ['admin', 'private', 'search'], + canAccessAllEngines: true, + canViewAccountCredentials: true, + // Truncated for brevity - see utils/role/index.test.ts for full output + }), }); }); + + it('gracefully handles missing initial data', () => { + AppLogic.actions.initializeAppData({}); + + expect(AppLogic.values).toEqual({ + ...DEFAULT_VALUES, + hasInitialized: true, + }); + }); + }); + + describe('setOnboardingComplete()', () => { + it('sets true', () => { + expect(AppLogic.values.account.onboardingComplete).toBeFalsy(); + AppLogic.actions.setOnboardingComplete(); + expect(AppLogic.values.account.onboardingComplete).toEqual(true); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts index 9388d61041b13e..932e84af45c2bf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts @@ -7,18 +7,27 @@ import { kea, MakeLogicType } from 'kea'; import { IInitialAppData } from '../../../common/types'; +import { IConfiguredLimits, IAccount, IRole } from './types'; + +import { getRoleAbilities } from './utils/role'; export interface IAppValues { hasInitialized: boolean; + ilmEnabled: boolean; + configuredLimits: Partial; + account: Partial; + myRole: Partial; } export interface IAppActions { - initializeAppData(props: IInitialAppData): void; + initializeAppData(props: IInitialAppData): Required; + setOnboardingComplete(): boolean; } export const AppLogic = kea>({ path: ['enterprise_search', 'app_search', 'app_logic'], actions: { initializeAppData: (props) => props, + setOnboardingComplete: () => true, }, reducers: { hasInitialized: [ @@ -27,5 +36,33 @@ export const AppLogic = kea>({ initializeAppData: () => true, }, ], + account: [ + {}, + { + initializeAppData: (_, { appSearch: account }) => account || {}, + setOnboardingComplete: (account) => ({ + ...account, + onboardingComplete: true, + }), + }, + ], + configuredLimits: [ + {}, + { + initializeAppData: (_, { configuredLimits }) => configuredLimits?.appSearch || {}, + }, + ], + ilmEnabled: [ + false, + { + initializeAppData: (_, { ilmEnabled }) => !!ilmEnabled, + }, + ], + }, + selectors: { + myRole: [ + (selectors) => [selectors.account], + ({ role }) => (role ? getRoleAbilities(role) : {}), + ], }, }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx index 31c7680fd2f1c9..350bc97085d7be 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx @@ -46,7 +46,7 @@ describe('AppSearchUnconfigured', () => { describe('AppSearchConfigured', () => { beforeEach(() => { // Mock resets - (useValues as jest.Mock).mockImplementation(() => ({})); + (useValues as jest.Mock).mockImplementation(() => ({ myRole: {} })); (useActions as jest.Mock).mockImplementation(() => ({ initializeAppData: () => {} })); }); @@ -70,7 +70,7 @@ describe('AppSearchConfigured', () => { it('does not re-initialize app data', () => { const initializeAppData = jest.fn(); (useActions as jest.Mock).mockImplementation(() => ({ initializeAppData })); - (useValues as jest.Mock).mockImplementation(() => ({ hasInitialized: true })); + (useValues as jest.Mock).mockImplementation(() => ({ myRole: {}, hasInitialized: true })); shallow(); @@ -78,7 +78,7 @@ describe('AppSearchConfigured', () => { }); it('renders ErrorConnecting', () => { - (useValues as jest.Mock).mockImplementation(() => ({ errorConnecting: true })); + (useValues as jest.Mock).mockImplementation(() => ({ myRole: {}, errorConnecting: true })); const wrapper = shallow(); @@ -86,20 +86,55 @@ describe('AppSearchConfigured', () => { }); it('passes readOnlyMode state', () => { - (useValues as jest.Mock).mockImplementation(() => ({ readOnlyMode: true })); + (useValues as jest.Mock).mockImplementation(() => ({ myRole: {}, readOnlyMode: true })); const wrapper = shallow(); expect(wrapper.find(Layout).prop('readOnlyMode')).toEqual(true); }); + + describe('ability checks', () => { + // TODO: Use this section for routes wrapped in canViewX conditionals + // e.g., it('renders settings if a user can view settings') + }); }); describe('AppSearchNav', () => { - it('renders', () => { + it('renders with the Engines link', () => { const wrapper = shallow(); expect(wrapper.find(SideNav)).toHaveLength(1); - expect(wrapper.find(SideNavLink).first().prop('to')).toEqual('/engines'); + expect(wrapper.find(SideNavLink).prop('to')).toEqual('/engines'); + }); + + it('renders the Settings link', () => { + (useValues as jest.Mock).mockImplementation(() => ({ + myRole: { canViewSettings: true }, + })); + const wrapper = shallow(); + + expect(wrapper.find(SideNavLink).last().prop('to')).toEqual( + 'http://localhost:3002/as/settings/account' + ); + }); + + it('renders the Credentials link', () => { + (useValues as jest.Mock).mockImplementation(() => ({ + myRole: { canViewAccountCredentials: true }, + })); + const wrapper = shallow(); + + expect(wrapper.find(SideNavLink).last().prop('to')).toEqual( + 'http://localhost:3002/as/credentials' + ); + }); + + it('renders the Role Mappings link', () => { + (useValues as jest.Mock).mockImplementation(() => ({ + myRole: { canViewRoleMappings: true }, + })); + const wrapper = shallow(); + expect(wrapper.find(SideNavLink).last().prop('to')).toEqual( 'http://localhost:3002/as#/role-mappings' ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx index 643c4b5ccc8731..c848415daf6123 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx @@ -49,8 +49,8 @@ export const AppSearchUnconfigured: React.FC = () => ( ); export const AppSearchConfigured: React.FC = (props) => { - const { hasInitialized } = useValues(AppLogic); const { initializeAppData } = useActions(AppLogic); + const { hasInitialized } = useValues(AppLogic); const { errorConnecting, readOnlyMode } = useValues(HttpLogic); useEffect(() => { @@ -90,6 +90,10 @@ export const AppSearchNav: React.FC = () => { externalUrl: { getAppSearchUrl }, } = useContext(KibanaContext) as IKibanaContext; + const { + myRole: { canViewSettings, canViewAccountCredentials, canViewRoleMappings }, + } = useValues(AppLogic); + return ( @@ -97,21 +101,27 @@ export const AppSearchNav: React.FC = () => { defaultMessage: 'Engines', })} - - {i18n.translate('xpack.enterpriseSearch.appSearch.nav.settings', { - defaultMessage: 'Account Settings', - })} - - - {i18n.translate('xpack.enterpriseSearch.appSearch.nav.credentials', { - defaultMessage: 'Credentials', - })} - - - {i18n.translate('xpack.enterpriseSearch.appSearch.nav.roleMappings', { - defaultMessage: 'Role Mappings', - })} - + {canViewSettings && ( + + {i18n.translate('xpack.enterpriseSearch.appSearch.nav.settings', { + defaultMessage: 'Account Settings', + })} + + )} + {canViewAccountCredentials && ( + + {i18n.translate('xpack.enterpriseSearch.appSearch.nav.credentials', { + defaultMessage: 'Credentials', + })} + + )} + {canViewRoleMappings && ( + + {i18n.translate('xpack.enterpriseSearch.appSearch.nav.roleMappings', { + defaultMessage: 'Role Mappings', + })} + + )} ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts new file mode 100644 index 00000000000000..3cabc1051c74aa --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from '../../../common/types/app_search'; +export { IRole, TRole, TAbility } from './utils/role'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.test.ts new file mode 100644 index 00000000000000..a2eb3d8fbc90dd --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.test.ts @@ -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 { DEFAULT_INITIAL_APP_DATA } from '../../../../../common/__mocks__'; + +import { getRoleAbilities } from './'; + +describe('getRoleAbilities', () => { + const mockRole = DEFAULT_INITIAL_APP_DATA.appSearch.role; + + it('transforms server role data into a flat role obj with helper shorthands', () => { + expect(getRoleAbilities(mockRole)).toEqual({ + id: 'account_id:somestring|user_oid:somestring', + roleType: 'owner', + availableRoleTypes: ['owner', 'admin'], + credentialTypes: ['admin', 'private', 'search'], + canAccessAllEngines: true, + can: expect.any(Function), + // Has access + canViewAccountCredentials: true, + canManageEngines: true, + // Does not have access + canViewMetaEngines: false, + canViewEngineAnalytics: false, + canViewEngineApiLogs: false, + canViewEngineCrawler: false, + canViewEngineCredentials: false, + canViewEngineDocuments: false, + canViewEngineSchema: false, + canViewEngineQueryTester: false, + canViewMetaEngineSourceEngines: false, + canViewSettings: false, + canViewRoleMappings: false, + canManageMetaEngines: false, + canManageLogSettings: false, + canManageSettings: false, + canManageEngineCrawler: false, + canManageEngineDocuments: false, + canManageEngineSynonyms: false, + canManageEngineCredentials: false, + canManageEngineCurations: false, + canManageEngineRelevanceTuning: false, + canManageEngineReferenceUi: false, + canManageEngineResultSettings: false, + canManageEngineSchema: false, + canManageMetaEngineSourceEngines: false, + }); + }); + + describe('can()', () => { + it('sets view abilities to true if manage abilities are true', () => { + const role = { ...mockRole }; + role.ability.view = []; + role.ability.manage = ['account_settings']; + + const myRole = getRoleAbilities(role); + + expect(myRole.canViewSettings).toEqual(true); + expect(myRole.canManageSettings).toEqual(true); + }); + + it('returns false for invalid actions & subjects', () => { + const myRole = getRoleAbilities(mockRole); + + expect(myRole.can('hello' as any, 'world')).toEqual(false); + expect(myRole.can('edit', 'fakeSubject')).toEqual(false); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts new file mode 100644 index 00000000000000..409aef3cd42ec9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts @@ -0,0 +1,104 @@ +/* + * 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 { IAccount } from '../../types'; + +export type TRole = 'owner' | 'admin' | 'dev' | 'editor' | 'analyst'; +export type TAbility = 'manage' | 'edit' | 'view'; + +export interface IRole { + id: string; + roleType: TRole; + availableRoleTypes: TRole[]; + credentialTypes: string[]; + canAccessAllEngines: boolean; + can(action: TAbility, subject: string): boolean; + canViewMetaEngines: boolean; + canViewAccountCredentials: boolean; + canViewEngineAnalytics: boolean; + canViewEngineApiLogs: boolean; + canViewEngineCrawler: boolean; + canViewEngineCredentials: boolean; + canViewEngineDocuments: boolean; + canViewEngineSchema: boolean; + canViewEngineQueryTester: boolean; + canViewMetaEngineSourceEngines: boolean; + canViewSettings: boolean; + canViewRoleMappings: boolean; + canManageEngines: boolean; + canManageMetaEngines: boolean; + canManageLogSettings: boolean; + canManageSettings: boolean; + canManageEngineCrawler: boolean; + canManageEngineDocuments: boolean; + canManageEngineSynonyms: boolean; + canManageEngineCredentials: boolean; + canManageEngineCurations: boolean; + canManageEngineRelevanceTuning: boolean; + canManageEngineReferenceUi: boolean; + canManageEngineResultSettings: boolean; + canManageEngineSchema: boolean; + canManageMetaEngineSourceEngines: boolean; +} + +/** + * Transforms the `role` data we receive from the Enterprise Search + * server into a more convenient format for front-end use + */ +export const getRoleAbilities = (role: IAccount['role']): IRole => { + // Role ability function helpers + const myRole = { + can: (action: TAbility, subject: string): boolean => { + return ( + role?.ability?.manage?.includes(subject) || + (Array.isArray(role.ability[action]) && role.ability[action].includes(subject)) + ); + }, + // TODO: canHaveScopedEngines fn + }; + + // Clone top-level role props, and move some props out of `ability` and into the top-level for convenience + const topLevelProps = { + id: role.id, + roleType: role.roleType as TRole, + availableRoleTypes: role.ability.availableRoleTypes as TRole[], + credentialTypes: role.ability.credentialTypes, + }; + + // Ability shorthands (also in top level of role obj for convenience) + // Example usage: `const { myRole: { canViewSettings } } = useValues(AppLogic);` + const abilities = { + canAccessAllEngines: role.ability.accessAllEngines, + canViewMetaEngines: myRole.can('view', 'account_meta_engines'), + canViewAccountCredentials: myRole.can('view', 'account_credentials'), + canViewEngineAnalytics: myRole.can('view', 'engine_analytics'), + canViewEngineApiLogs: myRole.can('view', 'engine_api_logs'), + canViewEngineCrawler: myRole.can('view', 'engine_crawler'), + canViewEngineCredentials: myRole.can('view', 'engine_credentials'), + canViewEngineDocuments: myRole.can('view', 'engine_documents'), + canViewEngineSchema: myRole.can('view', 'engine_schema'), + canViewEngineQueryTester: myRole.can('view', 'engine_query_tester'), + canViewMetaEngineSourceEngines: myRole.can('view', 'meta_engine_source_engines'), + canViewSettings: myRole.can('view', 'account_settings'), + canViewRoleMappings: myRole.can('view', 'role_mappings'), + canManageEngines: myRole.can('manage', 'account_engines'), + canManageMetaEngines: myRole.can('manage', 'account_meta_engines'), + canManageLogSettings: myRole.can('manage', 'account_log_settings'), + canManageSettings: myRole.can('manage', 'account_settings'), + canManageEngineCrawler: myRole.can('manage', 'engine_crawler'), + canManageEngineDocuments: myRole.can('manage', 'engine_documents'), + canManageEngineSynonyms: myRole.can('manage', 'engine_synonyms'), + canManageEngineCredentials: myRole.can('manage', 'engine_credentials'), + canManageEngineCurations: myRole.can('manage', 'engine_curations'), + canManageEngineRelevanceTuning: myRole.can('manage', 'engine_relevance_tuning'), + canManageEngineReferenceUi: myRole.can('manage', 'engine_reference_ui'), + canManageEngineResultSettings: myRole.can('manage', 'engine_result_settings'), + canManageEngineSchema: myRole.can('manage', 'engine_schema'), + canManageMetaEngineSourceEngines: myRole.can('manage', 'meta_engine_source_engines'), + }; + + return Object.assign(myRole, topLevelProps, abilities); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.test.tsx new file mode 100644 index 00000000000000..8d48875a8e1f5f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.test.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { ErrorStatePrompt } from '../../../shared/error_state'; +import { ErrorConnecting } from './'; + +describe('ErrorConnecting', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(ErrorStatePrompt)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx new file mode 100644 index 00000000000000..567c77792583d2 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiPage, EuiPageContent } from '@elastic/eui'; + +import { ErrorStatePrompt } from '../../../shared/error_state'; + +export const ErrorConnecting: React.FC = () => ( + + + + + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/index.ts new file mode 100644 index 00000000000000..c8b71e1a6e7918 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ErrorConnecting } from './error_connecting'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx index cd2a22a45bbb4c..b2918dac086f6c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.test.tsx @@ -6,13 +6,20 @@ import React from 'react'; import { shallow } from 'enzyme'; - import { EuiPage } from '@elastic/eui'; +import '../__mocks__/kea.mock'; +import { useValues } from 'kea'; + import { EnterpriseSearch } from './'; +import { ErrorConnecting } from './components/error_connecting'; import { ProductCard } from './components/product_card'; describe('EnterpriseSearch', () => { + beforeEach(() => { + (useValues as jest.Mock).mockReturnValue({ errorConnecting: false }); + }); + it('renders the overview page and product cards', () => { const wrapper = shallow( @@ -22,6 +29,14 @@ describe('EnterpriseSearch', () => { expect(wrapper.find(ProductCard)).toHaveLength(2); }); + it('renders the error connecting prompt', () => { + (useValues as jest.Mock).mockReturnValueOnce({ errorConnecting: true }); + const wrapper = shallow(); + + expect(wrapper.find(ErrorConnecting)).toHaveLength(1); + expect(wrapper.find(EuiPage)).toHaveLength(0); + }); + describe('access checks', () => { it('does not render the App Search card if the user does not have access to AS', () => { const wrapper = shallow( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx index 373f595a6a9ea5..3a3ba02e07058a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.tsx @@ -5,6 +5,7 @@ */ import React from 'react'; +import { useValues } from 'kea'; import { EuiPage, EuiPageBody, @@ -21,9 +22,11 @@ import { i18n } from '@kbn/i18n'; import { IInitialAppData } from '../../../common/types'; import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../common/constants'; +import { HttpLogic } from '../shared/http'; import { SetEnterpriseSearchChrome as SetPageChrome } from '../shared/kibana_chrome'; import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../shared/telemetry'; +import { ErrorConnecting } from './components/error_connecting'; import { ProductCard } from './components/product_card'; import AppSearchImage from './assets/app_search.png'; @@ -31,9 +34,12 @@ import WorkplaceSearchImage from './assets/workplace_search.png'; import './index.scss'; export const EnterpriseSearch: React.FC = ({ access = {} }) => { + const { errorConnecting } = useValues(HttpLogic); const { hasAppSearchAccess, hasWorkplaceSearchAccess } = access; - return ( + return errorConnecting ? ( + + ) : ( diff --git a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx index e0cf2814b46b4e..053c450ab925ef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx @@ -10,7 +10,7 @@ import { AppMountParameters } from 'src/core/public'; import { coreMock } from 'src/core/public/mocks'; import { licensingMock } from '../../../licensing/public/mocks'; -import { renderApp } from './'; +import { renderApp, renderHeaderActions } from './'; import { AppSearch } from './app_search'; import { WorkplaceSearch } from './workplace_search'; @@ -33,6 +33,7 @@ describe('renderApp', () => { const unmount = renderApp(MockApp, params, core, plugins, config, data); expect(params.element.querySelector('.hello-world')).not.toBeNull(); + unmount(); expect(params.element.innerHTML).toEqual(''); }); @@ -47,3 +48,16 @@ describe('renderApp', () => { expect(params.element.querySelector('.setupGuide')).not.toBeNull(); }); }); + +describe('renderHeaderActions', () => { + it('mounts and unmounts any HeaderActions component', () => { + const mockHeaderEl = document.createElement('header'); + const MockHeaderActions = () => ; + + const unmount = renderHeaderActions(MockHeaderActions, mockHeaderEl, {} as any); + expect(mockHeaderEl.querySelector('.hello-world')).not.toBeNull(); + + unmount(); + expect(mockHeaderEl.innerHTML).toEqual(''); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index 82f884644be4ad..43056f2f655385 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -88,3 +88,22 @@ export const renderApp = ( ReactDOM.unmountComponentAtNode(params.element); }; }; + +/** + * Render function for Kibana's header action menu chrome - + * reusable by any Enterprise Search plugin simply by passing in + * a custom HeaderActions component (e.g., WorkplaceSearchHeaderActions) + * @see https://github.com/elastic/kibana/blob/master/docs/development/core/public/kibana-plugin-core-public.appmountparameters.setheaderactionmenu.md + */ +interface IHeaderActionsProps { + externalUrl: IExternalUrl; +} + +export const renderHeaderActions = ( + HeaderActions: React.FC, + kibanaHeaderEl: HTMLElement, + externalUrl: IExternalUrl +) => { + ReactDOM.render(, kibanaHeaderEl); + return () => ReactDOM.unmountComponentAtNode(kibanaHeaderEl); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx index 8f7cf090e2d573..1d64b453b2c2c3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.test.tsx @@ -33,7 +33,7 @@ describe('Shared Telemetry Helpers', () => { metric: 'setup_guide', }); - expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/telemetry', { + expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { headers, body: '{"product":"enterprise_search","action":"viewed","metric":"setup_guide"}', }); @@ -54,7 +54,7 @@ describe('Shared Telemetry Helpers', () => { http: httpMock, }); - expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/telemetry', { + expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { headers, body: '{"product":"enterprise_search","action":"viewed","metric":"page"}', }); @@ -65,7 +65,7 @@ describe('Shared Telemetry Helpers', () => { http: httpMock, }); - expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/telemetry', { + expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { headers, body: '{"product":"app_search","action":"clicked","metric":"button"}', }); @@ -76,7 +76,7 @@ describe('Shared Telemetry Helpers', () => { http: httpMock, }); - expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/telemetry', { + expect(httpMock.put).toHaveBeenCalledWith('/api/enterprise_search/stats', { headers, body: '{"product":"workplace_search","action":"error","metric":"not_found"}', }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx index 4df1428221de61..e3c9ba9b8a218f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/telemetry/send_telemetry.tsx @@ -27,7 +27,7 @@ interface ISendTelemetry extends ISendTelemetryProps { export const sendTelemetry = async ({ http, product, action, metric }: ISendTelemetry) => { try { const body = JSON.stringify({ product, action, metric }); - await http.put('/api/enterprise_search/telemetry', { headers, body }); + await http.put('/api/enterprise_search/stats', { headers, body }); } catch (error) { throw new Error('Unable to send telemetry'); } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts index c52eceb2d2fddd..974e07069ddba4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts @@ -50,5 +50,15 @@ describe('AppLogic', () => { expect(AppLogic.values).toEqual(expectedLogicValues); }); + + it('gracefully handles missing initial data', () => { + AppLogic.actions.initializeAppData({}); + + expect(AppLogic.values).toEqual({ + ...DEFAULT_VALUES, + hasInitialized: true, + isFederatedAuth: false, + }); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts index 94bd1d529b65ff..629d1969a8f593 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts @@ -21,6 +21,9 @@ export interface IAppActions { initializeAppData(props: IInitialAppData): IInitialAppData; } +const emptyOrg = {} as IOrganization; +const emptyAccount = {} as IAccount; + export const AppLogic = kea>({ path: ['enterprise_search', 'workplace_search', 'app_logic'], actions: { @@ -43,15 +46,15 @@ export const AppLogic = kea>({ }, ], organization: [ - {} as IOrganization, + emptyOrg, { - initializeAppData: (_, { workplaceSearch }) => workplaceSearch!.organization, + initializeAppData: (_, { workplaceSearch }) => workplaceSearch?.organization || emptyOrg, }, ], account: [ - {} as IAccount, + emptyAccount, { - initializeAppData: (_, { workplaceSearch }) => workplaceSearch!.account, + initializeAppData: (_, { workplaceSearch }) => workplaceSearch?.account || emptyAccount, }, ], }, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts index 41861a8ee2dc50..915638246c00e8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/index.ts @@ -5,3 +5,4 @@ */ export { WorkplaceSearchNav } from './nav'; +export { WorkplaceSearchHeaderActions } from './kibana_header_actions'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx new file mode 100644 index 00000000000000..a006c5e3775d52 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.test.tsx @@ -0,0 +1,29 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { EuiButtonEmpty } from '@elastic/eui'; +import { ExternalUrl } from '../../../shared/enterprise_search_url'; + +import { WorkplaceSearchHeaderActions } from './'; + +describe('WorkplaceSearchHeaderActions', () => { + const externalUrl = new ExternalUrl('http://localhost:3002'); + + it('renders a link to the search application', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiButtonEmpty).prop('href')).toEqual('http://localhost:3002/ws/search'); + }); + + it('does not render without an Enterprise Search host URL set', () => { + const wrapper = shallow(); + + expect(wrapper.isEmptyRender()).toBe(true); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx new file mode 100644 index 00000000000000..fa32d598f848db --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx @@ -0,0 +1,28 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EuiButtonEmpty } from '@elastic/eui'; + +import { IExternalUrl } from '../../../shared/enterprise_search_url'; + +interface IProps { + externalUrl: IExternalUrl; +} + +export const WorkplaceSearchHeaderActions: React.FC = ({ externalUrl }) => { + const { enterpriseSearchUrl, getWorkplaceSearchUrl } = externalUrl; + if (!enterpriseSearchUrl) return null; + + return ( + + {i18n.translate('xpack.enterpriseSearch.workplaceSearch.headerActions.searchApplication', { + defaultMessage: 'Go to search application', + })} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index 0ef58a7c03f10e..c23bb23be3979e 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -103,9 +103,16 @@ export class EnterpriseSearchPlugin implements Plugin { await this.getInitialData(coreStart.http); - const { renderApp } = await import('./applications'); + const { renderApp, renderHeaderActions } = await import('./applications'); const { WorkplaceSearch } = await import('./applications/workplace_search'); + const { WorkplaceSearchHeaderActions } = await import( + './applications/workplace_search/components/layout' + ); + params.setHeaderActionMenu((element) => + renderHeaderActions(WorkplaceSearchHeaderActions, element, this.data.externalUrl) + ); + return renderApp(WorkplaceSearch, params, coreStart, plugins, this.config, this.data); }, }); diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts index 8e3ae2cfbeb86b..2bddc9f1c80bd7 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts @@ -70,7 +70,6 @@ describe('callEnterpriseSearchConfigAPI', () => { role_type: 'owner', ability: { access_all_engines: true, - destroy: ['session'], manage: ['account_credentials', 'account_engines'], // etc edit: ['LocoMoco::Account'], // etc view: ['Engine'], // etc @@ -145,13 +144,12 @@ describe('callEnterpriseSearchConfigAPI', () => { }, appSearch: { accountId: undefined, - onBoardingComplete: false, + onboardingComplete: false, role: { id: undefined, roleType: undefined, ability: { accessAllEngines: false, - destroy: [], manage: [], edit: [], view: [], diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts index 10a75e59cb249e..c63e3ff8ffb2b5 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts @@ -89,13 +89,12 @@ export const callEnterpriseSearchConfigAPI = async ({ }, appSearch: { accountId: data?.current_user?.app_search?.account?.id, - onBoardingComplete: !!data?.current_user?.app_search?.account?.onboarding_complete, + onboardingComplete: !!data?.current_user?.app_search?.account?.onboarding_complete, role: { id: data?.current_user?.app_search?.role?.id, roleType: data?.current_user?.app_search?.role?.role_type, ability: { accessAllEngines: !!data?.current_user?.app_search?.role?.ability?.access_all_engines, - destroy: data?.current_user?.app_search?.role?.ability?.destroy || [], manage: data?.current_user?.app_search?.role?.ability?.manage || [], edit: data?.current_user?.app_search?.role?.ability?.edit || [], view: data?.current_user?.app_search?.role?.ability?.view || [], diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts index acddd3539965a7..bd6f4b9da91fd6 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.test.ts @@ -35,7 +35,7 @@ describe('Enterprise Search Telemetry API', () => { }); }); - describe('PUT /api/enterprise_search/telemetry', () => { + describe('PUT /api/enterprise_search/stats', () => { it('increments the saved objects counter for App Search', async () => { (incrementUICounter as jest.Mock).mockImplementation(jest.fn(() => successResponse)); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts index bfc07c8b64ef50..8f6638ddc099ef 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/telemetry.ts @@ -25,7 +25,7 @@ export function registerTelemetryRoute({ }: IRouteDependencies) { router.put( { - path: '/api/enterprise_search/telemetry', + path: '/api/enterprise_search/stats', validate: { body: schema.object({ product: schema.oneOf([ diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx index e41f9243198ad9..54066cee414d80 100644 --- a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx +++ b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx @@ -11,6 +11,8 @@ import { EuiSelectableTemplateSitewide, EuiSelectableTemplateSitewideOption, EuiText, + EuiIcon, + EuiHeaderSectionItemButton, EuiSelectableMessage, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -138,6 +140,17 @@ export function SearchBar({ globalSearch, navigateToUrl }: Props) { + + + } searchProps={{ onKeyUpCapture: (e: React.KeyboardEvent) => setSearchValue(e.currentTarget.value), diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx new file mode 100644 index 00000000000000..0ee70d63ba6676 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx @@ -0,0 +1,158 @@ +/* + * 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 { act } from 'react-dom/test-utils'; + +import { componentHelpers, MappingsEditorTestBed } from '../helpers'; + +const { setup, getMappingsEditorDataFactory } = componentHelpers.mappingsEditor; + +// Parameters automatically added to the point datatype when saved (with the default values) +export const defaultPointParameters = { + type: 'point', + ignore_malformed: false, + ignore_z_value: true, +}; + +describe('Mappings editor: point datatype', () => { + /** + * Variable to store the mappings data forwarded to the consumer component + */ + let data: any; + let onChangeHandler: jest.Mock = jest.fn(); + let getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); + let testBed: MappingsEditorTestBed; + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + beforeEach(() => { + onChangeHandler = jest.fn(); + getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); + }); + + test('initial view and default parameters values', async () => { + const defaultMappings = { + properties: { + myField: { + type: 'point', + }, + }, + }; + + const updatedMappings = { ...defaultMappings }; + + await act(async () => { + testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + }); + testBed.component.update(); + + const { + component, + actions: { startEditField, updateFieldAndCloseFlyout }, + } = testBed; + + // Open the flyout to edit the field + await startEditField('myField'); + + // Save the field and close the flyout + await updateFieldAndCloseFlyout(); + + // It should have the default parameters values added + updatedMappings.properties.myField = defaultPointParameters; + + ({ data } = await getMappingsEditorData(component)); + expect(data).toEqual(updatedMappings); + }); + + describe('meta parameter', () => { + const defaultMappings = { + properties: { + myField: { + type: 'point', + }, + }, + }; + + const updatedMappings = { ...defaultMappings }; + + const metaParameter = { + meta: { + my_metadata: 'foobar', + }, + }; + + beforeEach(async () => { + await act(async () => { + testBed = setup({ value: defaultMappings, onChange: onChangeHandler }); + }); + testBed.component.update(); + }); + + test('valid meta object', async () => { + const { + component, + actions: { + startEditField, + updateFieldAndCloseFlyout, + showAdvancedSettings, + toggleFormRow, + updateJsonEditor, + }, + } = testBed; + + // Open the flyout to edit the field + await startEditField('myField'); + await showAdvancedSettings(); + + // Enable the meta parameter and add value + toggleFormRow('metaParameter'); + await act(async () => { + updateJsonEditor('metaParameterEditor', metaParameter.meta); + }); + component.update(); + + // Save the field and close the flyout + await updateFieldAndCloseFlyout(); + + // It should have the default parameters values added, plus metadata + updatedMappings.properties.myField = { + ...defaultPointParameters, + ...metaParameter, + }; + + ({ data } = await getMappingsEditorData(component)); + expect(data).toEqual(updatedMappings); + }); + + test('strip empty string', async () => { + const { + component, + actions: { startEditField, updateFieldAndCloseFlyout, showAdvancedSettings, toggleFormRow }, + } = testBed; + + // Open the flyout to edit the field + await startEditField('myField'); + await showAdvancedSettings(); + + // Enable the meta parameter + toggleFormRow('metaParameter'); + + // Save the field and close the flyout without adding any values to meta parameter + await updateFieldAndCloseFlyout(); + + // It should have the default parameters values added + updatedMappings.properties.myField = defaultPointParameters; + + ({ data } = await getMappingsEditorData(component)); + expect(data).toEqual(updatedMappings); + }); + }); +}); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index 2a4af89c46559f..e123dea6ff2ff6 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -239,6 +239,10 @@ const createActions = (testBed: TestBed) => { const getCheckboxValue = (testSubject: TestSubjects): boolean => find(testSubject).props().checked; + const toggleFormRow = (formRowName: string) => { + form.toggleEuiSwitch(`${formRowName}.formRowToggle`); + }; + return { selectTab, getFieldAt, @@ -252,6 +256,7 @@ const createActions = (testBed: TestBed) => { getComboBoxValue, getToggleValue, getCheckboxValue, + toggleFormRow, }; }; @@ -365,4 +370,6 @@ export type TestSubjects = | 'searchQuoteAnalyzer-custom' | 'searchQuoteAnalyzer-toggleCustomButton' | 'searchQuoteAnalyzer-custom.input' - | 'useSameAnalyzerForSearchCheckBox.input'; + | 'useSameAnalyzerForSearchCheckBox.input' + | 'metaParameterEditor' + | string; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/ignore_z_value_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/ignore_z_value_parameter.tsx index bd118ac08964fb..ce58a264db968c 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/ignore_z_value_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/ignore_z_value_parameter.tsx @@ -10,15 +10,18 @@ import { i18n } from '@kbn/i18n'; import { EditFieldFormRow } from '../fields/edit_field'; -export const IgnoreZValueParameter = () => ( +export const IgnoreZValueParameter = ({ description }: { description?: string }) => ( ); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/meta_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/meta_parameter.tsx index c8af296318b61c..a950ba82d0eac4 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/meta_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/meta_parameter.tsx @@ -32,6 +32,7 @@ export const MetaParameter: FunctionComponent = ({ defaultToggleValue }) }), href: documentationService.getMetaLink(), }} + data-test-subj="metaParameter" > = ({ defaultToggleValue }) component={JsonEditorField} componentProps={{ euiCodeEditorProps: { + ['data-test-subj']: 'metaParameterEditor', height: '300px', 'aria-label': i18n.translate('xpack.idxMgmt.mappingsEditor.metaParameterAriaLabel', { defaultMessage: 'metadata field data editor', diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/index.ts index 8fcd02e4a362ea..6b092c5561b3bf 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/index.ts @@ -32,6 +32,7 @@ import { HistogramType } from './histogram_type'; import { ConstantKeywordType } from './constant_keyword_type'; import { RankFeatureType } from './rank_feature_type'; import { WildcardType } from './wildcard_type'; +import { PointType } from './point_type'; const typeToParametersFormMap: { [key in DataType]?: ComponentType } = { alias: AliasType, @@ -60,6 +61,7 @@ const typeToParametersFormMap: { [key in DataType]?: ComponentType } = { constant_keyword: ConstantKeywordType, rank_feature: RankFeatureType, wildcard: WildcardType, + point: PointType, }; export const getParametersFormForType = ( diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/point_type.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/point_type.tsx new file mode 100644 index 00000000000000..9108c56e4496b7 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/point_type.tsx @@ -0,0 +1,76 @@ +/* + * 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, { FunctionComponent } from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { NormalizedField, Field as FieldType, ParameterName } from '../../../../types'; +import { UseField, TextAreaField } from '../../../../shared_imports'; +import { getFieldConfig } from '../../../../lib'; +import { + IgnoreMalformedParameter, + IgnoreZValueParameter, + NullValueParameter, + MetaParameter, +} from '../../field_parameters'; +import { AdvancedParametersSection, BasicParametersSection } from '../edit_field'; + +interface Props { + field: NormalizedField; +} + +const getDefaultToggleValue = (param: ParameterName, field: FieldType) => { + return field[param] !== undefined && field[param] !== getFieldConfig(param).defaultValue; +}; + +export const PointType: FunctionComponent = ({ field }) => { + return ( + <> + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx index a4d3bf3832d5c5..293ae56d57ace5 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx @@ -821,6 +821,26 @@ export const TYPE_DEFINITION: { [key in DataType]: DataTypeDefinition } = {

), }, + point: { + label: i18n.translate('xpack.idxMgmt.mappingsEditor.dataType.pointDescription', { + defaultMessage: 'Point', + }), + value: 'point', + documentation: { + main: '/point.html', + }, + description: () => ( +

+ {'x,y'}, + }} + /> +

+ ), + }, wildcard: { label: i18n.translate('xpack.idxMgmt.mappingsEditor.dataType.wildcardDescription', { defaultMessage: 'Wildcard', @@ -882,6 +902,7 @@ export const MAIN_TYPES: MainType[] = [ 'token_count', 'histogram', 'wildcard', + 'point', 'other', ]; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx index fd17dc1b8fd1ef..4ffedc8ca114d9 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx @@ -382,6 +382,50 @@ export const PARAMETERS_DEFINITION: { [key in ParameterName]: ParameterDefinitio }, schema: t.any, }, + null_value_point: { + fieldConfig: { + defaultValue: '', + label: nullValueLabel, + helpText: () => ( + + {i18n.translate( + 'xpack.idxMgmt.mappingsEditor.parameters.pointWellKnownTextDocumentationLink', + { + defaultMessage: 'Well-Known Text', + } + )} + + ), + }} + /> + ), + validations: [ + { + validator: nullValueValidateEmptyField, + }, + ], + deserializer: (value: any) => { + if (value === '') { + return value; + } + return JSON.stringify(value); + }, + serializer: (value: string) => { + try { + return JSON.parse(value); + } catch (error) { + // swallow error and return non-parsed value; + return value; + } + }, + }, + schema: t.any, + }, copy_to: { fieldConfig: { defaultValue: '', @@ -476,12 +520,22 @@ export const PARAMETERS_DEFINITION: { [key in ParameterName]: ParameterDefinitio return JSON.stringify(value, null, 2); }, serializer: (value: string) => { - const parsed = JSON.parse(value); - // If an empty object was passed, strip out this value entirely. - if (!Object.keys(parsed).length) { + // Strip out empty strings + if (value.trim() === '') { return undefined; } - return parsed; + + try { + const parsed = JSON.parse(value); + // If an empty object was passed, strip out this value entirely. + if (!Object.keys(parsed).length) { + return undefined; + } + return parsed; + } catch (error) { + // swallow error and return non-parsed value; + return value; + } }, }, schema: t.any, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts index 97dca49fc93ed5..ca38a8d1e6c33c 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts @@ -59,6 +59,7 @@ export type MainType = | 'geo_point' | 'geo_shape' | 'token_count' + | 'point' | 'histogram' | 'constant_keyword' | 'wildcard' @@ -109,6 +110,7 @@ export type ParameterName = | 'null_value_boolean' | 'null_value_geo_point' | 'null_value_ip' + | 'null_value_point' | 'copy_to' | 'dynamic' | 'dynamic_toggle' diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts index f6d8a4a807e95a..0ee123ed2946f8 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts @@ -262,18 +262,18 @@ async function fetchLogEntryAnomalies( bucket_span: duration, timestamp: anomalyStartTime, by_field_value: categoryId, - } = result._source; + } = result.fields; return { id: result._id, - anomalyScore, - dataset, + anomalyScore: anomalyScore[0], + dataset: dataset[0], typical: typical[0], actual: actual[0], - jobId: job_id, - startTime: anomalyStartTime, - duration: duration * 1000, - categoryId, + jobId: job_id[0], + startTime: parseInt(anomalyStartTime[0], 10), + duration: duration[0] * 1000, + categoryId: categoryId?.[0], }; }); @@ -417,8 +417,8 @@ export async function fetchLogEntryExamples( return { examples: hits.map((hit) => ({ id: hit._id, - dataset: hit._source.event?.dataset ?? '', - message: hit._source.message ?? '', + dataset: hit.fields['event.dataset']?.[0] ?? '', + message: hit.fields.message?.[0] ?? '', timestamp: hit.sort[0], tiebreaker: hit.sort[1], })), diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts index ff9e3c7d2167cf..1205c5ae9f61b9 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts @@ -23,6 +23,7 @@ import { } from './queries/log_entry_categories'; import { createLogEntryCategoryExamplesQuery, + LogEntryCategoryExampleHit, logEntryCategoryExamplesResponseRT, } from './queries/log_entry_category_examples'; import { @@ -423,11 +424,11 @@ async function fetchLogEntryCategoryExamples( return { examples: hits.map((hit) => ({ id: hit._id, - dataset: hit._source.event?.dataset ?? '', - message: hit._source.message ?? '', + dataset: hit.fields['event.dataset']?.[0] ?? '', + message: hit.fields.message?.[0] ?? '', timestamp: hit.sort[0], tiebreaker: hit.sort[1], - context: getContextFromSource(hit._source), + context: getContextFromFields(hit.fields), })), timing: { spans: [esSearchSpan], @@ -437,10 +438,10 @@ async function fetchLogEntryCategoryExamples( const parseCategoryId = (rawCategoryId: string) => parseInt(rawCategoryId, 10); -const getContextFromSource = (source: any): LogEntryContext => { - const containerId = source.container?.id; - const hostName = source.host?.name; - const logFilePath = source.log?.file?.path; +const getContextFromFields = (fields: LogEntryCategoryExampleHit['fields']): LogEntryContext => { + const containerId = fields['container.id']?.[0]; + const hostName = fields['host.name']?.[0]; + const logFilePath = fields['log.file.path']?.[0]; if (typeof containerId === 'string') { return { 'container.id': containerId }; diff --git a/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_anomalies.ts b/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_anomalies.ts index c722544c509aa0..e692ed019cf866 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_anomalies.ts @@ -46,13 +46,16 @@ export const createLogEntryAnomaliesQuery = ( ...createDatasetsFilters(datasets), ]; - const sourceFields = [ + const fields = [ 'job_id', 'record_score', 'typical', 'actual', 'partition_field_value', - 'timestamp', + { + field: 'timestamp', + format: 'epoch_millis', + }, 'bucket_span', 'by_field_value', ]; @@ -75,7 +78,8 @@ export const createLogEntryAnomaliesQuery = ( search_after: queryCursor, sort: sortOptions, size: pageSize, - _source: sourceFields, + _source: false, + fields, }, }; @@ -84,18 +88,18 @@ export const createLogEntryAnomaliesQuery = ( export const logEntryAnomalyHitRT = rt.type({ _id: rt.string, - _source: rt.intersection([ + fields: rt.intersection([ rt.type({ - job_id: rt.string, - record_score: rt.number, + job_id: rt.array(rt.string), + record_score: rt.array(rt.number), typical: rt.array(rt.number), actual: rt.array(rt.number), - partition_field_value: rt.string, - bucket_span: rt.number, - timestamp: rt.number, + partition_field_value: rt.array(rt.string), + bucket_span: rt.array(rt.number), + timestamp: rt.array(rt.string), }), rt.partial({ - by_field_value: rt.string, + by_field_value: rt.array(rt.string), }), ]), sort: rt.tuple([rt.union([rt.string, rt.number]), rt.union([rt.string, rt.number])]), diff --git a/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_category_examples.ts b/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_category_examples.ts index 6e2afa874b7573..c99353d01a3e87 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_category_examples.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_category_examples.ts @@ -43,30 +43,21 @@ export const createLogEntryCategoryExamplesQuery = ( }, }, sort: [{ [timestampField]: 'asc' }, { [tiebreakerField]: 'asc' }], + _source: false, + fields: ['event.dataset', 'message', 'container.id', 'host.name', 'log.file.path'], }, - _source: ['event.dataset', 'message', 'container.id', 'host.name', 'log.file.path'], index: indices, size: exampleCount, }); export const logEntryCategoryExampleHitRT = rt.type({ _id: rt.string, - _source: rt.partial({ - event: rt.partial({ - dataset: rt.string, - }), - message: rt.string, - container: rt.partial({ - id: rt.string, - }), - host: rt.partial({ - name: rt.string, - }), - log: rt.partial({ - file: rt.partial({ - path: rt.string, - }), - }), + fields: rt.partial({ + 'event.dataset': rt.array(rt.string), + message: rt.array(rt.string), + 'container.id': rt.array(rt.string), + 'host.name': rt.array(rt.string), + 'log.file.path': rt.array(rt.string), }), sort: rt.tuple([rt.number, rt.number]), }); diff --git a/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_examples.ts b/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_examples.ts index 74a664e78dcd63..eac5fa84d85a7f 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_examples.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_examples.ts @@ -58,19 +58,18 @@ export const createLogEntryExamplesQuery = ( }, }, sort: [{ [timestampField]: 'asc' }, { [tiebreakerField]: 'asc' }], + _source: false, + fields: ['event.dataset', 'message'], }, - _source: ['event.dataset', 'message'], index: indices, size: exampleCount, }); export const logEntryExampleHitRT = rt.type({ _id: rt.string, - _source: rt.partial({ - event: rt.partial({ - dataset: rt.string, - }), - message: rt.string, + fields: rt.partial({ + 'event.dataset': rt.array(rt.string), + message: rt.array(rt.string), }), sort: rt.tuple([rt.number, rt.number]), }); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/layout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/layout.tsx index 3bcf0aab9a5c83..6edce74d162bbb 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/layout.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/components/layout.tsx @@ -111,7 +111,7 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{ ) : ( ); }, [from]); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx index a02214a6fe7faa..39f35fed56ef51 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/index.tsx @@ -242,7 +242,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { notifications.toasts.addSuccess({ title: i18n.translate('xpack.ingestManager.createPackagePolicy.addedNotificationTitle', { - defaultMessage: `Successfully added '{packagePolicyName}'`, + defaultMessage: `'{packagePolicyName}' integration added.`, values: { packagePolicyName: packagePolicy.name, }, @@ -250,7 +250,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { text: agentCount && agentPolicy ? i18n.translate('xpack.ingestManager.createPackagePolicy.addedNotificationMessage', { - defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy`, + defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`, values: { agentPolicyName: agentPolicy.name, }, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/confirm_delete_modal.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/confirm_delete_modal.tsx index d2092f070a22ad..a115e03a369a26 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/confirm_delete_modal.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/components/confirm_delete_modal.tsx @@ -21,7 +21,7 @@ export const ConfirmEnrollmentTokenDelete = (props: Props) => { { confirmButtonText={i18n.translate( 'xpack.ingestManager.enrollmentTokenDeleteModal.deleteButton', { - defaultMessage: 'Delete', + defaultMessage: 'Revoke enrollment token', } )} defaultFocusedButton="confirm" @@ -42,7 +42,8 @@ export const ConfirmEnrollmentTokenDelete = (props: Props) => { > = ({

diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx index d85a6e8b5b833e..f447469a02df2e 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx @@ -268,7 +268,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { @@ -290,7 +290,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { setFlyoutOpen(true)}> diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx index eeade9036df000..fbd74f8b03e72d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx @@ -95,8 +95,7 @@ export const SetupPage: React.FunctionComponent<{ diff --git a/x-pack/plugins/ingest_manager/server/errors/handlers.ts b/x-pack/plugins/ingest_manager/server/errors/handlers.ts index 4f130376f44dbd..b621f2dd293315 100644 --- a/x-pack/plugins/ingest_manager/server/errors/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/errors/handlers.ts @@ -14,7 +14,6 @@ import { import { errors as LegacyESErrors } from 'elasticsearch'; import { appContextService } from '../services'; import { IngestManagerError, RegistryError, PackageNotFoundError } from './index'; -import { IBulkInstallPackageError } from '../../common'; type IngestErrorHandler = ( params: IngestErrorHandlerParams @@ -57,9 +56,7 @@ const getHTTPResponseCode = (error: IngestManagerError): number => { return 400; // Bad Request }; -export function formatBulkInstallError( - error: IngestManagerError | Boom | Error -): Pick { +export function ingestErrorToResponseOptions(error: IngestErrorHandlerParams['error']) { const logger = appContextService.getLogger(); if (isLegacyESClientError(error)) { // there was a problem communicating with ES (e.g. via `callCluster`) @@ -74,7 +71,7 @@ export function formatBulkInstallError( return { statusCode: error?.statusCode || error.status, - error: message, + body: { message }, }; } @@ -84,7 +81,7 @@ export function formatBulkInstallError( logger.error(error.message); return { statusCode: getHTTPResponseCode(error), - error: error.message, + body: { message: error.message }, }; } @@ -94,7 +91,7 @@ export function formatBulkInstallError( logger.error(error.output.payload.message); return { statusCode: error.output.statusCode, - error: error.output.payload.message, + body: { message: error.output.payload.message }, }; } @@ -102,7 +99,7 @@ export function formatBulkInstallError( logger.error(error); return { statusCode: 500, - error, + body: { message: error.message }, }; } @@ -110,48 +107,6 @@ export const defaultIngestErrorHandler: IngestErrorHandler = async ({ error, response, }: IngestErrorHandlerParams): Promise => { - const logger = appContextService.getLogger(); - if (isLegacyESClientError(error)) { - // there was a problem communicating with ES (e.g. via `callCluster`) - // only log the message - const message = - error?.path && error?.response - ? // if possible, return the failing endpoint and its response - `${error.message} response from ${error.path}: ${error.response}` - : error.message; - - logger.error(message); - - return response.customError({ - statusCode: error?.statusCode || error.status, - body: { message }, - }); - } - - // our "expected" errors - if (error instanceof IngestManagerError) { - // only log the message - logger.error(error.message); - return response.customError({ - statusCode: getHTTPResponseCode(error), - body: { message: error.message }, - }); - } - - // handle any older Boom-based errors or the few places our app uses them - if (isBoom(error)) { - // only log the message - logger.error(error.output.payload.message); - return response.customError({ - statusCode: error.output.statusCode, - body: { message: error.output.payload.message }, - }); - } - - // not sure what type of error this is. log as much as possible - logger.error(error); - return response.customError({ - statusCode: 500, - body: { message: error.message }, - }); + const options = ingestErrorToResponseOptions(error); + return response.customError(options); }; diff --git a/x-pack/plugins/ingest_manager/server/errors/index.ts b/x-pack/plugins/ingest_manager/server/errors/index.ts index 5e36a2ec9a884a..f495bf551dcff7 100644 --- a/x-pack/plugins/ingest_manager/server/errors/index.ts +++ b/x-pack/plugins/ingest_manager/server/errors/index.ts @@ -5,7 +5,7 @@ */ /* eslint-disable max-classes-per-file */ -export { defaultIngestErrorHandler } from './handlers'; +export { defaultIngestErrorHandler, ingestErrorToResponseOptions } from './handlers'; export class IngestManagerError extends Error { constructor(message?: string) { diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts index b0439b30e8973b..fb320b01dea975 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts @@ -10,6 +10,7 @@ import { RequestHandler } from 'kibana/server'; import { AcksService } from '../../services/agents'; import { AgentEvent } from '../../../common/types/models'; import { PostAgentAcksRequest, PostAgentAcksResponse } from '../../../common/types/rest_spec'; +import { defaultIngestErrorHandler } from '../../errors'; export const postAgentAcksHandlerBuilder = function ( ackService: AcksService @@ -43,18 +44,8 @@ export const postAgentAcksHandlerBuilder = function ( }; return response.ok({ body }); - } catch (e) { - if (e.isBoom) { - return response.customError({ - statusCode: e.output.statusCode, - body: { message: e.message }, - }); - } - - return response.customError({ - statusCode: 500, - body: { message: e.message }, - }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); } }; }; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts index 12a0956b791550..64a7795cc9dacd 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts @@ -11,6 +11,7 @@ import { TypeOf } from '@kbn/config-schema'; import { PostNewAgentActionRequestSchema } from '../../types/rest_spec'; import { ActionsService } from '../../services/agents'; import { PostNewAgentActionResponse } from '../../../common/types/rest_spec'; +import { defaultIngestErrorHandler } from '../../errors'; export const postNewAgentActionHandlerBuilder = function ( actionsService: ActionsService @@ -38,18 +39,8 @@ export const postNewAgentActionHandlerBuilder = function ( }; return response.ok({ body }); - } catch (e) { - if (e.isBoom) { - return response.customError({ - statusCode: e.output.statusCode, - body: { message: e.message }, - }); - } - - return response.customError({ - statusCode: 500, - body: { message: e.message }, - }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); } }; }; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts index b9789b770eb2e2..2ebb7a0667aab1 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts @@ -47,17 +47,14 @@ export const getAgentHandler: RequestHandler= 500) { - logger.error(err); - } - - return response.customError({ - statusCode: err.output.statusCode, - body: { message: err.output.payload.message }, - }); - } - - logger.error(err); - - return response.customError({ - statusCode: 500, - body: { message: err.message }, - }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); } }; @@ -247,18 +221,8 @@ export const postAgentEnrollHandler: RequestHandler< }; return response.ok({ body }); - } catch (e) { - if (e.isBoom) { - return response.customError({ - statusCode: e.output.statusCode, - body: { message: e.message }, - }); - } - - return response.customError({ - statusCode: 500, - body: { message: e.message }, - }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); } }; @@ -301,11 +265,8 @@ export const putAgentsReassignHandler: RequestHandler< const body: PutAgentReassignResponse = {}; return response.ok({ body }); - } catch (e) { - return response.customError({ - statusCode: 500, - body: { message: e.message }, - }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); } }; @@ -324,10 +285,7 @@ export const getAgentStatusForAgentPolicyHandler: RequestHandler< const body: GetAgentStatusResponse = { results }; return response.ok({ body }); - } catch (e) { - return response.customError({ - statusCode: 500, - body: { message: e.message }, - }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts b/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts index 5df695d248f5b6..fa200e912d6250 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts @@ -9,6 +9,7 @@ import { TypeOf } from '@kbn/config-schema'; import { PostAgentUnenrollResponse } from '../../../common/types'; import { PostAgentUnenrollRequestSchema } from '../../types'; import * as AgentService from '../../services/agents'; +import { defaultIngestErrorHandler } from '../../errors'; export const postAgentsUnenrollHandler: RequestHandler< TypeOf, @@ -25,10 +26,7 @@ export const postAgentsUnenrollHandler: RequestHandler< const body: PostAgentUnenrollResponse = {}; return response.ok({ body }); - } catch (e) { - return response.customError({ - statusCode: 500, - body: { message: e.message }, - }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_policy/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent_policy/handlers.ts index 7eb3df23461068..311b3bbf7f13ba 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_policy/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_policy/handlers.ts @@ -32,6 +32,7 @@ import { DeleteAgentPolicyResponse, GetFullAgentPolicyResponse, } from '../../../common'; +import { defaultIngestErrorHandler } from '../../errors'; export const getAgentPoliciesHandler: RequestHandler< undefined, @@ -64,11 +65,8 @@ export const getAgentPoliciesHandler: RequestHandler< ); return response.ok({ body }); - } catch (e) { - return response.customError({ - statusCode: 500, - body: { message: e.message }, - }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); } }; @@ -91,11 +89,8 @@ export const getOneAgentPolicyHandler: RequestHandler { const soClient = context.core.savedObjects.client; @@ -23,11 +24,8 @@ export const getOutputsHandler: RequestHandler = async (context, request, respon }; return response.ok({ body }); - } catch (e) { - return response.customError({ - statusCode: 500, - body: { message: e.message }, - }); + } catch (error) { + return defaultIngestErrorHandler({ error, response }); } }; @@ -43,17 +41,14 @@ export const getOneOuputHandler: RequestHandler { @@ -19,17 +19,14 @@ export const getSettingsHandler: RequestHandler = async (context, request, respo item: settings, }; return response.ok({ body }); - } catch (e) { - if (e.isBoom && e.output.statusCode === 404) { + } catch (error) { + if (error.isBoom && error.output.statusCode === 404) { return response.notFound({ body: { message: `Setings not found` }, }); } - return response.customError({ - statusCode: 500, - body: { message: e.message }, - }); + return defaultIngestErrorHandler({ error, response }); } }; @@ -49,17 +46,14 @@ export const putSettingsHandler: RequestHandler< item: settings, }; return response.ok({ body }); - } catch (e) { - if (e.isBoom && e.output.statusCode === 404) { + } catch (error) { + if (error.isBoom && error.output.statusCode === 404) { return response.notFound({ body: { message: `Setings not found` }, }); } - return response.customError({ - statusCode: 500, - body: { message: e.message }, - }); + return defaultIngestErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts index 866aa587b8a56a..c7b40988038271 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts @@ -57,7 +57,7 @@ describe('test agent acks services', () => { ); }); - it('should update config field on the agent if a policy change is acknowledged', async () => { + it('should update config field on the agent if a policy change is acknowledged with an agent without policy', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); const actionAttributes = { @@ -116,6 +116,114 @@ describe('test agent acks services', () => { `); }); + it('should update config field on the agent if a policy change is acknowledged with a higher revision than the agent one', async () => { + const mockSavedObjectsClient = savedObjectsClientMock.create(); + + const actionAttributes = { + type: 'CONFIG_CHANGE', + policy_id: 'policy1', + policy_revision: 4, + sent_at: '2020-03-14T19:45:02.620Z', + timestamp: '2019-01-04T14:32:03.36764-05:00', + created_at: '2020-03-14T19:45:02.620Z', + ack_data: JSON.stringify({ packages: ['system'] }), + }; + + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action2', + references: [], + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + attributes: actionAttributes, + }, + ], + } as SavedObjectsBulkResponse) + ); + + await acknowledgeAgentActions( + mockSavedObjectsClient, + ({ + id: 'id', + type: AGENT_TYPE_PERMANENT, + policy_id: 'policy1', + policy_revision: 3, + } as unknown) as Agent, + [ + { + type: 'ACTION_RESULT', + subtype: 'CONFIG', + timestamp: '2019-01-04T14:32:03.36764-05:00', + action_id: 'action2', + agent_id: 'id', + } as AgentEvent, + ] + ); + expect(mockSavedObjectsClient.bulkUpdate).toBeCalled(); + expect(mockSavedObjectsClient.bulkUpdate.mock.calls[0][0]).toHaveLength(1); + expect(mockSavedObjectsClient.bulkUpdate.mock.calls[0][0][0]).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "packages": Array [ + "system", + ], + "policy_revision": 4, + }, + "id": "id", + "type": "fleet-agents", + } + `); + }); + + it('should not update config field on the agent if a policy change is acknowledged with a lower revision than the agent one', async () => { + const mockSavedObjectsClient = savedObjectsClientMock.create(); + + const actionAttributes = { + type: 'CONFIG_CHANGE', + policy_id: 'policy1', + policy_revision: 4, + sent_at: '2020-03-14T19:45:02.620Z', + timestamp: '2019-01-04T14:32:03.36764-05:00', + created_at: '2020-03-14T19:45:02.620Z', + ack_data: JSON.stringify({ packages: ['system'] }), + }; + + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action2', + references: [], + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + attributes: actionAttributes, + }, + ], + } as SavedObjectsBulkResponse) + ); + + await acknowledgeAgentActions( + mockSavedObjectsClient, + ({ + id: 'id', + type: AGENT_TYPE_PERMANENT, + policy_id: 'policy1', + policy_revision: 5, + } as unknown) as Agent, + [ + { + type: 'ACTION_RESULT', + subtype: 'CONFIG', + timestamp: '2019-01-04T14:32:03.36764-05:00', + action_id: 'action2', + agent_id: 'id', + } as AgentEvent, + ] + ); + expect(mockSavedObjectsClient.bulkUpdate).toBeCalled(); + expect(mockSavedObjectsClient.bulkUpdate.mock.calls[0][0]).toHaveLength(0); + }); + it('should not update config field on the agent if a policy change for an old revision is acknowledged', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts index d29dfcec7ef307..1392710eb0eff4 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts @@ -139,16 +139,12 @@ function getLatestConfigChangePolicyActionIfUpdated( !isAgentPolicyAction(action) || action.type !== 'CONFIG_CHANGE' || action.policy_id !== agent.policy_id || - (acc?.policy_revision ?? 0) < (agent.policy_revision || 0) + (action?.policy_revision ?? 0) < (agent.policy_revision || 0) ) { return acc; } - if (action.policy_revision > (acc?.policy_revision ?? 0)) { - return action; - } - - return acc; + return action; }, null); } diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index b8c931eeb7a69f..d6ea8e339291b1 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -36,11 +36,14 @@ import { } from '../kibana/assets/install'; import { updateCurrentWriteIndices } from '../elasticsearch/template/template'; import { deleteKibanaSavedObjectsAssets, removeInstallation } from './remove'; -import { IngestManagerError, PackageOutdatedError } from '../../../errors'; +import { + IngestManagerError, + PackageOutdatedError, + ingestErrorToResponseOptions, +} from '../../../errors'; import { getPackageSavedObjects } from './get'; import { installTransformForDataset } from '../elasticsearch/transform/install'; import { appContextService } from '../../app_context'; -import { formatBulkInstallError } from '../../../errors/handlers'; export async function installLatestPackage(options: { savedObjectsClient: SavedObjectsClientContract; @@ -183,11 +186,11 @@ function bulkInstallErrorToOptions({ pkgToUpgrade: string; error: Error; }): IBulkInstallPackageError { - const { statusCode, error: err } = formatBulkInstallError(error); + const { statusCode, body } = ingestErrorToResponseOptions(error); return { name: pkgToUpgrade, statusCode, - error: err, + error: body.message, }; } diff --git a/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx b/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx index 3141f5bedc8f9c..7e7d74155b2d91 100644 --- a/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx +++ b/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx @@ -56,6 +56,12 @@ export const Editor = memo(({ licenseEnabled, initialValue, onEditorReady }: Pro setTextArea(licenseEnabled ? containerRef.current!.querySelector('textarea') : null); onEditorReady(createEditorShim(editorInstanceRef.current)); + + return () => { + if (editorInstanceRef.current) { + editorInstanceRef.current.destroy(); + } + }; }, [initialValue, onEditorReady, licenseEnabled]); return ( diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts index 7ada34ff5ccac3..86d1b68ba761ed 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts @@ -609,22 +609,83 @@ describe('#find', () => { await expectGeneralError(client.find, { type: type1 }); }); - test(`throws decorated ForbiddenError when type's singular and unauthorized`, async () => { + test(`returns empty result when unauthorized`, async () => { + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( + getMockCheckPrivilegesFailure + ); + const options = Object.freeze({ type: type1, namespaces: ['some-ns'] }); - await expectForbiddenError(client.find, { options }); - }); + const result = await client.find(options); - test(`throws decorated ForbiddenError when type's an array and unauthorized`, async () => { - const options = Object.freeze({ type: [type1, type2], namespaces: ['some-ns'] }); - await expectForbiddenError(client.find, { options }); + expect(clientOpts.baseClient.find).not.toHaveBeenCalled(); + expect(clientOpts.auditLogger.savedObjectsAuthorizationFailure).toHaveBeenCalledTimes(1); + expect(clientOpts.auditLogger.savedObjectsAuthorizationFailure).toHaveBeenCalledWith( + USERNAME, + 'find', + [type1], + options.namespaces, + [{ spaceId: 'some-ns', privilege: 'mock-saved_object:foo/find' }], + { options } + ); + expect(result).toEqual({ page: 1, per_page: 20, total: 0, saved_objects: [] }); }); - test(`returns result of baseClient.find when authorized`, async () => { + test(`returns result of baseClient.find when fully authorized`, async () => { const apiCallReturnValue = { saved_objects: [], foo: 'bar' }; clientOpts.baseClient.find.mockReturnValue(apiCallReturnValue as any); const options = Object.freeze({ type: type1, namespaces: ['some-ns'] }); const result = await expectSuccess(client.find, { options }); + expect(clientOpts.baseClient.find.mock.calls[0][0]).toEqual({ + ...options, + typeToNamespacesMap: undefined, + }); + expect(result).toEqual(apiCallReturnValue); + }); + + test(`returns result of baseClient.find when partially authorized`, async () => { + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockResolvedValue({ + hasAllRequested: false, + username: USERNAME, + privileges: { + kibana: [ + { resource: 'some-ns', privilege: 'mock-saved_object:foo/find', authorized: true }, + { resource: 'some-ns', privilege: 'mock-saved_object:bar/find', authorized: true }, + { resource: 'some-ns', privilege: 'mock-saved_object:baz/find', authorized: false }, + { resource: 'some-ns', privilege: 'mock-saved_object:qux/find', authorized: false }, + { resource: 'another-ns', privilege: 'mock-saved_object:foo/find', authorized: true }, + { resource: 'another-ns', privilege: 'mock-saved_object:bar/find', authorized: false }, + { resource: 'another-ns', privilege: 'mock-saved_object:baz/find', authorized: true }, + { resource: 'another-ns', privilege: 'mock-saved_object:qux/find', authorized: false }, + { resource: 'forbidden-ns', privilege: 'mock-saved_object:foo/find', authorized: false }, + { resource: 'forbidden-ns', privilege: 'mock-saved_object:bar/find', authorized: false }, + { resource: 'forbidden-ns', privilege: 'mock-saved_object:baz/find', authorized: false }, + { resource: 'forbidden-ns', privilege: 'mock-saved_object:qux/find', authorized: false }, + ], + }, + }); + + const apiCallReturnValue = { saved_objects: [], foo: 'bar' }; + clientOpts.baseClient.find.mockReturnValue(apiCallReturnValue as any); + + const options = Object.freeze({ + type: ['foo', 'bar', 'baz', 'qux'], + namespaces: ['some-ns', 'another-ns', 'forbidden-ns'], + }); + const result = await client.find(options); + // 'expect(clientOpts.baseClient.find).toHaveBeenCalledWith' resulted in false negatives, resorting to manually comparing mock call args + expect(clientOpts.baseClient.find.mock.calls[0][0]).toEqual({ + ...options, + typeToNamespacesMap: new Map([ + ['foo', ['some-ns', 'another-ns']], + ['bar', ['some-ns']], + ['baz', ['another-ns']], + // qux is not authorized, so there is no entry for it + // forbidden-ns is completely forbidden, so there are no entries with this namespace + ]), + type: '', + namespaces: [], + }); expect(result).toEqual(apiCallReturnValue); }); diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts index 16e52c69f274f0..f5de8f4b226f34 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts @@ -16,6 +16,7 @@ import { SavedObjectsUpdateOptions, SavedObjectsAddToNamespacesOptions, SavedObjectsDeleteFromNamespacesOptions, + SavedObjectsUtils, } from '../../../../../src/core/server'; import { SecurityAuditLogger } from '../audit'; import { Actions, CheckSavedObjectsPrivileges } from '../authorization'; @@ -39,8 +40,19 @@ interface SavedObjectsNamespaces { saved_objects: SavedObjectNamespaces[]; } -function uniq(arr: T[]): T[] { - return Array.from(new Set(arr)); +interface EnsureAuthorizedOptions { + args?: Record; + auditAction?: string; + requireFullAuthorization?: boolean; +} + +interface EnsureAuthorizedResult { + status: 'fully_authorized' | 'partially_authorized' | 'unauthorized'; + typeMap: Map; +} +interface EnsureAuthorizedTypeResult { + authorizedSpaces: string[]; + isGloballyAuthorized?: boolean; } export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContract { @@ -72,7 +84,8 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra attributes: T = {} as T, options: SavedObjectsCreateOptions = {} ) { - await this.ensureAuthorized(type, 'create', options.namespace, { type, attributes, options }); + const args = { type, attributes, options }; + await this.ensureAuthorized(type, 'create', options.namespace, { args }); const savedObject = await this.baseClient.create(type, attributes, options); return await this.redactSavedObjectNamespaces(savedObject); @@ -82,9 +95,12 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra objects: SavedObjectsCheckConflictsObject[] = [], options: SavedObjectsBaseOptions = {} ) { - const types = this.getUniqueObjectTypes(objects); const args = { objects, options }; - await this.ensureAuthorized(types, 'bulk_create', options.namespace, args, 'checkConflicts'); + const types = this.getUniqueObjectTypes(objects); + await this.ensureAuthorized(types, 'bulk_create', options.namespace, { + args, + auditAction: 'checkConflicts', + }); const response = await this.baseClient.checkConflicts(objects, options); return response; @@ -94,11 +110,12 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra objects: Array>, options: SavedObjectsBaseOptions = {} ) { + const args = { objects, options }; await this.ensureAuthorized( this.getUniqueObjectTypes(objects), 'bulk_create', options.namespace, - { objects, options } + { args } ); const response = await this.baseClient.bulkCreate(objects, options); @@ -106,7 +123,8 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra } public async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}) { - await this.ensureAuthorized(type, 'delete', options.namespace, { type, id, options }); + const args = { type, id, options }; + await this.ensureAuthorized(type, 'delete', options.namespace, { args }); return await this.baseClient.delete(type, id, options); } @@ -121,9 +139,29 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra `_find across namespaces is not permitted when the Spaces plugin is disabled.` ); } - await this.ensureAuthorized(options.type, 'find', options.namespaces, { options }); + const args = { options }; + const { status, typeMap } = await this.ensureAuthorized( + options.type, + 'find', + options.namespaces, + { args, requireFullAuthorization: false } + ); + + if (status === 'unauthorized') { + // return empty response + return SavedObjectsUtils.createEmptyFindResponse(options); + } - const response = await this.baseClient.find(options); + const typeToNamespacesMap = Array.from(typeMap).reduce>( + (acc, [type, { authorizedSpaces, isGloballyAuthorized }]) => + isGloballyAuthorized ? acc.set(type, options.namespaces) : acc.set(type, authorizedSpaces), + new Map() + ); + const response = await this.baseClient.find({ + ...options, + typeToNamespacesMap: undefined, // if the user is fully authorized, use `undefined` as the typeToNamespacesMap to prevent privilege escalation + ...(status === 'partially_authorized' && { typeToNamespacesMap, type: '', namespaces: [] }), // the repository requires that `type` and `namespaces` must be empty if `typeToNamespacesMap` is defined + }); return await this.redactSavedObjectsNamespaces(response); } @@ -131,9 +169,9 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra objects: SavedObjectsBulkGetObject[] = [], options: SavedObjectsBaseOptions = {} ) { + const args = { objects, options }; await this.ensureAuthorized(this.getUniqueObjectTypes(objects), 'bulk_get', options.namespace, { - objects, - options, + args, }); const response = await this.baseClient.bulkGet(objects, options); @@ -141,7 +179,8 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra } public async get(type: string, id: string, options: SavedObjectsBaseOptions = {}) { - await this.ensureAuthorized(type, 'get', options.namespace, { type, id, options }); + const args = { type, id, options }; + await this.ensureAuthorized(type, 'get', options.namespace, { args }); const savedObject = await this.baseClient.get(type, id, options); return await this.redactSavedObjectNamespaces(savedObject); @@ -154,7 +193,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra options: SavedObjectsUpdateOptions = {} ) { const args = { type, id, attributes, options }; - await this.ensureAuthorized(type, 'update', options.namespace, args); + await this.ensureAuthorized(type, 'update', options.namespace, { args }); const savedObject = await this.baseClient.update(type, id, attributes, options); return await this.redactSavedObjectNamespaces(savedObject); @@ -169,13 +208,19 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra const args = { type, id, namespaces, options }; const { namespace } = options; // To share an object, the user must have the "create" permission in each of the destination namespaces. - await this.ensureAuthorized(type, 'create', namespaces, args, 'addToNamespacesCreate'); + await this.ensureAuthorized(type, 'create', namespaces, { + args, + auditAction: 'addToNamespacesCreate', + }); // To share an object, the user must also have the "update" permission in one or more of the source namespaces. Because the // `addToNamespaces` operation is scoped to the current namespace, we can just check if the user has the "update" permission in the // current namespace. If the user has permission, but the saved object doesn't exist in this namespace, the base client operation will // result in a 404 error. - await this.ensureAuthorized(type, 'update', namespace, args, 'addToNamespacesUpdate'); + await this.ensureAuthorized(type, 'update', namespace, { + args, + auditAction: 'addToNamespacesUpdate', + }); const result = await this.baseClient.addToNamespaces(type, id, namespaces, options); return await this.redactSavedObjectNamespaces(result); @@ -189,7 +234,10 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra ) { const args = { type, id, namespaces, options }; // To un-share an object, the user must have the "delete" permission in each of the target namespaces. - await this.ensureAuthorized(type, 'delete', namespaces, args, 'deleteFromNamespaces'); + await this.ensureAuthorized(type, 'delete', namespaces, { + args, + auditAction: 'deleteFromNamespaces', + }); const result = await this.baseClient.deleteFromNamespaces(type, id, namespaces, options); return await this.redactSavedObjectNamespaces(result); @@ -205,9 +253,9 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra .filter(({ namespace }) => namespace !== undefined) .map(({ namespace }) => namespace!); const namespaces = [options?.namespace, ...objectNamespaces]; + const args = { objects, options }; await this.ensureAuthorized(this.getUniqueObjectTypes(objects), 'bulk_update', namespaces, { - objects, - options, + args, }); const response = await this.baseClient.bulkUpdate(objects, options); @@ -228,11 +276,10 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra private async ensureAuthorized( typeOrTypes: string | string[], action: string, - namespaceOrNamespaces?: string | Array, - args?: Record, - auditAction: string = action, - requiresAll = true - ) { + namespaceOrNamespaces: undefined | string | Array, + options: EnsureAuthorizedOptions = {} + ): Promise { + const { args, auditAction = action, requireFullAuthorization = true } = options; const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; const actionsToTypesMap = new Map( types.map((type) => [this.actions.savedObject.get(type, action), type]) @@ -245,27 +292,60 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra privileges.kibana.map(({ resource }) => resource).filter((x) => x !== undefined) ).sort() as string[]; - const isAuthorized = - (requiresAll && hasAllRequested) || - (!requiresAll && privileges.kibana.some(({ authorized }) => authorized)); - if (isAuthorized) { - this.auditLogger.savedObjectsAuthorizationSuccess( + const missingPrivileges = this.getMissingPrivileges(privileges); + const typeMap = privileges.kibana.reduce>( + (acc, { resource, privilege, authorized }) => { + if (!authorized) { + return acc; + } + const type = actionsToTypesMap.get(privilege)!; // always defined + const value = acc.get(type) ?? { authorizedSpaces: [] }; + if (resource === undefined) { + return acc.set(type, { ...value, isGloballyAuthorized: true }); + } + const authorizedSpaces = value.authorizedSpaces.concat(resource); + return acc.set(type, { ...value, authorizedSpaces }); + }, + new Map() + ); + + const logAuthorizationFailure = () => { + this.auditLogger.savedObjectsAuthorizationFailure( username, auditAction, types, spaceIds, + missingPrivileges, args ); - } else { - const missingPrivileges = this.getMissingPrivileges(privileges); - this.auditLogger.savedObjectsAuthorizationFailure( + }; + const logAuthorizationSuccess = (typeArray: string[], spaceIdArray: string[]) => { + this.auditLogger.savedObjectsAuthorizationSuccess( username, auditAction, - types, - spaceIds, - missingPrivileges, + typeArray, + spaceIdArray, args ); + }; + + if (hasAllRequested) { + logAuthorizationSuccess(types, spaceIds); + return { typeMap, status: 'fully_authorized' }; + } else if (!requireFullAuthorization) { + const isPartiallyAuthorized = privileges.kibana.some(({ authorized }) => authorized); + if (isPartiallyAuthorized) { + for (const [type, { isGloballyAuthorized, authorizedSpaces }] of typeMap.entries()) { + // generate an individual audit record for each authorized type + logAuthorizationSuccess([type], isGloballyAuthorized ? spaceIds : authorizedSpaces); + } + return { typeMap, status: 'partially_authorized' }; + } else { + logAuthorizationFailure(); + return { typeMap, status: 'unauthorized' }; + } + } else { + logAuthorizationFailure(); const targetTypes = uniq( missingPrivileges.map(({ privilege }) => actionsToTypesMap.get(privilege)).sort() ).join(','); @@ -303,19 +383,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra } private redactAndSortNamespaces(spaceIds: string[], privilegeMap: Record) { - const comparator = (a: string, b: string) => { - const _a = a.toLowerCase(); - const _b = b.toLowerCase(); - if (_a === '?') { - return 1; - } else if (_a < _b) { - return -1; - } else if (_a > _b) { - return 1; - } - return 0; - }; - return spaceIds.map((spaceId) => (privilegeMap[spaceId] ? spaceId : '?')).sort(comparator); + return spaceIds.map((x) => (privilegeMap[x] ? x : '?')).sort(namespaceComparator); } private async redactSavedObjectNamespaces( @@ -362,3 +430,25 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra }; } } + +/** + * Returns all unique elements of an array. + */ +function uniq(arr: T[]): T[] { + return Array.from(new Set(arr)); +} + +/** + * Utility function to sort potentially redacted namespaces. + * Sorts in a case-insensitive manner, and ensures that redacted namespaces ('?') always show up at the end of the array. + */ +function namespaceComparator(a: string, b: string) { + const A = a.toUpperCase(); + const B = b.toUpperCase(); + if (A === '?' && B !== '?') { + return 1; + } else if (A !== '?' && B === '?') { + return -1; + } + return A > B ? 1 : A < B ? -1 : 0; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts index 63a57c20a85932..a39638e48892dd 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts @@ -9,6 +9,7 @@ export * from './authentications'; export * from './common'; export * from './details'; export * from './first_last_seen'; +export * from './kpi'; export * from './overview'; export * from './uncommon_processes'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/authentications/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/authentications/index.ts new file mode 100644 index 00000000000000..cbf1f32c3b5fa7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/authentications/index.ts @@ -0,0 +1,24 @@ +/* + * 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 { Inspect, Maybe } from '../../../../common'; +import { RequestBasicOptions } from '../../..'; +import { HostsKpiHistogramData } from '../common'; + +export interface HostsKpiAuthenticationsHistogramCount { + doc_count: number; +} + +export type HostsKpiAuthenticationsRequestOptions = RequestBasicOptions; + +export interface HostsKpiAuthenticationsStrategyResponse extends IEsSearchResponse { + authenticationsSuccess: Maybe; + authenticationsSuccessHistogram: Maybe; + authenticationsFailure: Maybe; + authenticationsFailureHistogram: Maybe; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/common/index.ts new file mode 100644 index 00000000000000..52e65bb9957964 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/common/index.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Maybe } from '../../../../common'; + +export interface HostsKpiHistogramData { + x?: Maybe; + y?: Maybe; +} + +export interface HostsKpiHistogram { + key_as_string: string; + key: number; + doc_count: number; + count: T; +} + +export interface HostsKpiGeneralHistogramCount { + value: number; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts new file mode 100644 index 00000000000000..8e8bd97c9b60b3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/hosts/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { Inspect, Maybe } from '../../../../common'; +import { RequestBasicOptions } from '../../..'; +import { HostsKpiHistogramData } from '../common'; + +export type HostsKpiHostsRequestOptions = RequestBasicOptions; + +export interface HostsKpiHostsStrategyResponse extends IEsSearchResponse { + hosts: Maybe; + hostsHistogram: Maybe; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts new file mode 100644 index 00000000000000..dc34f619e0362d --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './authentications'; +export * from './common'; +export * from './hosts'; +export * from './unique_ips'; + +import { HostsKpiAuthenticationsStrategyResponse } from './authentications'; +import { HostsKpiHostsStrategyResponse } from './hosts'; +import { HostsKpiUniqueIpsStrategyResponse } from './unique_ips'; + +export enum HostsKpiQueries { + kpiAuthentications = 'hostsKpiAuthentications', + kpiHosts = 'hostsKpiHosts', + kpiUniqueIps = 'hostsKpiUniqueIps', +} + +export type HostsKpiStrategyResponse = + | Omit + | Omit + | Omit; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.ts new file mode 100644 index 00000000000000..18a603725f401f --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/unique_ips/index.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 { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { Inspect, Maybe } from '../../../../common'; +import { RequestBasicOptions } from '../../..'; +import { HostsKpiHistogramData } from '../common'; + +export type HostsKpiUniqueIpsRequestOptions = RequestBasicOptions; + +export interface HostsKpiUniqueIpsStrategyResponse extends IEsSearchResponse { + uniqueSourceIps: Maybe; + uniqueSourceIpsHistogram: Maybe; + uniqueDestinationIps: Maybe; + uniqueDestinationIpsHistogram: Maybe; + inspect?: Maybe; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index 95f3cd4fd7da7f..cfcf613b662bca 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -20,6 +20,13 @@ import { HostsStrategyResponse, HostUncommonProcessesStrategyResponse, HostUncommonProcessesRequestOptions, + HostsKpiQueries, + HostsKpiAuthenticationsStrategyResponse, + HostsKpiAuthenticationsRequestOptions, + HostsKpiHostsStrategyResponse, + HostsKpiHostsRequestOptions, + HostsKpiUniqueIpsStrategyResponse, + HostsKpiUniqueIpsRequestOptions, } from './hosts'; import { NetworkQueries, @@ -70,6 +77,7 @@ export * from './network'; export type FactoryQueryTypes = | HostsQueries + | HostsKpiQueries | NetworkQueries | NetworkKpiQueries | typeof MatrixHistogramQuery; @@ -106,6 +114,12 @@ export type StrategyResponseType = T extends HostsQ ? HostFirstLastSeenStrategyResponse : T extends HostsQueries.uncommonProcesses ? HostUncommonProcessesStrategyResponse + : T extends HostsKpiQueries.kpiAuthentications + ? HostsKpiAuthenticationsStrategyResponse + : T extends HostsKpiQueries.kpiHosts + ? HostsKpiHostsStrategyResponse + : T extends HostsKpiQueries.kpiUniqueIps + ? HostsKpiUniqueIpsStrategyResponse : T extends NetworkQueries.details ? NetworkDetailsStrategyResponse : T extends NetworkQueries.dns @@ -148,6 +162,12 @@ export type StrategyRequestType = T extends HostsQu ? HostFirstLastSeenRequestOptions : T extends HostsQueries.uncommonProcesses ? HostUncommonProcessesRequestOptions + : T extends HostsKpiQueries.kpiAuthentications + ? HostsKpiAuthenticationsRequestOptions + : T extends HostsKpiQueries.kpiHosts + ? HostsKpiHostsRequestOptions + : T extends HostsKpiQueries.kpiUniqueIps + ? HostsKpiUniqueIpsRequestOptions : T extends NetworkQueries.details ? NetworkDetailsRequestOptions : T extends NetworkQueries.dns diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts index 3de57b085a9c67..fdfa042e8fcc98 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts @@ -30,7 +30,8 @@ import { loginAndWaitForPage } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; -describe('Alerts', () => { +// FLAKY: https://github.com/elastic/kibana/issues/77957 +describe.skip('Alerts', () => { context('Closing alerts', () => { beforeEach(() => { esArchiverLoad('alerts'); diff --git a/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx index 664d8b2ff5598d..310d4c52ec5bce 100644 --- a/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/stat_items/index.test.tsx @@ -39,8 +39,10 @@ import { } from '../../mock'; import { State, createStore } from '../../store'; import { Provider as ReduxStoreProvider } from 'react-redux'; -import { KpiHostsData } from '../../../graphql/types'; -import { NetworkKpiStrategyResponse } from '../../../../common/search_strategy'; +import { + HostsKpiStrategyResponse, + NetworkKpiStrategyResponse, +} from '../../../../common/search_strategy'; const from = '2019-06-15T06:00:00.000Z'; const to = '2019-06-18T06:00:00.000Z'; @@ -242,7 +244,7 @@ describe('useKpiMatrixStatus', () => { data, }: { fieldsMapping: Readonly; - data: NetworkKpiStrategyResponse | KpiHostsData; + data: NetworkKpiStrategyResponse | HostsKpiStrategyResponse; }) => { const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( fieldsMapping, diff --git a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx index 13a93a784a2c9d..34fb344eed3c4c 100644 --- a/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/stat_items/index.tsx @@ -18,8 +18,10 @@ import { get, getOr } from 'lodash/fp'; import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; -import { NetworkKpiStrategyResponse } from '../../../../common/search_strategy'; -import { KpiHostsData } from '../../../graphql/types'; +import { + HostsKpiStrategyResponse, + NetworkKpiStrategyResponse, +} from '../../../../common/search_strategy'; import { AreaChart } from '../charts/areachart'; import { BarChart } from '../charts/barchart'; import { ChartSeriesData, ChartData, ChartSeriesConfigs, UpdateDateRange } from '../charts/common'; @@ -113,12 +115,12 @@ export const barchartConfigs = (config?: { onElementClick?: ElementClickListener export const addValueToFields = ( fields: StatItem[], - data: KpiHostsData | NetworkKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse ): StatItem[] => fields.map((field) => ({ ...field, value: get(field.key, data) })); export const addValueToAreaChart = ( fields: StatItem[], - data: KpiHostsData | NetworkKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse ): ChartSeriesData[] => fields .filter((field) => get(`${field.key}Histogram`, data) != null) @@ -130,7 +132,7 @@ export const addValueToAreaChart = ( export const addValueToBarChart = ( fields: StatItem[], - data: KpiHostsData | NetworkKpiStrategyResponse + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse ): ChartSeriesData[] => { if (fields.length === 0) return []; return fields.reduce((acc: ChartSeriesData[], field: StatItem, idx: number) => { @@ -159,7 +161,7 @@ export const addValueToBarChart = ( export const useKpiMatrixStatus = ( mappings: Readonly, - data: KpiHostsData | NetworkKpiStrategyResponse, + data: HostsKpiStrategyResponse | NetworkKpiStrategyResponse, id: string, from: string, to: string, diff --git a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts index 0e918275e2b183..d437a6b73f71a4 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts +++ b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/translations.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; export const AUTHENTICATIONS = i18n.translate( - 'xpack.securitySolution.authenticationsTable.authenticationFailures', + 'xpack.securitySolution.authenticationsTable.authentications', { defaultMessage: 'Authentications', } diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 2b2a35945bdf18..00000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,155 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`kpiHostsComponent render it should render KpiHostDetailsData 1`] = ` - - - - -`; - -exports[`kpiHostsComponent render it should render KpiHostsData 1`] = ` - - - - - -`; - -exports[`kpiHostsComponent render it should render spinner if it is loading 1`] = ` - - - - - -`; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx new file mode 100644 index 00000000000000..09496168274701 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/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 React from 'react'; + +import { StatItems } from '../../../../common/components/stat_items'; +import { useHostsKpiAuthentications } from '../../../containers/kpi_hosts/authentications'; +import { HostsKpiBaseComponentManage } from '../common'; +import { HostsKpiProps, HostsKpiChartColors } from '../types'; +import * as i18n from './translations'; + +export const fieldsMapping: Readonly = [ + { + key: 'authentication', + fields: [ + { + key: 'authenticationsSuccess', + name: i18n.SUCCESS_CHART_LABEL, + description: i18n.SUCCESS_UNIT_LABEL, + value: null, + color: HostsKpiChartColors.authenticationsSuccess, + icon: 'check', + }, + { + key: 'authenticationsFailure', + name: i18n.FAIL_CHART_LABEL, + description: i18n.FAIL_UNIT_LABEL, + value: null, + color: HostsKpiChartColors.authenticationsFailure, + icon: 'cross', + }, + ], + enableAreaChart: true, + enableBarChart: true, + description: i18n.USER_AUTHENTICATIONS, + }, +]; + +const HostsKpiAuthenticationsComponent: React.FC = ({ + filterQuery, + from, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useHostsKpiAuthentications({ + filterQuery, + endDate: to, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const HostsKpiAuthentications = React.memo(HostsKpiAuthenticationsComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts similarity index 55% rename from x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts rename to x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts index 82543e6f106fa0..5175781159c911 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/translations.ts @@ -3,11 +3,8 @@ * 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 HOSTS = i18n.translate('xpack.securitySolution.kpiHosts.hosts.title', { - defaultMessage: 'Hosts', -}); +import { i18n } from '@kbn/i18n'; export const USER_AUTHENTICATIONS = i18n.translate( 'xpack.securitySolution.kpiHosts.userAuthentications.title', @@ -43,35 +40,3 @@ export const FAIL_CHART_LABEL = i18n.translate( defaultMessage: 'Fail', } ); - -export const UNIQUE_IPS = i18n.translate('xpack.securitySolution.kpiHosts.uniqueIps.title', { - defaultMessage: 'Unique IPs', -}); - -export const SOURCE_UNIT_LABEL = i18n.translate( - 'xpack.securitySolution.kpiHosts.uniqueIps.sourceUnitLabel', - { - defaultMessage: 'source', - } -); - -export const DESTINATION_UNIT_LABEL = i18n.translate( - 'xpack.securitySolution.kpiHosts.uniqueIps.destinationUnitLabel', - { - defaultMessage: 'destination', - } -); - -export const SOURCE_CHART_LABEL = i18n.translate( - 'xpack.securitySolution.kpiHosts.uniqueIps.sourceChartLabel', - { - defaultMessage: 'Src.', - } -); - -export const DESTINATION_CHART_LABEL = i18n.translate( - 'xpack.securitySolution.kpiHosts.uniqueIps.destinationChartLabel', - { - defaultMessage: 'Dest.', - } -); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/common/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/common/index.tsx new file mode 100644 index 00000000000000..7c51a503092afa --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/common/index.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 { EuiFlexItem, EuiLoadingSpinner, EuiFlexGroup } from '@elastic/eui'; +import styled from 'styled-components'; + +import { manageQuery } from '../../../../common/components/page/manage_query'; +import { HostsKpiStrategyResponse } from '../../../../../common/search_strategy'; +import { + StatItemsComponent, + StatItemsProps, + useKpiMatrixStatus, + StatItems, +} from '../../../../common/components/stat_items'; +import { UpdateDateRange } from '../../../../common/components/charts/common'; + +const kpiWidgetHeight = 247; + +export const FlexGroup = styled(EuiFlexGroup)` + min-height: ${kpiWidgetHeight}px; +`; + +FlexGroup.displayName = 'FlexGroup'; + +export const HostsKpiBaseComponent = React.memo<{ + fieldsMapping: Readonly; + data: HostsKpiStrategyResponse; + loading?: boolean; + id: string; + from: string; + to: string; + narrowDateRange: UpdateDateRange; +}>(({ fieldsMapping, data, id, loading = false, from, to, narrowDateRange }) => { + const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( + fieldsMapping, + data, + id, + from, + to, + narrowDateRange + ); + + if (loading) { + return ( + + + + + + ); + } + + return ( + + {statItemsProps.map((mappedStatItemProps) => ( + + ))} + + ); +}); + +HostsKpiBaseComponent.displayName = 'HostsKpiBaseComponent'; + +export const HostsKpiBaseComponentManage = manageQuery(HostsKpiBaseComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx new file mode 100644 index 00000000000000..b1c4d6331e4504 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx @@ -0,0 +1,62 @@ +/* + * 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 { StatItems } from '../../../../common/components/stat_items'; +import { useHostsKpiHosts } from '../../../containers/kpi_hosts/hosts'; +import { HostsKpiBaseComponentManage } from '../common'; +import { HostsKpiProps, HostsKpiChartColors } from '../types'; +import * as i18n from './translations'; + +export const fieldsMapping: Readonly = [ + { + key: 'hosts', + fields: [ + { + key: 'hosts', + value: null, + color: HostsKpiChartColors.hosts, + icon: 'storage', + }, + ], + enableAreaChart: true, + description: i18n.HOSTS, + }, +]; + +const HostsKpiHostsComponent: React.FC = ({ + filterQuery, + from, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useHostsKpiHosts({ + filterQuery, + endDate: to, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const HostsKpiHosts = React.memo(HostsKpiHostsComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/translations.ts new file mode 100644 index 00000000000000..7754591ab415b2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/translations.ts @@ -0,0 +1,11 @@ +/* + * 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 HOSTS = i18n.translate('xpack.securitySolution.kpiHosts.hosts.title', { + defaultMessage: 'Hosts', +}); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.test.tsx deleted file mode 100644 index 7731881df6d2c7..00000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.test.tsx +++ /dev/null @@ -1,107 +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 { mockKpiHostsData, mockKpiHostDetailsData } from './mock'; -import React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; -import '../../../common/mock/match_media'; -import { KpiHostsComponentBase } from '.'; -import * as statItems from '../../../common/components/stat_items'; -import { kpiHostsMapping } from './kpi_hosts_mapping'; -import { kpiHostDetailsMapping } from './kpi_host_details_mapping'; - -describe('kpiHostsComponent', () => { - const ID = 'kpiHost'; - const from = '2019-06-15T06:00:00.000Z'; - const to = '2019-06-18T06:00:00.000Z'; - const narrowDateRange = () => {}; - describe('render', () => { - test('it should render spinner if it is loading', () => { - const wrapper: ShallowWrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - }); - - test('it should render KpiHostsData', () => { - const wrapper: ShallowWrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - }); - - test('it should render KpiHostDetailsData', () => { - const wrapper: ShallowWrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - }); - }); - - const table = [ - [mockKpiHostsData, kpiHostsMapping] as [typeof mockKpiHostsData, typeof kpiHostsMapping], - [mockKpiHostDetailsData, kpiHostDetailsMapping] as [ - typeof mockKpiHostDetailsData, - typeof kpiHostDetailsMapping - ], - ]; - - describe.each(table)( - 'it should handle KpiHostsProps and KpiHostDetailsProps', - (data, mapping) => { - let mockUseKpiMatrixStatus: jest.SpyInstance; - beforeAll(() => { - mockUseKpiMatrixStatus = jest.spyOn(statItems, 'useKpiMatrixStatus'); - }); - - beforeEach(() => { - shallow( - - ); - }); - - afterEach(() => { - mockUseKpiMatrixStatus.mockClear(); - }); - - afterAll(() => { - mockUseKpiMatrixStatus.mockRestore(); - }); - - test(`it should apply correct mapping by given data type`, () => { - expect(mockUseKpiMatrixStatus).toBeCalledWith(mapping, data, ID, from, to, narrowDateRange); - }); - } - ); -}); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx index c39e86591013f9..fff4c64900a8bb 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx @@ -4,81 +4,78 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import React from 'react'; -import styled from 'styled-components'; +import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; -import { KpiHostsData, KpiHostDetailsData } from '../../../graphql/types'; -import { - StatItemsComponent, - StatItemsProps, - useKpiMatrixStatus, -} from '../../../common/components/stat_items'; -import { kpiHostsMapping } from './kpi_hosts_mapping'; -import { kpiHostDetailsMapping } from './kpi_host_details_mapping'; -import { UpdateDateRange } from '../../../common/components/charts/common'; +import { HostsKpiAuthentications } from './authentications'; +import { HostsKpiHosts } from './hosts'; +import { HostsKpiUniqueIps } from './unique_ips'; +import { HostsKpiProps } from './types'; -const kpiWidgetHeight = 247; - -interface GenericKpiHostProps { - from: string; - id: string; - loading: boolean; - to: string; - narrowDateRange: UpdateDateRange; -} - -interface KpiHostsProps extends GenericKpiHostProps { - data: KpiHostsData; -} - -interface KpiHostDetailsProps extends GenericKpiHostProps { - data: KpiHostDetailsData; -} - -const FlexGroupSpinner = styled(EuiFlexGroup)` - { - min-height: ${kpiWidgetHeight}px; - } -`; - -FlexGroupSpinner.displayName = 'FlexGroupSpinner'; - -export const KpiHostsComponentBase = ({ - data, - from, - loading, - id, - to, - narrowDateRange, -}: KpiHostsProps | KpiHostDetailsProps) => { - const mappings = - (data as KpiHostsData).hosts !== undefined ? kpiHostsMapping : kpiHostDetailsMapping; - const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( - mappings, - data, - id, - from, - to, - narrowDateRange - ); - return loading ? ( - - - +export const HostsKpiComponent = React.memo( + ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => ( + + + + + + + + + - - ) : ( - - {statItemsProps.map((mappedStatItemProps, idx) => { - return ; - })} - ); -}; + ) +); -KpiHostsComponentBase.displayName = 'KpiHostsComponentBase'; +HostsKpiComponent.displayName = 'HostsKpiComponent'; -export const KpiHostsComponent = React.memo(KpiHostsComponentBase); +export const HostsDetailsKpiComponent = React.memo( + ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => ( + + + + + + + + + ) +); -KpiHostsComponent.displayName = 'KpiHostsComponent'; +HostsDetailsKpiComponent.displayName = 'HostsDetailsKpiComponent'; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_host_details_mapping.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_host_details_mapping.ts deleted file mode 100644 index b3e98b70c4cb0e..00000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_host_details_mapping.ts +++ /dev/null @@ -1,64 +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 * as i18n from './translations'; -import { StatItems } from '../../../common/components/stat_items'; -import { KpiHostsChartColors } from './types'; - -export const kpiHostDetailsMapping: Readonly = [ - { - key: 'authentication', - index: 0, - fields: [ - { - key: 'authSuccess', - name: i18n.SUCCESS_CHART_LABEL, - description: i18n.SUCCESS_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.authSuccess, - icon: 'check', - }, - { - key: 'authFailure', - name: i18n.FAIL_CHART_LABEL, - description: i18n.FAIL_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.authFailure, - icon: 'cross', - }, - ], - enableAreaChart: true, - enableBarChart: true, - grow: 1, - description: i18n.USER_AUTHENTICATIONS, - }, - { - key: 'uniqueIps', - index: 1, - fields: [ - { - key: 'uniqueSourceIps', - name: i18n.SOURCE_CHART_LABEL, - description: i18n.SOURCE_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.uniqueSourceIps, - icon: 'visMapCoordinate', - }, - { - key: 'uniqueDestinationIps', - name: i18n.DESTINATION_CHART_LABEL, - description: i18n.DESTINATION_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.uniqueDestinationIps, - icon: 'visMapCoordinate', - }, - ], - enableAreaChart: true, - enableBarChart: true, - grow: 1, - description: i18n.UNIQUE_IPS, - }, -]; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_hosts_mapping.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_hosts_mapping.ts deleted file mode 100644 index 78a9fd5b84d1fa..00000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/kpi_hosts_mapping.ts +++ /dev/null @@ -1,79 +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 * as i18n from './translations'; -import { KpiHostsChartColors } from './types'; -import { StatItems } from '../../../common/components/stat_items'; - -export const kpiHostsMapping: Readonly = [ - { - key: 'hosts', - index: 0, - fields: [ - { - key: 'hosts', - value: null, - color: KpiHostsChartColors.hosts, - icon: 'storage', - }, - ], - enableAreaChart: true, - grow: 2, - description: i18n.HOSTS, - }, - { - key: 'authentication', - index: 1, - fields: [ - { - key: 'authSuccess', - name: i18n.SUCCESS_CHART_LABEL, - description: i18n.SUCCESS_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.authSuccess, - icon: 'check', - }, - { - key: 'authFailure', - name: i18n.FAIL_CHART_LABEL, - description: i18n.FAIL_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.authFailure, - icon: 'cross', - }, - ], - enableAreaChart: true, - enableBarChart: true, - grow: 4, - description: i18n.USER_AUTHENTICATIONS, - }, - { - key: 'uniqueIps', - index: 2, - fields: [ - { - key: 'uniqueSourceIps', - name: i18n.SOURCE_CHART_LABEL, - description: i18n.SOURCE_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.uniqueSourceIps, - icon: 'visMapCoordinate', - }, - { - key: 'uniqueDestinationIps', - name: i18n.DESTINATION_CHART_LABEL, - description: i18n.DESTINATION_UNIT_LABEL, - value: null, - color: KpiHostsChartColors.uniqueDestinationIps, - icon: 'visMapCoordinate', - }, - ], - enableAreaChart: true, - enableBarChart: true, - grow: 4, - description: i18n.UNIQUE_IPS, - }, -]; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/mock.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/mock.tsx deleted file mode 100644 index a1d081af204357..00000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/mock.tsx +++ /dev/null @@ -1,145 +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 const mockKpiHostsData = { - hosts: 986, - hostsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 919, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 82, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 4, - }, - ], - authSuccess: 61, - authSuccessHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 8, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 52, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 1, - }, - ], - authFailure: 15722, - authFailureHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 11731, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 3979, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 12, - }, - ], - uniqueSourceIps: 1407, - uniqueSourceIpsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 1182, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 364, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 63, - }, - ], - uniqueDestinationIps: 1954, - uniqueDestinationIpsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 1809, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 407, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 64, - }, - ], -}; -export const mockKpiHostDetailsData = { - authSuccess: 61, - authSuccessHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 8, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 52, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 1, - }, - ], - authFailure: 15722, - authFailureHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 11731, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 3979, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 12, - }, - ], - uniqueSourceIps: 1407, - uniqueSourceIpsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 1182, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 364, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 63, - }, - ], - uniqueDestinationIps: 1954, - uniqueDestinationIpsHistogram: [ - { - x: new Date('2019-05-03T13:00:00.000Z').valueOf(), - y: 1809, - }, - { - x: new Date('2019-05-04T01:00:00.000Z').valueOf(), - y: 407, - }, - { - x: new Date('2019-05-04T13:00:00.000Z').valueOf(), - y: 64, - }, - ], -}; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts index fd483681247952..7cdbb16ee348cd 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts @@ -4,9 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -export enum KpiHostsChartColors { - authSuccess = '#54B399', - authFailure = '#E7664C', +import { UpdateDateRange } from '../../../common/components/charts/common'; +import { GlobalTimeArgs } from '../../../common/containers/use_global_time'; + +export interface HostsKpiProps { + filterQuery: string; + from: string; + to: string; + narrowDateRange: UpdateDateRange; + setQuery: GlobalTimeArgs['setQuery']; + skip: boolean; +} + +export enum HostsKpiChartColors { + authenticationsSuccess = '#54B399', + authenticationsFailure = '#E7664C', uniqueSourceIps = '#D36086', uniqueDestinationIps = '#9170B8', hosts = '#6092C0', diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/index.tsx new file mode 100644 index 00000000000000..c6f430faacccb9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/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 React from 'react'; + +import { StatItems } from '../../../../common/components/stat_items'; +import { useHostsKpiUniqueIps } from '../../../containers/kpi_hosts/unique_ips'; +import { HostsKpiBaseComponentManage } from '../common'; +import { HostsKpiProps, HostsKpiChartColors } from '../types'; +import * as i18n from './translations'; + +export const fieldsMapping: Readonly = [ + { + key: 'uniqueIps', + fields: [ + { + key: 'uniqueSourceIps', + name: i18n.SOURCE_CHART_LABEL, + description: i18n.SOURCE_UNIT_LABEL, + value: null, + color: HostsKpiChartColors.uniqueSourceIps, + icon: 'visMapCoordinate', + }, + { + key: 'uniqueDestinationIps', + name: i18n.DESTINATION_CHART_LABEL, + description: i18n.DESTINATION_UNIT_LABEL, + value: null, + color: HostsKpiChartColors.uniqueDestinationIps, + icon: 'visMapCoordinate', + }, + ], + enableAreaChart: true, + enableBarChart: true, + description: i18n.UNIQUE_IPS, + }, +]; + +const HostsKpiUniqueIpsComponent: React.FC = ({ + filterQuery, + from, + to, + narrowDateRange, + setQuery, + skip, +}) => { + const [loading, { refetch, id, inspect, ...data }] = useHostsKpiUniqueIps({ + filterQuery, + endDate: to, + startDate: from, + skip, + }); + + return ( + + ); +}; + +export const HostsKpiUniqueIps = React.memo(HostsKpiUniqueIpsComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/translations.ts new file mode 100644 index 00000000000000..6cc651880be7bf --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/unique_ips/translations.ts @@ -0,0 +1,39 @@ +/* + * 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 UNIQUE_IPS = i18n.translate('xpack.securitySolution.kpiHosts.uniqueIps.title', { + defaultMessage: 'Unique IPs', +}); + +export const SOURCE_UNIT_LABEL = i18n.translate( + 'xpack.securitySolution.kpiHosts.uniqueIps.sourceUnitLabel', + { + defaultMessage: 'source', + } +); + +export const DESTINATION_UNIT_LABEL = i18n.translate( + 'xpack.securitySolution.kpiHosts.uniqueIps.destinationUnitLabel', + { + defaultMessage: 'destination', + } +); + +export const SOURCE_CHART_LABEL = i18n.translate( + 'xpack.securitySolution.kpiHosts.uniqueIps.sourceChartLabel', + { + defaultMessage: 'Src.', + } +); + +export const DESTINATION_CHART_LABEL = i18n.translate( + 'xpack.securitySolution.kpiHosts.uniqueIps.destinationChartLabel', + { + defaultMessage: 'Dest.', + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx new file mode 100644 index 00000000000000..0d90b73e0a5843 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx @@ -0,0 +1,170 @@ +/* + * 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 deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + HostsKpiQueries, + HostsKpiAuthenticationsRequestOptions, + HostsKpiAuthenticationsStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'hostsKpiAuthenticationsQuery'; + +export interface HostsKpiAuthenticationsArgs + extends Omit { + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseHostsKpiAuthentications { + filterQuery?: ESTermQuery | string; + endDate: string; + skip?: boolean; + startDate: string; +} + +export const useHostsKpiAuthentications = ({ + filterQuery, + endDate, + skip = false, + startDate, +}: UseHostsKpiAuthentications): [boolean, HostsKpiAuthenticationsArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [hostsKpiAuthenticationsRequest, setHostsKpiAuthenticationsRequest] = useState< + HostsKpiAuthenticationsRequestOptions + >({ + defaultIndex, + factoryQueryType: HostsKpiQueries.kpiAuthentications, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [hostsKpiAuthenticationsResponse, setHostsKpiAuthenticationsResponse] = useState< + HostsKpiAuthenticationsArgs + >({ + authenticationsSuccess: 0, + authenticationsSuccessHistogram: [], + authenticationsFailure: 0, + authenticationsFailureHistogram: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); + + const hostsKpiAuthenticationsSearch = useCallback( + (request: HostsKpiAuthenticationsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search( + request, + { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + } + ) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setHostsKpiAuthenticationsResponse((prevResponse) => ({ + ...prevResponse, + authenticationsSuccess: response.authenticationsSuccess, + authenticationsSuccessHistogram: response.authenticationsSuccessHistogram, + authenticationsFailure: response.authenticationsFailure, + authenticationsFailureHistogram: response.authenticationsFailureHistogram, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_AUTHENTICATIONS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_HOSTS_KPI_AUTHENTICATIONS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setHostsKpiAuthenticationsRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + hostsKpiAuthenticationsSearch(hostsKpiAuthenticationsRequest); + }, [hostsKpiAuthenticationsRequest, hostsKpiAuthenticationsSearch]); + + return [loading, hostsKpiAuthenticationsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts new file mode 100644 index 00000000000000..fb5af83d0acef0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_HOSTS_KPI_AUTHENTICATIONS = i18n.translate( + 'xpack.securitySolution.hostsKpiAuthentications.errorSearchDescription', + { + defaultMessage: `An error has occurred on hosts kpi authentications search`, + } +); + +export const FAIL_HOSTS_KPI_AUTHENTICATIONS = i18n.translate( + 'xpack.securitySolution.hostsKpiAuthentications.failSearchDescription', + { + defaultMessage: `Failed to run search on hosts kpi authentications`, + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx new file mode 100644 index 00000000000000..190ce1aa7eae1c --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx @@ -0,0 +1,158 @@ +/* + * 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 deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + HostsKpiQueries, + HostsKpiHostsRequestOptions, + HostsKpiHostsStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'hostsKpiHostsQuery'; + +export interface HostsKpiHostsArgs extends Omit { + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseHostsKpiHosts { + filterQuery?: ESTermQuery | string; + endDate: string; + skip?: boolean; + startDate: string; +} + +export const useHostsKpiHosts = ({ + filterQuery, + endDate, + skip = false, + startDate, +}: UseHostsKpiHosts): [boolean, HostsKpiHostsArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [hostsKpiHostsRequest, setHostsKpiHostsRequest] = useState({ + defaultIndex, + factoryQueryType: HostsKpiQueries.kpiHosts, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [hostsKpiHostsResponse, setHostsKpiHostsResponse] = useState({ + hosts: 0, + hostsHistogram: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); + + const hostsKpiHostsSearch = useCallback( + (request: HostsKpiHostsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setHostsKpiHostsResponse((prevResponse) => ({ + ...prevResponse, + hosts: response.hosts, + hostsHistogram: response.hostsHistogram, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_HOSTS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_HOSTS_KPI_HOSTS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setHostsKpiHostsRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + hostsKpiHostsSearch(hostsKpiHostsRequest); + }, [hostsKpiHostsRequest, hostsKpiHostsSearch]); + + return [loading, hostsKpiHostsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/translations.ts new file mode 100644 index 00000000000000..2a15563a4b1cd8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_HOSTS_KPI_HOSTS = i18n.translate( + 'xpack.securitySolution.hostsKpiHosts.errorSearchDescription', + { + defaultMessage: `An error has occurred on hosts kpi hosts search`, + } +); + +export const FAIL_HOSTS_KPI_HOSTS = i18n.translate( + 'xpack.securitySolution.hostsKpiHosts.failSearchDescription', + { + defaultMessage: `Failed to run search on hosts kpi hosts`, + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx index 1a6df58f045976..c0ae767219aaea 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/index.tsx @@ -4,82 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getOr } from 'lodash/fp'; -import React from 'react'; -import { Query } from 'react-apollo'; -import { connect, ConnectedProps } from 'react-redux'; - -import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; -import { GetKpiHostsQuery, KpiHostsData } from '../../../graphql/types'; -import { inputsModel, inputsSelectors, State } from '../../../common/store'; -import { useUiSetting } from '../../../common/lib/kibana'; -import { createFilter, getDefaultFetchPolicy } from '../../../common/containers/helpers'; -import { QueryTemplateProps } from '../../../common/containers/query_template'; - -import { kpiHostsQuery } from './index.gql_query'; - -const ID = 'kpiHostsQuery'; - -export interface KpiHostsArgs { - id: string; - inspect: inputsModel.InspectQuery; - kpiHosts: KpiHostsData; - loading: boolean; - refetch: inputsModel.Refetch; -} - -export interface KpiHostsProps extends QueryTemplateProps { - children: (args: KpiHostsArgs) => React.ReactNode; -} - -const KpiHostsComponentQuery = React.memo( - ({ id = ID, children, endDate, filterQuery, isInspected, skip, sourceId, startDate }) => ( - - query={kpiHostsQuery} - fetchPolicy={getDefaultFetchPolicy()} - notifyOnNetworkStatusChange - skip={skip} - variables={{ - sourceId, - timerange: { - interval: '12h', - from: startDate!, - to: endDate!, - }, - filterQuery: createFilter(filterQuery), - defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), - inspect: isInspected, - }} - > - {({ data, loading, refetch }) => { - const kpiHosts = getOr({}, `source.KpiHosts`, data); - return children({ - id, - inspect: getOr(null, 'source.KpiHosts.inspect', data), - kpiHosts, - loading, - refetch, - }); - }} - - ) -); - -KpiHostsComponentQuery.displayName = 'KpiHostsComponentQuery'; - -const makeMapStateToProps = () => { - const getQuery = inputsSelectors.globalQueryByIdSelector(); - const mapStateToProps = (state: State, { id = ID }: KpiHostsProps) => { - const { isInspected } = getQuery(state, id); - return { - isInspected, - }; - }; - return mapStateToProps; -}; - -const connector = connect(makeMapStateToProps); - -type PropsFromRedux = ConnectedProps; - -export const KpiHostsQuery = connector(KpiHostsComponentQuery); +export * from './authentications'; +export * from './hosts'; +export * from './unique_ips'; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx new file mode 100644 index 00000000000000..ac5cc12807f007 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx @@ -0,0 +1,167 @@ +/* + * 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 deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; +import { inputsModel } from '../../../../common/store'; +import { createFilter } from '../../../../common/containers/helpers'; +import { useKibana } from '../../../../common/lib/kibana'; +import { + HostsKpiQueries, + HostsKpiUniqueIpsRequestOptions, + HostsKpiUniqueIpsStrategyResponse, +} from '../../../../../common/search_strategy'; +import { ESTermQuery } from '../../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../../src/plugins/data/common'; +import { getInspectResponse } from '../../../../helpers'; +import { InspectResponse } from '../../../../types'; + +const ID = 'hostsKpiUniqueIpsQuery'; + +export interface HostsKpiUniqueIpsArgs + extends Omit { + id: string; + inspect: InspectResponse; + isInspected: boolean; + refetch: inputsModel.Refetch; +} + +interface UseHostsKpiUniqueIps { + filterQuery?: ESTermQuery | string; + endDate: string; + skip?: boolean; + startDate: string; +} + +export const useHostsKpiUniqueIps = ({ + filterQuery, + endDate, + skip = false, + startDate, +}: UseHostsKpiUniqueIps): [boolean, HostsKpiUniqueIpsArgs] => { + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [hostsKpiUniqueIpsRequest, setHostsKpiUniqueIpsRequest] = useState< + HostsKpiUniqueIpsRequestOptions + >({ + defaultIndex, + factoryQueryType: HostsKpiQueries.kpiUniqueIps, + filterQuery: createFilter(filterQuery), + id: ID, + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }); + + const [hostsKpiUniqueIpsResponse, setHostsKpiUniqueIpsResponse] = useState( + { + uniqueSourceIps: 0, + uniqueSourceIpsHistogram: [], + uniqueDestinationIps: 0, + uniqueDestinationIpsHistogram: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + } + ); + + const hostsKpiUniqueIpsSearch = useCallback( + (request: HostsKpiUniqueIpsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setHostsKpiUniqueIpsResponse((prevResponse) => ({ + ...prevResponse, + uniqueSourceIps: response.uniqueSourceIps, + uniqueSourceIpsHistogram: response.uniqueSourceIpsHistogram, + uniqueDestinationIps: response.uniqueDestinationIps, + uniqueDestinationIpsHistogram: response.uniqueDestinationIpsHistogram, + inspect: getInspectResponse(response, prevResponse.inspect), + refetch: refetch.current, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_UNIQUE_IPS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_HOSTS_KPI_UNIQUE_IPS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setHostsKpiUniqueIpsRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + filterQuery: createFilter(filterQuery), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + }; + if (!skip && !deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [defaultIndex, endDate, filterQuery, skip, startDate]); + + useEffect(() => { + hostsKpiUniqueIpsSearch(hostsKpiUniqueIpsRequest); + }, [hostsKpiUniqueIpsRequest, hostsKpiUniqueIpsSearch]); + + return [loading, hostsKpiUniqueIpsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/translations.ts new file mode 100644 index 00000000000000..2d1574b080ac1f --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/translations.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_HOSTS_KPI_UNIQUE_IPS = i18n.translate( + 'xpack.securitySolution.hostsKpiUniqueIps.errorSearchDescription', + { + defaultMessage: `An error has occurred on hosts kpi unique ips search`, + } +); + +export const FAIL_HOSTS_KPI_UNIQUE_IPS = i18n.translate( + 'xpack.securitySolution.hostsKpiUniqueIps.failSearchDescription', + { + defaultMessage: `Failed to run search on hosts kpi unique ips`, + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index d8cd59f119d525..57e1b128ce64de 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -21,13 +21,12 @@ import { hasMlUserPermissions } from '../../../../common/machine_learning/has_ml import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities'; import { scoreIntervalToDateTime } from '../../../common/components/ml/score/score_interval_to_datetime'; import { SiemNavigation } from '../../../common/components/navigation'; -import { KpiHostsComponent } from '../../components/kpi_hosts'; +import { HostsDetailsKpiComponent } from '../../components/kpi_hosts'; import { HostOverview } from '../../../overview/components/host_overview'; import { manageQuery } from '../../../common/components/page/manage_query'; import { SiemSearchBar } from '../../../common/components/search_bar'; import { WrapperPage } from '../../../common/components/wrapper_page'; import { HostOverviewByNameQuery } from '../../containers/hosts/details'; -import { KpiHostDetailsQuery } from '../../containers/kpi_host_details'; import { useGlobalTime } from '../../../common/containers/use_global_time'; import { useWithSource } from '../../../common/containers/source'; import { LastEventIndexKey } from '../../../graphql/types'; @@ -54,7 +53,6 @@ import { TimelineId } from '../../../../common/types/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; const HostOverviewManage = manageQuery(HostOverview); -const KpiHostDetailsManage = manageQuery(KpiHostsComponent); const HostDetailsComponent = React.memo( ({ @@ -160,27 +158,14 @@ const HostDetailsComponent = React.memo( - - {({ kpiHostDetails, id, inspect, loading, refetch }) => ( - - )} - + /> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index ef88c255b1735a..4b8e3cc6987ac8 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -17,11 +17,9 @@ import { HeaderPage } from '../../common/components/header_page'; import { LastEventTime } from '../../common/components/last_event_time'; import { hasMlUserPermissions } from '../../../common/machine_learning/has_ml_user_permissions'; import { SiemNavigation } from '../../common/components/navigation'; -import { KpiHostsComponent } from '../components/kpi_hosts'; -import { manageQuery } from '../../common/components/page/manage_query'; +import { HostsKpiComponent } from '../components/kpi_hosts'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; -import { KpiHostsQuery } from '../containers/kpi_hosts'; import { useFullScreen } from '../../common/containers/use_full_screen'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { useWithSource } from '../../common/containers/source'; @@ -49,8 +47,6 @@ import { timelineSelectors } from '../../timelines/store/timeline'; import { timelineDefaults } from '../../timelines/store/timeline/defaults'; import { TimelineModel } from '../../timelines/store/timeline/model'; -const KpiHostsComponentManage = manageQuery(KpiHostsComponent); - export const HostsComponent = React.memo( ({ filters, graphEventId, query, setAbsoluteRangeDatePicker, hostsPagePath }) => { const { to, from, deleteQuery, setQuery, isInitializing } = useGlobalTime(); @@ -109,27 +105,14 @@ export const HostsComponent = React.memo( title={i18n.PAGE_TITLE} /> - - {({ kpiHosts, loading, id, inspect, refetch }) => ( - - )} - + narrowDateRange={narrowDateRange} + /> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx index 65ddb9305f607d..d3fc68874ce912 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx @@ -16,7 +16,7 @@ import { MatrixHistogramConfigs, } from '../../../common/components/matrix_histogram/types'; import { MatrixHistogram } from '../../../common/components/matrix_histogram'; -import { KpiHostsChartColors } from '../../components/kpi_hosts/types'; +import { HostsKpiChartColors } from '../../components/kpi_hosts/types'; import * as i18n from '../translations'; import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution'; @@ -24,7 +24,7 @@ const AuthenticationTableManage = manageQuery(AuthenticationTable); const ID = 'authenticationsHistogramQuery'; -const authStackByOptions: MatrixHistogramOption[] = [ +const authenticationsStackByOptions: MatrixHistogramOption[] = [ { text: 'event.outcome', value: 'event.outcome', @@ -32,31 +32,32 @@ const authStackByOptions: MatrixHistogramOption[] = [ ]; const DEFAULT_STACK_BY = 'event.outcome'; -enum AuthMatrixDataGroup { - authSuccess = 'success', - authFailure = 'failure', +enum AuthenticationsMatrixDataGroup { + authenticationsSuccess = 'success', + authenticationsFailure = 'failure', } -export const authMatrixDataMappingFields: MatrixHistogramMappingTypes = { - [AuthMatrixDataGroup.authSuccess]: { - key: AuthMatrixDataGroup.authSuccess, +export const authenticationsMatrixDataMappingFields: MatrixHistogramMappingTypes = { + [AuthenticationsMatrixDataGroup.authenticationsSuccess]: { + key: AuthenticationsMatrixDataGroup.authenticationsSuccess, value: null, - color: KpiHostsChartColors.authSuccess, + color: HostsKpiChartColors.authenticationsSuccess, }, - [AuthMatrixDataGroup.authFailure]: { - key: AuthMatrixDataGroup.authFailure, + [AuthenticationsMatrixDataGroup.authenticationsFailure]: { + key: AuthenticationsMatrixDataGroup.authenticationsFailure, value: null, - color: KpiHostsChartColors.authFailure, + color: HostsKpiChartColors.authenticationsFailure, }, }; const histogramConfigs: MatrixHistogramConfigs = { defaultStackByOption: - authStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? authStackByOptions[0], + authenticationsStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? + authenticationsStackByOptions[0], errorMessage: i18n.ERROR_FETCHING_AUTHENTICATIONS_DATA, histogramType: MatrixHistogramType.authentications, - mapping: authMatrixDataMappingFields, - stackByOptions: authStackByOptions, + mapping: authenticationsMatrixDataMappingFields, + stackByOptions: authenticationsStackByOptions, title: i18n.NAVIGATION_AUTHENTICATIONS_TITLE, }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts index 4faef85afbdc8d..61bcd222b1b1ee 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts @@ -58,6 +58,7 @@ describe('EndpointList store concerns', () => { patternsError: undefined, isAutoRefreshEnabled: true, autoRefreshInterval: DEFAULT_POLL_INTERVAL, + queryStrategyVersion: undefined, }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index 7673702f543702..7872c8824a8eef 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -17,6 +17,7 @@ import { nonExistingPolicies, patterns, searchBarQuery, + isTransformEnabled, } from './selectors'; import { EndpointState, PolicyIds } from '../types'; import { @@ -70,24 +71,6 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory HostResultList = (options = {}) => { const { total = 1, request_page_size: requestPageSize = 10, request_page_index: requestPageIndex = 0, + query_strategy_version: queryStrategyVersion = MetadataQueryStrategyVersions.VERSION_2, } = options; // Skip any that are before the page we're on @@ -50,7 +52,7 @@ export const mockEndpointResultList: (options?: { hosts.push({ metadata: generator.generateHostMetadata(), host_status: HostStatus.ERROR, - query_strategy_version: MetadataQueryStrategyVersions.VERSION_2, + query_strategy_version: queryStrategyVersion, }); } const mock: HostResultList = { @@ -58,7 +60,7 @@ export const mockEndpointResultList: (options?: { total, request_page_size: requestPageSize, request_page_index: requestPageIndex, - query_strategy_version: MetadataQueryStrategyVersions.VERSION_2, + query_strategy_version: queryStrategyVersion, }; return mock; }; @@ -84,6 +86,7 @@ const endpointListApiPathHandlerMocks = ({ endpointPackagePolicies = [], policyResponse = generator.generatePolicyResponse(), agentPolicy = generator.generateAgentPolicy(), + queryStrategyVersion = MetadataQueryStrategyVersions.VERSION_2, }: { /** route handlers will be setup for each individual host in this array */ endpointsResults?: HostResultList['hosts']; @@ -91,6 +94,7 @@ const endpointListApiPathHandlerMocks = ({ endpointPackagePolicies?: GetPolicyListResponse['items']; policyResponse?: HostPolicyResponse; agentPolicy?: GetAgentPoliciesResponseItem; + queryStrategyVersion?: MetadataQueryStrategyVersions; } = {}) => { const apiHandlers = { // endpoint package info @@ -107,7 +111,7 @@ const endpointListApiPathHandlerMocks = ({ request_page_size: 10, request_page_index: 0, total: endpointsResults?.length || 0, - query_strategy_version: MetadataQueryStrategyVersions.VERSION_2, + query_strategy_version: queryStrategyVersion, }; }, @@ -164,11 +168,16 @@ export const setEndpointListApiMockImplementation: ( apiResponses?: Parameters[0] ) => void = ( mockedHttpService, - { endpointsResults = mockEndpointResultList({ total: 3 }).hosts, ...pathHandlersOptions } = {} + { + endpointsResults = mockEndpointResultList({ total: 3 }).hosts, + queryStrategyVersion = MetadataQueryStrategyVersions.VERSION_2, + ...pathHandlersOptions + } = {} ) => { const apiHandlers = endpointListApiPathHandlerMocks({ ...pathHandlersOptions, endpointsResults, + queryStrategyVersion, }); mockedHttpService.post diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index 99a1df7eb40024..0f948f74a48e4b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -36,6 +36,7 @@ export const initialEndpointListState: Immutable = { patternsError: undefined, isAutoRefreshEnabled: true, autoRefreshInterval: DEFAULT_POLL_INTERVAL, + queryStrategyVersion: undefined, }; /* eslint-disable-next-line complexity */ @@ -49,6 +50,7 @@ export const endpointListReducer: ImmutableReducer = ( total, request_page_size: pageSize, request_page_index: pageIndex, + query_strategy_version: queryStrategyVersion, } = action.payload; return { ...state, @@ -56,6 +58,7 @@ export const endpointListReducer: ImmutableReducer = ( total, pageSize, pageIndex, + queryStrategyVersion, loading: false, error: undefined, }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts index a3eee8063d6f7e..fe47d60afc339e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts @@ -14,6 +14,7 @@ import { HostPolicyResponseAppliedAction, HostPolicyResponseConfiguration, HostPolicyResponseActionStatus, + MetadataQueryStrategyVersions, } from '../../../../../common/endpoint/types'; import { EndpointState, EndpointIndexUIQueryParams } from '../types'; import { extractListPaginationParams } from '../../../common/routing'; @@ -54,11 +55,18 @@ export const isAutoRefreshEnabled = (state: Immutable) => state.i export const autoRefreshInterval = (state: Immutable) => state.autoRefreshInterval; +const queryStrategyVersion = (state: Immutable) => state.queryStrategyVersion; + export const endpointPackageVersion = createSelector( endpointPackageInfo, (info) => info?.version ?? undefined ); +export const isTransformEnabled = createSelector( + queryStrategyVersion, + (version) => version !== MetadataQueryStrategyVersions.VERSION_1 +); + /** * Returns the index patterns for the SearchBar to use for autosuggest */ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index 77f21243ea120d..bdd0d5e942cef2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -11,6 +11,7 @@ import { HostPolicyResponse, AppLocation, PolicyData, + MetadataQueryStrategyVersions, } from '../../../../common/endpoint/types'; import { ServerApiError } from '../../../common/types'; import { GetPackagesResponse } from '../../../../../ingest_manager/common'; @@ -65,6 +66,8 @@ export interface EndpointState { isAutoRefreshEnabled: boolean; /** The current auto refresh interval for data in ms */ autoRefreshInterval: number; + /** The query strategy version that informs whether the transform for KQL is enabled or not */ + queryStrategyVersion?: MetadataQueryStrategyVersions; } /** diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index bd8344f41fe3ac..51a6be18471aa9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -108,6 +108,31 @@ describe('when on the list page', () => { }); }); + describe('when loading data with the query_strategy_version is `v1`', () => { + beforeEach(() => { + reactTestingLibrary.act(() => { + const mockedEndpointListData = mockEndpointResultList({ + total: 4, + query_strategy_version: MetadataQueryStrategyVersions.VERSION_1, + }); + setEndpointListApiMockImplementation(coreStart.http, { + endpointsResults: mockedEndpointListData.hosts, + queryStrategyVersion: mockedEndpointListData.query_strategy_version, + }); + }); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should not display the KQL bar', async () => { + const renderResult = render(); + await reactTestingLibrary.act(async () => { + await middlewareSpy.waitForAction('serverReturnedEndpointList'); + }); + expect(renderResult.queryByTestId('adminSearchBar')).toBeNull(); + }); + }); + describe('when there is no selected host in the url', () => { it('should not show the flyout', () => { const renderResult = render(); @@ -123,7 +148,9 @@ describe('when on the list page', () => { let firstPolicyID: string; beforeEach(() => { reactTestingLibrary.act(() => { - const hostListData = mockEndpointResultList({ total: 4 }).hosts; + const mockedEndpointData = mockEndpointResultList({ total: 4 }); + const hostListData = mockedEndpointData.hosts; + const queryStrategyVersion = mockedEndpointData.query_strategy_version; firstPolicyID = hostListData[0].metadata.Endpoint.policy.applied.id; @@ -132,7 +159,7 @@ describe('when on the list page', () => { hostListData[index] = { metadata: hostListData[index].metadata, host_status: status, - query_strategy_version: MetadataQueryStrategyVersions.VERSION_2, + query_strategy_version: queryStrategyVersion, }; } ); @@ -682,11 +709,11 @@ describe('when on the list page', () => { let renderAndWaitForData: () => Promise>; const mockEndpointListApi = () => { - const { hosts } = mockEndpointResultList(); + const { hosts, query_strategy_version: queryStrategyVersion } = mockEndpointResultList(); hostInfo = { host_status: hosts[0].host_status, metadata: hosts[0].metadata, - query_strategy_version: MetadataQueryStrategyVersions.VERSION_2, + query_strategy_version: queryStrategyVersion, }; const packagePolicy = docGenerator.generatePolicyPackagePolicy(); packagePolicy.id = hosts[0].metadata.Endpoint.policy.applied.id; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index ecee2bee0c58b4..3e1f08eee7b947 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -135,6 +135,7 @@ export const EndpointList = () => { autoRefreshInterval, isAutoRefreshEnabled, patternsError, + isTransformEnabled, } = useEndpointSelector(selector); const { formatUrl, search } = useFormatUrl(SecurityPageName.administration); @@ -532,8 +533,8 @@ export const EndpointList = () => { const hasListData = listData && listData.length > 0; const refreshStyle = useMemo(() => { - return { display: endpointsExist ? 'flex' : 'none', maxWidth: 200 }; - }, [endpointsExist]); + return { display: endpointsExist && isTransformEnabled ? 'flex' : 'none', maxWidth: 200 }; + }, [endpointsExist, isTransformEnabled]); const refreshIsPaused = useMemo(() => { return !endpointsExist ? false : hasSelectedEndpoint ? true : !isAutoRefreshEnabled; @@ -543,6 +544,10 @@ export const EndpointList = () => { return !endpointsExist ? DEFAULT_POLL_INTERVAL : autoRefreshInterval; }, [endpointsExist, autoRefreshInterval]); + const shouldShowKQLBar = useMemo(() => { + return endpointsExist && !patternsError && isTransformEnabled; + }, [endpointsExist, patternsError, isTransformEnabled]); + return ( { {hasSelectedEndpoint && } <> - {endpointsExist && !patternsError && ( + {shouldShowKQLBar && ( diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/type_guards.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/type_guards.ts index 1e8e0bc042b86f..23b13695b0595b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/type_guards.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/type_guards.ts @@ -12,6 +12,7 @@ import { } from './trusted_apps_list_page_state'; import { Immutable, + MacosLinuxConditionEntry, NewTrustedApp, WindowsConditionEntry, } from '../../../../../common/endpoint/types'; @@ -51,5 +52,11 @@ export const isWindowsTrustedAppCondition = (condition: { return condition.field === 'process.code_signature' || true; }; +export const isMacosLinuxTrustedAppCondition = (condition: { + field: string; +}): condition is MacosLinuxConditionEntry => { + return condition.field !== 'process.code_signature' || true; +}; + export const isTrustedAppSupportedOs = (os: string): os is NewTrustedApp['os'] => TRUSTED_APPS_SUPPORTED_OS_TYPES.includes(os); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx index b6eb9795acd1dc..51d02105bdaac8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx @@ -25,6 +25,7 @@ import { import { LogicalConditionBuilderProps } from './logical_condition/logical_condition_builder'; import { OS_TITLES } from '../constants'; import { + isMacosLinuxTrustedAppCondition, isTrustedAppSupportedOs, isWindowsTrustedApp, isWindowsTrustedAppCondition, @@ -123,7 +124,7 @@ export const CreateTrustedAppForm = memo( return { ...prevState, entries: [ - ...prevState.entries.filter((entry) => !isWindowsTrustedAppCondition(entry)), + ...prevState.entries.filter((entry) => isMacosLinuxTrustedAppCondition(entry)), generateNewEntry(), ] as MacosLinuxConditionEntry[], }; @@ -154,10 +155,15 @@ export const CreateTrustedAppForm = memo( }; if (!isWindowsTrustedApp(updatedState)) { updatedState.entries.push( - ...(prevState.entries.filter( - (entry) => !isWindowsTrustedAppCondition(entry) + ...(prevState.entries.filter((entry) => + isMacosLinuxTrustedAppCondition(entry) ) as MacosLinuxConditionEntry[]) ); + if (updatedState.entries.length === 0) { + updatedState.entries.push(generateNewEntry() as MacosLinuxConditionEntry); + } + } else { + updatedState.entries.push(...prevState.entries); } return updatedState; } diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/logical_condition_builder.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/logical_condition_builder.tsx index e5e84acd0c4ad8..ef799343d96d60 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/logical_condition_builder.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/logical_condition_builder.tsx @@ -5,7 +5,7 @@ */ import React, { memo, useCallback } from 'react'; -import { EuiButton, CommonProps, EuiText, EuiSpacer } from '@elastic/eui'; +import { EuiButton, CommonProps, EuiText, EuiSpacer, EuiPanel } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { ConditionGroup, ConditionGroupProps } from './components/condition_group'; @@ -75,14 +75,14 @@ LogicalConditionBuilder.displayName = 'LogicalConditionBuilder'; // FIXME:PT need to style this better. const NoEntries = memo(() => { return ( -
- + + -
+ ); }); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap index 49562162e94a8c..a03d7c2317517d 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`KpiNetwork Component rendering it renders the default widget 1`] = ` -; data: NetworkKpiStrategyResponse; loading?: boolean; @@ -64,6 +64,6 @@ export const KpiNetworkBaseComponent = React.memo<{ ); }); -KpiNetworkBaseComponent.displayName = 'KpiNetworkBaseComponent'; +NetworkKpiBaseComponent.displayName = 'NetworkKpiBaseComponent'; -export const KpiNetworkBaseComponentManage = manageQuery(KpiNetworkBaseComponent); +export const NetworkKpiBaseComponentManage = manageQuery(NetworkKpiBaseComponent); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx index 889f3dacc2d98a..0f13b0e8f874e7 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/dns/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { StatItems } from '../../../../common/components/stat_items'; import { useNetworkKpiDns } from '../../../containers/kpi_network/dns'; -import { KpiNetworkBaseComponentManage } from '../common'; +import { NetworkKpiBaseComponentManage } from '../common'; import { NetworkKpiProps } from '../types'; import * as i18n from './translations'; @@ -41,7 +41,7 @@ const NetworkKpiDnsComponent: React.FC = ({ }); return ( - { +describe('NetworkKpiComponent', () => { const state: State = mockGlobalState; const props = { from: '2019-06-15T06:00:00.000Z', @@ -53,11 +53,11 @@ describe('KpiNetwork Component', () => { test('it renders the default widget', () => { const wrapper = shallow( - + ); - expect(wrapper.find('KpiNetworkComponent')).toMatchSnapshot(); + expect(wrapper.find('NetworkKpiComponent')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx index 674e592940fa61..95534e1a61988b 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx @@ -14,7 +14,7 @@ import { NetworkKpiUniqueFlows } from './unique_flows'; import { NetworkKpiUniquePrivateIps } from './unique_private_ips'; import { NetworkKpiProps } from './types'; -export const KpiNetworkComponent = React.memo( +export const NetworkKpiComponent = React.memo( ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => ( @@ -78,4 +78,4 @@ export const KpiNetworkComponent = React.memo( ) ); -KpiNetworkComponent.displayName = 'KpiNetworkComponent'; +NetworkKpiComponent.displayName = 'NetworkKpiComponent'; diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx index 3ee2acf1a115c0..18217e41f2a27e 100644 --- a/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/network_events/index.tsx @@ -9,7 +9,7 @@ import { euiPaletteColorBlind } from '@elastic/eui'; import { StatItems } from '../../../../common/components/stat_items'; import { useNetworkKpiNetworkEvents } from '../../../containers/kpi_network/network_events'; -import { KpiNetworkBaseComponentManage } from '../common'; +import { NetworkKpiBaseComponentManage } from '../common'; import { NetworkKpiProps } from '../types'; import * as i18n from './translations'; @@ -46,7 +46,7 @@ const NetworkKpiNetworkEventsComponent: React.FC = ({ }); return ( - = ({ }); return ( - = ({ }); return ( - = ({ }); return ( - ( - > = { @@ -32,7 +32,7 @@ export const buildQuery = ({ defaultIndex, docValueFields, }: HostAuthenticationsRequestOptions) => { - const esFields = reduceFields(authenticationFields, { ...hostFieldsMap, ...sourceFieldsMap }); + const esFields = reduceFields(authenticationsFields, { ...hostFieldsMap, ...sourceFieldsMap }); const filter = [ ...createQueryFilterClauses(filterQuery), diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts index d61914fda7d06f..ce8900a5781026 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts @@ -15,7 +15,7 @@ import { StrategyResponseType, } from '../../../../../../common/search_strategy/security_solution'; -export const authenticationFields = [ +export const authenticationsFields = [ '_id', 'failures', 'successes', @@ -31,7 +31,7 @@ export const authenticationFields = [ ]; export const formatAuthenticationData = ( - fields: readonly string[] = authenticationFields, + fields: readonly string[] = authenticationsFields, hit: AuthenticationHit, fieldMap: Readonly> ): AuthenticationsEdges => diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx index a43f53880587ad..e09d8de7ba9457 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/index.tsx @@ -20,7 +20,7 @@ import { import { inspectStringifyObject } from '../../../../../utils/build_query'; import { SecuritySolutionFactory } from '../../types'; import { auditdFieldsMap, buildQuery as buildAuthenticationQuery } from './dsl/query.dsl'; -import { authenticationFields, formatAuthenticationData, getHits } from './helpers'; +import { authenticationsFields, formatAuthenticationData, getHits } from './helpers'; export const authentications: SecuritySolutionFactory = { buildDsl: (options: HostAuthenticationsRequestOptions) => { @@ -40,7 +40,7 @@ export const authentications: SecuritySolutionFactory - formatAuthenticationData(authenticationFields, hit, auditdFieldsMap) + formatAuthenticationData(authenticationsFields, hit, auditdFieldsMap) ); const edges = authenticationEdges.splice(cursorStart, querySize - cursorStart); 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 index edcba88a0cd89a..44c55ab6e7c9d3 100644 --- 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 @@ -5,13 +5,16 @@ */ import { hostsFactory } from '.'; -import { HostsQueries } from '../../../../../common/search_strategy'; +import { HostsQueries, HostsKpiQueries } 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'; +import { hostsKpiAuthentications } from './kpi/authentications'; +import { hostsKpiHosts } from './kpi/hosts'; +import { hostsKpiUniqueIps } from './kpi/unique_ips'; jest.mock('./all'); jest.mock('./details'); @@ -19,6 +22,9 @@ jest.mock('./overview'); jest.mock('./last_first_seen'); jest.mock('./uncommon_processes'); jest.mock('./authentications'); +jest.mock('./kpi/authentications'); +jest.mock('./kpi/hosts'); +jest.mock('./kpi/unique_ips'); describe('hostsFactory', () => { test('should include correct apis', () => { @@ -29,6 +35,9 @@ describe('hostsFactory', () => { [HostsQueries.firstLastSeen]: firstLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, [HostsQueries.authentications]: authentications, + [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, + [HostsKpiQueries.kpiHosts]: hostsKpiHosts, + [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps, }; expect(hostsFactory).toEqual(expectedHostsFactory); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts index 85619cfec62ce7..ad6a6182d331b0 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts @@ -7,6 +7,7 @@ import { FactoryQueryTypes, HostsQueries, + HostsKpiQueries, } from '../../../../../common/search_strategy/security_solution'; import { SecuritySolutionFactory } from '../types'; @@ -16,12 +17,21 @@ import { hostOverview } from './overview'; import { firstLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; import { authentications } from './authentications'; +import { hostsKpiAuthentications } from './kpi/authentications'; +import { hostsKpiHosts } from './kpi/hosts'; +import { hostsKpiUniqueIps } from './kpi/unique_ips'; -export const hostsFactory: Record> = { +export const hostsFactory: Record< + HostsQueries | HostsKpiQueries, + SecuritySolutionFactory +> = { [HostsQueries.details]: hostDetails, [HostsQueries.hosts]: allHosts, [HostsQueries.overview]: hostOverview, [HostsQueries.firstLastSeen]: firstLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, [HostsQueries.authentications]: authentications, + [HostsKpiQueries.kpiAuthentications]: hostsKpiAuthentications, + [HostsKpiQueries.kpiHosts]: hostsKpiHosts, + [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts new file mode 100644 index 00000000000000..513e361b5be05c --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/helpers.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + HostsKpiHistogram, + HostsKpiAuthenticationsHistogramCount, + HostsKpiHistogramData, +} from '../../../../../../../common/search_strategy'; + +export const formatAuthenticationsHistogramData = ( + data: Array> +): HostsKpiHistogramData[] | null => + data && data.length > 0 + ? data.map(({ key, count }) => ({ + x: key, + y: count.doc_count, + })) + : null; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts new file mode 100644 index 00000000000000..bafc9a3accc6ee --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts @@ -0,0 +1,63 @@ +/* + * 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 { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + HostsKpiQueries, + HostsKpiAuthenticationsStrategyResponse, + HostsKpiAuthenticationsRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildHostsKpiAuthenticationsQuery } from './query.hosts_kpi_authentications.dsl'; +import { formatAuthenticationsHistogramData } from './helpers'; + +export const hostsKpiAuthentications: SecuritySolutionFactory = { + buildDsl: (options: HostsKpiAuthenticationsRequestOptions) => + buildHostsKpiAuthenticationsQuery(options), + parse: async ( + options: HostsKpiAuthenticationsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildHostsKpiAuthenticationsQuery(options))], + }; + + const authenticationsSuccessHistogram = getOr( + null, + 'aggregations.authentication_success_histogram.buckets', + response.rawResponse + ); + const authenticationsFailureHistogram = getOr( + null, + 'aggregations.authentication_failure_histogram.buckets', + response.rawResponse + ); + + return { + ...response, + inspect, + authenticationsSuccess: getOr( + null, + 'aggregations.authentication_success.doc_count', + response.rawResponse + ), + authenticationsSuccessHistogram: formatAuthenticationsHistogramData( + authenticationsSuccessHistogram + ), + authenticationsFailure: getOr( + null, + 'aggregations.authentication_failure.doc_count', + response.rawResponse + ), + authenticationsFailureHistogram: formatAuthenticationsHistogramData( + authenticationsFailureHistogram + ), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts new file mode 100644 index 00000000000000..8da5f7f95c5d11 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/query.hosts_kpi_authentications.dsl.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HostsKpiAuthenticationsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; + +export const buildHostsKpiAuthenticationsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: HostsKpiAuthenticationsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + bool: { + filter: [ + { + term: { + 'event.category': 'authentication', + }, + }, + ], + }, + }, + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggs: { + authentication_success: { + filter: { + term: { + 'event.outcome': 'success', + }, + }, + }, + authentication_success_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + filter: { + term: { + 'event.outcome': 'success', + }, + }, + }, + }, + }, + authentication_failure: { + filter: { + term: { + 'event.outcome': 'failure', + }, + }, + }, + authentication_failure_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + filter: { + term: { + 'event.outcome': 'failure', + }, + }, + }, + }, + }, + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/common/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/common/index.ts new file mode 100644 index 00000000000000..080ef05c991365 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/common/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + HostsKpiHistogram, + HostsKpiGeneralHistogramCount, + HostsKpiHistogramData, +} from '../../../../../../../common/search_strategy'; + +export const formatGeneralHistogramData = ( + data: Array> +): HostsKpiHistogramData[] | null => + data && data.length > 0 + ? data.map(({ key, count }) => ({ + x: key, + y: count.value, + })) + : null; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts new file mode 100644 index 00000000000000..6d91ebf09895ea --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/index.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + HostsKpiQueries, + HostsKpiHostsStrategyResponse, + HostsKpiHostsRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildHostsKpiHostsQuery } from './query.hosts_kpi_hosts.dsl'; +import { formatGeneralHistogramData } from '../common'; + +export const hostsKpiHosts: SecuritySolutionFactory = { + buildDsl: (options: HostsKpiHostsRequestOptions) => buildHostsKpiHostsQuery(options), + parse: async ( + options: HostsKpiHostsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildHostsKpiHostsQuery(options))], + }; + + const hostsHistogram = getOr( + null, + 'aggregations.hosts_histogram.buckets', + response.rawResponse + ); + return { + ...response, + inspect, + hosts: getOr(null, 'aggregations.hosts.value', response.rawResponse), + hostsHistogram: formatGeneralHistogramData(hostsHistogram), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts new file mode 100644 index 00000000000000..704743cc434eda --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/hosts/query.hosts_kpi_hosts.dsl.ts @@ -0,0 +1,64 @@ +/* + * 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 { HostsKpiHostsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; + +export const buildHostsKpiHostsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: HostsKpiHostsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggregations: { + hosts: { + cardinality: { + field: 'host.name', + }, + }, + hosts_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + cardinality: { + field: 'host.name', + }, + }, + }, + }, + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts new file mode 100644 index 00000000000000..f4793ecd53f8f3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/index.ts @@ -0,0 +1,10 @@ +/* + * 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 * from './authentications'; +export * from './common'; +export * from './hosts'; +export * from './unique_ips'; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts new file mode 100644 index 00000000000000..2f890e6fdacca5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/index.ts @@ -0,0 +1,55 @@ +/* + * 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 { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { + HostsKpiQueries, + HostsKpiUniqueIpsStrategyResponse, + HostsKpiUniqueIpsRequestOptions, +} from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../../../types'; +import { buildHostsKpiUniqueIpsQuery } from './query.hosts_kpi_unique_ips.dsl'; +import { formatGeneralHistogramData } from '../common'; + +export const hostsKpiUniqueIps: SecuritySolutionFactory = { + buildDsl: (options: HostsKpiUniqueIpsRequestOptions) => buildHostsKpiUniqueIpsQuery(options), + parse: async ( + options: HostsKpiUniqueIpsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildHostsKpiUniqueIpsQuery(options))], + }; + + const uniqueSourceIpsHistogram = getOr( + null, + 'aggregations.unique_source_ips_histogram.buckets', + response.rawResponse + ); + + const uniqueDestinationIpsHistogram = getOr( + null, + 'aggregations.unique_destination_ips_histogram.buckets', + response.rawResponse + ); + + return { + ...response, + inspect, + uniqueSourceIps: getOr(null, 'aggregations.unique_source_ips.value', response.rawResponse), + uniqueSourceIpsHistogram: formatGeneralHistogramData(uniqueSourceIpsHistogram), + uniqueDestinationIps: getOr( + null, + 'aggregations.unique_destination_ips.value', + response.rawResponse + ), + uniqueDestinationIpsHistogram: formatGeneralHistogramData(uniqueDestinationIpsHistogram), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts new file mode 100644 index 00000000000000..618c6cb51f6661 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/unique_ips/query.hosts_kpi_unique_ips.dsl.ts @@ -0,0 +1,82 @@ +/* + * 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 { HostsKpiUniqueIpsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; + +export const buildHostsKpiUniqueIpsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: HostsKpiUniqueIpsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggregations: { + unique_source_ips: { + cardinality: { + field: 'source.ip', + }, + }, + unique_source_ips_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + cardinality: { + field: 'source.ip', + }, + }, + }, + }, + unique_destination_ips: { + cardinality: { + field: 'destination.ip', + }, + }, + unique_destination_ips_histogram: { + auto_date_histogram: { + field: '@timestamp', + buckets: '6', + }, + aggs: { + count: { + cardinality: { + field: 'destination.ip', + }, + }, + }, + }, + }, + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts b/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts index acb00a87bf7d93..5ef0b5375d7969 100644 --- a/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts +++ b/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts @@ -104,7 +104,7 @@ export class SpacesClient { `SpacesClient.getAll(), using RBAC. returning 403/Forbidden. Not authorized for any spaces for ${purpose} purpose.` ); this.auditLogger.spacesAuthorizationFailure(username, 'getAll'); - throw Boom.forbidden(); + throw Boom.forbidden(); // Note: there is a catch for this in `SpacesSavedObjectsClient.find`; if we get rid of this error, remove that too } this.auditLogger.spacesAuthorizationSuccess(username, 'getAll', authorized as string[]); diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts index c9c17d091cd55c..f7621f11a1c053 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts @@ -10,6 +10,8 @@ import { spacesServiceMock } from '../spaces_service/spaces_service.mock'; import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { SavedObjectTypeRegistry } from 'src/core/server'; import { SpacesClient } from '../lib/spaces_client'; +import { spacesClientMock } from '../lib/spaces_client/spaces_client.mock'; +import Boom from 'boom'; const typeRegistry = new SavedObjectTypeRegistry(); typeRegistry.registerType({ @@ -129,6 +131,34 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; }); describe('#find', () => { + const EMPTY_RESPONSE = { saved_objects: [], total: 0, per_page: 20, page: 1 }; + + test(`returns empty result if user is unauthorized in this space`, async () => { + const { client, baseClient, spacesService } = await createSpacesSavedObjectsClient(); + const spacesClient = spacesClientMock.create(); + spacesClient.getAll.mockResolvedValue([]); + spacesService.scopedClient.mockResolvedValue(spacesClient); + + const options = Object.freeze({ type: 'foo', namespaces: ['some-ns'] }); + const actualReturnValue = await client.find(options); + + expect(actualReturnValue).toEqual(EMPTY_RESPONSE); + expect(baseClient.find).not.toHaveBeenCalled(); + }); + + test(`returns empty result if user is unauthorized in any space`, async () => { + const { client, baseClient, spacesService } = await createSpacesSavedObjectsClient(); + const spacesClient = spacesClientMock.create(); + spacesClient.getAll.mockRejectedValue(Boom.unauthorized()); + spacesService.scopedClient.mockResolvedValue(spacesClient); + + const options = Object.freeze({ type: 'foo', namespaces: ['some-ns'] }); + const actualReturnValue = await client.find(options); + + expect(actualReturnValue).toEqual(EMPTY_RESPONSE); + expect(baseClient.find).not.toHaveBeenCalled(); + }); + test(`passes options.type to baseClient if valid singular type specified`, async () => { const { client, baseClient } = await createSpacesSavedObjectsClient(); const expectedReturnValue = { diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts index 4e830d6149537c..a65e0431aef920 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import Boom from 'boom'; import { SavedObjectsBaseOptions, SavedObjectsBulkCreateObject, @@ -16,8 +17,9 @@ import { SavedObjectsUpdateOptions, SavedObjectsAddToNamespacesOptions, SavedObjectsDeleteFromNamespacesOptions, + SavedObjectsUtils, ISavedObjectTypeRegistry, -} from 'src/core/server'; +} from '../../../../../src/core/server'; import { SpacesServiceSetup } from '../spaces_service/spaces_service'; import { spaceIdToNamespace } from '../lib/utils/namespace'; import { SpacesClient } from '../lib/spaces_client'; @@ -164,19 +166,26 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { let namespaces = options.namespaces; if (namespaces) { const spacesClient = await this.getSpacesClient; - const availableSpaces = await spacesClient.getAll('findSavedObjects'); - if (namespaces.includes('*')) { - namespaces = availableSpaces.map((space) => space.id); - } else { - namespaces = namespaces.filter((namespace) => - availableSpaces.some((space) => space.id === namespace) - ); - } - // This forbidden error allows this scenario to be consistent - // with the way the SpacesClient behaves when no spaces are authorized - // there. - if (namespaces.length === 0) { - throw this.errors.decorateForbiddenError(new Error()); + + try { + const availableSpaces = await spacesClient.getAll('findSavedObjects'); + if (namespaces.includes('*')) { + namespaces = availableSpaces.map((space) => space.id); + } else { + namespaces = namespaces.filter((namespace) => + availableSpaces.some((space) => space.id === namespace) + ); + } + if (namespaces.length === 0) { + // return empty response, since the user is unauthorized in this space (or these spaces), but we don't return forbidden errors for `find` operations + return SavedObjectsUtils.createEmptyFindResponse(options); + } + } catch (err) { + if (Boom.isBoom(err) && err.output.payload.statusCode === 403) { + // return empty response, since the user is unauthorized in any space, but we don't return forbidden errors for `find` operations + return SavedObjectsUtils.createEmptyFindResponse(options); + } + throw err; } } else { namespaces = [this.spaceId]; diff --git a/x-pack/plugins/transform/common/api_schemas/update_transforms.ts b/x-pack/plugins/transform/common/api_schemas/update_transforms.ts index e303d94ef0536b..39c5a9e7baa4f0 100644 --- a/x-pack/plugins/transform/common/api_schemas/update_transforms.ts +++ b/x-pack/plugins/transform/common/api_schemas/update_transforms.ts @@ -8,12 +8,18 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { TransformPivotConfig } from '../types/transform'; -import { destSchema, settingsSchema, sourceSchema, syncSchema } from './transforms'; +import { settingsSchema, sourceSchema, syncSchema } from './transforms'; // POST _transform/{transform_id}/_update export const postTransformsUpdateRequestSchema = schema.object({ description: schema.maybe(schema.string()), - dest: schema.maybe(destSchema), + // we cannot reuse `destSchema` because `index` is optional for the update request + dest: schema.maybe( + schema.object({ + index: schema.string(), + pipeline: schema.maybe(schema.string()), + }) + ), frequency: schema.maybe(schema.string()), settings: schema.maybe(settingsSchema), source: schema.maybe(sourceSchema), diff --git a/x-pack/plugins/transform/common/utils/object_utils.ts b/x-pack/plugins/transform/common/utils/object_utils.ts index dfdcd0959260d9..f21460e08098d5 100644 --- a/x-pack/plugins/transform/common/utils/object_utils.ts +++ b/x-pack/plugins/transform/common/utils/object_utils.ts @@ -17,3 +17,21 @@ export const getNestedProperty = ( return value; }; + +export const setNestedProperty = (obj: Record, accessor: string, value: any) => { + let ref = obj; + const accessors = accessor.split('.'); + const len = accessors.length; + for (let i = 0; i < len - 1; i++) { + const attribute = accessors[i]; + if (ref[attribute] === undefined) { + ref[attribute] = {}; + } + + ref = ref[attribute]; + } + + ref[accessors[len - 1]] = value; + + return obj; +}; diff --git a/x-pack/plugins/transform/public/app/common/request.test.ts b/x-pack/plugins/transform/public/app/common/request.test.ts index 416927c460842e..913ea8964eaf0e 100644 --- a/x-pack/plugins/transform/public/app/common/request.test.ts +++ b/x-pack/plugins/transform/public/app/common/request.test.ts @@ -157,6 +157,8 @@ describe('Transform: Common', () => { isContinuousModeEnabled: false, transformId: 'the-transform-id', transformDescription: 'the-transform-description', + transformFrequency: '1m', + transformSettingsMaxPageSearchSize: 100, destinationIndex: 'the-destination-index', touched: true, valid: true, @@ -171,10 +173,14 @@ describe('Transform: Common', () => { expect(request).toEqual({ description: 'the-transform-description', dest: { index: 'the-destination-index' }, + frequency: '1m', pivot: { aggregations: { 'the-agg-agg-name': { avg: { field: 'the-agg-field' } } }, group_by: { 'the-group-by-agg-name': { terms: { field: 'the-group-by-field' } } }, }, + settings: { + max_page_search_size: 100, + }, source: { index: ['the-index-pattern-title'], query: { query_string: { default_operator: 'AND', query: 'the-search-query' } }, diff --git a/x-pack/plugins/transform/public/app/common/request.ts b/x-pack/plugins/transform/public/app/common/request.ts index 10f3a63477029d..45160d125309db 100644 --- a/x-pack/plugins/transform/public/app/common/request.ts +++ b/x-pack/plugins/transform/public/app/common/request.ts @@ -145,6 +145,10 @@ export const getCreateTransformRequestBody = ( ...(transformDetailsState.transformDescription !== '' ? { description: transformDetailsState.transformDescription } : {}), + // conditionally add optional frequency + ...(transformDetailsState.transformFrequency !== '' + ? { frequency: transformDetailsState.transformFrequency } + : {}), dest: { index: transformDetailsState.destinationIndex, }, @@ -159,6 +163,14 @@ export const getCreateTransformRequestBody = ( }, } : {}), + // conditionally add additional settings + ...(transformDetailsState.transformSettingsMaxPageSearchSize + ? { + settings: { + max_page_search_size: transformDetailsState.transformSettingsMaxPageSearchSize, + }, + } + : {}), }); export function isHttpFetchError(error: any): error is HttpFetchError { diff --git a/x-pack/plugins/transform/public/app/common/validators.test.ts b/x-pack/plugins/transform/public/app/common/validators.test.ts index 3e8140d9d14aa1..ad7a7f58724578 100644 --- a/x-pack/plugins/transform/public/app/common/validators.test.ts +++ b/x-pack/plugins/transform/public/app/common/validators.test.ts @@ -4,26 +4,63 @@ * you may not use this file except in compliance with the Elastic License. */ -import { delayValidator } from './validators'; +import { continuousModeDelayValidator, transformFrequencyValidator } from './validators'; -describe('delayValidator', () => { - test('it should allow 0 input without unit', () => { - expect(delayValidator('0')).toBe(true); +describe('continuousModeDelayValidator', () => { + it('should allow 0 input without unit', () => { + expect(continuousModeDelayValidator('0')).toBe(true); }); - test('it should allow 0 input with unit provided', () => { - expect(delayValidator('0s')).toBe(true); + it('should allow 0 input with unit provided', () => { + expect(continuousModeDelayValidator('0s')).toBe(true); }); - test('it should allow integer input with unit provided', () => { - expect(delayValidator('234nanos')).toBe(true); + it('should allow integer input with unit provided', () => { + expect(continuousModeDelayValidator('234nanos')).toBe(true); }); - test('it should not allow integer input without unit provided', () => { - expect(delayValidator('90000')).toBe(false); + it('should not allow integer input without unit provided', () => { + expect(continuousModeDelayValidator('90000')).toBe(false); }); - test('it should not allow float input', () => { - expect(delayValidator('122.5d')).toBe(false); + it('should not allow float input', () => { + expect(continuousModeDelayValidator('122.5d')).toBe(false); + }); +}); + +describe('transformFrequencyValidator', () => { + it('should fail when the input is not an integer and valid time unit.', () => { + expect(transformFrequencyValidator('0')).toBe(false); + expect(transformFrequencyValidator('0.1s')).toBe(false); + expect(transformFrequencyValidator('1.1m')).toBe(false); + expect(transformFrequencyValidator('10.1asdf')).toBe(false); + }); + + it('should only allow s/m/h as time unit.', () => { + expect(transformFrequencyValidator('1ms')).toBe(false); + expect(transformFrequencyValidator('1s')).toBe(true); + expect(transformFrequencyValidator('1m')).toBe(true); + expect(transformFrequencyValidator('1h')).toBe(true); + expect(transformFrequencyValidator('1d')).toBe(false); + }); + + it('should only allow values above 0 and up to 1 hour.', () => { + expect(transformFrequencyValidator('0s')).toBe(false); + expect(transformFrequencyValidator('1s')).toBe(true); + expect(transformFrequencyValidator('3599s')).toBe(true); + expect(transformFrequencyValidator('3600s')).toBe(true); + expect(transformFrequencyValidator('3601s')).toBe(false); + expect(transformFrequencyValidator('10000s')).toBe(false); + + expect(transformFrequencyValidator('0m')).toBe(false); + expect(transformFrequencyValidator('1m')).toBe(true); + expect(transformFrequencyValidator('59m')).toBe(true); + expect(transformFrequencyValidator('60m')).toBe(true); + expect(transformFrequencyValidator('61m')).toBe(false); + expect(transformFrequencyValidator('100m')).toBe(false); + + expect(transformFrequencyValidator('0h')).toBe(false); + expect(transformFrequencyValidator('1h')).toBe(true); + expect(transformFrequencyValidator('2h')).toBe(false); }); }); diff --git a/x-pack/plugins/transform/public/app/common/validators.ts b/x-pack/plugins/transform/public/app/common/validators.ts index a54c885686422f..e9b1478489563a 100644 --- a/x-pack/plugins/transform/public/app/common/validators.ts +++ b/x-pack/plugins/transform/public/app/common/validators.ts @@ -5,10 +5,54 @@ */ /** - * Validates time delay input. + * Validates continuous mode time delay input. * Doesn't allow floating intervals. * @param value User input value. */ -export function delayValidator(value: string): boolean { +export function continuousModeDelayValidator(value: string): boolean { return value.match(/^(0|\d*(nanos|micros|ms|s|m|h|d))$/) !== null; } + +/** + * Validates transform frequency input. + * Allows time units of s/m/h only. + * Must be above 0 and only up to 1h. + * @param value User input value. + */ +export const transformFrequencyValidator = (value: string): boolean => { + if (typeof value !== 'string' || value === null) { + return false; + } + + // split string by groups of numbers and letters + const regexStr = value.match(/[a-z]+|[^a-z]+/gi); + + // only valid if one group of numbers and one group of letters + if (regexStr === null || (Array.isArray(regexStr) && regexStr.length !== 2)) { + return false; + } + + const valueNumber = +regexStr[0]; + const valueTimeUnit = regexStr[1]; + + // only valid if number is an integer above 0 + if (isNaN(valueNumber) || !Number.isInteger(valueNumber) || valueNumber === 0) { + return false; + } + + // only valid if value is up to 1 hour + return ( + (valueTimeUnit === 's' && valueNumber <= 3600) || + (valueTimeUnit === 'm' && valueNumber <= 60) || + (valueTimeUnit === 'h' && valueNumber === 1) + ); +}; + +/** + * Validates transform max_page_search_size input. + * Must be a number between 10 and 10000. + * @param value User input value. + */ +export function transformSettingsMaxPageSearchSizeValidator(value: number): boolean { + return value >= 10 && value <= 10000; +} diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor/advanced_pivot_editor.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor/advanced_pivot_editor.tsx index 983d36a20e87fb..cb888444ca867b 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor/advanced_pivot_editor.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_pivot_editor/advanced_pivot_editor.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash'; +import isEqual from 'lodash/isEqual'; import React, { memo, FC } from 'react'; import { EuiCodeEditor, EuiFormRow } from '@elastic/eui'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx index 30e8c2b594db7c..6204aeff0306e8 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx @@ -18,7 +18,7 @@ import { EuiSelectOption, } from '@elastic/eui'; -import { cloneDeep } from 'lodash'; +import cloneDeep from 'lodash/cloneDeep'; import { useUpdateEffect } from 'react-use'; import { AggName } from '../../../../../../common/types/aggregations'; import { dictionaryToArray } from '../../../../../../common/types/common'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx index ffd6c30db45f4a..75251fce98424d 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { StepCreateForm } from './step_create_form'; +import { StepCreateForm, StepCreateFormProps } from './step_create_form'; jest.mock('../../../../../shared_imports'); jest.mock('../../../../../app/app_dependencies'); @@ -15,10 +15,21 @@ jest.mock('../../../../../app/app_dependencies'); describe('Transform: ', () => { test('Minimal initialization', () => { // Arrange - const props = { + const props: StepCreateFormProps = { createIndexPattern: false, transformId: 'the-transform-id', - transformConfig: {}, + transformConfig: { + dest: { + index: 'the-destination-index', + }, + pivot: { + group_by: {}, + aggregations: {}, + }, + source: { + index: 'the-source-index', + }, + }, overrides: { created: false, started: false, indexPatternId: undefined }, onChange() {}, }; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx index 675bd0f9f88edb..fa601bbaed91e2 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx @@ -9,13 +9,8 @@ import { i18n } from '@kbn/i18n'; import { EuiButton, - // Module '"@elastic/eui"' has no exported member 'EuiCard'. - // @ts-ignore EuiCard, EuiCopy, - // Module '"@elastic/eui"' has no exported member 'EuiDescribedFormGroup'. - // @ts-ignore - EuiDescribedFormGroup, EuiFlexGrid, EuiFlexGroup, EuiFlexItem, @@ -30,7 +25,10 @@ import { import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public'; -import type { PutTransformsResponseSchema } from '../../../../../../common/api_schemas/transforms'; +import type { + PutTransformsRequestSchema, + PutTransformsResponseSchema, +} from '../../../../../../common/api_schemas/transforms'; import { isGetTransformsStatsResponseSchema, isPutTransformsResponseSchema, @@ -60,16 +58,16 @@ export function getDefaultStepCreateState(): StepDetailsExposedState { }; } -interface Props { +export interface StepCreateFormProps { createIndexPattern: boolean; transformId: string; - transformConfig: any; + transformConfig: PutTransformsRequestSchema; overrides: StepDetailsExposedState; timeFieldName?: string | undefined; onChange(s: StepDetailsExposedState): void; } -export const StepCreateForm: FC = React.memo( +export const StepCreateForm: FC = React.memo( ({ createIndexPattern, transformConfig, transformId, onChange, overrides, timeFieldName }) => { const defaults = { ...getDefaultStepCreateState(), ...overrides }; @@ -267,8 +265,11 @@ export const StepCreateForm: FC = React.memo( ) { const percent = getTransformProgress({ - id: transformConfig.id, - config: transformConfig, + id: transformId, + config: { + ...transformConfig, + id: transformId, + }, stats: stats.transforms[0], }) || 0; setProgressPercentComplete(percent); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/apply_transform_config_to_define_state.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/apply_transform_config_to_define_state.ts index 1523a0d9a89f9c..f00102a2f87c00 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/apply_transform_config_to_define_state.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/apply_transform_config_to_define_state.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash'; +import isEqual from 'lodash/isEqual'; import { Dictionary } from '../../../../../../../common/types/common'; import { PivotSupportedAggs } from '../../../../../../../common/types/pivot_aggs'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx index d59f99192621cf..f27d50ac2773c4 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useContext, useEffect, useState } from 'react'; import { EuiComboBox, EuiFormRow } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { debounce } from 'lodash'; +import debounce from 'lodash/debounce'; import { useUpdateEffect } from 'react-use'; import { i18n } from '@kbn/i18n'; import { isEsSearchResponse } from '../../../../../../../../../common/api_schemas/type_guards'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts index 9a67e7af155435..1b6324bf30eaa6 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts @@ -4,13 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; import { EuiComboBoxOptionOption } from '@elastic/eui'; import { IndexPattern, KBN_FIELD_TYPES, } from '../../../../../../../../../../src/plugins/data/public'; +import { getNestedProperty } from '../../../../../../../common/utils/object_utils'; + import { DropDownLabel, DropDownOption, @@ -42,7 +43,7 @@ export function getPivotDropdownOptions(indexPattern: IndexPattern) { fields.forEach((field) => { // Group by - const availableGroupByAggs: [] = get(pivotGroupByFieldSupport, field.type); + const availableGroupByAggs: [] = getNestedProperty(pivotGroupByFieldSupport, field.type); if (availableGroupByAggs !== undefined) { availableGroupByAggs.forEach((groupByAgg) => { @@ -63,7 +64,7 @@ export function getPivotDropdownOptions(indexPattern: IndexPattern) { // Aggregations const aggOption: DropDownOption = { label: field.name, options: [] }; - const availableAggs: [] = get(pivotAggsFieldSupport, field.type); + const availableAggs: [] = getNestedProperty(pivotAggsFieldSupport, field.type); if (availableAggs !== undefined) { availableAggs.forEach((agg) => { diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index 43d4f11cffc9d6..00ab516f625fe9 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -8,7 +8,16 @@ import React, { Fragment, FC, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiLink, EuiSwitch, EuiFieldText, EuiForm, EuiFormRow, EuiSelect } from '@elastic/eui'; +import { + EuiAccordion, + EuiLink, + EuiSwitch, + EuiFieldText, + EuiForm, + EuiFormRow, + EuiSelect, + EuiSpacer, +} from '@elastic/eui'; import { KBN_FIELD_TYPES } from '../../../../../../../../../src/plugins/data/common'; import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public'; @@ -35,7 +44,11 @@ import { isTransformIdValid, } from '../../../../common'; import { EsIndexName, IndexPatternTitle } from './common'; -import { delayValidator } from '../../../../common/validators'; +import { + continuousModeDelayValidator, + transformFrequencyValidator, + transformSettingsMaxPageSearchSizeValidator, +} from '../../../../common/validators'; import { StepDefineExposedState } from '../step_define/common'; import { dictionaryToArray } from '../../../../../../common/types/common'; @@ -48,11 +61,15 @@ export interface StepDetailsExposedState { touched: boolean; transformId: TransformId; transformDescription: string; + transformFrequency: string; + transformSettingsMaxPageSearchSize: number; valid: boolean; - indexPatternDateField?: string | undefined; + indexPatternTimeField?: string | undefined; } const defaultContinuousModeDelay = '60s'; +const defaultTransformFrequency = '1m'; +const defaultTransformSettingsMaxPageSearchSize = 500; export function getDefaultStepDetailsState(): StepDetailsExposedState { return { @@ -62,10 +79,12 @@ export function getDefaultStepDetailsState(): StepDetailsExposedState { isContinuousModeEnabled: false, transformId: '', transformDescription: '', + transformFrequency: defaultTransformFrequency, + transformSettingsMaxPageSearchSize: defaultTransformSettingsMaxPageSearchSize, destinationIndex: '', touched: false, valid: false, - indexPatternDateField: undefined, + indexPatternTimeField: undefined, }; } @@ -113,8 +132,10 @@ export const StepDetailsForm: FC = React.memo( // Index pattern state const [indexPatternTitles, setIndexPatternTitles] = useState([]); const [createIndexPattern, setCreateIndexPattern] = useState(defaults.createIndexPattern); - const [previewDateColumns, setPreviewDateColumns] = useState([]); - const [indexPatternDateField, setIndexPatternDateField] = useState(); + const [indexPatternAvailableTimeFields, setIndexPatternAvailableTimeFields] = useState< + string[] + >([]); + const [indexPatternTimeField, setIndexPatternTimeField] = useState(); const onTimeFieldChanged = React.useCallback( (e: React.ChangeEvent) => { @@ -125,11 +146,11 @@ export const StepDetailsForm: FC = React.memo( } // Find the time field based on the selected value // this is to account for undefined when user chooses not to use a date field - const timeField = previewDateColumns.find((col) => col === value); + const timeField = indexPatternAvailableTimeFields.find((col) => col === value); - setIndexPatternDateField(timeField); + setIndexPatternTimeField(timeField); }, - [setIndexPatternDateField, previewDateColumns] + [setIndexPatternTimeField, indexPatternAvailableTimeFields] ); // Continuous mode state @@ -158,12 +179,12 @@ export const StepDetailsForm: FC = React.memo( if (isPostTransformsPreviewResponseSchema(transformPreview)) { const properties = transformPreview.generated_dest_index.mappings.properties; - const datetimeColumns: string[] = Object.keys(properties).filter( + const timeFields: string[] = Object.keys(properties).filter( (col) => properties[col].type === 'date' ); - setPreviewDateColumns(datetimeColumns); - setIndexPatternDateField(datetimeColumns[0]); + setIndexPatternAvailableTimeFields(timeFields); + setIndexPatternTimeField(timeFields[0]); } else { toastNotifications.addDanger({ title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformPreview', { @@ -237,7 +258,7 @@ export const StepDetailsForm: FC = React.memo( isContinuousModeAvailable ? dateFieldNames[0] : '' ); const [continuousModeDelay, setContinuousModeDelay] = useState(defaults.continuousModeDelay); - const isContinuousModeDelayValid = delayValidator(continuousModeDelay); + const isContinuousModeDelayValid = continuousModeDelayValidator(continuousModeDelay); const transformIdExists = transformIds.some((id) => transformId === id); const transformIdEmpty = transformId === ''; @@ -248,10 +269,22 @@ export const StepDetailsForm: FC = React.memo( const indexNameValid = isValidIndexName(destinationIndex); const indexPatternTitleExists = indexPatternTitles.some((name) => destinationIndex === name); + const [transformFrequency, setTransformFrequency] = useState(defaults.transformFrequency); + const isTransformFrequencyValid = transformFrequencyValidator(transformFrequency); + + const [transformSettingsMaxPageSearchSize, setTransformSettingsMaxPageSearchSize] = useState( + defaults.transformSettingsMaxPageSearchSize + ); + const isTransformSettingsMaxPageSearchSizeValid = transformSettingsMaxPageSearchSizeValidator( + transformSettingsMaxPageSearchSize + ); + const valid = !transformIdEmpty && transformIdValid && !transformIdExists && + isTransformFrequencyValid && + isTransformSettingsMaxPageSearchSizeValid && !indexNameEmpty && indexNameValid && (!indexPatternTitleExists || !createIndexPattern) && @@ -266,10 +299,12 @@ export const StepDetailsForm: FC = React.memo( isContinuousModeEnabled, transformId, transformDescription, + transformFrequency, + transformSettingsMaxPageSearchSize, destinationIndex, touched: true, valid, - indexPatternDateField, + indexPatternTimeField, }); // custom comparison /* eslint-disable react-hooks/exhaustive-deps */ @@ -280,9 +315,11 @@ export const StepDetailsForm: FC = React.memo( isContinuousModeEnabled, transformId, transformDescription, + transformFrequency, + transformSettingsMaxPageSearchSize, destinationIndex, valid, - indexPatternDateField, + indexPatternTimeField, /* eslint-enable react-hooks/exhaustive-deps */ ]); @@ -413,13 +450,15 @@ export const StepDetailsForm: FC = React.memo( data-test-subj="transformCreateIndexPatternSwitch" /> - {createIndexPattern && !indexPatternTitleExists && previewDateColumns.length > 0 && ( - - )} + {createIndexPattern && + !indexPatternTitleExists && + indexPatternAvailableTimeFields.length > 0 && ( + + )} = React.memo( )} + + + + + + setTransformFrequency(e.target.value)} + aria-label={i18n.translate('xpack.transform.stepDetailsForm.frequencyAriaLabel', { + defaultMessage: 'Choose a frequency.', + })} + isInvalid={!isTransformFrequencyValid} + data-test-subj="transformFrequencyInput" + /> + + + + + setTransformSettingsMaxPageSearchSize(parseInt(e.target.value, 10)) + } + aria-label={i18n.translate( + 'xpack.transform.stepDetailsForm.maxPageSearchSizeAriaLabel', + { + defaultMessage: 'Choose a maximum page search size.', + } + )} + isInvalid={!isTransformFrequencyValid} + data-test-subj="transformMaxPageSearchSizeInput" + /> + +
); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx index bd6dc8f709e990..45cd8aa465522c 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx @@ -8,83 +8,110 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFieldText, EuiFormRow, EuiSelect } from '@elastic/eui'; +import { EuiAccordion, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { StepDetailsExposedState } from './step_details_form'; -export const StepDetailsSummary: FC = React.memo( - ({ +export const StepDetailsSummary: FC = React.memo((props) => { + const { continuousModeDateField, createIndexPattern, isContinuousModeEnabled, transformId, transformDescription, + transformFrequency, + transformSettingsMaxPageSearchSize, destinationIndex, touched, - indexPatternDateField, - }) => { - if (touched === false) { - return null; - } + indexPatternTimeField, + } = props; - const destinationIndexHelpText = createIndexPattern - ? i18n.translate('xpack.transform.stepDetailsSummary.createIndexPatternMessage', { - defaultMessage: 'A Kibana index pattern will be created for this transform.', - }) - : ''; + if (touched === false) { + return null; + } - return ( -
- - - + const destinationIndexHelpText = createIndexPattern + ? i18n.translate('xpack.transform.stepDetailsSummary.createIndexPatternMessage', { + defaultMessage: 'A Kibana index pattern will be created for this transform.', + }) + : ''; + + return ( +
+ + {transformId} + + + {transformDescription !== '' && ( - + {transformDescription} + )} + + + {destinationIndex} + + {createIndexPattern && indexPatternTimeField !== undefined && indexPatternTimeField !== '' && ( - + {indexPatternTimeField} + )} + + {isContinuousModeEnabled && ( - + {continuousModeDateField} + )} - {isContinuousModeEnabled && ( - - - + + + - ); - } -); + paddingSize="s" + > + + {transformFrequency} + + + {transformSettingsMaxPageSearchSize} + + +
+ ); +}); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx index 2572b3ee57d7df..8ee2093a1e8021 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_time_field.tsx @@ -10,20 +10,20 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; interface Props { - previewDateColumns: string[]; - indexPatternDateField: string | undefined; + indexPatternAvailableTimeFields: string[]; + indexPatternTimeField: string | undefined; onTimeFieldChanged: (e: React.ChangeEvent) => void; } export const StepDetailsTimeField: FC = ({ - previewDateColumns, - indexPatternDateField, + indexPatternAvailableTimeFields, + indexPatternTimeField, onTimeFieldChanged, }) => { const noTimeFieldLabel = i18n.translate( 'xpack.transform.stepDetailsForm.noTimeFieldOptionLabel', { - defaultMessage: "I don't want to use the Time Filter", + defaultMessage: "I don't want to use the time filter", } ); @@ -42,26 +42,26 @@ export const StepDetailsTimeField: FC = ({ } helpText={ } > ({ text })), + ...indexPatternAvailableTimeFields.map((text) => ({ text })), disabledDividerOption, noTimeFieldOption, ]} - value={indexPatternDateField} + value={indexPatternTimeField} onChange={onTimeFieldChanged} - data-test-subj="transformIndexPatternDateFieldSelect" + data-test-subj="transformIndexPatternTimeFieldSelect" /> ); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx index 0ca018972cac96..2724470896833c 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx @@ -165,7 +165,7 @@ export const Wizard: FC = React.memo(({ cloneConfig, searchItems }) transformConfig={transformConfig} onChange={setStepCreateState} overrides={stepCreateState} - timeFieldName={stepDetailsState.indexPatternDateField} + timeFieldName={stepDetailsState.indexPatternTimeField} /> ) : ( diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx index 5836898755224e..a566afcfa65567 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx @@ -6,7 +6,7 @@ import React, { FC } from 'react'; -import { EuiForm } from '@elastic/eui'; +import { EuiForm, EuiAccordion, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -33,26 +33,11 @@ export const EditTransformFlyoutForm: FC = ({ onChange={(value) => dispatch({ field: 'description', value })} value={formFields.description.value} /> - dispatch({ field: 'docsPerSecond', value })} - value={formFields.docsPerSecond.value} - /> + = ({ defaultMessage: 'Frequency', })} onChange={(value) => dispatch({ field: 'frequency', value })} - placeholder="1m" + placeholder={i18n.translate( + 'xpack.transform.transformList.editFlyoutFormFrequencyPlaceholderText', + { + defaultMessage: 'Default: {defaultValue}', + values: { defaultValue: formFields.frequency.defaultValue }, + } + )} value={formFields.frequency.value} /> + + + + + dispatch({ field: 'destinationIndex', value })} + value={formFields.destinationIndex.value} + /> + + dispatch({ field: 'destinationPipeline', value })} + value={formFields.destinationPipeline.value} + /> + + + + + +
+ dispatch({ field: 'docsPerSecond', value })} + value={formFields.docsPerSecond.value} + /> + + dispatch({ field: 'maxPageSearchSize', value })} + value={formFields.maxPageSearchSize.value} + placeholder={i18n.translate( + 'xpack.transform.transformList.editFlyoutFormMaxPageSearchSizePlaceholderText', + { + defaultMessage: 'Default: {defaultValue}', + values: { defaultValue: formFields.maxPageSearchSize.defaultValue }, + } + )} + /> +
+
); }; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.test.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.test.ts index 12e60c2af55564..b7f82388b02bca 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.test.ts +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.test.ts @@ -11,8 +11,8 @@ import { formReducerFactory, frequencyValidator, getDefaultState, - numberAboveZeroValidator, - FormField, + integerAboveZeroValidator, + stringValidator, } from './use_edit_transform_flyout'; const getTransformConfigMock = (): TransformPivotConfig => ({ @@ -45,79 +45,149 @@ const getTransformConfigMock = (): TransformPivotConfig => ({ description: 'the-description', }); -const getDescriptionFieldMock = (value = ''): FormField => ({ - isOptional: true, - value, - errorMessages: [], - validator: 'string', -}); - -const getDocsPerSecondFieldMock = (value = ''): FormField => ({ - isOptional: true, - value, - errorMessages: [], - validator: 'numberAboveZero', -}); - -const getFrequencyFieldMock = (value = ''): FormField => ({ - isOptional: true, - value, - errorMessages: [], - validator: 'frequency', -}); - describe('Transform: applyFormFieldsToTransformConfig()', () => { - test('should exclude unchanged form fields', () => { + it('should exclude unchanged form fields', () => { const transformConfigMock = getTransformConfigMock(); - const updateConfig = applyFormFieldsToTransformConfig(transformConfigMock, { - description: getDescriptionFieldMock(transformConfigMock.description), - docsPerSecond: getDocsPerSecondFieldMock(), - frequency: getFrequencyFieldMock(), - }); + const formState = getDefaultState(transformConfigMock); + + const updateConfig = applyFormFieldsToTransformConfig( + transformConfigMock, + formState.formFields + ); // This case will return an empty object. In the actual UI, this case should not happen // because the Update-Button will be disabled when no form field was changed. expect(Object.keys(updateConfig)).toHaveLength(0); expect(updateConfig.description).toBe(undefined); + // Destination index `index` attribute is nested under `dest` so we're just checking against that. + expect(updateConfig.dest).toBe(undefined); // `docs_per_second` is nested under `settings` so we're just checking against that. expect(updateConfig.settings).toBe(undefined); expect(updateConfig.frequency).toBe(undefined); }); - test('should include previously nonexisting attributes', () => { + it('should include previously nonexisting attributes', () => { const { description, frequency, ...transformConfigMock } = getTransformConfigMock(); - const updateConfig = applyFormFieldsToTransformConfig(transformConfigMock, { - description: getDescriptionFieldMock('the-new-description'), - docsPerSecond: getDocsPerSecondFieldMock('10'), - frequency: getFrequencyFieldMock('1m'), + const formState = getDefaultState({ + ...transformConfigMock, + description: 'the-new-description', + dest: { + index: 'the-new-destination-index', + }, + frequency: '10m', + settings: { + docs_per_second: 10, + }, }); - expect(Object.keys(updateConfig)).toHaveLength(3); + const updateConfig = applyFormFieldsToTransformConfig( + transformConfigMock, + formState.formFields + ); + + expect(Object.keys(updateConfig)).toHaveLength(4); expect(updateConfig.description).toBe('the-new-description'); + expect(updateConfig.dest?.index).toBe('the-new-destination-index'); expect(updateConfig.settings?.docs_per_second).toBe(10); - expect(updateConfig.frequency).toBe('1m'); + expect(updateConfig.frequency).toBe('10m'); }); - test('should only include changed form fields', () => { + it('should only include changed form fields', () => { const transformConfigMock = getTransformConfigMock(); - const updateConfig = applyFormFieldsToTransformConfig(transformConfigMock, { - description: getDescriptionFieldMock('the-updated-description'), - docsPerSecond: getDocsPerSecondFieldMock(), - frequency: getFrequencyFieldMock(), + + const formState = getDefaultState({ + ...transformConfigMock, + description: 'the-updated-description', + dest: { + index: 'the-updated-destination-index', + pipeline: 'the-updated-destination-index', + }, }); - expect(Object.keys(updateConfig)).toHaveLength(1); + const updateConfig = applyFormFieldsToTransformConfig( + transformConfigMock, + formState.formFields + ); + + expect(Object.keys(updateConfig)).toHaveLength(2); expect(updateConfig.description).toBe('the-updated-description'); + expect(updateConfig.dest?.index).toBe('the-updated-destination-index'); // `docs_per_second` is nested under `settings` so we're just checking against that. expect(updateConfig.settings).toBe(undefined); expect(updateConfig.frequency).toBe(undefined); }); + + it('should include dependent form fields', () => { + const transformConfigMock = getTransformConfigMock(); + + const formState = getDefaultState({ + ...transformConfigMock, + dest: { + ...transformConfigMock.dest, + pipeline: 'the-updated-destination-index', + }, + }); + + const updateConfig = applyFormFieldsToTransformConfig( + transformConfigMock, + formState.formFields + ); + expect(Object.keys(updateConfig)).toHaveLength(1); + // It should include the dependent unchanged destination index + expect(updateConfig.dest?.index).toBe(transformConfigMock.dest.index); + expect(updateConfig.dest?.pipeline).toBe('the-updated-destination-index'); + }); + + it('should include the destination index when pipeline is unset', () => { + const transformConfigMock = { + ...getTransformConfigMock(), + dest: { + index: 'the-untouched-destination-index', + pipeline: 'the-original-pipeline', + }, + }; + + const formState = getDefaultState({ + ...transformConfigMock, + dest: { + ...transformConfigMock.dest, + pipeline: '', + }, + }); + + const updateConfig = applyFormFieldsToTransformConfig( + transformConfigMock, + formState.formFields + ); + expect(Object.keys(updateConfig)).toHaveLength(1); + // It should include the dependent unchanged destination index + expect(updateConfig.dest?.index).toBe(transformConfigMock.dest.index); + expect(typeof updateConfig.dest?.pipeline).toBe('undefined'); + }); + + it('should exclude unrelated dependent form fields', () => { + const transformConfigMock = getTransformConfigMock(); + + const formState = getDefaultState({ + ...transformConfigMock, + description: 'the-updated-description', + }); + + const updateConfig = applyFormFieldsToTransformConfig( + transformConfigMock, + formState.formFields + ); + expect(Object.keys(updateConfig)).toHaveLength(1); + // It should exclude the dependent unchanged destination section + expect(typeof updateConfig.dest).toBe('undefined'); + expect(updateConfig.description).toBe('the-updated-description'); + }); }); describe('Transform: formReducerFactory()', () => { - test('field updates should trigger form validation', () => { + it('field updates should trigger form validation', () => { const transformConfigMock = getTransformConfigMock(); const reducer = formReducerFactory(transformConfigMock); @@ -150,52 +220,73 @@ describe('Transform: formReducerFactory()', () => { }); }); +describe('Transfom: stringValidator()', () => { + it('should allow an empty string for optional fields', () => { + expect(stringValidator('')).toHaveLength(0); + }); + + it('should not allow an empty string for required fields', () => { + expect(stringValidator('', false)).toHaveLength(1); + }); +}); + describe('Transform: frequencyValidator()', () => { - test('it should only allow values between 1s and 1h', () => { - // frequencyValidator() returns an array of error messages so - // an array with a length of 0 means a successful validation. + const transformFrequencyValidator = (arg: string) => frequencyValidator(arg).length === 0; - // invalid - expect(frequencyValidator(0)).toHaveLength(1); - expect(frequencyValidator('0')).toHaveLength(1); - expect(frequencyValidator('0s')).toHaveLength(1); - expect(frequencyValidator(1)).toHaveLength(1); - expect(frequencyValidator('1')).toHaveLength(1); - expect(frequencyValidator('1ms')).toHaveLength(1); - expect(frequencyValidator('1d')).toHaveLength(1); - expect(frequencyValidator('60s')).toHaveLength(1); - expect(frequencyValidator('60m')).toHaveLength(1); - expect(frequencyValidator('60h')).toHaveLength(1); - expect(frequencyValidator('2h')).toHaveLength(1); - expect(frequencyValidator('h2')).toHaveLength(1); - expect(frequencyValidator('2h2')).toHaveLength(1); - expect(frequencyValidator('h2h')).toHaveLength(1); + it('should fail when the input is not an integer and valid time unit.', () => { + expect(transformFrequencyValidator('0')).toBe(false); + expect(transformFrequencyValidator('0.1s')).toBe(false); + expect(transformFrequencyValidator('1.1m')).toBe(false); + expect(transformFrequencyValidator('10.1asdf')).toBe(false); + }); - // valid - expect(frequencyValidator('1s')).toHaveLength(0); - expect(frequencyValidator('1m')).toHaveLength(0); - expect(frequencyValidator('1h')).toHaveLength(0); - expect(frequencyValidator('10s')).toHaveLength(0); - expect(frequencyValidator('10m')).toHaveLength(0); - expect(frequencyValidator('59s')).toHaveLength(0); - expect(frequencyValidator('59m')).toHaveLength(0); + it('should only allow s/m/h as time unit.', () => { + expect(transformFrequencyValidator('1ms')).toBe(false); + expect(transformFrequencyValidator('1s')).toBe(true); + expect(transformFrequencyValidator('1m')).toBe(true); + expect(transformFrequencyValidator('1h')).toBe(true); + expect(transformFrequencyValidator('1d')).toBe(false); + }); + + it('should only allow values above 0 and up to 1 hour.', () => { + expect(transformFrequencyValidator('0s')).toBe(false); + expect(transformFrequencyValidator('1s')).toBe(true); + expect(transformFrequencyValidator('3599s')).toBe(true); + expect(transformFrequencyValidator('3600s')).toBe(true); + expect(transformFrequencyValidator('3601s')).toBe(false); + expect(transformFrequencyValidator('10000s')).toBe(false); + + expect(transformFrequencyValidator('0m')).toBe(false); + expect(transformFrequencyValidator('1m')).toBe(true); + expect(transformFrequencyValidator('59m')).toBe(true); + expect(transformFrequencyValidator('60m')).toBe(true); + expect(transformFrequencyValidator('61m')).toBe(false); + expect(transformFrequencyValidator('100m')).toBe(false); + + expect(transformFrequencyValidator('0h')).toBe(false); + expect(transformFrequencyValidator('1h')).toBe(true); + expect(transformFrequencyValidator('2h')).toBe(false); }); }); -describe('Transform: numberValidator()', () => { - test('it should only allow numbers', () => { - // numberValidator() returns an array of error messages so +describe('Transform: integerAboveZeroValidator()', () => { + it('should only allow integers above zero', () => { + // integerAboveZeroValidator() returns an array of error messages so // an array with a length of 0 means a successful validation. // invalid - expect(numberAboveZeroValidator('a-string')).toHaveLength(1); - expect(numberAboveZeroValidator('0s')).toHaveLength(1); - expect(numberAboveZeroValidator('1m')).toHaveLength(1); - expect(numberAboveZeroValidator(-1)).toHaveLength(1); - expect(numberAboveZeroValidator(0)).toHaveLength(1); + expect(integerAboveZeroValidator('a-string')).toHaveLength(1); + expect(integerAboveZeroValidator('0s')).toHaveLength(1); + expect(integerAboveZeroValidator('1m')).toHaveLength(1); + expect(integerAboveZeroValidator('1.')).toHaveLength(1); + expect(integerAboveZeroValidator('1..')).toHaveLength(1); + expect(integerAboveZeroValidator('1.0')).toHaveLength(1); + expect(integerAboveZeroValidator(-1)).toHaveLength(1); + expect(integerAboveZeroValidator(0)).toHaveLength(1); + expect(integerAboveZeroValidator(0.1)).toHaveLength(1); // valid - expect(numberAboveZeroValidator(1)).toHaveLength(0); - expect(numberAboveZeroValidator('1')).toHaveLength(0); + expect(integerAboveZeroValidator(1)).toHaveLength(0); + expect(integerAboveZeroValidator('1')).toHaveLength(0); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts index d622a7e9cc0404..c3430ed94c6284 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts @@ -4,17 +4,70 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash'; +import isEqual from 'lodash/isEqual'; +import merge from 'lodash/merge'; + import { useReducer } from 'react'; import { i18n } from '@kbn/i18n'; import { PostTransformsUpdateRequestSchema } from '../../../../../../common/api_schemas/update_transforms'; import { TransformPivotConfig } from '../../../../../../common/types/transform'; +import { getNestedProperty, setNestedProperty } from '../../../../../../common/utils/object_utils'; + +// This custom hook uses nested reducers to provide a generic framework to manage form state +// and apply it to a final possibly nested configuration object suitable for passing on +// directly to an API call. For now this is only used for the transform edit form. +// Once we apply the functionality to other places, e.g. the transform creation wizard, +// the generic framework code in this file should be moved to a dedicated location. + +// The outer most level reducer defines a flat structure of names for form fields. +// This is a flat structure regardless of whether the final request object will be nested. +// For example, `destinationIndex` and `destinationPipeline` will later be nested under `dest`. +interface EditTransformFlyoutFieldsState { + [key: string]: FormField; + description: FormField; + destinationIndex: FormField; + destinationPipeline: FormField; + frequency: FormField; + docsPerSecond: FormField; +} + +// The inner reducers apply validation based on supplied attributes of each field. +export interface FormField { + formFieldName: string; + configFieldName: string; + defaultValue: string; + dependsOn: string[]; + errorMessages: string[]; + isNullable: boolean; + isOptional: boolean; + validator: keyof typeof validate; + value: string; + valueParser: (value: string) => any; +} + +// The reducers and utility functions in this file provide the following features: +// - getDefaultState() +// Sets up the initial form state. It supports overrides to apply a pre-existing configuration. +// The implementation of this function is the only one that's specifically required to define +// the features of the transform edit form. All other functions are generic and could be reused +// in the future for other forms. +// +// - formReducerFactory() / formFieldReducer() +// These nested reducers take care of updating and validating the form state. +// +// - applyFormFieldsToTransformConfig() (iterates over getUpdateValue()) +// Once a user hits the update button, these functions take care of extracting the information +// necessary to create the update request. They take into account whether a field needs to +// be included at all in the request (for example, if it hadn't been changed). +// The code is also able to identify relationships/dependencies between form fields. +// For example, if the `pipeline` field was changed, it's necessary to make the `index` +// field part of the request, otherwise the update would fail. // A Validator function takes in a value to check and returns an array of error messages. // If no messages (empty array) get returned, the value is valid. -type Validator = (arg: any) => string[]; +type Validator = (value: any, isOptional?: boolean) => string[]; // Note on the form validation and input components used: // All inputs use `EuiFieldText` which means all form values will be treated as strings. @@ -25,22 +78,48 @@ type Validator = (arg: any) => string[]; const numberAboveZeroNotValidErrorMessage = i18n.translate( 'xpack.transform.transformList.editFlyoutFormNumberNotValidErrorMessage', { - defaultMessage: 'Value needs to be a number above zero.', + defaultMessage: 'Value needs to be an integer above zero.', + } +); +export const integerAboveZeroValidator: Validator = (value) => + !isNaN(value) && Number.isInteger(+value) && +value > 0 && !(value + '').includes('.') + ? [] + : [numberAboveZeroNotValidErrorMessage]; + +const numberRange10To10000NotValidErrorMessage = i18n.translate( + 'xpack.transform.transformList.editFlyoutFormNumberRange10To10000NotValidErrorMessage', + { + defaultMessage: 'Value needs to be an integer between 10 and 10000.', } ); -export const numberAboveZeroValidator: Validator = (arg) => - !isNaN(arg) && parseInt(arg, 10) > 0 ? [] : [numberAboveZeroNotValidErrorMessage]; +export const integerRange10To10000Validator: Validator = (value) => + integerAboveZeroValidator(value).length === 0 && +value >= 10 && +value <= 10000 + ? [] + : [numberRange10To10000NotValidErrorMessage]; -// The way the current form is set up, this validator is just a sanity check, -// it should never trigger an error, because `EuiFieldText` always returns a string. +const requiredErrorMessage = i18n.translate( + 'xpack.transform.transformList.editFlyoutFormRequiredErrorMessage', + { + defaultMessage: 'Required field.', + } +); const stringNotValidErrorMessage = i18n.translate( 'xpack.transform.transformList.editFlyoutFormStringNotValidErrorMessage', { defaultMessage: 'Value needs to be of type string.', } ); -const stringValidator: Validator = (arg) => - typeof arg === 'string' ? [] : [stringNotValidErrorMessage]; +export const stringValidator: Validator = (value, isOptional = true) => { + if (typeof value !== 'string') { + return [stringNotValidErrorMessage]; + } + + if (value.length === 0 && !isOptional) { + return [requiredErrorMessage]; + } + + return []; +}; // Only allow frequencies in the form of 1s/1h etc. const frequencyNotValidErrorMessage = i18n.translate( @@ -57,55 +136,59 @@ export const frequencyValidator: Validator = (arg) => { // split string by groups of numbers and letters const regexStr = arg.match(/[a-z]+|[^a-z]+/gi); - return ( - // only valid if one group of numbers and one group of letters - regexStr !== null && - regexStr.length === 2 && - // only valid if time unit is one of s/m/h - ['s', 'm', 'h'].includes(regexStr[1]) && - // only valid if number is between 1 and 59 - parseInt(regexStr[0], 10) > 0 && - parseInt(regexStr[0], 10) < 60 && - // if time unit is 'h' then number must not be higher than 1 - !(parseInt(regexStr[0], 10) > 1 && regexStr[1] === 'h') - ? [] - : [frequencyNotValidErrorMessage] - ); -}; + // only valid if one group of numbers and one group of letters + if (regexStr === null || (Array.isArray(regexStr) && regexStr.length !== 2)) { + return [frequencyNotValidErrorMessage]; + } -type Validators = 'string' | 'frequency' | 'numberAboveZero'; + const valueNumber = +regexStr[0]; + const valueTimeUnit = regexStr[1]; + + // only valid if number is an integer above 0 + if (isNaN(valueNumber) || !Number.isInteger(valueNumber) || valueNumber === 0) { + return [frequencyNotValidErrorMessage]; + } -type Validate = { - [key in Validators]: Validator; + // only valid if value is up to 1 hour + return (valueTimeUnit === 's' && valueNumber <= 3600) || + (valueTimeUnit === 'm' && valueNumber <= 60) || + (valueTimeUnit === 'h' && valueNumber === 1) + ? [] + : [frequencyNotValidErrorMessage]; }; -const validate: Validate = { +const validate = { string: stringValidator, frequency: frequencyValidator, - numberAboveZero: numberAboveZeroValidator, -}; + integerAboveZero: integerAboveZeroValidator, + integerRange10To10000: integerRange10To10000Validator, +} as const; -export interface FormField { - errorMessages: string[]; - isOptional: boolean; - validator: keyof Validate; - value: string; -} +export const initializeField = ( + formFieldName: string, + configFieldName: string, + config: TransformPivotConfig, + overloads?: Partial +): FormField => { + const defaultValue = overloads?.defaultValue !== undefined ? overloads.defaultValue : ''; + const rawValue = getNestedProperty(config, configFieldName, undefined); + const value = rawValue !== null && rawValue !== undefined ? rawValue.toString() : ''; -const defaultField: FormField = { - errorMessages: [], - isOptional: true, - validator: 'string', - value: '', + return { + formFieldName, + configFieldName, + defaultValue, + dependsOn: [], + errorMessages: [], + isNullable: false, + isOptional: true, + validator: 'string', + value, + valueParser: (v) => v, + ...(overloads !== undefined ? { ...overloads } : {}), + }; }; -interface EditTransformFlyoutFieldsState { - [key: string]: FormField; - description: FormField; - frequency: FormField; - docsPerSecond: FormField; -} - export interface EditTransformFlyoutState { formFields: EditTransformFlyoutFieldsState; isFormTouched: boolean; @@ -119,48 +202,95 @@ interface Action { value: string; } +// Takes a value from form state and applies it to the structure +// of the expected final configuration request object. +// Considers options like if a value is nullable or optional. +const getUpdateValue = ( + attribute: keyof EditTransformFlyoutFieldsState, + config: TransformPivotConfig, + formState: EditTransformFlyoutFieldsState, + enforceFormValue = false +) => { + const formStateAttribute = formState[attribute]; + const fallbackValue = formStateAttribute.isNullable ? null : formStateAttribute.defaultValue; + + const formValue = + formStateAttribute.value !== '' + ? formStateAttribute.valueParser(formStateAttribute.value) + : fallbackValue; + + const configValue = getNestedProperty(config, formStateAttribute.configFieldName, fallbackValue); + + // only get depending values if we're not already in a call to get depending values. + const dependsOnConfig: PostTransformsUpdateRequestSchema = + enforceFormValue === false + ? formStateAttribute.dependsOn.reduce((_dependsOnConfig, dependsOnField) => { + return merge( + { ..._dependsOnConfig }, + getUpdateValue(dependsOnField, config, formState, true) + ); + }, {}) + : {}; + + if (formValue === formStateAttribute.defaultValue && formStateAttribute.isOptional) { + return formValue !== configValue ? dependsOnConfig : {}; + } + + return formValue !== configValue || enforceFormValue + ? setNestedProperty(dependsOnConfig, formStateAttribute.configFieldName, formValue) + : {}; +}; + // Takes in the form configuration and returns a // request object suitable to be sent to the // transform update API endpoint. export const applyFormFieldsToTransformConfig = ( config: TransformPivotConfig, - { description, docsPerSecond, frequency }: EditTransformFlyoutFieldsState -): PostTransformsUpdateRequestSchema => { - // if the input field was left empty, - // fall back to the default value of `null` - // which will disable throttling. - const docsPerSecondFormValue = - docsPerSecond.value !== '' ? parseInt(docsPerSecond.value, 10) : null; - const docsPerSecondConfigValue = config.settings?.docs_per_second ?? null; - - return { - // set the values only if they changed from the default - // and actually differ from the previous value. - ...(!(config.frequency === undefined && frequency.value === '') && - config.frequency !== frequency.value - ? { frequency: frequency.value } - : {}), - ...(!(config.description === undefined && description.value === '') && - config.description !== description.value - ? { description: description.value } - : {}), - ...(docsPerSecondFormValue !== docsPerSecondConfigValue - ? { settings: { docs_per_second: docsPerSecondFormValue } } - : {}), - }; -}; + formState: EditTransformFlyoutFieldsState +): PostTransformsUpdateRequestSchema => + // Iterates over all form fields and only if necessary applies them to + // the request object used for updating the transform. + Object.keys(formState).reduce( + (updateConfig, field) => merge({ ...updateConfig }, getUpdateValue(field, config, formState)), + {} + ); // Takes in a transform configuration and returns // the default state to populate the form. export const getDefaultState = (config: TransformPivotConfig): EditTransformFlyoutState => ({ formFields: { - description: { ...defaultField, value: config?.description ?? '' }, - frequency: { ...defaultField, value: config?.frequency ?? '', validator: 'frequency' }, - docsPerSecond: { - ...defaultField, - value: config?.settings?.docs_per_second?.toString() ?? '', - validator: 'numberAboveZero', - }, + // top level attributes + description: initializeField('description', 'description', config), + frequency: initializeField('frequency', 'frequency', config, { + defaultValue: '1m', + validator: 'frequency', + }), + + // dest.* + destinationIndex: initializeField('destinationIndex', 'dest.index', config, { + dependsOn: ['destinationPipeline'], + isOptional: false, + }), + destinationPipeline: initializeField('destinationPipeline', 'dest.pipeline', config, { + dependsOn: ['destinationIndex'], + }), + + // settings.* + docsPerSecond: initializeField('docsPerSecond', 'settings.docs_per_second', config, { + isNullable: true, + validator: 'integerAboveZero', + valueParser: (v) => (v === '' ? null : +v), + }), + maxPageSearchSize: initializeField( + 'maxPageSearchSize', + 'settings.max_page_search_size', + config, + { + defaultValue: '500', + validator: 'integerRange10To10000', + valueParser: (v) => +v, + } + ), }, isFormTouched: false, isFormValid: true, @@ -180,7 +310,7 @@ const formFieldReducer = (state: FormField, value: string): FormField => { errorMessages: state.isOptional && typeof value === 'string' && value.length === 0 ? [] - : validate[state.validator](value), + : validate[state.validator](value, state.isOptional), value, }; }; @@ -191,6 +321,8 @@ const formFieldReducer = (state: FormField, value: string): FormField => { // - sets `isFormValid` to have a flag if any of the form fields contains an error. export const formReducerFactory = (config: TransformPivotConfig) => { const defaultState = getDefaultState(config); + const defaultFieldValues = Object.values(defaultState.formFields).map((f) => f.value); + return (state: EditTransformFlyoutState, { field, value }: Action): EditTransformFlyoutState => { const formFields = { ...state.formFields, @@ -200,7 +332,10 @@ export const formReducerFactory = (config: TransformPivotConfig) => { return { ...state, formFields, - isFormTouched: !isEqual(defaultState.formFields, formFields), + isFormTouched: !isEqual( + defaultFieldValues, + Object.values(formFields).map((f) => f.value) + ), isFormValid: isFormValid(formFields), }; }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1223b3f42f8cd5..f626835da8e11e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -499,6 +499,9 @@ "core.ui.securityNavList.label": "セキュリティ", "core.ui.welcomeErrorMessage": "Elasticが正常に読み込まれませんでした。詳細はサーバーアウトプットを確認してください。", "core.ui.welcomeMessage": "Elasticの読み込み中", + "core.status.greenTitle": "緑", + "core.status.redTitle": "赤", + "core.status.yellowTitle": "黄色", "dashboard.actions.toggleExpandPanelMenuItem.expandedDisplayName": "最小化", "dashboard.actions.toggleExpandPanelMenuItem.notExpandedDisplayName": "全画面", "dashboard.addExistingVisualizationLinkText": "既存のユーザーを追加", @@ -1462,7 +1465,6 @@ "discover.uninitializedText": "クエリを作成、フィルターを追加、または[更新]をクリックして、現在のクエリの結果を取得します。", "discover.uninitializedTitle": "検索開始", "discover.valueIsNotConfiguredIndexPatternIDWarningTitle": "{stateVal}は設定されたインデックスパターンIDではありません", - "embeddableApi.actions.applyFilterActionTitle": "現在のビューにフィルターを適用", "embeddableApi.addPanel.createNewDefaultOption": "新規作成...", "embeddableApi.addPanel.displayName": "パネルの追加", "embeddableApi.addPanel.noMatchingObjectsMessage": "一致するオブジェクトが見つかりませんでした。", @@ -2926,9 +2928,6 @@ "savedObjectsManagement.view.viewItemButtonLabel": "{title}を表示", "savedObjectsManagement.view.viewItemTitle": "{title}を表示", "usageCollection.stats.notReadyMessage": "まだ統計が準備できていません。後程再試行してください", - "core.status.greenTitle": "緑", - "core.status.redTitle": "赤", - "core.status.yellowTitle": "黄色", "share.advancedSettings.csv.quoteValuesText": "csvエクスポートに値を引用するかどうかです", "share.advancedSettings.csv.quoteValuesTitle": "CSVの値を引用", "share.advancedSettings.csv.separatorText": "エクスポートされた値をこの文字列で区切ります", @@ -4756,10 +4755,6 @@ "xpack.apm.metadataTable.section.urlLabel": "URL", "xpack.apm.metadataTable.section.userAgentLabel": "ユーザーエージェント", "xpack.apm.metadataTable.section.userLabel": "ユーザー", - "xpack.apm.metrics.durationByCountryMap.avgPageLoadByCountryLabel": "国ごとの平均ページ読み込み時間の分布", - "xpack.apm.metrics.durationByCountryMap.RegionMapChart.ToolTip.avgPageLoadDuration": "平均ページ読み込み時間:", - "xpack.apm.metrics.durationByCountryMap.RegionMapChart.ToolTip.countPageLoads": "{docCount} ページの読み込み", - "xpack.apm.metrics.pageLoadCharts.avgPageLoadByBrowser": "ブラウザごとの平均ページ読み込み時間の分布", "xpack.apm.metrics.plot.noDataLabel": "この時間範囲のデータがありません。", "xpack.apm.metrics.transactionChart.machineLearningLabel": "機械学習:", "xpack.apm.metrics.transactionChart.machineLearningTooltip": "平均期間の周りのストリームには予測バウンドが表示されます。異常スコアが>= 75の場合、注釈が表示されます。", @@ -4811,8 +4806,6 @@ "xpack.apm.serviceMap.avgMemoryUsagePopoverStat": "メモリー使用状況(平均)", "xpack.apm.serviceMap.avgReqPerMinutePopoverMetric": "1分あたりのリクエスト(平均)", "xpack.apm.serviceMap.avgTransDurationPopoverStat": "トランザクションの長さ(平均)", - "xpack.apm.serviceMap.betaBadge": "ベータ", - "xpack.apm.serviceMap.betaTooltipMessage": "現在、この機能はベータです。不具合を見つけた場合やご意見がある場合、サポートに問い合わせるか、またはディスカッションフォーラムにご報告ください。", "xpack.apm.serviceMap.center": "中央", "xpack.apm.serviceMap.download": "ダウンロード", "xpack.apm.serviceMap.emptyBanner.docsLink": "詳細はドキュメントをご覧ください", @@ -14895,7 +14888,7 @@ "xpack.securitySolution.auditd.violatedSeLinuxPolicyDescription": "selinuxポリシーに違反しました", "xpack.securitySolution.auditd.wasAuthorizedToUseDescription": "が以下の使用を承認されました。", "xpack.securitySolution.auditd.withResultDescription": "結果付き", - "xpack.securitySolution.authenticationsTable.authenticationFailures": "認証", + "xpack.securitySolution.authenticationsTable.authentications": "認証", "xpack.securitySolution.authenticationsTable.failures": "失敗", "xpack.securitySolution.authenticationsTable.lastFailedDestination": "前回失敗したデスティネーション", "xpack.securitySolution.authenticationsTable.lastFailedSource": "前回失敗したソース", @@ -17767,8 +17760,6 @@ "xpack.transform.stepDetailsForm.errorGettingIndexPatternTitles": "既存のインデックスパターンのタイトルの取得中にエラーが発生しました:", "xpack.transform.stepDetailsForm.errorGettingTransformList": "既存の変換 ID の取得中にエラーが発生しました:", "xpack.transform.stepDetailsForm.errorGettingTransformPreview": "変換プレビューの取得中にエラーが発生しました。", - "xpack.transform.stepDetailsForm.indexPatternTimeFilterHelpText": "時間フィルターはこのフィールドを使って時間でフィールドを絞ります。時間フィールドを使わないこともできますが、その場合データを時間範囲で絞ることができません。", - "xpack.transform.stepDetailsForm.indexPatternTimeFilterLabel": "時間フィルターのフィールド名", "xpack.transform.stepDetailsForm.indexPatternTitleError": "このタイトルのインデックスパターンが既に存在します。", "xpack.transform.stepDetailsForm.noTimeFieldOptionLabel": "時間フィルターを使用しない", "xpack.transform.stepDetailsForm.transformDescriptionInputAriaLabel": "オプションの変換の説明を選択してください。", @@ -17781,7 +17772,6 @@ "xpack.transform.stepDetailsSummary.continuousModeDateFieldLabel": "連続モード日付フィールド", "xpack.transform.stepDetailsSummary.createIndexPatternMessage": "このジョブの Kibana インデックスパターンが作成されます。", "xpack.transform.stepDetailsSummary.destinationIndexLabel": "デスティネーションインデックス", - "xpack.transform.stepDetailsSummary.indexPatternTimeFilterLabel": "時間フィルター", "xpack.transform.stepDetailsSummary.transformDescriptionLabel": "変換の説明", "xpack.transform.stepDetailsSummary.transformIdLabel": "ジョブ ID", "xpack.transform.tableActionLabel": "アクション", @@ -17810,8 +17800,6 @@ "xpack.transform.transformList.editFlyoutCancelButtonText": "キャンセル", "xpack.transform.transformList.editFlyoutFormDescriptionLabel": "説明", "xpack.transform.transformList.editFlyoutFormDocsPerSecondHelptext": "スロットリングを有効にするには、インプットドキュメントの毎秒あたりのドキュメントの上限を設定します。", - "xpack.transform.transformList.editFlyoutFormdocsPerSecondLabel": "毎秒あたりのドキュメント", - "xpack.transform.transformList.editFlyoutFormFrequencyHelptext": "変換が連続実行されているときにソースインデックスで変更を確認する間の間隔。また、変換が検索またはインデックス中に一時障害が発生した場合に、再試行する間隔も決定します。最小値は1秒で、最大値は1時間です。", "xpack.transform.transformList.editFlyoutFormFrequencyLabel": "頻度", "xpack.transform.transformList.editFlyoutFormFrequencyNotValidErrorMessage": "頻度値が無効です。", "xpack.transform.transformList.editFlyoutFormNumberNotValidErrorMessage": "値はゼロより大きい数値でなければなりません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f1bab383d53f55..d6baa87ca9e2f0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -499,6 +499,9 @@ "core.ui.securityNavList.label": "安全", "core.ui.welcomeErrorMessage": "Elastic 未正确加载。检查服务器输出以了解详情。", "core.ui.welcomeMessage": "正在加载 Elastic", + "core.status.greenTitle": "绿", + "core.status.redTitle": "红", + "core.status.yellowTitle": "黄", "dashboard.actions.toggleExpandPanelMenuItem.expandedDisplayName": "最小化", "dashboard.actions.toggleExpandPanelMenuItem.notExpandedDisplayName": "全屏", "dashboard.addExistingVisualizationLinkText": "将现有", @@ -1463,7 +1466,6 @@ "discover.uninitializedText": "编写查询,添加一些筛选,或只需单击“刷新”来检索当前查询的结果。", "discover.uninitializedTitle": "开始搜索", "discover.valueIsNotConfiguredIndexPatternIDWarningTitle": "{stateVal} 不是配置的索引模式 ID", - "embeddableApi.actions.applyFilterActionTitle": "将筛选应用于当前视图", "embeddableApi.addPanel.createNewDefaultOption": "新建", "embeddableApi.addPanel.displayName": "添加面板", "embeddableApi.addPanel.noMatchingObjectsMessage": "未找到任何匹配对象。", @@ -2927,9 +2929,6 @@ "savedObjectsManagement.view.viewItemButtonLabel": "查看“{title}”", "savedObjectsManagement.view.viewItemTitle": "查看“{title}”", "usageCollection.stats.notReadyMessage": "统计尚未就绪。请稍后重试", - "core.status.greenTitle": "绿", - "core.status.redTitle": "红", - "core.status.yellowTitle": "黄", "share.advancedSettings.csv.quoteValuesText": "在 CSV 导出中是否应使用引号引起值?", "share.advancedSettings.csv.quoteValuesTitle": "使用引号引起 CSV 值", "share.advancedSettings.csv.separatorText": "使用此字符串分隔导出的值", @@ -4758,10 +4757,6 @@ "xpack.apm.metadataTable.section.urlLabel": "URL", "xpack.apm.metadataTable.section.userAgentLabel": "用户代理", "xpack.apm.metadataTable.section.userLabel": "用户", - "xpack.apm.metrics.durationByCountryMap.avgPageLoadByCountryLabel": "页面加载平均时长分布(按国家/地区)", - "xpack.apm.metrics.durationByCountryMap.RegionMapChart.ToolTip.avgPageLoadDuration": "页面加载平均时长:", - "xpack.apm.metrics.durationByCountryMap.RegionMapChart.ToolTip.countPageLoads": "{docCount} 个页面加载", - "xpack.apm.metrics.pageLoadCharts.avgPageLoadByBrowser": "平均页面加载持续时间分布 - 按浏览器", "xpack.apm.metrics.plot.noDataLabel": "此时间范围内没有数据。", "xpack.apm.metrics.transactionChart.machineLearningLabel": "Machine Learning", "xpack.apm.metrics.transactionChart.machineLearningTooltip": "环绕平均持续时间的流显示预期边界。对 ≥ 75 的异常分数显示标注。", @@ -4814,8 +4809,6 @@ "xpack.apm.serviceMap.avgMemoryUsagePopoverStat": "内存使用率(平均值)", "xpack.apm.serviceMap.avgReqPerMinutePopoverMetric": "每分钟请求数(平均)", "xpack.apm.serviceMap.avgTransDurationPopoverStat": "事务持续时间(平均值)", - "xpack.apm.serviceMap.betaBadge": "公测版", - "xpack.apm.serviceMap.betaTooltipMessage": "此功能当前为公测版。如果遇到任何错误或有任何反馈,请报告问题或访问我们的论坛。", "xpack.apm.serviceMap.center": "中", "xpack.apm.serviceMap.download": "下载", "xpack.apm.serviceMap.emptyBanner.docsLink": "在文档中了解详情", @@ -14904,7 +14897,7 @@ "xpack.securitySolution.auditd.violatedSeLinuxPolicyDescription": "已违反 selinux 策略", "xpack.securitySolution.auditd.wasAuthorizedToUseDescription": "有权使用", "xpack.securitySolution.auditd.withResultDescription": ",结果为", - "xpack.securitySolution.authenticationsTable.authenticationFailures": "身份验证", + "xpack.securitySolution.authenticationsTable.authentications": "身份验证", "xpack.securitySolution.authenticationsTable.failures": "错误", "xpack.securitySolution.authenticationsTable.lastFailedDestination": "上一失败目标", "xpack.securitySolution.authenticationsTable.lastFailedSource": "上一失败源", @@ -17777,8 +17770,6 @@ "xpack.transform.stepDetailsForm.errorGettingIndexPatternTitles": "获取现有索引模式标题时发生错误:", "xpack.transform.stepDetailsForm.errorGettingTransformList": "获取现有转换 ID 时发生错误:", "xpack.transform.stepDetailsForm.errorGettingTransformPreview": "获取转换预览时发生错误", - "xpack.transform.stepDetailsForm.indexPatternTimeFilterHelpText": "时间筛选将使用此字段按时间筛选您的数据。您可以选择不使用时间字段,但将无法通过时间范围缩小您的数据范围。", - "xpack.transform.stepDetailsForm.indexPatternTimeFilterLabel": "时间筛选字段名称", "xpack.transform.stepDetailsForm.indexPatternTitleError": "具有此名称的索引模式已存在。", "xpack.transform.stepDetailsForm.noTimeFieldOptionLabel": "我不想使用时间筛选", "xpack.transform.stepDetailsForm.transformDescriptionInputAriaLabel": "选择可选的转换描述。", @@ -17791,7 +17782,6 @@ "xpack.transform.stepDetailsSummary.continuousModeDateFieldLabel": "连续模式日期字段", "xpack.transform.stepDetailsSummary.createIndexPatternMessage": "将为此作业创建 Kibana 索引模式。", "xpack.transform.stepDetailsSummary.destinationIndexLabel": "目标 IP", - "xpack.transform.stepDetailsSummary.indexPatternTimeFilterLabel": "时间筛选", "xpack.transform.stepDetailsSummary.transformDescriptionLabel": "转换描述", "xpack.transform.stepDetailsSummary.transformIdLabel": "作业 ID", "xpack.transform.tableActionLabel": "操作", @@ -17820,8 +17810,6 @@ "xpack.transform.transformList.editFlyoutCancelButtonText": "取消", "xpack.transform.transformList.editFlyoutFormDescriptionLabel": "描述", "xpack.transform.transformList.editFlyoutFormDocsPerSecondHelptext": "要启用节流,请设置输入文档限制(每秒文档数)。", - "xpack.transform.transformList.editFlyoutFormdocsPerSecondLabel": "每秒文档数", - "xpack.transform.transformList.editFlyoutFormFrequencyHelptext": "转换不间断运行时检查源索引更改的时间间隔。还决定转换在搜索或索引时发生临时失败时的重试时间间隔。最小值为 1s,最大值为 1h。", "xpack.transform.transformList.editFlyoutFormFrequencyLabel": "频率", "xpack.transform.transformList.editFlyoutFormFrequencyNotValidErrorMessage": "频率值无效。", "xpack.transform.transformList.editFlyoutFormNumberNotValidErrorMessage": "值需要是大于零的数字。", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx index 0742ed8a778efb..2bcd87830901b3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx @@ -61,6 +61,7 @@ export const AddMessageVariables: React.FunctionComponent = ({ setIsVariablesPopoverOpen(true)} iconType="indexOpen" diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx index 495707db4975cf..0a04db1b5ddfa2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx @@ -32,48 +32,47 @@ export const IndexParamsFields = ({ }; return ( - <> - 0 ? ((documents[0] as unknown) as string) : undefined + 0 ? ((documents[0] as unknown) as string) : undefined + } + label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.documentsFieldLabel', + { + defaultMessage: 'Document to index', } - label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.documentsFieldLabel', - { - defaultMessage: 'Document to index', - } - )} - aria-label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.jsonDocAriaLabel', - { - defaultMessage: 'Code editor', - } - )} - errors={errors.documents as string[]} - onDocumentsChange={onDocumentsChange} - helpText={ - - - + )} + aria-label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.jsonDocAriaLabel', + { + defaultMessage: 'Code editor', } - onBlur={() => { - if ( - !(documents && documents.length > 0 ? ((documents[0] as unknown) as string) : undefined) - ) { - // set document as empty to turn on the validation for non empty valid JSON object - onDocumentsChange('{}'); - } - }} - /> - + )} + errors={errors.documents as string[]} + onDocumentsChange={onDocumentsChange} + helpText={ + + + + } + onBlur={() => { + if ( + !(documents && documents.length > 0 ? ((documents[0] as unknown) as string) : undefined) + ) { + // set document as empty to turn on the validation for non empty valid JSON object + onDocumentsChange('{}'); + } + }} + /> ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts index 43b22361aea36f..ad3a5b40bd00de 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.test.ts @@ -12,6 +12,7 @@ import { loadActionTypes, loadAllActions, updateActionConnector, + executeAction, } from './action_connector_api'; const http = httpServiceMock.createStartContract(); @@ -128,3 +129,32 @@ describe('deleteActions', () => { `); }); }); + +describe('executeAction', () => { + test('should call execute API', async () => { + const id = '123'; + const params = { + stringParams: 'someString', + numericParams: 123, + }; + + http.post.mockResolvedValueOnce({ + actionId: id, + status: 'ok', + }); + + const result = await executeAction({ id, http, params }); + expect(result).toEqual({ + actionId: id, + status: 'ok', + }); + expect(http.post.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/actions/action/123/_execute", + Object { + "body": "{\\"params\\":{\\"stringParams\\":\\"someString\\",\\"numericParams\\":123}}", + }, + ] + `); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts index 46a676ac06539e..c2c7139d13bf0f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_connector_api.ts @@ -7,6 +7,7 @@ import { HttpSetup } from 'kibana/public'; import { BASE_ACTION_API_PATH } from '../constants'; import { ActionConnector, ActionConnectorWithoutId, ActionType } from '../../types'; +import { ActionTypeExecutorResult } from '../../../../../plugins/actions/common'; export async function loadActionTypes({ http }: { http: HttpSetup }): Promise { return await http.get(`${BASE_ACTION_API_PATH}/list_action_types`); @@ -65,3 +66,17 @@ export async function deleteActions({ ); return { successes, errors }; } + +export async function executeAction({ + id, + params, + http, +}: { + id: string; + http: HttpSetup; + params: Record; +}): Promise> { + return await http.post(`${BASE_ACTION_API_PATH}/action/${id}/_execute`, { + body: JSON.stringify({ params }), + }); +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss new file mode 100644 index 00000000000000..873a3ceb762cdf --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.scss @@ -0,0 +1,3 @@ +.connectorEditFlyoutTabs { + margin-bottom: '-25px'; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx index dd9eeae2669874..0c2f4df0ca52b1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx @@ -152,6 +152,6 @@ describe('connector_edit_flyout', () => { const preconfiguredBadge = wrapper.find('[data-test-subj="preconfiguredBadge"]'); expect(preconfiguredBadge.exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="saveEditedActionButton"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="saveAndCloseEditedActionButton"]').exists()).toBeFalsy(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx index ca75e730062ab2..fc902a4fabcd84 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx @@ -19,15 +19,21 @@ import { EuiBetaBadge, EuiText, EuiLink, + EuiTabs, + EuiTab, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { Option, none, some } from 'fp-ts/lib/Option'; import { ActionConnectorForm, validateBaseProperties } from './action_connector_form'; +import { TestConnectorForm } from './test_connector_form'; import { ActionConnectorTableItem, ActionConnector, IErrorObject } from '../../../types'; import { connectorReducer } from './connector_reducer'; -import { updateActionConnector } from '../../lib/action_connector_api'; +import { updateActionConnector, executeAction } from '../../lib/action_connector_api'; import { hasSaveActionsCapability } from '../../lib/capabilities'; import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; import { PLUGIN } from '../../constants/plugin'; +import { ActionTypeExecutorResult } from '../../../../../actions/common'; +import './connector_edit_flyout.scss'; export interface ConnectorEditProps { initialConnector: ActionConnectorTableItem; @@ -40,7 +46,6 @@ export const ConnectorEditFlyout = ({ editFlyoutVisible, setEditFlyoutVisibility, }: ConnectorEditProps) => { - let hasErrors = false; const { http, toastNotifications, @@ -56,13 +61,26 @@ export const ConnectorEditFlyout = ({ connector: { ...initialConnector, secrets: {} }, }); const [isSaving, setIsSaving] = useState(false); + const [selectedTab, setTab] = useState<'config' | 'test'>('config'); + + const [hasChanges, setHasChanges] = useState(false); const setConnector = (key: string, value: any) => { dispatch({ command: { type: 'setConnector' }, payload: { key, value } }); }; + const [testExecutionActionParams, setTestExecutionActionParams] = useState< + Record + >({}); + const [testExecutionResult, setTestExecutionResult] = useState< + Option> + >(none); + const [isExecutingAction, setIsExecutinAction] = useState(false); + const closeFlyout = useCallback(() => { setEditFlyoutVisibility(false); setConnector('connector', { ...initialConnector, secrets: {} }); + setHasChanges(false); + setTestExecutionResult(none); // eslint-disable-next-line react-hooks/exhaustive-deps }, [setEditFlyoutVisibility]); @@ -71,11 +89,13 @@ export const ConnectorEditFlyout = ({ } const actionTypeModel = actionTypeRegistry.get(connector.actionTypeId); - const errors = { + const errorsInConnectorConfig = { ...actionTypeModel?.validateConnector(connector).errors, ...validateBaseProperties(connector).errors, } as IErrorObject; - hasErrors = !!Object.keys(errors).find((errorKey) => errors[errorKey].length >= 1); + const hasErrorsInConnectorConfig = !!Object.keys(errorsInConnectorConfig).find( + (errorKey) => errorsInConnectorConfig[errorKey].length >= 1 + ); const onActionConnectorSave = async (): Promise => await updateActionConnector({ http, connector, id: connector.id }) @@ -173,6 +193,32 @@ export const ConnectorEditFlyout = ({ ); + const onExecutAction = () => { + setIsExecutinAction(true); + return executeAction({ id: connector.id, params: testExecutionActionParams, http }).then( + (result) => { + setIsExecutinAction(false); + setTestExecutionResult(some(result)); + return result; + } + ); + }; + + const onSaveClicked = async (closeAfterSave: boolean = true) => { + setIsSaving(true); + const savedAction = await onActionConnectorSave(); + setIsSaving(false); + if (savedAction) { + setHasChanges(false); + if (closeAfterSave) { + closeFlyout(); + } + if (reloadConnectors) { + reloadConnectors(); + } + } + }; + return ( @@ -184,40 +230,78 @@ export const ConnectorEditFlyout = ({ ) : null} {flyoutTitle} + + setTab('config')} + data-test-subj="configureConnectorTab" + isSelected={'config' === selectedTab} + > + {i18n.translate('xpack.triggersActionsUI.sections.editConnectorForm.tabText', { + defaultMessage: 'Configuration', + })} + + setTab('test')} + data-test-subj="testConnectorTab" + isSelected={'test' === selectedTab} + > + {i18n.translate('xpack.triggersActionsUI.sections.testConnectorForm.tabText', { + defaultMessage: 'Test', + })} + + - {!connector.isPreconfigured ? ( - { + setHasChanges(true); + // if the user changes the connector, "forget" the last execution + // so the user comes back to a clean form ready to run a fresh test + setTestExecutionResult(none); + dispatch(changes); + }} + actionTypeRegistry={actionTypeRegistry} + http={http} + docLinks={docLinks} + capabilities={capabilities} + consumer={consumer} + /> + ) : ( + + + {i18n.translate( + 'xpack.triggersActionsUI.sections.editConnectorForm.descriptionText', + { + defaultMessage: 'This connector is readonly.', + } + )} + + + + + + ) + ) : ( + - ) : ( - - - {i18n.translate( - 'xpack.triggersActionsUI.sections.editConnectorForm.descriptionText', - { - defaultMessage: 'This connector is readonly.', - } - )} - - - - - )} @@ -232,35 +316,48 @@ export const ConnectorEditFlyout = ({ )} - {canSave && actionTypeModel && !connector.isPreconfigured ? ( - - { - setIsSaving(true); - const savedAction = await onActionConnectorSave(); - setIsSaving(false); - if (savedAction) { - closeFlyout(); - if (reloadConnectors) { - reloadConnectors(); - } - } - }} - > - - - - ) : null} + + + {canSave && actionTypeModel && !connector.isPreconfigured ? ( + + + { + await onSaveClicked(false); + }} + > + + + + + { + await onSaveClicked(); + }} + > + + + + + ) : null} + + diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx new file mode 100644 index 00000000000000..482bccb5517f1b --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx @@ -0,0 +1,212 @@ +/* + * 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, { lazy } from 'react'; +import { I18nProvider } from '@kbn/i18n/react'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import TestConnectorForm from './test_connector_form'; +import { none, some } from 'fp-ts/lib/Option'; +import { ActionConnector, ValidationResult } from '../../../types'; +import { actionTypeRegistryMock } from '../../action_type_registry.mock'; +import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context'; +import { EuiFormRow, EuiFieldText, EuiText, EuiLink, EuiForm, EuiSelect } from '@elastic/eui'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; + +const mockedActionParamsFields = lazy(async () => ({ + default() { + return ( + + + + + + + Link to some help + + } + > + + + + ); + }, +})); + +const actionType = { + id: 'my-action-type', + iconClass: 'test', + selectMessage: 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: mockedActionParamsFields, +}; + +describe('test_connector_form', () => { + let deps: any; + let actionTypeRegistry; + beforeAll(async () => { + actionTypeRegistry = actionTypeRegistryMock.create(); + + const mocks = coreMock.createSetup(); + const [ + { + application: { capabilities }, + }, + ] = await mocks.getStartServices(); + deps = { + http: mocks.http, + toastNotifications: mocks.notifications.toasts, + docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, + actionTypeRegistry, + capabilities, + }; + actionTypeRegistry.get.mockReturnValue(actionType); + }); + + it('renders initially as the action form and execute button and no result', async () => { + const connector = { + actionTypeId: actionType.id, + config: {}, + secrets: {}, + } as ActionConnector; + const wrapper = mountWithIntl( + + { + return new Promise(() => {}); + }, + docLinks: deps!.docLinks, + }} + > + {}} + isExecutingAction={false} + onExecutAction={async () => ({ + actionId: '', + status: 'ok', + })} + executionResult={none} + /> + + + ); + const executeActionButton = wrapper?.find('[data-test-subj="executeActionButton"]'); + expect(executeActionButton?.exists()).toBeTruthy(); + expect(executeActionButton?.first().prop('isDisabled')).toBe(false); + + const result = wrapper?.find('[data-test-subj="executionAwaiting"]'); + expect(result?.exists()).toBeTruthy(); + }); + + it('renders successful results', async () => { + const connector = { + actionTypeId: actionType.id, + config: {}, + secrets: {}, + } as ActionConnector; + const wrapper = mountWithIntl( + + { + return new Promise(() => {}); + }, + docLinks: deps!.docLinks, + }} + > + {}} + isExecutingAction={false} + onExecutAction={async () => ({ + actionId: '', + status: 'ok', + })} + executionResult={some({ + actionId: '', + status: 'ok', + })} + /> + + + ); + const result = wrapper?.find('[data-test-subj="executionSuccessfulResult"]'); + expect(result?.exists()).toBeTruthy(); + }); + + it('renders failure results', async () => { + const connector = { + actionTypeId: actionType.id, + config: {}, + secrets: {}, + } as ActionConnector; + const wrapper = mountWithIntl( + + { + return new Promise(() => {}); + }, + docLinks: deps!.docLinks, + }} + > + {}} + isExecutingAction={false} + onExecutAction={async () => ({ + actionId: '', + status: 'error', + message: 'Error Message', + })} + executionResult={some({ + actionId: '', + status: 'error', + message: 'Error Message', + })} + /> + + + ); + const result = wrapper?.find('[data-test-subj="executionFailureResult"]'); + expect(result?.exists()).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx new file mode 100644 index 00000000000000..a73fd4e22e6371 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx @@ -0,0 +1,224 @@ +/* + * 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, { Fragment, Suspense } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiButton, + EuiSteps, + EuiLoadingSpinner, + EuiDescriptionList, + EuiCallOut, + EuiSpacer, +} from '@elastic/eui'; +import { Option, map, getOrElse } from 'fp-ts/lib/Option'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { ActionConnector } from '../../../types'; +import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; +import { ActionTypeExecutorResult } from '../../../../../actions/common'; + +export interface ConnectorAddFlyoutProps { + connector: ActionConnector; + executeEnabled: boolean; + isExecutingAction: boolean; + setActionParams: (params: Record) => void; + actionParams: Record; + onExecutAction: () => Promise>; + executionResult: Option>; +} + +export const TestConnectorForm = ({ + connector, + executeEnabled, + executionResult, + actionParams, + setActionParams, + onExecutAction, + isExecutingAction, +}: ConnectorAddFlyoutProps) => { + const { actionTypeRegistry, docLinks } = useActionsConnectorsContext(); + const actionTypeModel = actionTypeRegistry.get(connector.actionTypeId); + const ParamsFieldsComponent = actionTypeModel.actionParamsFields; + + const actionErrors = actionTypeModel?.validateParams(actionParams); + const hasErrors = !!Object.values(actionErrors.errors).find((errors) => errors.length > 0); + + const steps = [ + { + title: 'Create an action', + children: ParamsFieldsComponent ? ( + + + + + + } + > + + setActionParams({ + ...actionParams, + [field]: value, + }) + } + messageVariables={[]} + docLinks={docLinks} + actionConnector={connector} + /> + + ) : ( + +

This Connector does not require any Action Parameter.

+
+ ), + }, + { + title: 'Run the action', + children: ( + + {executeEnabled ? null : ( + + +

+ +

+
+ +
+ )} + + + + + +
+ ), + }, + { + title: 'Results', + children: pipe( + executionResult, + map((result) => + result.status === 'ok' ? ( + + ) : ( + + ) + ), + getOrElse(() => ) + ), + }, + ]; + + return ; +}; + +const AwaitingExecution = () => ( + +

+ +

+
+); + +const SuccessfulExecution = () => ( + +

+ +

+
+); + +const FailedExecussion = ({ + executionResult: { message, serviceMessage }, +}: { + executionResult: ActionTypeExecutorResult; +}) => { + const items = [ + { + title: i18n.translate( + 'xpack.triggersActionsUI.sections.testConnectorForm.executionFailureDescription', + { + defaultMessage: 'The following error was found:', + } + ), + description: + message ?? + i18n.translate( + 'xpack.triggersActionsUI.sections.testConnectorForm.executionFailureUnknownReason', + { + defaultMessage: 'Unknown reason', + } + ), + }, + ]; + if (serviceMessage) { + items.push({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.testConnectorForm.executionFailureAdditionalDetails', + { + defaultMessage: 'Details:', + } + ), + description: serviceMessage, + }); + } + return ( + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { TestConnectorForm as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 837529bfc938d1..352c9a67bbee2a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -194,55 +194,15 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { truncateText: true, }, { - field: 'isPreconfigured', name: '', - render: (value: number, item: ActionConnectorTableItem) => { - if (item.isPreconfigured) { - return ( - - - - - - ); - } + render: (item: ActionConnectorTableItem) => { return ( - - - setConnectorsToDelete([item.id])} - iconType={'trash'} - /> - - + setConnectorsToDelete([item.id])} + /> ); }, @@ -344,28 +304,6 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { /> ); - const noPermissionPrompt = ( - - - - } - body={ -

- -

- } - /> - ); - return (
{ {data.length === 0 && canSave && !isLoadingActions && !isLoadingActionTypes && ( setAddFlyoutVisibility(true)} /> )} - {data.length === 0 && !canSave && noPermissionPrompt} + {data.length === 0 && !canSave && } { function getActionsCountByActionType(actions: ActionConnector[], actionTypeId: string) { return actions.filter((action) => action.actionTypeId === actionTypeId).length; } + +const DeleteOperation: React.FunctionComponent<{ + item: ActionConnectorTableItem; + canDelete: boolean; + onDelete: () => void; +}> = ({ item, canDelete, onDelete }) => { + if (item.isPreconfigured) { + return ( + + + + ); + } + return ( + + + + + + ); +}; + +const NoPermissionPrompt: React.FunctionComponent<{}> = () => ( + + + + } + body={ +

+ +

+ } + /> +); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx index 146cebabbb3826..110eff36e3df90 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx @@ -239,7 +239,7 @@ export class UpgradeAssistantTabs extends React.Component { this.setState({ telemetryState: TelemetryState.Running }); - await this.props.http.fetch('/api/upgrade_assistant/telemetry/ui_open', { + await this.props.http.fetch('/api/upgrade_assistant/stats/ui_open', { method: 'PUT', body: JSON.stringify(set({}, tabName, true)), }); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx index a20f4117f693d5..747430f455f226 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx @@ -239,7 +239,7 @@ export class ReindexButton extends React.Component { }); afterEach(() => jest.clearAllMocks()); - describe('PUT /api/upgrade_assistant/telemetry/ui_open', () => { + describe('PUT /api/upgrade_assistant/stats/ui_open', () => { it('returns correct payload with single option', async () => { const returnPayload = { overview: true, @@ -51,7 +51,7 @@ describe('Upgrade Assistant Telemetry API', () => { const resp = await routeDependencies.router.getHandler({ method: 'put', - pathPattern: '/api/upgrade_assistant/telemetry/ui_open', + pathPattern: '/api/upgrade_assistant/stats/ui_open', })( routeHandlerContextMock, createRequestMock({ body: returnPayload }), @@ -72,7 +72,7 @@ describe('Upgrade Assistant Telemetry API', () => { const resp = await routeDependencies.router.getHandler({ method: 'put', - pathPattern: '/api/upgrade_assistant/telemetry/ui_open', + pathPattern: '/api/upgrade_assistant/stats/ui_open', })( routeHandlerContextMock, createRequestMock({ @@ -93,7 +93,7 @@ describe('Upgrade Assistant Telemetry API', () => { const resp = await routeDependencies.router.getHandler({ method: 'put', - pathPattern: '/api/upgrade_assistant/telemetry/ui_open', + pathPattern: '/api/upgrade_assistant/stats/ui_open', })( routeHandlerContextMock, createRequestMock({ @@ -108,7 +108,7 @@ describe('Upgrade Assistant Telemetry API', () => { }); }); - describe('PUT /api/upgrade_assistant/telemetry/ui_reindex', () => { + describe('PUT /api/upgrade_assistant/stats/ui_reindex', () => { it('returns correct payload with single option', async () => { const returnPayload = { close: false, @@ -121,7 +121,7 @@ describe('Upgrade Assistant Telemetry API', () => { const resp = await routeDependencies.router.getHandler({ method: 'put', - pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex', + pathPattern: '/api/upgrade_assistant/stats/ui_reindex', })( routeHandlerContextMock, createRequestMock({ @@ -147,7 +147,7 @@ describe('Upgrade Assistant Telemetry API', () => { const resp = await routeDependencies.router.getHandler({ method: 'put', - pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex', + pathPattern: '/api/upgrade_assistant/stats/ui_reindex', })( routeHandlerContextMock, createRequestMock({ @@ -169,7 +169,7 @@ describe('Upgrade Assistant Telemetry API', () => { const resp = await routeDependencies.router.getHandler({ method: 'put', - pathPattern: '/api/upgrade_assistant/telemetry/ui_reindex', + pathPattern: '/api/upgrade_assistant/stats/ui_reindex', })( routeHandlerContextMock, createRequestMock({ diff --git a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts index 900a5e64c55c3e..71f5de01f6a445 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts @@ -12,7 +12,7 @@ import { RouteDependencies } from '../types'; export function registerTelemetryRoutes({ router, getSavedObjectsService }: RouteDependencies) { router.put( { - path: '/api/upgrade_assistant/telemetry/ui_open', + path: '/api/upgrade_assistant/stats/ui_open', validate: { body: schema.object({ overview: schema.boolean({ defaultValue: false }), @@ -40,7 +40,7 @@ export function registerTelemetryRoutes({ router, getSavedObjectsService }: Rout router.put( { - path: '/api/upgrade_assistant/telemetry/ui_reindex', + path: '/api/upgrade_assistant/stats/ui_reindex', validate: { body: schema.object({ close: schema.boolean({ defaultValue: false }), diff --git a/x-pack/plugins/uptime/public/components/common/charts/duration_line_series_list.tsx b/x-pack/plugins/uptime/public/components/common/charts/duration_line_series_list.tsx index 4223e918393b61..edb7e13ed064f6 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/duration_line_series_list.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/duration_line_series_list.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { LineSeries, CurveType } from '@elastic/charts'; +import { LineSeries, CurveType, Fit } from '@elastic/charts'; import { LocationDurationLine } from '../../../../common/types'; import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../../lib/helper'; @@ -28,6 +28,7 @@ export const DurationLineSeriesList = ({ lines }: Props) => ( yAccessors={[1]} yScaleToDataExtent={false} yScaleType="linear" + fit={Fit.Linear} /> ))} diff --git a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx index 39b8a38f60982c..3f252c2a436ae0 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/ping_histogram.tsx @@ -17,6 +17,7 @@ import { EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; +import numeral from '@elastic/numeral'; import moment from 'moment'; import { getChartDateLabel } from '../../../lib/helper'; import { ChartWrapper } from './chart_wrapper'; @@ -144,6 +145,8 @@ export const PingHistogramComponent: React.FC = ({ defaultMessage: 'Ping Y Axis', })} position="left" + tickFormat={(d) => numeral(d).format('0')} + labelFormat={(d) => numeral(d).format('0a')} title={i18n.translate('xpack.uptime.snapshotHistogram.yAxis.title', { defaultMessage: 'Pings', description: diff --git a/x-pack/test/apm_api_integration/basic/tests/index.ts b/x-pack/test/apm_api_integration/basic/tests/index.ts index 8aa509b0899ceb..9e1cb1f5872f10 100644 --- a/x-pack/test/apm_api_integration/basic/tests/index.ts +++ b/x-pack/test/apm_api_integration/basic/tests/index.ts @@ -45,7 +45,6 @@ export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderCont loadTestFile(require.resolve('./transaction_groups/transaction_charts')); loadTestFile(require.resolve('./transaction_groups/error_rate')); loadTestFile(require.resolve('./transaction_groups/breakdown')); - loadTestFile(require.resolve('./transaction_groups/avg_duration_by_browser')); }); describe('Observability overview', function () { diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/avg_duration_by_browser.snap b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/avg_duration_by_browser.snap deleted file mode 100644 index ab7b71cdf9e53b..00000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/__snapshots__/avg_duration_by_browser.snap +++ /dev/null @@ -1,438 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Average duration by browser when data is loaded returns the average duration by browser 1`] = ` -Array [ - Object { - "data": Array [ - Object { - "x": 1600159980000, - "y": 1131000, - }, - Object { - "x": 1600160010000, - "y": 617000, - }, - Object { - "x": 1600160040000, - }, - Object { - "x": 1600160070000, - "y": 1963000, - }, - Object { - "x": 1600160100000, - "y": 1190000, - }, - Object { - "x": 1600160130000, - "y": 1697000, - }, - Object { - "x": 1600160160000, - }, - Object { - "x": 1600160190000, - "y": 710000, - }, - Object { - "x": 1600160220000, - "y": 675250, - }, - Object { - "x": 1600160250000, - }, - Object { - "x": 1600160280000, - }, - Object { - "x": 1600160310000, - "y": 1763500, - }, - Object { - "x": 1600160340000, - "y": 539333.3333333334, - }, - Object { - "x": 1600160370000, - }, - Object { - "x": 1600160400000, - }, - Object { - "x": 1600160430000, - "y": 1650000, - }, - Object { - "x": 1600160460000, - "y": 781000, - }, - Object { - "x": 1600160490000, - }, - Object { - "x": 1600160520000, - }, - Object { - "x": 1600160550000, - "y": 1710000, - }, - Object { - "x": 1600160580000, - "y": 718000, - }, - Object { - "x": 1600160610000, - "y": 11912000, - }, - Object { - "x": 1600160640000, - }, - Object { - "x": 1600160670000, - "y": 885000, - }, - Object { - "x": 1600160700000, - "y": 1043000, - }, - Object { - "x": 1600160730000, - "y": 406000, - }, - Object { - "x": 1600160760000, - }, - Object { - "x": 1600160790000, - "y": 1296000, - }, - Object { - "x": 1600160820000, - "y": 570500, - }, - Object { - "x": 1600160850000, - }, - Object { - "x": 1600160880000, - }, - Object { - "x": 1600160910000, - "y": 1110000, - }, - Object { - "x": 1600160940000, - "y": 533500, - }, - Object { - "x": 1600160970000, - "y": 782500, - }, - Object { - "x": 1600161000000, - }, - Object { - "x": 1600161030000, - "y": 1200000, - }, - Object { - "x": 1600161060000, - "y": 522000, - }, - Object { - "x": 1600161090000, - }, - Object { - "x": 1600161120000, - }, - Object { - "x": 1600161150000, - "y": 1006000, - }, - Object { - "x": 1600161180000, - "y": 1203000, - }, - Object { - "x": 1600161210000, - }, - Object { - "x": 1600161240000, - }, - Object { - "x": 1600161270000, - "y": 1908000, - }, - Object { - "x": 1600161300000, - "y": 549000, - }, - Object { - "x": 1600161330000, - "y": 685000, - }, - Object { - "x": 1600161360000, - }, - Object { - "x": 1600161390000, - "y": 2055000, - }, - Object { - "x": 1600161420000, - "y": 699750, - }, - Object { - "x": 1600161450000, - }, - Object { - "x": 1600161480000, - }, - Object { - "x": 1600161510000, - "y": 1557000, - }, - Object { - "x": 1600161540000, - "y": 4993000, - }, - Object { - "x": 1600161570000, - }, - Object { - "x": 1600161600000, - }, - Object { - "x": 1600161630000, - "y": 898000, - }, - Object { - "x": 1600161660000, - "y": 4100500, - }, - Object { - "x": 1600161690000, - }, - Object { - "x": 1600161720000, - }, - Object { - "x": 1600161750000, - "y": 1305000, - }, - Object { - "x": 1600161780000, - }, - ], - "title": "Electron", - }, -] -`; - -exports[`Average duration by browser when data is loaded returns the average duration by browser filtering by transaction name 2`] = ` -Array [ - Object { - "data": Array [ - Object { - "x": 1600159980000, - }, - Object { - "x": 1600160010000, - }, - Object { - "x": 1600160040000, - }, - Object { - "x": 1600160070000, - "y": 1096000, - }, - Object { - "x": 1600160100000, - }, - Object { - "x": 1600160130000, - }, - Object { - "x": 1600160160000, - }, - Object { - "x": 1600160190000, - "y": 710000, - }, - Object { - "x": 1600160220000, - }, - Object { - "x": 1600160250000, - }, - Object { - "x": 1600160280000, - }, - Object { - "x": 1600160310000, - "y": 1108000, - }, - Object { - "x": 1600160340000, - }, - Object { - "x": 1600160370000, - }, - Object { - "x": 1600160400000, - }, - Object { - "x": 1600160430000, - "y": 1221000, - }, - Object { - "x": 1600160460000, - }, - Object { - "x": 1600160490000, - }, - Object { - "x": 1600160520000, - }, - Object { - "x": 1600160550000, - "y": 1325000, - }, - Object { - "x": 1600160580000, - }, - Object { - "x": 1600160610000, - }, - Object { - "x": 1600160640000, - }, - Object { - "x": 1600160670000, - "y": 885000, - }, - Object { - "x": 1600160700000, - }, - Object { - "x": 1600160730000, - }, - Object { - "x": 1600160760000, - }, - Object { - "x": 1600160790000, - "y": 1296000, - }, - Object { - "x": 1600160820000, - }, - Object { - "x": 1600160850000, - }, - Object { - "x": 1600160880000, - }, - Object { - "x": 1600160910000, - "y": 1110000, - }, - Object { - "x": 1600160940000, - }, - Object { - "x": 1600160970000, - }, - Object { - "x": 1600161000000, - }, - Object { - "x": 1600161030000, - "y": 1200000, - }, - Object { - "x": 1600161060000, - }, - Object { - "x": 1600161090000, - }, - Object { - "x": 1600161120000, - }, - Object { - "x": 1600161150000, - "y": 1006000, - }, - Object { - "x": 1600161180000, - }, - Object { - "x": 1600161210000, - }, - Object { - "x": 1600161240000, - }, - Object { - "x": 1600161270000, - "y": 1908000, - }, - Object { - "x": 1600161300000, - }, - Object { - "x": 1600161330000, - }, - Object { - "x": 1600161360000, - }, - Object { - "x": 1600161390000, - "y": 1420000, - }, - Object { - "x": 1600161420000, - }, - Object { - "x": 1600161450000, - }, - Object { - "x": 1600161480000, - }, - Object { - "x": 1600161510000, - "y": 1215000, - }, - Object { - "x": 1600161540000, - }, - Object { - "x": 1600161570000, - }, - Object { - "x": 1600161600000, - }, - Object { - "x": 1600161630000, - "y": 898000, - }, - Object { - "x": 1600161660000, - }, - Object { - "x": 1600161690000, - }, - Object { - "x": 1600161720000, - }, - Object { - "x": 1600161750000, - "y": 1305000, - }, - Object { - "x": 1600161780000, - }, - ], - "title": "Electron", - }, -] -`; diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/avg_duration_by_browser.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/avg_duration_by_browser.ts deleted file mode 100644 index 087bf1f0655e69..00000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/avg_duration_by_browser.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - const start = encodeURIComponent(metadata.start); - const end = encodeURIComponent(metadata.end); - const transactionName = '/products'; - const uiFilters = encodeURIComponent(JSON.stringify({})); - - describe('Average duration by browser', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/client/transaction_groups/avg_duration_by_browser?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the average duration by browser', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-rum/transaction_groups/avg_duration_by_browser?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - - expect(response.status).to.be(200); - - expect(response.body.length).to.be.greaterThan(0); - - expectSnapshot(response.body).toMatch(); - - expectSnapshot(response.body.length).toMatchInline(`1`); - }); - - it('returns the average duration by browser filtering by transaction name', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-rum/transaction_groups/avg_duration_by_browser?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionName=${transactionName}` - ); - - expect(response.status).to.be(200); - - expect(response.body.length).to.be.greaterThan(0); - - expectSnapshot(response.body.length).toMatchInline(`1`); - - expectSnapshot(response.body).toMatch(); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap b/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap new file mode 100644 index 00000000000000..38b009fc73d346 --- /dev/null +++ b/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap @@ -0,0 +1,280 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CSM page views when there is data returns page views 1`] = ` +Object { + "items": Array [ + Object { + "x": 1600149947000, + "y": 1, + }, + Object { + "x": 1600149957000, + "y": 0, + }, + Object { + "x": 1600149967000, + "y": 0, + }, + Object { + "x": 1600149977000, + "y": 0, + }, + Object { + "x": 1600149987000, + "y": 0, + }, + Object { + "x": 1600149997000, + "y": 0, + }, + Object { + "x": 1600150007000, + "y": 0, + }, + Object { + "x": 1600150017000, + "y": 0, + }, + Object { + "x": 1600150027000, + "y": 1, + }, + Object { + "x": 1600150037000, + "y": 0, + }, + Object { + "x": 1600150047000, + "y": 0, + }, + Object { + "x": 1600150057000, + "y": 0, + }, + Object { + "x": 1600150067000, + "y": 0, + }, + Object { + "x": 1600150077000, + "y": 1, + }, + Object { + "x": 1600150087000, + "y": 0, + }, + Object { + "x": 1600150097000, + "y": 0, + }, + Object { + "x": 1600150107000, + "y": 0, + }, + Object { + "x": 1600150117000, + "y": 0, + }, + Object { + "x": 1600150127000, + "y": 0, + }, + Object { + "x": 1600150137000, + "y": 0, + }, + Object { + "x": 1600150147000, + "y": 0, + }, + Object { + "x": 1600150157000, + "y": 0, + }, + Object { + "x": 1600150167000, + "y": 0, + }, + Object { + "x": 1600150177000, + "y": 1, + }, + Object { + "x": 1600150187000, + "y": 0, + }, + Object { + "x": 1600150197000, + "y": 0, + }, + Object { + "x": 1600150207000, + "y": 1, + }, + Object { + "x": 1600150217000, + "y": 0, + }, + Object { + "x": 1600150227000, + "y": 0, + }, + Object { + "x": 1600150237000, + "y": 1, + }, + ], + "topItems": Array [], +} +`; + +exports[`CSM page views when there is data returns page views with breakdown 1`] = ` +Object { + "items": Array [ + Object { + "Chrome": 1, + "x": 1600149947000, + "y": 1, + }, + Object { + "x": 1600149957000, + "y": 0, + }, + Object { + "x": 1600149967000, + "y": 0, + }, + Object { + "x": 1600149977000, + "y": 0, + }, + Object { + "x": 1600149987000, + "y": 0, + }, + Object { + "x": 1600149997000, + "y": 0, + }, + Object { + "x": 1600150007000, + "y": 0, + }, + Object { + "x": 1600150017000, + "y": 0, + }, + Object { + "Chrome": 1, + "x": 1600150027000, + "y": 1, + }, + Object { + "x": 1600150037000, + "y": 0, + }, + Object { + "x": 1600150047000, + "y": 0, + }, + Object { + "x": 1600150057000, + "y": 0, + }, + Object { + "x": 1600150067000, + "y": 0, + }, + Object { + "Chrome": 1, + "x": 1600150077000, + "y": 1, + }, + Object { + "x": 1600150087000, + "y": 0, + }, + Object { + "x": 1600150097000, + "y": 0, + }, + Object { + "x": 1600150107000, + "y": 0, + }, + Object { + "x": 1600150117000, + "y": 0, + }, + Object { + "x": 1600150127000, + "y": 0, + }, + Object { + "x": 1600150137000, + "y": 0, + }, + Object { + "x": 1600150147000, + "y": 0, + }, + Object { + "x": 1600150157000, + "y": 0, + }, + Object { + "x": 1600150167000, + "y": 0, + }, + Object { + "Chrome": 1, + "x": 1600150177000, + "y": 1, + }, + Object { + "x": 1600150187000, + "y": 0, + }, + Object { + "x": 1600150197000, + "y": 0, + }, + Object { + "Chrome Mobile": 1, + "x": 1600150207000, + "y": 1, + }, + Object { + "x": 1600150217000, + "y": 0, + }, + Object { + "x": 1600150227000, + "y": 0, + }, + Object { + "Chrome Mobile": 1, + "x": 1600150237000, + "y": 1, + }, + ], + "topItems": Array [ + "Chrome", + "Chrome Mobile", + ], +} +`; + +exports[`CSM page views when there is no data returns empty list 1`] = ` +Object { + "items": Array [], + "topItems": Array [], +} +`; + +exports[`CSM page views when there is no data returns empty list with breakdowns 1`] = ` +Object { + "items": Array [], + "topItems": Array [], +} +`; diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts new file mode 100644 index 00000000000000..ca5670d41d8ee0 --- /dev/null +++ b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { expectSnapshot } from '../../../common/match_snapshot'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('CSM page views', () => { + describe('when there is no data', () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + it('returns empty list with breakdowns', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + }); + + describe('when there is data', () => { + before(async () => { + await esArchiver.load('8.0.0'); + await esArchiver.load('rum_8.0.0'); + }); + after(async () => { + await esArchiver.unload('8.0.0'); + await esArchiver.unload('rum_8.0.0'); + }); + + it('returns page views', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + it('returns page views with breakdown', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/trial/tests/index.ts b/x-pack/test/apm_api_integration/trial/tests/index.ts index ae62253c62d816..a026f91a02cd77 100644 --- a/x-pack/test/apm_api_integration/trial/tests/index.ts +++ b/x-pack/test/apm_api_integration/trial/tests/index.ts @@ -35,6 +35,7 @@ export default function observabilityApiIntegrationTests({ loadTestFile }: FtrPr loadTestFile(require.resolve('./csm/csm_services.ts')); loadTestFile(require.resolve('./csm/web_core_vitals.ts')); loadTestFile(require.resolve('./csm/long_task_metrics.ts')); + loadTestFile(require.resolve('./csm/page_views.ts')); }); }); } diff --git a/x-pack/test/functional/apps/discover/error_handling.ts b/x-pack/test/functional/apps/discover/error_handling.ts new file mode 100644 index 00000000000000..515e5e293ae280 --- /dev/null +++ b/x-pack/test/functional/apps/discover/error_handling.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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'discover', 'timePicker']); + + describe('errors', function describeIndexTests() { + before(async function () { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.load('invalid_scripted_field'); + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await PageObjects.common.navigateToApp('discover'); + }); + + after(async function () { + await esArchiver.unload('invalid_scripted_field'); + }); + // this is the same test as in OSS but it catches different error message issue in different licences + describe('invalid scripted field error', () => { + it('is rendered', async () => { + const isFetchErrorVisible = await testSubjects.exists('discoverFetchError'); + expect(isFetchErrorVisible).to.be(true); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/discover/index.ts b/x-pack/test/functional/apps/discover/index.ts index 323b728e164544..759225d80fa206 100644 --- a/x-pack/test/functional/apps/discover/index.ts +++ b/x-pack/test/functional/apps/discover/index.ts @@ -13,5 +13,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./preserve_url')); loadTestFile(require.resolve('./async_scripted_fields')); loadTestFile(require.resolve('./reporting')); + loadTestFile(require.resolve('./error_handling')); }); } diff --git a/x-pack/test/functional/apps/transform/editing.ts b/x-pack/test/functional/apps/transform/editing.ts index ac955bde4ad5d5..498678e7ba4b55 100644 --- a/x-pack/test/functional/apps/transform/editing.ts +++ b/x-pack/test/functional/apps/transform/editing.ts @@ -99,6 +99,7 @@ export default function ({ getService }: FtrProviderContext) { await transform.testExecution.logTestStep( 'should update the transform documents per second' ); + await transform.editFlyout.openTransformEditAccordionAdvancedSettings(); await transform.editFlyout.assertTransformEditFlyoutInputExists('DocsPerSecond'); await transform.editFlyout.assertTransformEditFlyoutInputValue('DocsPerSecond', ''); await transform.editFlyout.setTransformEditFlyoutInputValue( diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/agent_only/data.json b/x-pack/test/functional/es_archives/endpoint/telemetry/agent_only/data.json new file mode 100644 index 00000000000000..9befbcc23470a7 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/agent_only/data.json @@ -0,0 +1,200 @@ +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:8a0a4a79-c1e3-4d13-951a-36c168b4be87", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-17T09:46:00.818Z", + "fleet-agents": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "default_api_key": "/XDWfZFwXdENtpxpo74Rl5fjAFX0mL8cwrPmOXXPtnri/QW6591rkVO7wcsZe8zN4A4Ld4SXBw1EpBMQILwtL2jVBj/t34qjXJDcQFkAf6CPmO1YP3ilC/L49tGQX0J3MTLVoSmwo/N9ni8v/ywdl8IkhB4dqTp6me/SrL+I31D6vUFxYupWdlg=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-17T09:37:50.171Z", + "default_api_key_id": "oShtm3QBy3Gd1kyfzhpC", + "last_checkin": "2020-09-17T09:46:00.818Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "_8ptm3QB3yUjiqk7XrOs", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.7.158/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:371f/64", + "fe80::250:56ff:feb1:371f/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "mac": [ + "00:50:56:b1:37:1f" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-38-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.0", + "id": "8a0a4a79-c1e3-4d13-951a-36c168b4be87" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:f490a4f2-5022-4a15-b447-86204ebbdd35", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-17T09:46:00.818Z", + "fleet-agents": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "default_api_key": "S3lDCj6T1USDsJUQfe/W77/D0sDl6V6K4n+Sk6WxUlev1cInze2xS3DVqETXdK0n4hQrTuBwVMjroXf8I329Yi1qevbPTGH7NhR/dhAPWOQfuVWCWh3auRdBLF3vwupqFZXJPfmo4XFA+nL0o/DHgmhPK6Ur0aKQowrCYOyvSEu9krK1AtPfK8k=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-17T09:38:06.809Z", + "default_api_key_id": "xihum3QBy3Gd1kyfaiLd", + "last_checkin": "2020-09-17T09:46:00.818Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "WMptm3QB3yUjiqk7n7R7", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::4de:9ad6:320f:79f5/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.116/22", + "fdbb:cb5c:fb4:68:3d24:2d96:b634:3165/64", + "fdbb:cb5c:fb4:68:6896:8ae7:78be:f9d0/64", + "fdbb:cb5c:fb4:68:78d9:60b3:632c:c908/64", + "fdbb:cb5c:fb4:68:f1a6:a826:7f63:6b77/64", + "fdbb:cb5c:fb4:68:997d:b66b:2555:6bb7/64", + "fdbb:cb5c:fb4:68:4c21:58bc:9122:1adf/64", + "fdbb:cb5c:fb4:68:d536:51f3:fcbe:7f3/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "mac": [ + "00:50:56:b1:7e:49" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.0", + "id": "f490a4f2-5022-4a15-b447-86204ebbdd35" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-17T09:46:00.946Z", + "fleet-agents": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "default_api_key": "vAM73FEHbqUVOnjN4FY0jdI0EtE+7MLqN5rNj4n6TJRz1Shx3rhNnDi6S555R9pGR2ZLtN57j9Z+5sO4nT9DYW3h8RK4Z15ifgnEvNVFR1Lsp7S/GUbPM6ooQMgNo63eoU8plYSfh1/98Cbe4dOwe+FhB5tbwGK1GqWb2TOshzH01JUbvfKb5jc=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-17T09:37:19.476Z", + "default_api_key_id": "LChtm3QBy3Gd1kyfGhoG", + "last_checkin": "2020-09-17T09:46:00.818Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "xspsm3QB3yUjiqk75rL_", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "DESKTOP-QBBSCUT", + "ip": [ + "fdbb:cb5c:fb4:68:6ca6:5ea3:ae36:af51/64", + "fdbb:cb5c:fb4:68:2c1b:d329:2b41:de28/128", + "fdbb:cb5c:fb4:68:6d89:d2f8:2825:d739/128", + "fdbb:cb5c:fb4:68:a484:d8c6:87c:c2ee/128", + "fdbb:cb5c:fb4:68:b03f:b84f:8b8a:77b9/128", + "fdbb:cb5c:fb4:68:b42f:f72:b857:40e9/128", + "fdbb:cb5c:fb4:68:e9b5:f8c7:f06d:6fe1/128", + "fdbb:cb5c:fb4:68:f05b:8555:cc24:dfa4/128", + "fe80::6ca6:5ea3:ae36:af51/64", + "10.0.7.235/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "DESKTOP-QBBSCUT", + "mac": [ + "00:50:56:b1:65:cb" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.0", + "id": "58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/agent_only/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/agent_only/mappings.json new file mode 100644 index 00000000000000..507611b8b4b575 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/agent_only/mappings.json @@ -0,0 +1,2587 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "43b8830d5d0df85a6823d290885fc9fd", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", + "exception-list": "497afa2f881a675d72d58e20057f3d8b", + "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", + "fleet-agents": "034346488514b7058a79140b19ddf631", + "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", + "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749", + "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "5c4b9a6effceb17ae8a0ab22d0c49767", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "inventoryDefaultView": { + "type": "keyword" + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_configs": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_url": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "source": { + "type": "keyword" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_disabled/data.json b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_disabled/data.json new file mode 100644 index 00000000000000..ce140e66d14418 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_disabled/data.json @@ -0,0 +1,272 @@ +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:8a0a4a79-c1e3-4d13-951a-36c168b4be87", + "source": { + "references": [], + "type": "fleet-agents", + "updated_at": "2020-09-17T15:04:31.916Z", + "fleet-agents": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "default_api_key": "/XDWfZFwXdENtpxpo74Rl5fjAFX0mL8cwrPmOXXPtnri/QW6591rkVO7wcsZe8zN4A4Ld4SXBw1EpBMQILwtL2jVBj/t34qjXJDcQFkAf6CPmO1YP3ilC/L49tGQX0J3MTLVoSmwo/N9ni8v/ywdl8IkhB4dqTp6me/SrL+I31D6vUFxYupWdlg=", + "current_error_events": "[]", + "config_revision": 3, + "enrolled_at": "2020-09-17T09:37:50.171Z", + "last_checkin": "2020-09-17T15:04:31.915Z", + "default_api_key_id": "oShtm3QBy3Gd1kyfzhpC", + "access_api_key_id": "_8ptm3QB3yUjiqk7XrOs", + "user_provided_metadata": {}, + "active": true, + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.7.158/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:371f/64", + "fe80::250:56ff:feb1:371f/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "mac": [ + "00:50:56:b1:37:1f" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-38-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.0", + "id": "8a0a4a79-c1e3-4d13-951a-36c168b4be87" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5", + "source": { + "references": [], + "type": "fleet-agents", + "updated_at": "2020-09-17T15:04:31.916Z", + "fleet-agents": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "default_api_key": "vAM73FEHbqUVOnjN4FY0jdI0EtE+7MLqN5rNj4n6TJRz1Shx3rhNnDi6S555R9pGR2ZLtN57j9Z+5sO4nT9DYW3h8RK4Z15ifgnEvNVFR1Lsp7S/GUbPM6ooQMgNo63eoU8plYSfh1/98Cbe4dOwe+FhB5tbwGK1GqWb2TOshzH01JUbvfKb5jc=", + "current_error_events": "[]", + "config_revision": 3, + "enrolled_at": "2020-09-17T09:37:19.476Z", + "last_checkin": "2020-09-17T15:04:31.915Z", + "default_api_key_id": "LChtm3QBy3Gd1kyfGhoG", + "access_api_key_id": "xspsm3QB3yUjiqk75rL_", + "user_provided_metadata": {}, + "active": true, + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "DESKTOP-QBBSCUT", + "ip": [ + "fdbb:cb5c:fb4:68:6ca6:5ea3:ae36:af51/64", + "fdbb:cb5c:fb4:68:2c1b:d329:2b41:de28/128", + "fdbb:cb5c:fb4:68:6d89:d2f8:2825:d739/128", + "fdbb:cb5c:fb4:68:a484:d8c6:87c:c2ee/128", + "fdbb:cb5c:fb4:68:b03f:b84f:8b8a:77b9/128", + "fdbb:cb5c:fb4:68:b42f:f72:b857:40e9/128", + "fdbb:cb5c:fb4:68:e9b5:f8c7:f06d:6fe1/128", + "fdbb:cb5c:fb4:68:f05b:8555:cc24:dfa4/128", + "fe80::6ca6:5ea3:ae36:af51/64", + "10.0.7.235/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "DESKTOP-QBBSCUT", + "mac": [ + "00:50:56:b1:65:cb" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.0", + "id": "58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:f490a4f2-5022-4a15-b447-86204ebbdd35", + "source": { + "references": [], + "type": "fleet-agents", + "updated_at": "2020-09-17T15:04:31.916Z", + "fleet-agents": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "default_api_key": "S3lDCj6T1USDsJUQfe/W77/D0sDl6V6K4n+Sk6WxUlev1cInze2xS3DVqETXdK0n4hQrTuBwVMjroXf8I329Yi1qevbPTGH7NhR/dhAPWOQfuVWCWh3auRdBLF3vwupqFZXJPfmo4XFA+nL0o/DHgmhPK6Ur0aKQowrCYOyvSEu9krK1AtPfK8k=", + "current_error_events": "[]", + "config_revision": 3, + "enrolled_at": "2020-09-17T09:38:06.809Z", + "last_checkin": "2020-09-17T15:04:31.915Z", + "default_api_key_id": "xihum3QBy3Gd1kyfaiLd", + "access_api_key_id": "WMptm3QB3yUjiqk7n7R7", + "user_provided_metadata": {}, + "active": true, + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::4de:9ad6:320f:79f5/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.116/22", + "fdbb:cb5c:fb4:68:3d24:2d96:b634:3165/64", + "fdbb:cb5c:fb4:68:6896:8ae7:78be:f9d0/64", + "fdbb:cb5c:fb4:68:78d9:60b3:632c:c908/64", + "fdbb:cb5c:fb4:68:f1a6:a826:7f63:6b77/64", + "fdbb:cb5c:fb4:68:997d:b66b:2555:6bb7/64", + "fdbb:cb5c:fb4:68:4c21:58bc:9122:1adf/64", + "fdbb:cb5c:fb4:68:d536:51f3:fcbe:7f3/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "mac": [ + "00:50:56:b1:7e:49" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.0", + "id": "f490a4f2-5022-4a15-b447-86204ebbdd35" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:78858140-f8f6-11ea-b3d9-2d2b39f3a393", + "source": { + "fleet-agent-events": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "timestamp": "2020-09-17T10:59:18.7281236-04:00", + "subtype": "RUNNING", + "agent_id": "58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5", + "message": "Application: endpoint-security--7.9.0[58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-17T14:59:18.26119700Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"off\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"off\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[3617,34,0,0,1,2,1,1,4,1,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":0.156187721707846,\"mean\":0.277454799155371}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":96870400,\"mean\":87327732}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0.00545851528384279},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0.256550218340611},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0.0709645723019816},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0.0218364450267496},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0.0163782278757438},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0.0163782278757438},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":18317,\"system\":660526}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware off configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"6a546aade5563d3e8dffc1fe2d93d33edda8f9ca3e17ac3cc9ac707620cb9ecd\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"04f9f87accc5d5aea433427bd1bd4ec6908f8528c78ceed26f70df7875a99385\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"1471838597fcd79a54ea4a3ec9a9beee1a86feaedab6c98e61102559ced822a8\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"ea57f15d-71a2-4b5e-8101-7155d9cdd4a2\",\"version\":\"7.9.0\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"c85e6c40-d4a1-db21-7458-2565a6b857f3\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "references": [], + "updated_at": "2020-09-17T15:00:05.588Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:9a206900-f8f6-11ea-b3d9-2d2b39f3a393", + "source": { + "fleet-agent-events": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "timestamp": "2020-09-17T11:00:52.879069-04:00", + "subtype": "RUNNING", + "agent_id": "f490a4f2-5022-4a15-b447-86204ebbdd35", + "message": "Application: endpoint-security--7.9.0[f490a4f2-5022-4a15-b447-86204ebbdd35]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-17T15:00:52.870465905Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"off\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"off\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[3571,29,4,2,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":0.76328957,\"mean\":0.580389267551738}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":58580992,\"mean\":62785576}}},\"threads\":[{\"cpu\":{\"mean\":0.00543212559074366},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0.00543212559074366},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0.0217285023629746},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0.103210386224129},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0.211852898039003},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0.016296376772231},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0.217344055640078},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0.0326016083460117},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0.0271709596782958},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0.0271709596782958},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0.0271709596782958},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0.0271709596782958},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0.0271709596782958},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0.0979538528515455},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0.0326530612244898},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.0166527893422148},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0.133222314737719},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":18409,\"system\":3198488}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware off configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"2bd3e07d-b528-4cd7-8ca6-2af2236ca446\",\"version\":\"7.9.0\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"ec5403f8-6708-0d58-7aff-b2137b48b816\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "references": [], + "updated_at": "2020-09-17T15:01:01.968Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:5619b4f0-f8f6-11ea-b3d9-2d2b39f3a393", + "source": { + "fleet-agent-events": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "timestamp": "2020-09-17T10:58:59.660363289-04:00", + "subtype": "RUNNING", + "agent_id": "8a0a4a79-c1e3-4d13-951a-36c168b4be87", + "message": "Application: endpoint-security--7.9.0[8a0a4a79-c1e3-4d13-951a-36c168b4be87]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-17T14:58:59.634499439Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"off\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"off\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[3655,2,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":0.2,\"mean\":0.711852054295345}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":57106432,\"mean\":55650691}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0.0109289617486339},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0.00546448087431694},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0.60207991242474},\"name\":\"EventsLoopThread\"},{\"cpu\":{\"mean\":0},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":18300,\"system\":4390784}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"145f6503-0c88-484d-a00a-40c68e3cc796\",\"version\":\"7.9.0\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"e9909692-0e35-fd30-e3a3-e2e7253bb5c7\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "references": [], + "updated_at": "2020-09-17T14:59:07.839Z", + "type": "fleet-agent-events" + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_disabled/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_disabled/mappings.json new file mode 100644 index 00000000000000..507611b8b4b575 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_disabled/mappings.json @@ -0,0 +1,2587 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "43b8830d5d0df85a6823d290885fc9fd", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", + "exception-list": "497afa2f881a675d72d58e20057f3d8b", + "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", + "fleet-agents": "034346488514b7058a79140b19ddf631", + "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", + "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749", + "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "5c4b9a6effceb17ae8a0ab22d0c49767", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "inventoryDefaultView": { + "type": "keyword" + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_configs": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_url": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "source": { + "type": "keyword" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_enabled/data.json b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_enabled/data.json new file mode 100644 index 00000000000000..752877a6014d1f --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_enabled/data.json @@ -0,0 +1,272 @@ +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:d26f4ae1-f8cb-11ea-b3d9-2d2b39f3a393", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "timestamp": "2020-09-17T05:54:12.675688-04:00", + "subtype": "RUNNING", + "agent_id": "58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5", + "message": "Application: endpoint-security--7.9.0[58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5]: State changed to RUNNING: Protecting with policy {a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393}", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-17T09:54:12.73687100Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[1,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":0.156156153275005,\"mean\":30.7709502014208}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":117456896,\"mean\":87985152}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"name\":\"checkinAPIThread\"},{\"name\":\"actionsAPIThread\"},{\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":23,\"system\":642232}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfuly started sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"6a546aade5563d3e8dffc1fe2d93d33edda8f9ca3e17ac3cc9ac707620cb9ecd\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"04f9f87accc5d5aea433427bd1bd4ec6908f8528c78ceed26f70df7875a99385\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"1471838597fcd79a54ea4a3ec9a9beee1a86feaedab6c98e61102559ced822a8\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"ea57f15d-71a2-4b5e-8101-7155d9cdd4a2\",\"version\":\"7.9.0\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"c85e6c40-d4a1-db21-7458-2565a6b857f3\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-17T09:54:48.078Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:d5ec3ca0-f8cb-11ea-b3d9-2d2b39f3a393", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "timestamp": "2020-09-17T05:54:08.471216392-04:00", + "subtype": "RUNNING", + "agent_id": "8a0a4a79-c1e3-4d13-951a-36c168b4be87", + "message": "Application: endpoint-security--7.9.0[8a0a4a79-c1e3-4d13-951a-36c168b4be87]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-17T09:54:08.470069104Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"00000000-0000-0000-0000-000000000000\",\"policy\":{\"linux\":{\"events\":{},\"logging\":{\"file\":\"info\",\"stdout\":\"info\"}},\"mac\":{\"events\":{},\"logging\":{\"file\":\"info\",\"stdout\":\"info\"},\"malware\":{}},\"windows\":{\"events\":{},\"logging\":{\"file\":\"info\",\"stdout\":\"info\"},\"malware\":{}}}}]},\"metrics\":{\"cpu\":{},\"memory\":{},\"uptime\":{}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success disabling process events; current state is disabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success disabling network events; current state is disabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success disabling file events; current state is disabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success disabling file events; current state is disabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success disabling network events; current state is disabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success disabling process events; current state is disabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"00000000-0000-0000-0000-000000000000\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"145f6503-0c88-484d-a00a-40c68e3cc796\",\"version\":\"7.9.0\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"e9909692-0e35-fd30-e3a3-e2e7253bb5c7\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-17T09:54:53.930Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:ba82e9e0-f8cc-11ea-b3d9-2d2b39f3a393", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "timestamp": "2020-09-17T06:00:29.153626-04:00", + "subtype": "RUNNING", + "agent_id": "f490a4f2-5022-4a15-b447-86204ebbdd35", + "message": "Application: endpoint-security--7.9.0[f490a4f2-5022-4a15-b447-86204ebbdd35]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-17T10:00:29.144191298Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[5,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":0.25202279,\"mean\":2.207890305}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":39948288,\"mean\":37541888}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"name\":\"checkinAPIThread\"},{\"name\":\"actionsAPIThread\"},{\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":34,\"system\":3180112}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"2bd3e07d-b528-4cd7-8ca6-2af2236ca446\",\"version\":\"7.9.0\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"ec5403f8-6708-0d58-7aff-b2137b48b816\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-17T10:01:17.438Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:f490a4f2-5022-4a15-b447-86204ebbdd35", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-17T10:42:01.472Z", + "fleet-agents": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "default_api_key": "S3lDCj6T1USDsJUQfe/W77/D0sDl6V6K4n+Sk6WxUlev1cInze2xS3DVqETXdK0n4hQrTuBwVMjroXf8I329Yi1qevbPTGH7NhR/dhAPWOQfuVWCWh3auRdBLF3vwupqFZXJPfmo4XFA+nL0o/DHgmhPK6Ur0aKQowrCYOyvSEu9krK1AtPfK8k=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-17T09:38:06.809Z", + "default_api_key_id": "xihum3QBy3Gd1kyfaiLd", + "last_checkin": "2020-09-17T10:42:01.471Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "WMptm3QB3yUjiqk7n7R7", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::4de:9ad6:320f:79f5/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.116/22", + "fdbb:cb5c:fb4:68:3d24:2d96:b634:3165/64", + "fdbb:cb5c:fb4:68:6896:8ae7:78be:f9d0/64", + "fdbb:cb5c:fb4:68:78d9:60b3:632c:c908/64", + "fdbb:cb5c:fb4:68:f1a6:a826:7f63:6b77/64", + "fdbb:cb5c:fb4:68:997d:b66b:2555:6bb7/64", + "fdbb:cb5c:fb4:68:4c21:58bc:9122:1adf/64", + "fdbb:cb5c:fb4:68:d536:51f3:fcbe:7f3/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "mac": [ + "00:50:56:b1:7e:49" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.0", + "id": "f490a4f2-5022-4a15-b447-86204ebbdd35" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:8a0a4a79-c1e3-4d13-951a-36c168b4be87", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-17T10:42:01.472Z", + "fleet-agents": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "default_api_key": "/XDWfZFwXdENtpxpo74Rl5fjAFX0mL8cwrPmOXXPtnri/QW6591rkVO7wcsZe8zN4A4Ld4SXBw1EpBMQILwtL2jVBj/t34qjXJDcQFkAf6CPmO1YP3ilC/L49tGQX0J3MTLVoSmwo/N9ni8v/ywdl8IkhB4dqTp6me/SrL+I31D6vUFxYupWdlg=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-17T09:37:50.171Z", + "default_api_key_id": "oShtm3QBy3Gd1kyfzhpC", + "last_checkin": "2020-09-17T10:42:01.471Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "_8ptm3QB3yUjiqk7XrOs", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.7.158/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:371f/64", + "fe80::250:56ff:feb1:371f/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "mac": [ + "00:50:56:b1:37:1f" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-38-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.0", + "id": "8a0a4a79-c1e3-4d13-951a-36c168b4be87" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-17T10:42:28.549Z", + "fleet-agents": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "default_api_key": "vAM73FEHbqUVOnjN4FY0jdI0EtE+7MLqN5rNj4n6TJRz1Shx3rhNnDi6S555R9pGR2ZLtN57j9Z+5sO4nT9DYW3h8RK4Z15ifgnEvNVFR1Lsp7S/GUbPM6ooQMgNo63eoU8plYSfh1/98Cbe4dOwe+FhB5tbwGK1GqWb2TOshzH01JUbvfKb5jc=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-17T09:37:19.476Z", + "default_api_key_id": "LChtm3QBy3Gd1kyfGhoG", + "last_checkin": "2020-09-17T10:42:01.471Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "xspsm3QB3yUjiqk75rL_", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "DESKTOP-QBBSCUT", + "ip": [ + "fdbb:cb5c:fb4:68:6ca6:5ea3:ae36:af51/64", + "fdbb:cb5c:fb4:68:2c1b:d329:2b41:de28/128", + "fdbb:cb5c:fb4:68:6d89:d2f8:2825:d739/128", + "fdbb:cb5c:fb4:68:a484:d8c6:87c:c2ee/128", + "fdbb:cb5c:fb4:68:b03f:b84f:8b8a:77b9/128", + "fdbb:cb5c:fb4:68:b42f:f72:b857:40e9/128", + "fdbb:cb5c:fb4:68:e9b5:f8c7:f06d:6fe1/128", + "fdbb:cb5c:fb4:68:f05b:8555:cc24:dfa4/128", + "fe80::6ca6:5ea3:ae36:af51/64", + "10.0.7.235/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "DESKTOP-QBBSCUT", + "mac": [ + "00:50:56:b1:65:cb" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.0", + "id": "58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_enabled/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_enabled/mappings.json new file mode 100644 index 00000000000000..507611b8b4b575 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_malware_enabled/mappings.json @@ -0,0 +1,2587 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "43b8830d5d0df85a6823d290885fc9fd", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", + "exception-list": "497afa2f881a675d72d58e20057f3d8b", + "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", + "fleet-agents": "034346488514b7058a79140b19ddf631", + "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", + "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749", + "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "5c4b9a6effceb17ae8a0ab22d0c49767", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "inventoryDefaultView": { + "type": "keyword" + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_configs": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_url": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "source": { + "type": "keyword" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_uninstalled/data.json b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_uninstalled/data.json new file mode 100644 index 00000000000000..4f903d3bb52ce5 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_uninstalled/data.json @@ -0,0 +1,269 @@ +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:f490a4f2-5022-4a15-b447-86204ebbdd35", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-17T16:50:40.506Z", + "fleet-agents": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "default_api_key": "S3lDCj6T1USDsJUQfe/W77/D0sDl6V6K4n+Sk6WxUlev1cInze2xS3DVqETXdK0n4hQrTuBwVMjroXf8I329Yi1qevbPTGH7NhR/dhAPWOQfuVWCWh3auRdBLF3vwupqFZXJPfmo4XFA+nL0o/DHgmhPK6Ur0aKQowrCYOyvSEu9krK1AtPfK8k=", + "current_error_events": "[]", + "config_revision": 4, + "enrolled_at": "2020-09-17T09:38:06.809Z", + "default_api_key_id": "xihum3QBy3Gd1kyfaiLd", + "last_checkin": "2020-09-17T16:50:32.344Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "WMptm3QB3yUjiqk7n7R7", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::4de:9ad6:320f:79f5/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.116/22", + "fdbb:cb5c:fb4:68:3d24:2d96:b634:3165/64", + "fdbb:cb5c:fb4:68:6896:8ae7:78be:f9d0/64", + "fdbb:cb5c:fb4:68:78d9:60b3:632c:c908/64", + "fdbb:cb5c:fb4:68:f1a6:a826:7f63:6b77/64", + "fdbb:cb5c:fb4:68:997d:b66b:2555:6bb7/64", + "fdbb:cb5c:fb4:68:4c21:58bc:9122:1adf/64", + "fdbb:cb5c:fb4:68:d536:51f3:fcbe:7f3/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "mac": [ + "00:50:56:b1:7e:49" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.0", + "id": "f490a4f2-5022-4a15-b447-86204ebbdd35" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-17T16:50:33.463Z", + "fleet-agents": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "default_api_key": "vAM73FEHbqUVOnjN4FY0jdI0EtE+7MLqN5rNj4n6TJRz1Shx3rhNnDi6S555R9pGR2ZLtN57j9Z+5sO4nT9DYW3h8RK4Z15ifgnEvNVFR1Lsp7S/GUbPM6ooQMgNo63eoU8plYSfh1/98Cbe4dOwe+FhB5tbwGK1GqWb2TOshzH01JUbvfKb5jc=", + "current_error_events": "[]", + "config_revision": 4, + "enrolled_at": "2020-09-17T09:37:19.476Z", + "default_api_key_id": "LChtm3QBy3Gd1kyfGhoG", + "last_checkin": "2020-09-17T16:50:32.344Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "xspsm3QB3yUjiqk75rL_", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "DESKTOP-QBBSCUT", + "ip": [ + "fdbb:cb5c:fb4:68:6ca6:5ea3:ae36:af51/64", + "fdbb:cb5c:fb4:68:2c1b:d329:2b41:de28/128", + "fdbb:cb5c:fb4:68:6d89:d2f8:2825:d739/128", + "fdbb:cb5c:fb4:68:a484:d8c6:87c:c2ee/128", + "fdbb:cb5c:fb4:68:b03f:b84f:8b8a:77b9/128", + "fdbb:cb5c:fb4:68:b42f:f72:b857:40e9/128", + "fdbb:cb5c:fb4:68:e9b5:f8c7:f06d:6fe1/128", + "fdbb:cb5c:fb4:68:f05b:8555:cc24:dfa4/128", + "fe80::6ca6:5ea3:ae36:af51/64", + "10.0.7.235/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "DESKTOP-QBBSCUT", + "mac": [ + "00:50:56:b1:65:cb" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.0", + "id": "58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:8a0a4a79-c1e3-4d13-951a-36c168b4be87", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-17T16:50:32.345Z", + "fleet-agents": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "default_api_key": "/XDWfZFwXdENtpxpo74Rl5fjAFX0mL8cwrPmOXXPtnri/QW6591rkVO7wcsZe8zN4A4Ld4SXBw1EpBMQILwtL2jVBj/t34qjXJDcQFkAf6CPmO1YP3ilC/L49tGQX0J3MTLVoSmwo/N9ni8v/ywdl8IkhB4dqTp6me/SrL+I31D6vUFxYupWdlg=", + "current_error_events": "[]", + "config_revision": 4, + "enrolled_at": "2020-09-17T09:37:50.171Z", + "default_api_key_id": "oShtm3QBy3Gd1kyfzhpC", + "last_checkin": "2020-09-17T16:50:32.344Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "_8ptm3QB3yUjiqk7XrOs", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.7.158/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:371f/64", + "fe80::250:56ff:feb1:371f/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "mac": [ + "00:50:56:b1:37:1f" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-38-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.0", + "id": "8a0a4a79-c1e3-4d13-951a-36c168b4be87" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:d26db7e1-f905-11ea-b3d9-2d2b39f3a393", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "timestamp": "2020-09-17T12:49:38.703352816-04:00", + "subtype": "RUNNING", + "agent_id": "8a0a4a79-c1e3-4d13-951a-36c168b4be87", + "message": "Application: endpoint-security--7.9.0[8a0a4a79-c1e3-4d13-951a-36c168b4be87]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-17T14:58:59.634499439Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"off\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"off\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[3655,2,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":0.2,\"mean\":0.711852054295345}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":57106432,\"mean\":55650691}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0.0109289617486339},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0.00546448087431694},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0.60207991242474},\"name\":\"EventsLoopThread\"},{\"cpu\":{\"mean\":0},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":18300,\"system\":4390784}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"145f6503-0c88-484d-a00a-40c68e3cc796\",\"version\":\"7.9.0\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"e9909692-0e35-fd30-e3a3-e2e7253bb5c7\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-17T16:49:58.878Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:cf5ddfd1-f905-11ea-b3d9-2d2b39f3a393", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "timestamp": "2020-09-17T12:49:09.3059615-04:00", + "subtype": "RUNNING", + "agent_id": "58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5", + "message": "Application: endpoint-security--7.9.0[58bed8d4-fba1-40fa-a7b0-1ceec2fb89e5]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-17T14:59:18.26119700Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"off\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"off\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[3617,34,0,0,1,2,1,1,4,1,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":0.156187721707846,\"mean\":0.277454799155371}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":96870400,\"mean\":87327732}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0.00545851528384279},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0.256550218340611},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0.0709645723019816},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0.0218364450267496},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0.0163782278757438},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0.0163782278757438},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":18317,\"system\":660526}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware off configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"6a546aade5563d3e8dffc1fe2d93d33edda8f9ca3e17ac3cc9ac707620cb9ecd\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"04f9f87accc5d5aea433427bd1bd4ec6908f8528c78ceed26f70df7875a99385\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"1471838597fcd79a54ea4a3ec9a9beee1a86feaedab6c98e61102559ced822a8\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"ea57f15d-71a2-4b5e-8101-7155d9cdd4a2\",\"version\":\"7.9.0\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"c85e6c40-d4a1-db21-7458-2565a6b857f3\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-17T16:49:53.741Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:9a206900-f8f6-11ea-b3d9-2d2b39f3a393", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "25129070-f8c9-11ea-b3d9-2d2b39f3a393", + "timestamp": "2020-09-17T11:00:52.879069-04:00", + "subtype": "RUNNING", + "agent_id": "f490a4f2-5022-4a15-b447-86204ebbdd35", + "message": "Application: endpoint-security--7.9.0[f490a4f2-5022-4a15-b447-86204ebbdd35]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-17T15:00:52.870465905Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"off\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"off\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[3571,29,4,2,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":0.76328957,\"mean\":0.580389267551738}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":58580992,\"mean\":62785576}}},\"threads\":[{\"cpu\":{\"mean\":0.00543212559074366},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0.00543212559074366},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0.0217285023629746},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0.103210386224129},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0.211852898039003},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0.016296376772231},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0.217344055640078},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0.0326016083460117},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0.0271709596782958},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0.0271709596782958},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0.0271709596782958},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0.0271709596782958},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0.0271709596782958},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0.0979538528515455},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0.0326530612244898},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.0166527893422148},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0.133222314737719},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":18409,\"system\":3198488}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware off configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"a7343ed0-f8cb-11ea-b3d9-2d2b39f3a393\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"2bd3e07d-b528-4cd7-8ca6-2af2236ca446\",\"version\":\"7.9.0\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"ec5403f8-6708-0d58-7aff-b2137b48b816\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-17T15:01:01.968Z", + "type": "fleet-agent-events" + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_uninstalled/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_uninstalled/mappings.json new file mode 100644 index 00000000000000..507611b8b4b575 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/endpoint_uninstalled/mappings.json @@ -0,0 +1,2587 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "43b8830d5d0df85a6823d290885fc9fd", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", + "exception-list": "497afa2f881a675d72d58e20057f3d8b", + "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", + "fleet-agents": "034346488514b7058a79140b19ddf631", + "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", + "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749", + "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "5c4b9a6effceb17ae8a0ab22d0c49767", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "inventoryDefaultView": { + "type": "keyword" + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_configs": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_url": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "source": { + "type": "keyword" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/invalid_scripted_field/data.json.gz b/x-pack/test/functional/es_archives/invalid_scripted_field/data.json.gz new file mode 100644 index 00000000000000..380dd6049179a5 Binary files /dev/null and b/x-pack/test/functional/es_archives/invalid_scripted_field/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/invalid_scripted_field/mappings.json b/x-pack/test/functional/es_archives/invalid_scripted_field/mappings.json new file mode 100644 index 00000000000000..0024c6943ed1c0 --- /dev/null +++ b/x-pack/test/functional/es_archives/invalid_scripted_field/mappings.json @@ -0,0 +1,250 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "mappings": { + "dynamic": "strict", + "properties": { + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "defaultIndex": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/services/transform/edit_flyout.ts b/x-pack/test/functional/services/transform/edit_flyout.ts index f9504deb39f6a5..2ba35bd35d2723 100644 --- a/x-pack/test/functional/services/transform/edit_flyout.ts +++ b/x-pack/test/functional/services/transform/edit_flyout.ts @@ -35,6 +35,12 @@ export function TransformEditFlyoutProvider({ getService }: FtrProviderContext) ); }, + // for now we expect this to be used only for opening the accordion + async openTransformEditAccordionAdvancedSettings() { + await testSubjects.click('transformEditAccordionAdvancedSettings'); + await testSubjects.existOrFail('transformEditAccordionAdvancedSettingsContent'); + }, + async setTransformEditFlyoutInputValue(input: string, value: string) { await testSubjects.setValue(`transformEditFlyout${input}Input`, value, { clearWithKeyboard: true, diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts index 86e355988da0b0..151c8376402282 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts @@ -17,6 +17,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const testSubjects = getService('testSubjects'); const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']); const find = getService('find'); + const retry = getService('retry'); + const comboBox = getService('comboBox'); describe('Connectors', function () { before(async () => { @@ -76,7 +78,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.setValue('slackWebhookUrlInput', 'https://test'); - await find.clickByCssSelector('[data-test-subj="saveEditedActionButton"]:not(disabled)'); + await find.clickByCssSelector( + '[data-test-subj="saveAndCloseEditedActionButton"]:not(disabled)' + ); const toastTitle = await pageObjects.common.closeToast(); expect(toastTitle).to.eql(`Updated '${updatedConnectorName}'`); @@ -92,6 +96,64 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ]); }); + it('should test a connector and display a successful result', async () => { + const connectorName = generateUniqueKey(); + const indexName = generateUniqueKey(); + await createIndexConnector(connectorName, indexName); + + await pageObjects.triggersActionsUI.searchConnectors(connectorName); + + const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResultsBeforeEdit.length).to.eql(1); + + await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button'); + + await find.clickByCssSelector('[data-test-subj="testConnectorTab"]'); + + // test success + await testSubjects.setValue('documentsJsonEditor', '{ "key": "value" }'); + + await find.clickByCssSelector('[data-test-subj="executeActionButton"]:not(disabled)'); + + await retry.try(async () => { + await testSubjects.find('executionSuccessfulResult'); + }); + + await find.clickByCssSelector( + '[data-test-subj="cancelSaveEditedConnectorButton"]:not(disabled)' + ); + }); + + it('should test a connector and display a failure result', async () => { + const connectorName = generateUniqueKey(); + const indexName = generateUniqueKey(); + await createIndexConnector(connectorName, indexName); + + await pageObjects.triggersActionsUI.searchConnectors(connectorName); + + const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResultsBeforeEdit.length).to.eql(1); + + await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button'); + + await find.clickByCssSelector('[data-test-subj="testConnectorTab"]'); + + await testSubjects.setValue('documentsJsonEditor', '{ "": "value" }'); + + await find.clickByCssSelector('[data-test-subj="executeActionButton"]:not(disabled)'); + + await retry.try(async () => { + const executionFailureResultCallout = await testSubjects.find('executionFailureResult'); + expect(await executionFailureResultCallout.getVisibleText()).to.match( + /error indexing documents/ + ); + }); + + await find.clickByCssSelector( + '[data-test-subj="cancelSaveEditedConnectorButton"]:not(disabled)' + ); + }); + it('should reset connector when canceling an edit', async () => { const connectorName = generateUniqueKey(); await createConnector(connectorName); @@ -193,7 +255,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button'); expect(await testSubjects.exists('preconfiguredBadge')).to.be(true); - expect(await testSubjects.exists('saveEditedActionButton')).to.be(false); + expect(await testSubjects.exists('saveAndCloseEditedActionButton')).to.be(false); }); }); @@ -209,4 +271,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)'); await pageObjects.common.closeToast(); } + + async function createIndexConnector(connectorName: string, indexName: string) { + await pageObjects.triggersActionsUI.clickCreateConnectorButton(); + + await testSubjects.click('.index-card'); + + await testSubjects.setValue('nameInput', connectorName); + + await comboBox.set('connectorIndexesComboBox', indexName); + + await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)'); + await pageObjects.common.closeToast(); + } }; diff --git a/x-pack/test/ingest_manager_api_integration/apis/agent_policy/agent_policy.ts b/x-pack/test/ingest_manager_api_integration/apis/agent_policy/agent_policy.ts index 410b0fe0930024..71e2bbc4e4c0a0 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/agent_policy/agent_policy.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/agent_policy/agent_policy.ts @@ -82,7 +82,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should return a 500 with invalid source policy', async () => { + it('should return a 404 with invalid source policy', async () => { await supertest .post(`/api/ingest_manager/agent_policies/INVALID_POLICY_ID/copy`) .set('kbn-xsrf', 'xxxx') @@ -90,7 +90,7 @@ export default function ({ getService }: FtrProviderContext) { name: 'Copied policy', description: '', }) - .expect(500); + .expect(404); }); it('should return a 400 with invalid payload', async () => { diff --git a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_cases.ts b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_cases.ts index b32950538f8e53..190b12e038b276 100644 --- a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_cases.ts +++ b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_cases.ts @@ -4,30 +4,48 @@ * you may not use this file except in compliance with the Elastic License. */ -export const SAVED_OBJECT_TEST_CASES = Object.freeze({ +import { SPACES } from './spaces'; +import { TestCase } from './types'; + +const { + DEFAULT: { spaceId: DEFAULT_SPACE_ID }, + SPACE_1: { spaceId: SPACE_1_ID }, + SPACE_2: { spaceId: SPACE_2_ID }, +} = SPACES; +const EACH_SPACE = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID]; + +type CommonTestCase = Omit & { originId?: string }; + +export const SAVED_OBJECT_TEST_CASES: Record = Object.freeze({ SINGLE_NAMESPACE_DEFAULT_SPACE: Object.freeze({ type: 'isolatedtype', id: 'defaultspace-isolatedtype-id', + expectedNamespaces: [DEFAULT_SPACE_ID], }), SINGLE_NAMESPACE_SPACE_1: Object.freeze({ type: 'isolatedtype', id: 'space1-isolatedtype-id', + expectedNamespaces: [SPACE_1_ID], }), SINGLE_NAMESPACE_SPACE_2: Object.freeze({ type: 'isolatedtype', id: 'space2-isolatedtype-id', + expectedNamespaces: [SPACE_2_ID], }), MULTI_NAMESPACE_DEFAULT_AND_SPACE_1: Object.freeze({ type: 'sharedtype', id: 'default_and_space_1', + expectedNamespaces: [DEFAULT_SPACE_ID, SPACE_1_ID], }), MULTI_NAMESPACE_ONLY_SPACE_1: Object.freeze({ type: 'sharedtype', id: 'only_space_1', + expectedNamespaces: [SPACE_1_ID], }), MULTI_NAMESPACE_ONLY_SPACE_2: Object.freeze({ type: 'sharedtype', id: 'only_space_2', + expectedNamespaces: [SPACE_2_ID], }), NAMESPACE_AGNOSTIC: Object.freeze({ type: 'globaltype', @@ -38,3 +56,37 @@ export const SAVED_OBJECT_TEST_CASES = Object.freeze({ id: 'any', }), }); + +/** + * These objects exist in the test data for all saved object test suites, but they are only used to test various conflict scenarios. + */ +export const CONFLICT_TEST_CASES: Record = Object.freeze({ + CONFLICT_1_OBJ: Object.freeze({ + type: 'sharedtype', + id: 'conflict_1', + expectedNamespaces: EACH_SPACE, + }), + CONFLICT_2A_OBJ: Object.freeze({ + type: 'sharedtype', + id: 'conflict_2a', + originId: 'conflict_2', + expectedNamespaces: EACH_SPACE, + }), + CONFLICT_2B_OBJ: Object.freeze({ + type: 'sharedtype', + id: 'conflict_2b', + originId: 'conflict_2', + expectedNamespaces: EACH_SPACE, + }), + CONFLICT_3_OBJ: Object.freeze({ + type: 'sharedtype', + id: 'conflict_3', + expectedNamespaces: EACH_SPACE, + }), + CONFLICT_4A_OBJ: Object.freeze({ + type: 'sharedtype', + id: 'conflict_4a', + originId: 'conflict_4', + expectedNamespaces: EACH_SPACE, + }), +}); diff --git a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts index 595986c08efc1d..9d4b5e80e9c3db 100644 --- a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts +++ b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; -import { SAVED_OBJECT_TEST_CASES as CASES } from './saved_object_test_cases'; import { SPACES } from './spaces'; import { AUTHENTICATION } from './authentication'; import { TestCase, TestUser, ExpectResponseBody } from './types'; @@ -73,6 +72,28 @@ export const getTestTitle = ( return `${list.join(' and ')}`; }; +export const isUserAuthorizedAtSpace = (user: TestUser | undefined, namespace: string) => + !user || user.authorizedAtSpaces.includes('*') || user.authorizedAtSpaces.includes(namespace); + +export const getRedactedNamespaces = ( + user: TestUser | undefined, + namespaces: string[] | undefined +) => namespaces?.map((x) => (isUserAuthorizedAtSpace(user, x) ? x : '?')).sort(namespaceComparator); +function namespaceComparator(a: string, b: string) { + // namespaces get sorted so that they're all in alphabetical order, and unknown ones appear at the end + // this is to prevent information disclosure + if (a === '?' && b !== '?') { + return 1; + } else if (b === '?' && a !== '?') { + return -1; + } else if (a > b) { + return 1; + } else if (a < b) { + return -1; + } + return 0; +} + export const testCaseFailures = { // test suites need explicit return types for number primitives fail400: (condition?: boolean): { failure?: 400 } => @@ -150,7 +171,7 @@ export const expectResponses = { } }, /** - * Additional assertions that we use in `bulk_create` and `create` to ensure that + * Additional assertions that we use in `import` and `resolve_import_errors` to ensure that * newly-created (or overwritten) objects don't have unexpected properties */ successCreated: async (es: any, spaceId: string, type: string, id: string) => { @@ -161,26 +182,6 @@ export const expectResponses = { id: `${expectedSpacePrefix}${type}:${id}`, index: '.kibana', }); - const { namespace: actualNamespace, namespaces: actualNamespaces } = savedObject._source; - if (isNamespaceUndefined) { - expect(actualNamespace).to.eql(undefined); - } else { - expect(actualNamespace).to.eql(spaceId); - } - if (isMultiNamespace(type)) { - if (['conflict_1', 'conflict_2a', 'conflict_2b', 'conflict_3', 'conflict_4a'].includes(id)) { - expect(actualNamespaces).to.eql([DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID]); - } else if (id === CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1.id) { - expect(actualNamespaces).to.eql([DEFAULT_SPACE_ID, SPACE_1_ID]); - } else if (id === CASES.MULTI_NAMESPACE_ONLY_SPACE_1.id) { - expect(actualNamespaces).to.eql([SPACE_1_ID]); - } else if (id === CASES.MULTI_NAMESPACE_ONLY_SPACE_2.id) { - expect(actualNamespaces).to.eql([SPACE_2_ID]); - } else { - // newly created in this space - expect(actualNamespaces).to.eql([spaceId]); - } - } return savedObject; }, }; diff --git a/x-pack/test/saved_object_api_integration/common/lib/types.ts b/x-pack/test/saved_object_api_integration/common/lib/types.ts index 56e6a992b6b626..b52a84f352999b 100644 --- a/x-pack/test/saved_object_api_integration/common/lib/types.ts +++ b/x-pack/test/saved_object_api_integration/common/lib/types.ts @@ -21,6 +21,7 @@ export interface TestSuite { export interface TestCase { type: string; id: string; + expectedNamespaces?: string[]; failure?: 400 | 403 | 404 | 409; } diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts index e3163ef77d4279..b1608946b8e62f 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts @@ -14,8 +14,9 @@ import { expectResponses, getUrlPrefix, getTestTitle, + getRedactedNamespaces, } from '../lib/saved_object_test_utils'; -import { ExpectResponseBody, TestCase, TestDefinition, TestSuite } from '../lib/types'; +import { ExpectResponseBody, TestCase, TestDefinition, TestSuite, TestUser } from '../lib/types'; export interface BulkCreateTestDefinition extends TestDefinition { request: Array<{ type: string; id: string }>; @@ -33,7 +34,7 @@ const NEW_ATTRIBUTE_VAL = `New attribute value ${Date.now()}`; const NEW_SINGLE_NAMESPACE_OBJ = Object.freeze({ type: 'dashboard', id: 'new-dashboard-id' }); const NEW_MULTI_NAMESPACE_OBJ = Object.freeze({ type: 'sharedtype', id: 'new-sharedtype-id' }); const NEW_NAMESPACE_AGNOSTIC_OBJ = Object.freeze({ type: 'globaltype', id: 'new-globaltype-id' }); -export const TEST_CASES = Object.freeze({ +export const TEST_CASES: Record = Object.freeze({ ...CASES, NEW_SINGLE_NAMESPACE_OBJ, NEW_MULTI_NAMESPACE_OBJ, @@ -45,7 +46,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: const expectResponseBody = ( testCases: BulkCreateTestCase | BulkCreateTestCase[], statusCode: 200 | 403, - spaceId = SPACES.DEFAULT.spaceId + user?: TestUser ): ExpectResponseBody => async (response: Record) => { const testCaseArray = Array.isArray(testCases) ? testCases : [testCases]; if (statusCode === 403) { @@ -70,7 +71,8 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: await expectResponses.permitted(object, testCase); if (!testCase.failure) { expect(object.attributes[NEW_ATTRIBUTE_KEY]).to.eql(NEW_ATTRIBUTE_VAL); - await expectResponses.successCreated(es, spaceId, object.type, object.id); + const redactedNamespaces = getRedactedNamespaces(user, testCase.expectedNamespaces); + expect(object.namespaces).to.eql(redactedNamespaces); } } } @@ -81,6 +83,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: overwrite: boolean, options?: { spaceId?: string; + user?: TestUser; singleRequest?: boolean; responseBodyOverride?: ExpectResponseBody; } @@ -95,8 +98,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: request: [createRequest(x)], responseStatusCode, responseBody: - options?.responseBodyOverride || - expectResponseBody(x, responseStatusCode, options?.spaceId), + options?.responseBodyOverride || expectResponseBody(x, responseStatusCode, options?.user), overwrite, })); } @@ -108,7 +110,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: responseStatusCode, responseBody: options?.responseBodyOverride || - expectResponseBody(cases, responseStatusCode, options?.spaceId), + expectResponseBody(cases, responseStatusCode, options?.user), overwrite, }, ]; diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts index 8de54fe499c071..71ece1265347c5 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts @@ -25,7 +25,10 @@ export interface BulkGetTestCase extends TestCase { } const DOES_NOT_EXIST = Object.freeze({ type: 'dashboard', id: 'does-not-exist' }); -export const TEST_CASES = Object.freeze({ ...CASES, DOES_NOT_EXIST }); +export const TEST_CASES: Record = Object.freeze({ + ...CASES, + DOES_NOT_EXIST, +}); export function bulkGetTestSuiteFactory(esArchiver: any, supertest: SuperTest) { const expectForbidden = expectResponses.forbiddenTypes('bulk_get'); diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_update.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_update.ts index 2e3c55f029d297..c3020b2da32197 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_update.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_update.ts @@ -24,7 +24,10 @@ const NEW_ATTRIBUTE_KEY = 'title'; // all type mappings include this attribute, const NEW_ATTRIBUTE_VAL = `Updated attribute value ${Date.now()}`; const DOES_NOT_EXIST = Object.freeze({ type: 'dashboard', id: 'does-not-exist' }); -export const TEST_CASES = Object.freeze({ ...CASES, DOES_NOT_EXIST }); +export const TEST_CASES: Record = Object.freeze({ + ...CASES, + DOES_NOT_EXIST, +}); const createRequest = ({ type, id, namespace }: BulkUpdateTestCase) => ({ type, diff --git a/x-pack/test/saved_object_api_integration/common/suites/create.ts b/x-pack/test/saved_object_api_integration/common/suites/create.ts index 2a5ab696c4f53d..7e28d5ed9ed94f 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/create.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/create.ts @@ -13,8 +13,9 @@ import { expectResponses, getUrlPrefix, getTestTitle, + getRedactedNamespaces, } from '../lib/saved_object_test_utils'; -import { ExpectResponseBody, TestCase, TestDefinition, TestSuite } from '../lib/types'; +import { ExpectResponseBody, TestCase, TestDefinition, TestSuite, TestUser } from '../lib/types'; export interface CreateTestDefinition extends TestDefinition { request: { type: string; id: string }; @@ -33,7 +34,7 @@ const NEW_ATTRIBUTE_VAL = `New attribute value ${Date.now()}`; const NEW_SINGLE_NAMESPACE_OBJ = Object.freeze({ type: 'dashboard', id: '' }); const NEW_MULTI_NAMESPACE_OBJ = Object.freeze({ type: 'sharedtype', id: 'new-sharedtype-id' }); const NEW_NAMESPACE_AGNOSTIC_OBJ = Object.freeze({ type: 'globaltype', id: 'new-globaltype-id' }); -export const TEST_CASES = Object.freeze({ +export const TEST_CASES: Record = Object.freeze({ ...CASES, NEW_SINGLE_NAMESPACE_OBJ, NEW_MULTI_NAMESPACE_OBJ, @@ -44,7 +45,7 @@ export function createTestSuiteFactory(es: any, esArchiver: any, supertest: Supe const expectForbidden = expectResponses.forbiddenTypes('create'); const expectResponseBody = ( testCase: CreateTestCase, - spaceId = SPACES.DEFAULT.spaceId + user?: TestUser ): ExpectResponseBody => async (response: Record) => { if (testCase.failure === 403) { await expectForbidden(testCase.type)(response); @@ -54,7 +55,8 @@ export function createTestSuiteFactory(es: any, esArchiver: any, supertest: Supe await expectResponses.permitted(object, testCase); if (!testCase.failure) { expect(object.attributes[NEW_ATTRIBUTE_KEY]).to.eql(NEW_ATTRIBUTE_VAL); - await expectResponses.successCreated(es, spaceId, object.type, object.id); + const redactedNamespaces = getRedactedNamespaces(user, testCase.expectedNamespaces); + expect(object.namespaces).to.eql(redactedNamespaces); } } }; @@ -64,6 +66,7 @@ export function createTestSuiteFactory(es: any, esArchiver: any, supertest: Supe overwrite: boolean, options?: { spaceId?: string; + user?: TestUser; responseBodyOverride?: ExpectResponseBody; } ): CreateTestDefinition[] => { @@ -76,7 +79,7 @@ export function createTestSuiteFactory(es: any, esArchiver: any, supertest: Supe title: getTestTitle(x), responseStatusCode: x.failure ?? 200, request: createRequest(x), - responseBody: options?.responseBodyOverride || expectResponseBody(x, options?.spaceId), + responseBody: options?.responseBodyOverride || expectResponseBody(x, options?.user), overwrite, })); }; diff --git a/x-pack/test/saved_object_api_integration/common/suites/delete.ts b/x-pack/test/saved_object_api_integration/common/suites/delete.ts index 3179b1b0c9ac5d..228e7977f99ac0 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/delete.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/delete.ts @@ -25,7 +25,10 @@ export interface DeleteTestCase extends TestCase { } const DOES_NOT_EXIST = Object.freeze({ type: 'dashboard', id: 'does-not-exist' }); -export const TEST_CASES = Object.freeze({ ...CASES, DOES_NOT_EXIST }); +export const TEST_CASES: Record = Object.freeze({ + ...CASES, + DOES_NOT_EXIST, +}); export function deleteTestSuiteFactory(esArchiver: any, supertest: SuperTest) { const expectForbidden = expectResponses.forbiddenTypes('delete'); diff --git a/x-pack/test/saved_object_api_integration/common/suites/export.ts b/x-pack/test/saved_object_api_integration/common/suites/export.ts index 4a8eff1fb380c0..4eb967a952c604 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/export.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/export.ts @@ -30,7 +30,10 @@ export interface ExportTestCase { type: string; id?: string; successResult?: SuccessResult | SuccessResult[]; - failure?: 400 | 403; + failure?: { + statusCode: 200 | 400 | 403; // if the user searches for only types they are not authorized for, they will get an empty 200 result + reason: 'unauthorized' | 'bad_request'; + }; } // additional sharedtype objects that exist but do not have common test cases defined @@ -90,41 +93,45 @@ export const getTestCases = (spaceId?: string): { [key: string]: ExportTestCase type: 'globaltype', successResult: CASES.NAMESPACE_AGNOSTIC, }, - hiddenObject: { title: 'hidden object', ...CASES.HIDDEN, failure: 400 }, - hiddenType: { title: 'hidden type', type: 'hiddentype', failure: 400 }, + hiddenObject: { + title: 'hidden object', + ...CASES.HIDDEN, + failure: { statusCode: 400, reason: 'bad_request' }, + }, + hiddenType: { + title: 'hidden type', + type: 'hiddentype', + failure: { statusCode: 400, reason: 'bad_request' }, + }, }); export const createRequest = ({ type, id }: ExportTestCase) => id ? { objects: [{ type, id }] } : { type }; -const getTestTitle = ({ failure, title }: ExportTestCase) => { - let description = 'success'; - if (failure === 400) { - description = 'bad request'; - } else if (failure === 403) { - description = 'forbidden'; - } - return `${description} ["${title}"]`; -}; +const getTestTitle = ({ failure, title }: ExportTestCase) => + `${failure?.reason || 'success'} ["${title}"]`; + +const EMPTY_RESULT = { exportedCount: 0, missingRefCount: 0, missingReferences: [] }; export function exportTestSuiteFactory(esArchiver: any, supertest: SuperTest) { const expectForbiddenBulkGet = expectResponses.forbiddenTypes('bulk_get'); - const expectForbiddenFind = expectResponses.forbiddenTypes('find'); const expectResponseBody = (testCase: ExportTestCase): ExpectResponseBody => async ( response: Record ) => { const { type, id, successResult = { type, id } as SuccessResult, failure } = testCase; - if (failure === 403) { - // In export only, the API uses "bulk_get" or "find" depending on the parameters it receives. - // The best that could be done here is to have an if statement to ensure at least one of the - // two errors has been thrown. - if (id) { + if (failure?.reason === 'unauthorized') { + // In export only, the API uses "bulkGet" or "find" depending on the parameters it receives. + if (failure.statusCode === 403) { + // "bulkGet" was unauthorized, which returns a forbidden error await expectForbiddenBulkGet(type)(response); + } else if (failure.statusCode === 200) { + // "find" was unauthorized, which returns an empty result + expect(response.body).not.to.have.property('error'); + expect(response.text).to.equal(JSON.stringify(EMPTY_RESULT)); } else { - await expectForbiddenFind(type)(response); + throw new Error(`Unexpected failure status code: ${failure.statusCode}`); } - } else if (failure === 400) { - // 400 + } else if (failure?.reason === 'bad_request') { expect(response.body.error).to.eql('Bad Request'); - expect(response.body.statusCode).to.eql(failure); + expect(response.body.statusCode).to.eql(failure.statusCode); if (id) { expect(response.body.message).to.eql( `Trying to export object(s) with non-exportable types: ${type}:${id}` @@ -132,6 +139,8 @@ export function exportTestSuiteFactory(esArchiver: any, supertest: SuperTest { let cases = Array.isArray(testCases) ? testCases : [testCases]; - if (forbidden) { + if (failure) { // override the expected result in each test case - cases = cases.map((x) => ({ ...x, failure: 403 })); + cases = cases.map((x) => ({ ...x, failure })); } return cases.map((x) => ({ title: getTestTitle(x), - responseStatusCode: x.failure ?? 200, + responseStatusCode: x.failure?.statusCode ?? 200, request: createRequest(x), responseBody: options?.responseBodyOverride || expectResponseBody(x), })); diff --git a/x-pack/test/saved_object_api_integration/common/suites/find.ts b/x-pack/test/saved_object_api_integration/common/suites/find.ts index bab4a4d88534a8..381306f8101223 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/find.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/find.ts @@ -7,10 +7,13 @@ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; import querystring from 'querystring'; -import { Assign } from '@kbn/utility-types'; -import { SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases'; +import { SAVED_OBJECT_TEST_CASES, CONFLICT_TEST_CASES } from '../lib/saved_object_test_cases'; import { SPACES } from '../lib/spaces'; -import { expectResponses, getUrlPrefix } from '../lib/saved_object_test_utils'; +import { + getUrlPrefix, + isUserAuthorizedAtSpace, + getRedactedNamespaces, +} from '../lib/saved_object_test_utils'; import { ExpectResponseBody, TestCase, TestDefinition, TestSuite, TestUser } from '../lib/types'; const { @@ -22,80 +25,34 @@ export interface FindTestDefinition extends TestDefinition { } export type FindTestSuite = TestSuite; -type FindSavedObjectCase = Assign; - export interface FindTestCase { title: string; query: string; successResult?: { - savedObjects?: FindSavedObjectCase | FindSavedObjectCase[]; + savedObjects?: TestCase | TestCase[]; page?: number; perPage?: number; total?: number; }; failure?: { - statusCode: 400 | 403; - reason: - | 'forbidden_types' - | 'forbidden_namespaces' - | 'cross_namespace_not_permitted' - | 'bad_request'; + statusCode: 200 | 400; // if the user searches for types and/or namespaces they are not authorized for, they will get a 200 result with those types/namespaces omitted + reason: 'unauthorized' | 'cross_namespace_not_permitted' | 'bad_request'; }; } -// additional sharedtype objects that exist but do not have common test cases defined -const CONFLICT_1_OBJ = Object.freeze({ - type: 'sharedtype', - id: 'conflict_1', - namespaces: ['default', 'space_1', 'space_2'], -}); -const CONFLICT_2A_OBJ = Object.freeze({ - type: 'sharedtype', - id: 'conflict_2a', - originId: 'conflict_2', - namespaces: ['default', 'space_1', 'space_2'], -}); -const CONFLICT_2B_OBJ = Object.freeze({ - type: 'sharedtype', - id: 'conflict_2b', - originId: 'conflict_2', - namespaces: ['default', 'space_1', 'space_2'], -}); -const CONFLICT_3_OBJ = Object.freeze({ - type: 'sharedtype', - id: 'conflict_3', - namespaces: ['default', 'space_1', 'space_2'], -}); -const CONFLICT_4A_OBJ = Object.freeze({ - type: 'sharedtype', - id: 'conflict_4a', - originId: 'conflict_4', - namespaces: ['default', 'space_1', 'space_2'], -}); - const TEST_CASES = [ - { ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, namespaces: ['default'] }, - { ...CASES.SINGLE_NAMESPACE_SPACE_1, namespaces: ['space_1'] }, - { ...CASES.SINGLE_NAMESPACE_SPACE_2, namespaces: ['space_2'] }, - { ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, namespaces: ['default', 'space_1'] }, - { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, namespaces: ['space_1'] }, - { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, namespaces: ['space_2'] }, - { ...CASES.NAMESPACE_AGNOSTIC, namespaces: undefined }, - { ...CASES.HIDDEN, namespaces: undefined }, + ...Object.values(SAVED_OBJECT_TEST_CASES), + ...Object.values(CONFLICT_TEST_CASES), ]; -expect(TEST_CASES.length).to.eql( - Object.values(CASES).length, - 'Unhandled test cases in `find` suite' -); - export const getTestCases = ( { currentSpace, crossSpaceSearch }: { currentSpace?: string; crossSpaceSearch?: string[] } = { currentSpace: undefined, crossSpaceSearch: undefined, } ) => { - const crossSpaceIds = crossSpaceSearch?.filter((s) => s !== (currentSpace ?? 'default')) ?? []; + const crossSpaceIds = + crossSpaceSearch?.filter((s) => s !== (currentSpace ?? DEFAULT_SPACE_ID)) ?? []; // intentionally exclude the current space const isCrossSpaceSearch = crossSpaceIds.length > 0; const isWildcardSearch = crossSpaceIds.includes('*'); @@ -104,7 +61,7 @@ export const getTestCases = ( : ''; const buildTitle = (title: string) => - crossSpaceSearch ? `${title} (cross-space ${isWildcardSearch ? 'with wildcard' : ''})` : title; + crossSpaceSearch ? `${title} (cross-space${isWildcardSearch ? ' with wildcard' : ''})` : title; type CasePredicate = (testCase: TestCase) => boolean; const getExpectedSavedObjects = (predicate: CasePredicate) => { @@ -117,13 +74,16 @@ export const getTestCases = ( return TEST_CASES.filter((t) => { const hasOtherNamespaces = - Array.isArray(t.namespaces) && - t.namespaces!.some((ns) => ns !== (currentSpace ?? 'default')); + !t.expectedNamespaces || // namespace-agnostic types do not have an expectedNamespaces field + t.expectedNamespaces.some((ns) => ns !== (currentSpace ?? DEFAULT_SPACE_ID)); return hasOtherNamespaces && predicate(t); }); } return TEST_CASES.filter( - (t) => (!t.namespaces || t.namespaces.includes(currentSpace ?? 'default')) && predicate(t) + (t) => + (!t.expectedNamespaces || + t.expectedNamespaces.includes(currentSpace ?? DEFAULT_SPACE_ID)) && + predicate(t) ); }; @@ -140,19 +100,13 @@ export const getTestCases = ( query: `type=sharedtype&fields=title${namespacesQueryParam}`, successResult: { // expected depends on which spaces the user is authorized against... - savedObjects: getExpectedSavedObjects((t) => t.type === 'sharedtype').concat( - CONFLICT_1_OBJ, - CONFLICT_2A_OBJ, - CONFLICT_2B_OBJ, - CONFLICT_3_OBJ, - CONFLICT_4A_OBJ - ), + savedObjects: getExpectedSavedObjects((t) => t.type === 'sharedtype'), }, } as FindTestCase, namespaceAgnosticType: { title: buildTitle('find namespace-agnostic type'), query: `type=globaltype&fields=title${namespacesQueryParam}`, - successResult: { savedObjects: CASES.NAMESPACE_AGNOSTIC }, + successResult: { savedObjects: SAVED_OBJECT_TEST_CASES.NAMESPACE_AGNOSTIC }, } as FindTestCase, hiddenType: { title: buildTitle('find hidden type'), @@ -162,6 +116,15 @@ export const getTestCases = ( title: buildTitle('find unknown type'), query: `type=wigwags${namespacesQueryParam}`, } as FindTestCase, + eachType: { + title: buildTitle('find each type'), + query: `type=isolatedtype&type=sharedtype&type=globaltype&type=hiddentype&type=wigwags${namespacesQueryParam}`, + successResult: { + savedObjects: getExpectedSavedObjects((t) => + ['isolatedtype', 'sharedtype', 'globaltype'].includes(t.type) + ), + }, + } as FindTestCase, pageBeyondTotal: { title: buildTitle('find page beyond total'), query: `type=isolatedtype&page=100&per_page=100${namespacesQueryParam}`, @@ -179,7 +142,7 @@ export const getTestCases = ( filterWithNamespaceAgnosticType: { title: buildTitle('filter with namespace-agnostic type'), query: `type=globaltype&filter=globaltype.attributes.title:*global*${namespacesQueryParam}`, - successResult: { savedObjects: CASES.NAMESPACE_AGNOSTIC }, + successResult: { savedObjects: SAVED_OBJECT_TEST_CASES.NAMESPACE_AGNOSTIC }, } as FindTestCase, filterWithHiddenType: { title: buildTitle('filter with hidden type'), @@ -200,49 +163,48 @@ export const getTestCases = ( }; }; +function objectComparator(a: { id: string }, b: { id: string }) { + return a.id > b.id ? 1 : a.id < b.id ? -1 : 0; +} + export const createRequest = ({ query }: FindTestCase) => ({ query }); -const getTestTitle = ({ failure, title }: FindTestCase) => { - let description = 'success'; - if (failure?.statusCode === 400) { - description = 'bad request'; - } else if (failure?.statusCode === 403) { - description = 'forbidden'; - } - return `${description} ["${title}"]`; -}; +const getTestTitle = ({ failure, title }: FindTestCase) => + `${failure?.reason || 'success'} ["${title}"]`; export function findTestSuiteFactory(esArchiver: any, supertest: SuperTest) { - const expectForbiddenTypes = expectResponses.forbiddenTypes('find'); - const expectForbiddeNamespaces = expectResponses.forbiddenSpaces; const expectResponseBody = ( testCase: FindTestCase, user?: TestUser ): ExpectResponseBody => async (response: Record) => { const { failure, successResult = {}, query } = testCase; const parsedQuery = querystring.parse(query); - if (failure?.statusCode === 403) { - if (failure?.reason === 'forbidden_types') { - const type = parsedQuery.type; - await expectForbiddenTypes(type)(response); - } else if (failure?.reason === 'forbidden_namespaces') { - await expectForbiddeNamespaces(response); + if (failure?.statusCode === 200) { + if (failure?.reason === 'unauthorized') { + // if the user is completely unauthorized, they will receive an empty response body + const expected = { + page: parsedQuery.page || 1, + per_page: parsedQuery.per_page || 20, + total: 0, + saved_objects: [], + }; + expect(response.body).to.eql(expected); } else { - throw new Error(`Unexpected failure reason: ${failure?.reason}`); + throw new Error(`Unexpected failure reason: ${failure.reason}`); } } else if (failure?.statusCode === 400) { - if (failure?.reason === 'bad_request') { + if (failure.reason === 'bad_request') { const type = (parsedQuery.filter as string).split('.')[0]; expect(response.body.error).to.eql('Bad Request'); - expect(response.body.statusCode).to.eql(failure?.statusCode); + expect(response.body.statusCode).to.eql(failure.statusCode); expect(response.body.message).to.eql(`This type ${type} is not allowed: Bad Request`); - } else if (failure?.reason === 'cross_namespace_not_permitted') { + } else if (failure.reason === 'cross_namespace_not_permitted') { expect(response.body.error).to.eql('Bad Request'); - expect(response.body.statusCode).to.eql(failure?.statusCode); + expect(response.body.statusCode).to.eql(failure.statusCode); expect(response.body.message).to.eql( `_find across namespaces is not permitted when the Spaces plugin is disabled.: Bad Request` ); } else { - throw new Error(`Unexpected failure reason: ${failure?.reason}`); + throw new Error(`Unexpected failure reason: ${failure.reason}`); } } else { // 2xx @@ -251,11 +213,8 @@ export function findTestSuiteFactory(esArchiver: any, supertest: SuperTest) const savedObjectsArray = Array.isArray(savedObjects) ? savedObjects : [savedObjects]; const authorizedSavedObjects = savedObjectsArray.filter( (so) => - !user || - !so.namespaces || - so.namespaces.some( - (ns) => user.authorizedAtSpaces.includes(ns) || user.authorizedAtSpaces.includes('*') - ) + !so.expectedNamespaces || + so.expectedNamespaces.some((x) => isUserAuthorizedAtSpace(user, x)) ); expect(response.body.page).to.eql(page); expect(response.body.per_page).to.eql(perPage); @@ -265,16 +224,17 @@ export function findTestSuiteFactory(esArchiver: any, supertest: SuperTest) expect(response.body.total).to.eql(total || authorizedSavedObjects.length); } - authorizedSavedObjects.sort((s1, s2) => (s1.id < s2.id ? -1 : 1)); - response.body.saved_objects.sort((s1: any, s2: any) => (s1.id < s2.id ? -1 : 1)); + authorizedSavedObjects.sort(objectComparator); + response.body.saved_objects.sort(objectComparator); for (let i = 0; i < authorizedSavedObjects.length; i++) { const object = response.body.saved_objects[i]; - const { type: expectedType, id: expectedId } = authorizedSavedObjects[i]; - expect(object.type).to.eql(expectedType); - expect(object.id).to.eql(expectedId); + const expected = authorizedSavedObjects[i]; + const expectedNamespaces = getRedactedNamespaces(user, expected.expectedNamespaces); + expect(object.type).to.eql(expected.type); + expect(object.id).to.eql(expected.id); expect(object.updated_at).to.match(/^[\d-]{10}T[\d:\.]{12}Z$/); - expect(object.namespaces).to.eql(object.namespaces); + expect(object.namespaces).to.eql(expectedNamespaces); // don't test attributes, version, or references } } diff --git a/x-pack/test/saved_object_api_integration/common/suites/get.ts b/x-pack/test/saved_object_api_integration/common/suites/get.ts index fb03cd548d41a8..8d8938b5ee79f2 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/get.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/get.ts @@ -21,7 +21,7 @@ export type GetTestSuite = TestSuite; export type GetTestCase = TestCase; const DOES_NOT_EXIST = Object.freeze({ type: 'dashboard', id: 'does-not-exist' }); -export const TEST_CASES = Object.freeze({ ...CASES, DOES_NOT_EXIST }); +export const TEST_CASES: Record = Object.freeze({ ...CASES, DOES_NOT_EXIST }); export function getTestSuiteFactory(esArchiver: any, supertest: SuperTest) { const expectForbidden = expectResponses.forbiddenTypes('get'); diff --git a/x-pack/test/saved_object_api_integration/common/suites/import.ts b/x-pack/test/saved_object_api_integration/common/suites/import.ts index 5036d7b2008810..b0d0b4f8a815a5 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/import.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/import.ts @@ -36,7 +36,7 @@ const NEW_ATTRIBUTE_VAL = `New attribute value ${Date.now()}`; // * id: conflict_4a, originId: conflict_4 // using the seven conflict test case objects below, we can exercise various permutations of exact/inexact/ambiguous conflict scenarios const CID = 'conflict_'; -export const TEST_CASES = Object.freeze({ +export const TEST_CASES: Record = Object.freeze({ ...CASES, CONFLICT_1_OBJ: Object.freeze({ type: 'sharedtype', id: `${CID}1` }), CONFLICT_1A_OBJ: Object.freeze({ type: 'sharedtype', id: `${CID}1a`, originId: `${CID}1` }), diff --git a/x-pack/test/saved_object_api_integration/common/suites/resolve_import_errors.ts b/x-pack/test/saved_object_api_integration/common/suites/resolve_import_errors.ts index 6d294aed9b4de7..02fa614ac2b55d 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/resolve_import_errors.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/resolve_import_errors.ts @@ -37,7 +37,7 @@ const NEW_ATTRIBUTE_VAL = `New attribute value ${Date.now()}`; // * id: conflict_3 // * id: conflict_4a, originId: conflict_4 // using the five conflict test case objects below, we can exercise various permutations of exact/inexact/ambiguous conflict scenarios -export const TEST_CASES = Object.freeze({ +export const TEST_CASES: Record = Object.freeze({ ...CASES, CONFLICT_1A_OBJ: Object.freeze({ type: 'sharedtype', diff --git a/x-pack/test/saved_object_api_integration/common/suites/update.ts b/x-pack/test/saved_object_api_integration/common/suites/update.ts index 82f4699babf462..19921a82b2eb44 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/update.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/update.ts @@ -28,7 +28,10 @@ const NEW_ATTRIBUTE_KEY = 'title'; // all type mappings include this attribute, const NEW_ATTRIBUTE_VAL = `Updated attribute value ${Date.now()}`; const DOES_NOT_EXIST = Object.freeze({ type: 'dashboard', id: 'does-not-exist' }); -export const TEST_CASES = Object.freeze({ ...CASES, DOES_NOT_EXIST }); +export const TEST_CASES: Record = Object.freeze({ + ...CASES, + DOES_NOT_EXIST, +}); export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest) { const expectForbidden = expectResponses.forbiddenTypes('update'); diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts index 0cc5969e2b7ab0..93ae439d011667 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts @@ -26,13 +26,23 @@ const unresolvableConflict = (condition?: boolean) => const createTestCases = (overwrite: boolean, spaceId: string) => { // for each permitted (non-403) outcome, if failure !== undefined then we expect // to receive an error; otherwise, we expect to receive a success result + const expectedNamespaces = [spaceId]; // newly created objects should have this `namespaces` array in their return value const normalTypes = [ { ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, ...fail409(!overwrite && spaceId === DEFAULT_SPACE_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_1, + ...fail409(!overwrite && spaceId === SPACE_1_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_2, + ...fail409(!overwrite && spaceId === SPACE_2_ID), + expectedNamespaces, }, - { ...CASES.SINGLE_NAMESPACE_SPACE_1, ...fail409(!overwrite && spaceId === SPACE_1_ID) }, - { ...CASES.SINGLE_NAMESPACE_SPACE_2, ...fail409(!overwrite && spaceId === SPACE_2_ID) }, { ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, ...fail409(!overwrite || (spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID)), @@ -49,8 +59,8 @@ const createTestCases = (overwrite: boolean, spaceId: string) => { ...unresolvableConflict(spaceId !== SPACE_2_ID), }, { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, - CASES.NEW_SINGLE_NAMESPACE_OBJ, - CASES.NEW_MULTI_NAMESPACE_OBJ, + { ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces }, + { ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces }, CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, ]; const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }]; @@ -68,22 +78,28 @@ export default function ({ getService }: FtrProviderContext) { esArchiver, supertest ); - const createTests = (overwrite: boolean, spaceId: string) => { + const createTests = (overwrite: boolean, spaceId: string, user: TestUser) => { const { normalTypes, hiddenType, allTypes } = createTestCases(overwrite, spaceId); // use singleRequest to reduce execution time and/or test combined cases return { - unauthorized: createTestDefinitions(allTypes, true, overwrite, { spaceId }), + unauthorized: createTestDefinitions(allTypes, true, overwrite, { spaceId, user }), authorized: [ - createTestDefinitions(normalTypes, false, overwrite, { spaceId, singleRequest: true }), - createTestDefinitions(hiddenType, true, overwrite, { spaceId }), + createTestDefinitions(normalTypes, false, overwrite, { + spaceId, + user, + singleRequest: true, + }), + createTestDefinitions(hiddenType, true, overwrite, { spaceId, user }), createTestDefinitions(allTypes, true, overwrite, { spaceId, + user, singleRequest: true, responseBodyOverride: expectForbidden(['hiddentype']), }), ].flat(), superuser: createTestDefinitions(allTypes, false, overwrite, { spaceId, + user, singleRequest: true, }), }; @@ -93,7 +109,6 @@ export default function ({ getService }: FtrProviderContext) { getTestScenarios([false, true]).securityAndSpaces.forEach( ({ spaceId, users, modifier: overwrite }) => { const suffix = ` within the ${spaceId} space${overwrite ? ' with overwrite enabled' : ''}`; - const { unauthorized, authorized, superuser } = createTests(overwrite!, spaceId); const _addTests = (user: TestUser, tests: BulkCreateTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, spaceId, tests }); }; @@ -106,11 +121,14 @@ export default function ({ getService }: FtrProviderContext) { users.readAtSpace, users.allAtOtherSpace, ].forEach((user) => { + const { unauthorized } = createTests(overwrite!, spaceId, user); _addTests(user, unauthorized); }); [users.dualAll, users.allGlobally, users.allAtSpace].forEach((user) => { + const { authorized } = createTests(overwrite!, spaceId, user); _addTests(user, authorized); }); + const { superuser } = createTests(overwrite!, spaceId, users.superuser); _addTests(users.superuser, superuser); } ); diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts index f81488603dc83e..7353dafb5e1b5b 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts @@ -24,13 +24,23 @@ const { fail400, fail409 } = testCaseFailures; const createTestCases = (overwrite: boolean, spaceId: string) => { // for each permitted (non-403) outcome, if failure !== undefined then we expect // to receive an error; otherwise, we expect to receive a success result + const expectedNamespaces = [spaceId]; // newly created objects should have this `namespaces` array in their return value const normalTypes = [ { ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, ...fail409(!overwrite && spaceId === DEFAULT_SPACE_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_1, + ...fail409(!overwrite && spaceId === SPACE_1_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_2, + ...fail409(!overwrite && spaceId === SPACE_2_ID), + expectedNamespaces, }, - { ...CASES.SINGLE_NAMESPACE_SPACE_1, ...fail409(!overwrite && spaceId === SPACE_1_ID) }, - { ...CASES.SINGLE_NAMESPACE_SPACE_2, ...fail409(!overwrite && spaceId === SPACE_2_ID) }, { ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, ...fail409(!overwrite || (spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID)), @@ -38,8 +48,8 @@ const createTestCases = (overwrite: boolean, spaceId: string) => { { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail409(!overwrite || spaceId !== SPACE_1_ID) }, { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail409(!overwrite || spaceId !== SPACE_2_ID) }, { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, - CASES.NEW_SINGLE_NAMESPACE_OBJ, - CASES.NEW_MULTI_NAMESPACE_OBJ, + { ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces }, + { ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces }, CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, ]; const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }]; @@ -53,15 +63,15 @@ export default function ({ getService }: FtrProviderContext) { const es = getService('legacyEs'); const { addTests, createTestDefinitions } = createTestSuiteFactory(es, esArchiver, supertest); - const createTests = (overwrite: boolean, spaceId: string) => { + const createTests = (overwrite: boolean, spaceId: string, user: TestUser) => { const { normalTypes, hiddenType, allTypes } = createTestCases(overwrite, spaceId); return { - unauthorized: createTestDefinitions(allTypes, true, overwrite, { spaceId }), + unauthorized: createTestDefinitions(allTypes, true, overwrite, { spaceId, user }), authorized: [ - createTestDefinitions(normalTypes, false, overwrite, { spaceId }), - createTestDefinitions(hiddenType, true, overwrite, { spaceId }), + createTestDefinitions(normalTypes, false, overwrite, { spaceId, user }), + createTestDefinitions(hiddenType, true, overwrite, { spaceId, user }), ].flat(), - superuser: createTestDefinitions(allTypes, false, overwrite, { spaceId }), + superuser: createTestDefinitions(allTypes, false, overwrite, { spaceId, user }), }; }; @@ -69,7 +79,6 @@ export default function ({ getService }: FtrProviderContext) { getTestScenarios([false, true]).securityAndSpaces.forEach( ({ spaceId, users, modifier: overwrite }) => { const suffix = ` within the ${spaceId} space${overwrite ? ' with overwrite enabled' : ''}`; - const { unauthorized, authorized, superuser } = createTests(overwrite!, spaceId); const _addTests = (user: TestUser, tests: CreateTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, spaceId, tests }); }; @@ -82,11 +91,14 @@ export default function ({ getService }: FtrProviderContext) { users.readAtSpace, users.allAtOtherSpace, ].forEach((user) => { + const { unauthorized } = createTests(overwrite!, spaceId, user); _addTests(user, unauthorized); }); [users.dualAll, users.allGlobally, users.allAtSpace].forEach((user) => { + const { authorized } = createTests(overwrite!, spaceId, user); _addTests(user, authorized); }); + const { superuser } = createTests(overwrite!, spaceId, users.superuser); _addTests(users.superuser, superuser); } ); diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/export.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/export.ts index c581a1757565e7..be3906209032f8 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/export.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/export.ts @@ -15,17 +15,23 @@ import { const createTestCases = (spaceId: string) => { const cases = getTestCases(spaceId); - const exportableTypes = [ + const exportableObjects = [ cases.singleNamespaceObject, - cases.singleNamespaceType, cases.multiNamespaceObject, - cases.multiNamespaceType, cases.namespaceAgnosticObject, + ]; + const exportableTypes = [ + cases.singleNamespaceType, + cases.multiNamespaceType, cases.namespaceAgnosticType, ]; - const nonExportableTypes = [cases.hiddenObject, cases.hiddenType]; - const allTypes = exportableTypes.concat(nonExportableTypes); - return { exportableTypes, nonExportableTypes, allTypes }; + const nonExportableObjectsAndTypes = [cases.hiddenObject, cases.hiddenType]; + const allObjectsAndTypes = [ + exportableObjects, + exportableTypes, + nonExportableObjectsAndTypes, + ].flat(); + return { exportableObjects, exportableTypes, nonExportableObjectsAndTypes, allObjectsAndTypes }; }; export default function ({ getService }: FtrProviderContext) { @@ -34,13 +40,19 @@ export default function ({ getService }: FtrProviderContext) { const { addTests, createTestDefinitions } = exportTestSuiteFactory(esArchiver, supertest); const createTests = (spaceId: string) => { - const { exportableTypes, nonExportableTypes, allTypes } = createTestCases(spaceId); + const { + exportableObjects, + exportableTypes, + nonExportableObjectsAndTypes, + allObjectsAndTypes, + } = createTestCases(spaceId); return { unauthorized: [ - createTestDefinitions(exportableTypes, true), - createTestDefinitions(nonExportableTypes, false), + createTestDefinitions(exportableObjects, { statusCode: 403, reason: 'unauthorized' }), + createTestDefinitions(exportableTypes, { statusCode: 200, reason: 'unauthorized' }), // failure with empty result + createTestDefinitions(nonExportableObjectsAndTypes, false), ].flat(), - authorized: createTestDefinitions(allTypes, false), + authorized: createTestDefinitions(allObjectsAndTypes, false), }; }; diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts index 6ac77507df473c..afd4783fab7921 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts @@ -4,18 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getTestScenarios } from '../../common/lib/saved_object_test_utils'; +import { SPACES } from '../../common/lib/spaces'; +import { AUTHENTICATION } from '../../common/lib/authentication'; +import { + getTestScenarios, + isUserAuthorizedAtSpace, +} from '../../common/lib/saved_object_test_utils'; import { TestUser } from '../../common/lib/types'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { findTestSuiteFactory, getTestCases } from '../../common/suites/find'; -const createTestCases = (currentSpace: string, crossSpaceSearch: string[]) => { +const { + DEFAULT: { spaceId: DEFAULT_SPACE_ID }, + SPACE_1: { spaceId: SPACE_1_ID }, + SPACE_2: { spaceId: SPACE_2_ID }, +} = SPACES; + +const createTestCases = (currentSpace: string, crossSpaceSearch?: string[]) => { const cases = getTestCases({ currentSpace, crossSpaceSearch }); const normalTypes = [ cases.singleNamespaceType, cases.multiNamespaceType, cases.namespaceAgnosticType, + cases.eachType, cases.pageBeyondTotal, cases.unknownSearchField, cases.filterWithNamespaceAgnosticType, @@ -37,89 +49,72 @@ export default function ({ getService }: FtrProviderContext) { const { addTests, createTestDefinitions } = findTestSuiteFactory(esArchiver, supertest); const createTests = (spaceId: string, user: TestUser) => { - const currentSpaceCases = createTestCases(spaceId, []); + const currentSpaceCases = createTestCases(spaceId); - const explicitCrossSpace = createTestCases(spaceId, ['default', 'space_1', 'space_2']); + const EACH_SPACE = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID]; + const explicitCrossSpace = createTestCases(spaceId, EACH_SPACE); const wildcardCrossSpace = createTestCases(spaceId, ['*']); - if (user.username === 'elastic') { + if (user.username === AUTHENTICATION.SUPERUSER.username) { return { currentSpace: createTestDefinitions(currentSpaceCases.allTypes, false, { user }), - crossSpace: createTestDefinitions(explicitCrossSpace.allTypes, false, { user }), + crossSpace: [ + createTestDefinitions(explicitCrossSpace.allTypes, false, { user }), + createTestDefinitions(wildcardCrossSpace.allTypes, false, { user }), + ].flat(), }; } - const authorizedAtCurrentSpace = - user.authorizedAtSpaces.includes(spaceId) || user.authorizedAtSpaces.includes('*'); - - const authorizedExplicitCrossSpaces = ['default', 'space_1', 'space_2'].filter( - (s) => - user.authorizedAtSpaces.includes('*') || - (s !== spaceId && user.authorizedAtSpaces.includes(s)) + const isAuthorizedExplicitCrossSpaces = EACH_SPACE.some( + (s) => s !== spaceId && isUserAuthorizedAtSpace(user, s) ); - - const authorizedWildcardCrossSpaces = ['default', 'space_1', 'space_2'].filter( - (s) => user.authorizedAtSpaces.includes('*') || user.authorizedAtSpaces.includes(s) + const isAuthorizedWildcardCrossSpaces = EACH_SPACE.some((s) => + isUserAuthorizedAtSpace(user, s) ); - const explicitCrossSpaceDefinitions = - authorizedExplicitCrossSpaces.length > 0 - ? [ - createTestDefinitions(explicitCrossSpace.normalTypes, false, { user }), - createTestDefinitions( - explicitCrossSpace.hiddenAndUnknownTypes, - { - statusCode: 403, - reason: 'forbidden_types', - }, - { user } - ), - ].flat() - : createTestDefinitions( - explicitCrossSpace.allTypes, - { - statusCode: 403, - reason: 'forbidden_namespaces', - }, + const explicitCrossSpaceDefinitions = isAuthorizedExplicitCrossSpaces + ? [ + createTestDefinitions(explicitCrossSpace.normalTypes, false, { user }), + createTestDefinitions( + explicitCrossSpace.hiddenAndUnknownTypes, + { statusCode: 200, reason: 'unauthorized' }, { user } - ); - - const wildcardCrossSpaceDefinitions = - authorizedWildcardCrossSpaces.length > 0 - ? [ - createTestDefinitions(wildcardCrossSpace.normalTypes, false, { user }), - createTestDefinitions( - wildcardCrossSpace.hiddenAndUnknownTypes, - { - statusCode: 403, - reason: 'forbidden_types', - }, - { user } - ), - ].flat() - : createTestDefinitions( - wildcardCrossSpace.allTypes, - { - statusCode: 403, - reason: 'forbidden_namespaces', - }, + ), + ].flat() + : createTestDefinitions( + explicitCrossSpace.allTypes, + { statusCode: 200, reason: 'unauthorized' }, + { user } + ); + const wildcardCrossSpaceDefinitions = isAuthorizedWildcardCrossSpaces + ? [ + createTestDefinitions(wildcardCrossSpace.normalTypes, false, { user }), + createTestDefinitions( + wildcardCrossSpace.hiddenAndUnknownTypes, + { statusCode: 200, reason: 'unauthorized' }, { user } - ); + ), + ].flat() + : createTestDefinitions( + wildcardCrossSpace.allTypes, + { statusCode: 200, reason: 'unauthorized' }, + { user } + ); return { - currentSpace: authorizedAtCurrentSpace + currentSpace: isUserAuthorizedAtSpace(user, spaceId) ? [ createTestDefinitions(currentSpaceCases.normalTypes, false, { user, }), createTestDefinitions(currentSpaceCases.hiddenAndUnknownTypes, { - statusCode: 403, - reason: 'forbidden_types', + statusCode: 200, + reason: 'unauthorized', }), ].flat() : createTestDefinitions(currentSpaceCases.allTypes, { - statusCode: 403, - reason: 'forbidden_types', + statusCode: 200, + reason: 'unauthorized', }), crossSpace: [...explicitCrossSpaceDefinitions, ...wildcardCrossSpaceDefinitions], }; diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts b/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts index 725120687c2313..cc2c5e2e7fc005 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/bulk_create.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SPACES } from '../../common/lib/spaces'; import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils'; import { TestUser } from '../../common/lib/types'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -13,22 +14,26 @@ import { BulkCreateTestDefinition, } from '../../common/suites/bulk_create'; +const { + DEFAULT: { spaceId: DEFAULT_SPACE_ID }, +} = SPACES; const { fail400, fail409 } = testCaseFailures; const unresolvableConflict = () => ({ fail409Param: 'unresolvableConflict' }); const createTestCases = (overwrite: boolean) => { // for each permitted (non-403) outcome, if failure !== undefined then we expect // to receive an error; otherwise, we expect to receive a success result + const expectedNamespaces = [DEFAULT_SPACE_ID]; // newly created objects should have this `namespaces` array in their return value const normalTypes = [ { ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, ...fail409(!overwrite) }, - CASES.SINGLE_NAMESPACE_SPACE_1, - CASES.SINGLE_NAMESPACE_SPACE_2, + { ...CASES.SINGLE_NAMESPACE_SPACE_1, expectedNamespaces }, + { ...CASES.SINGLE_NAMESPACE_SPACE_2, expectedNamespaces }, { ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, ...fail409(!overwrite) }, { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail409(), ...unresolvableConflict() }, { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail409(), ...unresolvableConflict() }, { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, - CASES.NEW_SINGLE_NAMESPACE_OBJ, - CASES.NEW_MULTI_NAMESPACE_OBJ, + { ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces }, + { ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces }, CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, ]; const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }]; @@ -46,27 +51,27 @@ export default function ({ getService }: FtrProviderContext) { esArchiver, supertest ); - const createTests = (overwrite: boolean) => { + const createTests = (overwrite: boolean, user: TestUser) => { const { normalTypes, hiddenType, allTypes } = createTestCases(overwrite); // use singleRequest to reduce execution time and/or test combined cases return { - unauthorized: createTestDefinitions(allTypes, true, overwrite), + unauthorized: createTestDefinitions(allTypes, true, overwrite, { user }), authorized: [ - createTestDefinitions(normalTypes, false, overwrite, { singleRequest: true }), - createTestDefinitions(hiddenType, true, overwrite), + createTestDefinitions(normalTypes, false, overwrite, { user, singleRequest: true }), + createTestDefinitions(hiddenType, true, overwrite, { user }), createTestDefinitions(allTypes, true, overwrite, { + user, singleRequest: true, responseBodyOverride: expectForbidden(['hiddentype']), }), ].flat(), - superuser: createTestDefinitions(allTypes, false, overwrite, { singleRequest: true }), + superuser: createTestDefinitions(allTypes, false, overwrite, { user, singleRequest: true }), }; }; describe('_bulk_create', () => { getTestScenarios([false, true]).security.forEach(({ users, modifier: overwrite }) => { const suffix = overwrite ? ' with overwrite enabled' : ''; - const { unauthorized, authorized, superuser } = createTests(overwrite!); const _addTests = (user: TestUser, tests: BulkCreateTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, tests }); }; @@ -81,11 +86,14 @@ export default function ({ getService }: FtrProviderContext) { users.allAtSpace1, users.readAtSpace1, ].forEach((user) => { + const { unauthorized } = createTests(overwrite!, user); _addTests(user, unauthorized); }); [users.dualAll, users.allGlobally].forEach((user) => { + const { authorized } = createTests(overwrite!, user); _addTests(user, authorized); }); + const { superuser } = createTests(overwrite!, users.superuser); _addTests(users.superuser, superuser); }); }); diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/create.ts b/x-pack/test/saved_object_api_integration/security_only/apis/create.ts index 88d096f05d8466..b7c6ecef979bda 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/create.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/create.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SPACES } from '../../common/lib/spaces'; import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils'; import { TestUser } from '../../common/lib/types'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -13,21 +14,25 @@ import { CreateTestDefinition, } from '../../common/suites/create'; +const { + DEFAULT: { spaceId: DEFAULT_SPACE_ID }, +} = SPACES; const { fail400, fail409 } = testCaseFailures; const createTestCases = (overwrite: boolean) => { // for each permitted (non-403) outcome, if failure !== undefined then we expect // to receive an error; otherwise, we expect to receive a success result + const expectedNamespaces = [DEFAULT_SPACE_ID]; // newly created objects should have this `namespaces` array in their return value const normalTypes = [ { ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, ...fail409(!overwrite) }, - CASES.SINGLE_NAMESPACE_SPACE_1, - CASES.SINGLE_NAMESPACE_SPACE_2, + { ...CASES.SINGLE_NAMESPACE_SPACE_1, expectedNamespaces }, + { ...CASES.SINGLE_NAMESPACE_SPACE_2, expectedNamespaces }, { ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, ...fail409(!overwrite) }, { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail409() }, { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail409() }, { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, - CASES.NEW_SINGLE_NAMESPACE_OBJ, - CASES.NEW_MULTI_NAMESPACE_OBJ, + { ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces }, + { ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces }, CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, ]; const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }]; @@ -41,22 +46,21 @@ export default function ({ getService }: FtrProviderContext) { const es = getService('legacyEs'); const { addTests, createTestDefinitions } = createTestSuiteFactory(es, esArchiver, supertest); - const createTests = (overwrite: boolean) => { + const createTests = (overwrite: boolean, user: TestUser) => { const { normalTypes, hiddenType, allTypes } = createTestCases(overwrite); return { - unauthorized: createTestDefinitions(allTypes, true, overwrite), + unauthorized: createTestDefinitions(allTypes, true, overwrite, { user }), authorized: [ - createTestDefinitions(normalTypes, false, overwrite), - createTestDefinitions(hiddenType, true, overwrite), + createTestDefinitions(normalTypes, false, overwrite, { user }), + createTestDefinitions(hiddenType, true, overwrite, { user }), ].flat(), - superuser: createTestDefinitions(allTypes, false, overwrite), + superuser: createTestDefinitions(allTypes, false, overwrite, { user }), }; }; describe('_create', () => { getTestScenarios([false, true]).security.forEach(({ users, modifier: overwrite }) => { const suffix = overwrite ? ' with overwrite enabled' : ''; - const { unauthorized, authorized, superuser } = createTests(overwrite!); const _addTests = (user: TestUser, tests: CreateTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, tests }); }; @@ -71,11 +75,14 @@ export default function ({ getService }: FtrProviderContext) { users.allAtSpace1, users.readAtSpace1, ].forEach((user) => { + const { unauthorized } = createTests(overwrite!, user); _addTests(user, unauthorized); }); [users.dualAll, users.allGlobally].forEach((user) => { + const { authorized } = createTests(overwrite!, user); _addTests(user, authorized); }); + const { superuser } = createTests(overwrite!, users.superuser); _addTests(users.superuser, superuser); }); }); diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/export.ts b/x-pack/test/saved_object_api_integration/security_only/apis/export.ts index 99babf683ccfa2..ea1ed56921d22e 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/export.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/export.ts @@ -15,17 +15,23 @@ import { const createTestCases = () => { const cases = getTestCases(); - const exportableTypes = [ + const exportableObjects = [ cases.singleNamespaceObject, - cases.singleNamespaceType, cases.multiNamespaceObject, - cases.multiNamespaceType, cases.namespaceAgnosticObject, + ]; + const exportableTypes = [ + cases.singleNamespaceType, + cases.multiNamespaceType, cases.namespaceAgnosticType, ]; - const nonExportableTypes = [cases.hiddenObject, cases.hiddenType]; - const allTypes = exportableTypes.concat(nonExportableTypes); - return { exportableTypes, nonExportableTypes, allTypes }; + const nonExportableObjectsAndTypes = [cases.hiddenObject, cases.hiddenType]; + const allObjectsAndTypes = [ + exportableObjects, + exportableTypes, + nonExportableObjectsAndTypes, + ].flat(); + return { exportableObjects, exportableTypes, nonExportableObjectsAndTypes, allObjectsAndTypes }; }; export default function ({ getService }: FtrProviderContext) { @@ -34,13 +40,19 @@ export default function ({ getService }: FtrProviderContext) { const { addTests, createTestDefinitions } = exportTestSuiteFactory(esArchiver, supertest); const createTests = () => { - const { exportableTypes, nonExportableTypes, allTypes } = createTestCases(); + const { + exportableObjects, + exportableTypes, + nonExportableObjectsAndTypes, + allObjectsAndTypes, + } = createTestCases(); return { unauthorized: [ - createTestDefinitions(exportableTypes, true), - createTestDefinitions(nonExportableTypes, false), + createTestDefinitions(exportableObjects, { statusCode: 403, reason: 'unauthorized' }), + createTestDefinitions(exportableTypes, { statusCode: 200, reason: 'unauthorized' }), // failure with empty result + createTestDefinitions(nonExportableObjectsAndTypes, false), ].flat(), - authorized: createTestDefinitions(allTypes, false), + authorized: createTestDefinitions(allObjectsAndTypes, false), }; }; diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/find.ts b/x-pack/test/saved_object_api_integration/security_only/apis/find.ts index 3a435119436ca1..aa18f32600949a 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/find.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/find.ts @@ -4,18 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SPACES } from '../../common/lib/spaces'; +import { AUTHENTICATION } from '../../common/lib/authentication'; import { getTestScenarios } from '../../common/lib/saved_object_test_utils'; import { TestUser } from '../../common/lib/types'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { findTestSuiteFactory, getTestCases } from '../../common/suites/find'; -const createTestCases = (crossSpaceSearch: string[]) => { +const { + DEFAULT: { spaceId: DEFAULT_SPACE_ID }, + SPACE_1: { spaceId: SPACE_1_ID }, + SPACE_2: { spaceId: SPACE_2_ID }, +} = SPACES; + +const createTestCases = (crossSpaceSearch?: string[]) => { const cases = getTestCases({ crossSpaceSearch }); const normalTypes = [ cases.singleNamespaceType, cases.multiNamespaceType, cases.namespaceAgnosticType, + cases.eachType, cases.pageBeyondTotal, cases.unknownSearchField, cases.filterWithNamespaceAgnosticType, @@ -37,46 +46,35 @@ export default function ({ getService }: FtrProviderContext) { const { addTests, createTestDefinitions } = findTestSuiteFactory(esArchiver, supertest); const createTests = (user: TestUser) => { - const defaultCases = createTestCases([]); - const crossSpaceCases = createTestCases(['default', 'space_1', 'space_2']); + const defaultCases = createTestCases(); + const crossSpaceCases = createTestCases([DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID]); - if (user.username === 'elastic') { + if (user.username === AUTHENTICATION.SUPERUSER.username) { return { defaultCases: createTestDefinitions(defaultCases.allTypes, false, { user }), crossSpace: createTestDefinitions( crossSpaceCases.allTypes, - { - statusCode: 400, - reason: 'cross_namespace_not_permitted', - }, + { statusCode: 400, reason: 'cross_namespace_not_permitted' }, { user } ), }; } - const authorizedGlobally = user.authorizedAtSpaces.includes('*'); + const isAuthorizedGlobally = user.authorizedAtSpaces.includes('*'); return { - defaultCases: authorizedGlobally + defaultCases: isAuthorizedGlobally ? [ - createTestDefinitions(defaultCases.normalTypes, false, { - user, - }), + createTestDefinitions(defaultCases.normalTypes, false, { user }), createTestDefinitions(defaultCases.hiddenAndUnknownTypes, { - statusCode: 403, - reason: 'forbidden_types', + statusCode: 200, + reason: 'unauthorized', }), ].flat() - : createTestDefinitions(defaultCases.allTypes, { - statusCode: 403, - reason: 'forbidden_types', - }), + : createTestDefinitions(defaultCases.allTypes, { statusCode: 200, reason: 'unauthorized' }), crossSpace: createTestDefinitions( crossSpaceCases.allTypes, - { - statusCode: 400, - reason: 'cross_namespace_not_permitted', - }, + { statusCode: 400, reason: 'cross_namespace_not_permitted' }, { user } ), }; diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts index 74fade39bf7a58..ef47b09eddbc83 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_create.ts @@ -19,36 +19,48 @@ const { fail400, fail409 } = testCaseFailures; const unresolvableConflict = (condition?: boolean) => condition !== false ? { fail409Param: 'unresolvableConflict' } : {}; -const createTestCases = (overwrite: boolean, spaceId: string) => [ +const createTestCases = (overwrite: boolean, spaceId: string) => { // for each outcome, if failure !== undefined then we expect to receive // an error; otherwise, we expect to receive a success result - { - ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, - ...fail409(!overwrite && spaceId === DEFAULT_SPACE_ID), - }, - { ...CASES.SINGLE_NAMESPACE_SPACE_1, ...fail409(!overwrite && spaceId === SPACE_1_ID) }, - { ...CASES.SINGLE_NAMESPACE_SPACE_2, ...fail409(!overwrite && spaceId === SPACE_2_ID) }, - { - ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, - ...fail409(!overwrite || (spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID)), - ...unresolvableConflict(spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID), - }, - { - ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, - ...fail409(!overwrite || spaceId !== SPACE_1_ID), - ...unresolvableConflict(spaceId !== SPACE_1_ID), - }, - { - ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, - ...fail409(!overwrite || spaceId !== SPACE_2_ID), - ...unresolvableConflict(spaceId !== SPACE_2_ID), - }, - { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, - { ...CASES.HIDDEN, ...fail400() }, - CASES.NEW_SINGLE_NAMESPACE_OBJ, - CASES.NEW_MULTI_NAMESPACE_OBJ, - CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, -]; + const expectedNamespaces = [spaceId]; // newly created objects should have this `namespaces` array in their return value + return [ + { + ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, + ...fail409(!overwrite && spaceId === DEFAULT_SPACE_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_1, + ...fail409(!overwrite && spaceId === SPACE_1_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_2, + ...fail409(!overwrite && spaceId === SPACE_2_ID), + expectedNamespaces, + }, + { + ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, + ...fail409(!overwrite || (spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID)), + ...unresolvableConflict(spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID), + }, + { + ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, + ...fail409(!overwrite || spaceId !== SPACE_1_ID), + ...unresolvableConflict(spaceId !== SPACE_1_ID), + }, + { + ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, + ...fail409(!overwrite || spaceId !== SPACE_2_ID), + ...unresolvableConflict(spaceId !== SPACE_2_ID), + }, + { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, + { ...CASES.HIDDEN, ...fail400() }, + { ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces }, + { ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces }, + CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, + ]; +}; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts index 1040f7fd81ddee..10e57b4db82dc7 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/create.ts @@ -16,27 +16,39 @@ const { } = SPACES; const { fail400, fail409 } = testCaseFailures; -const createTestCases = (overwrite: boolean, spaceId: string) => [ +const createTestCases = (overwrite: boolean, spaceId: string) => { // for each outcome, if failure !== undefined then we expect to receive // an error; otherwise, we expect to receive a success result - { - ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, - ...fail409(!overwrite && spaceId === DEFAULT_SPACE_ID), - }, - { ...CASES.SINGLE_NAMESPACE_SPACE_1, ...fail409(!overwrite && spaceId === SPACE_1_ID) }, - { ...CASES.SINGLE_NAMESPACE_SPACE_2, ...fail409(!overwrite && spaceId === SPACE_2_ID) }, - { - ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, - ...fail409(!overwrite || (spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID)), - }, - { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail409(!overwrite || spaceId !== SPACE_1_ID) }, - { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail409(!overwrite || spaceId !== SPACE_2_ID) }, - { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, - { ...CASES.HIDDEN, ...fail400() }, - CASES.NEW_SINGLE_NAMESPACE_OBJ, - CASES.NEW_MULTI_NAMESPACE_OBJ, - CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, -]; + const expectedNamespaces = [spaceId]; // newly created objects should have this `namespaces` array in their return value + return [ + { + ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, + ...fail409(!overwrite && spaceId === DEFAULT_SPACE_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_1, + ...fail409(!overwrite && spaceId === SPACE_1_ID), + expectedNamespaces, + }, + { + ...CASES.SINGLE_NAMESPACE_SPACE_2, + ...fail409(!overwrite && spaceId === SPACE_2_ID), + expectedNamespaces, + }, + { + ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, + ...fail409(!overwrite || (spaceId !== DEFAULT_SPACE_ID && spaceId !== SPACE_1_ID)), + }, + { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail409(!overwrite || spaceId !== SPACE_1_ID) }, + { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail409(!overwrite || spaceId !== SPACE_2_ID) }, + { ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) }, + { ...CASES.HIDDEN, ...fail400() }, + { ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces }, + { ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces }, + CASES.NEW_NAMESPACE_AGNOSTIC_OBJ, + ]; +}; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/find.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/find.ts index 1d46985916cd50..c6779402d3291a 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/find.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/find.ts @@ -4,11 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SPACES } from '../../common/lib/spaces'; import { getTestScenarios } from '../../common/lib/saved_object_test_utils'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { findTestSuiteFactory, getTestCases } from '../../common/suites/find'; -const createTestCases = (spaceId: string, crossSpaceSearch: string[]) => { +const { + DEFAULT: { spaceId: DEFAULT_SPACE_ID }, + SPACE_1: { spaceId: SPACE_1_ID }, + SPACE_2: { spaceId: SPACE_2_ID }, +} = SPACES; + +const createTestCases = (spaceId: string, crossSpaceSearch?: string[]) => { const cases = getTestCases({ currentSpace: spaceId, crossSpaceSearch }); return Object.values(cases); }; @@ -18,15 +25,19 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const { addTests, createTestDefinitions } = findTestSuiteFactory(esArchiver, supertest); - const createTests = (spaceId: string, crossSpaceSearch: string[]) => { + const createTests = (spaceId: string, crossSpaceSearch?: string[]) => { const testCases = createTestCases(spaceId, crossSpaceSearch); return createTestDefinitions(testCases, false); }; describe('_find', () => { getTestScenarios().spaces.forEach(({ spaceId }) => { - const currentSpaceTests = createTests(spaceId, []); - const explicitCrossSpaceTests = createTests(spaceId, ['default', 'space_1', 'space_2']); + const currentSpaceTests = createTests(spaceId); + const explicitCrossSpaceTests = createTests(spaceId, [ + DEFAULT_SPACE_ID, + SPACE_1_ID, + SPACE_2_ID, + ]); const wildcardCrossSpaceTests = createTests(spaceId, ['*']); addTests(`within the ${spaceId} space`, { spaceId, diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts new file mode 100644 index 00000000000000..533ce49b143253 --- /dev/null +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const telemetryTestResources = getService('telemetryTestResources'); + + describe('security solution endpoint telemetry', () => { + after(async () => { + await esArchiver.load('empty_kibana'); + }); + + describe('when no agents are connected', () => { + before(async () => { + await esArchiver.load('empty_kibana'); + }); + + it('reports no endpoints or policies', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry).to.eql({ + total_installed: 0, + active_within_last_24_hours: 0, + os: [], + policies: { + malware: { + active: 0, + inactive: 0, + failure: 0, + }, + }, + }); + }); + }); + describe('when agents are connected with endpoint integration disabled', () => { + before(async () => { + await esArchiver.load('endpoint/telemetry/agent_only'); + }); + + it('reports no endpoints or policies', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry).to.eql({ + total_installed: 0, + active_within_last_24_hours: 0, + os: [], + policies: { + malware: { + active: 0, + inactive: 0, + failure: 0, + }, + }, + }); + }); + }); + describe('when agents are connected with endpoints seen in past 24 hours', () => { + before(async () => { + await telemetryTestResources.getArchiveSetCheckIn( + 'endpoint_malware_enabled', + 'checkin_now', + 0 + ); + await esArchiver.load('endpoint/telemetry/checkin_now'); + await telemetryTestResources.deleteArchive('checkin_now'); + }); + + it('reports the correct number of endpoints seen total and in past 24 hours', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry.total_installed).to.eql(3); + expect(endpointTelemetry.active_within_last_24_hours).to.eql(3); + }); + it('reports the separate OS types of installed endpoints', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry.os.length).to.eql(3); + const osPlatforms: string[] = []; + endpointTelemetry.os.forEach((os) => { + expect(os.count).to.eql(1); + osPlatforms.push(os.platform); + }); + expect(osPlatforms).to.contain('windows'); + expect(osPlatforms).to.contain('darwin'); + expect(osPlatforms).to.contain('ubuntu'); + }); + }); + describe('when agents are connected with endpoints not seen in past 24 hours', () => { + before(async () => { + await telemetryTestResources.getArchiveSetCheckIn( + 'endpoint_malware_enabled', + 'checkin_2_days_ago', + 2 + ); + await esArchiver.load('endpoint/telemetry/checkin_2_days_ago'); + await telemetryTestResources.deleteArchive('checkin_2_days_ago'); + }); + + it('reports the correct number of endpoints seen total and in past 24 hours', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry.total_installed).to.eql(3); + expect(endpointTelemetry.active_within_last_24_hours).to.eql(0); + }); + it('reports the separate OS types of installed endpoints', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry.os.length).to.eql(3); + const osPlatforms: string[] = []; + endpointTelemetry.os.forEach((os) => { + expect(os.count).to.eql(1); + osPlatforms.push(os.platform); + }); + expect(osPlatforms).to.contain('windows'); + expect(osPlatforms).to.contain('darwin'); + expect(osPlatforms).to.contain('ubuntu'); + }); + }); + describe('when agents are connected with endpoints integration malware enabled', () => { + before(async () => { + await esArchiver.load('endpoint/telemetry/endpoint_malware_enabled'); + }); + + it('reports the correct number of windows and macos endpoints under policies malware', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry.policies.malware.active).to.eql(2); + expect(endpointTelemetry.policies.malware.inactive).to.eql(0); + }); + }); + describe('when agents are connected with endpoints integration malware disabled', () => { + before(async () => { + await esArchiver.load('endpoint/telemetry/endpoint_malware_disabled'); + }); + it('reports the correct number of windows and macos endpoints under policies malware', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry.policies.malware.active).to.eql(0); + expect(endpointTelemetry.policies.malware.inactive).to.eql(2); + }); + }); + describe('when agents are connected with endpoints integration uninstalled', () => { + before(async () => { + await esArchiver.load('endpoint/telemetry/endpoint_uninstalled'); + }); + it('reports no endpoints or policies', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry).to.eql({ + total_installed: 0, + active_within_last_24_hours: 0, + os: [], + policies: { + malware: { + active: 0, + inactive: 0, + failure: 0, + }, + }, + }); + }); + }); + }); +} diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index c25233a0c82ebb..654aa18fba523c 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -31,5 +31,6 @@ export default function (providerContext: FtrProviderContext) { loadTestFile(require.resolve('./endpoint_list')); loadTestFile(require.resolve('./policy_details')); loadTestFile(require.resolve('./resolver')); + loadTestFile(require.resolve('./endpoint_telemetry')); }); } diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 325283f5e3440f..9610144d3846df 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -29,7 +29,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { port, }); - describe('When on the Endpoint Policy Details Page', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/72102 + describe.skip('When on the Endpoint Policy Details Page', function () { this.tags(['ciGroup7']); describe('with an invalid policy id', () => { diff --git a/x-pack/test/security_solution_endpoint/services/endpoint_telemetry.ts b/x-pack/test/security_solution_endpoint/services/endpoint_telemetry.ts new file mode 100644 index 00000000000000..0f158da5d2f8cd --- /dev/null +++ b/x-pack/test/security_solution_endpoint/services/endpoint_telemetry.ts @@ -0,0 +1,98 @@ +/* + * 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 fs from 'fs'; +import Path from 'path'; +import { FtrProviderContext } from '../ftr_provider_context'; + +const TELEMETRY_API_ROOT = '/api/stats?extended=true'; +const TELEMETRY_DATA_ROOT = 'test/functional/es_archives/endpoint/telemetry/'; + +interface EndpointTelemetry { + total_installed: number; + active_within_last_24_hours: number; + os: Array<{ + full_name: string; + platform: string; + version: string; + count: number; + }>; + policies: { + malware: { + active: number; + inactive: number; + failure: number; + }; + }; +} + +export function EndpointTelemetryTestResourcesProvider({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + return { + /** + * Return the endpoint telemetry object from /api/stats?extended=true + */ + async getEndpointTelemetry(): Promise { + const kibanaTelemetry = await supertest.get(TELEMETRY_API_ROOT); + return kibanaTelemetry.body.usage.security_solution.endpoints; + }, + /** + * Create an telemetry archive from a source archive with updated last_checkin field to test + * active_within_last_24_hours + */ + async getArchiveSetCheckIn( + sourceArchive: string, + destinationArchive: string, + checkInOffset: number = 0 + ) { + const sourcePath = Path.join(TELEMETRY_DATA_ROOT, sourceArchive); + const destinationPath = Path.join(TELEMETRY_DATA_ROOT, destinationArchive); + + if (fs.existsSync(destinationPath)) { + this.deleteArchive(destinationArchive); + } + fs.mkdirSync(destinationPath); + + fs.readdirSync(sourcePath).forEach((file) => { + const sourceFilePath = Path.join(sourcePath, file); + const destinationFilePath = Path.join(destinationPath, file); + fs.copyFileSync(sourceFilePath, destinationFilePath); + }); + + fs.readdirSync(destinationPath).forEach((file) => { + if (file !== 'mappings.json') { + const dataFilePath = Path.join(destinationPath, file); + const rawDocuments = fs.readFileSync(dataFilePath).toString().split('\n\n'); + + const dateTime = new Date(); + dateTime.setDate(dateTime.getDate() - checkInOffset); + rawDocuments.forEach((rawDocument, index) => { + const document = JSON.parse(rawDocument); + if (document.value.source.type === 'fleet-agents') { + document.value.source['fleet-agents'].last_checkin = dateTime.toISOString(); + rawDocuments[index] = JSON.stringify(document); + } + }); + + fs.writeFileSync(dataFilePath, rawDocuments.join('\n\n')); + } + }); + }, + /** + * Delete archives created for testing + */ + async deleteArchive(archiveName: string) { + const archivePath = Path.join(TELEMETRY_DATA_ROOT, archiveName); + if (fs.existsSync(archivePath)) { + fs.readdirSync(archivePath).forEach((file) => { + const archiveFile = Path.join(archivePath, file); + fs.unlinkSync(archiveFile); + }); + } + fs.rmdirSync(archivePath); + }, + }; +} diff --git a/x-pack/test/security_solution_endpoint/services/index.ts b/x-pack/test/security_solution_endpoint/services/index.ts index 7eecae41aae4a3..a9409998547d5b 100644 --- a/x-pack/test/security_solution_endpoint/services/index.ts +++ b/x-pack/test/security_solution_endpoint/services/index.ts @@ -7,9 +7,11 @@ import { services as xPackFunctionalServices } from '../../functional/services'; import { EndpointPolicyTestResourcesProvider } from './endpoint_policy'; import { IngestManagerProvider } from '../../common/services/ingest_manager'; +import { EndpointTelemetryTestResourcesProvider } from './endpoint_telemetry'; export const services = { ...xPackFunctionalServices, policyTestResources: EndpointPolicyTestResourcesProvider, + telemetryTestResources: EndpointTelemetryTestResourcesProvider, ingestManager: IngestManagerProvider, }; diff --git a/yarn.lock b/yarn.lock index cec2697f6c15cc..9e96158771cded 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2458,6 +2458,25 @@ jsonwebtoken "^8.3.0" lru-cache "^5.1.1" +"@octokit/auth-token@^2.4.0": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.2.tgz#10d0ae979b100fa6b72fa0e8e63e27e6d0dbff8a" + integrity sha512-jE/lE/IKIz2v1+/P0u4fJqv0kYwXOTujKemJMFr6FeopsxlIK3+wKDCJGnysg81XID5TgZQbIfuJ5J0lnTiuyQ== + dependencies: + "@octokit/types" "^5.0.0" + +"@octokit/core@^3.0.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.1.2.tgz#c937d5f9621b764573068fcd2e5defcc872fd9cc" + integrity sha512-AInOFULmwOa7+NFi9F8DlDkm5qtZVmDQayi7TUgChE3yeIGPq0Y+6cAEXPexQ3Ea+uZy66hKEazR7DJyU+4wfw== + dependencies: + "@octokit/auth-token" "^2.4.0" + "@octokit/graphql" "^4.3.1" + "@octokit/request" "^5.4.0" + "@octokit/types" "^5.0.0" + before-after-hook "^2.1.0" + universal-user-agent "^6.0.0" + "@octokit/endpoint@^3.2.0": version "3.2.3" resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-3.2.3.tgz#bd9aea60cd94ce336656b57a5c9cb7f10be8f4f3" @@ -2468,6 +2487,44 @@ universal-user-agent "^2.0.1" url-template "^2.0.8" +"@octokit/endpoint@^6.0.1": + version "6.0.6" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.6.tgz#4f09f2b468976b444742a1d5069f6fa45826d999" + integrity sha512-7Cc8olaCoL/mtquB7j/HTbPM+sY6Ebr4k2X2y4JoXpVKQ7r5xB4iGQE0IoO58wIPsUk4AzoT65AMEpymSbWTgQ== + dependencies: + "@octokit/types" "^5.0.0" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^4.3.1": + version "4.5.6" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.5.6.tgz#708143ba15cf7c1879ed6188266e7f270be805d4" + integrity sha512-Rry+unqKTa3svswT2ZAuqenpLrzJd+JTv89LTeVa5UM/5OX8o4KTkPL7/1ABq4f/ZkELb0XEK/2IEoYwykcLXg== + dependencies: + "@octokit/request" "^5.3.0" + "@octokit/types" "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/plugin-paginate-rest@^2.2.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.4.0.tgz#92f951ddc8a1cd505353fa07650752ca25ed7e93" + integrity sha512-YT6Klz3LLH6/nNgi0pheJnUmTFW4kVnxGft+v8Itc41IIcjl7y1C8TatmKQBbCSuTSNFXO5pCENnqg6sjwpJhg== + dependencies: + "@octokit/types" "^5.5.0" + +"@octokit/plugin-request-log@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz#eef87a431300f6148c39a7f75f8cfeb218b2547e" + integrity sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw== + +"@octokit/plugin-rest-endpoint-methods@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.2.0.tgz#c5a0691b3aba5d8b4ef5dffd6af3649608f167ba" + integrity sha512-1/qn1q1C1hGz6W/iEDm9DoyNoG/xdFDt78E3eZ5hHeUfJTLJgyAMdj9chL/cNBHjcjd+FH5aO1x0VCqR2RE0mw== + dependencies: + "@octokit/types" "^5.5.0" + deprecation "^2.3.1" + "@octokit/plugin-retry@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@octokit/plugin-retry/-/plugin-retry-2.2.0.tgz#11f3957a46ccdb7b7f33caabf8c17e57b25b80b2" @@ -2475,6 +2532,15 @@ dependencies: bottleneck "^2.15.3" +"@octokit/request-error@^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.2.tgz#0e76b83f5d8fdda1db99027ea5f617c2e6ba9ed0" + integrity sha512-2BrmnvVSV1MXQvEkrb9zwzP0wXFNbPJij922kYBTLIlIafukrGOb+ABBT2+c6wZiuyWDH1K1zmjGQ0toN/wMWw== + dependencies: + "@octokit/types" "^5.0.1" + deprecation "^2.0.0" + once "^1.4.0" + "@octokit/request@2.4.2", "@octokit/request@^2.1.2", "@octokit/request@^2.4.2": version "2.4.2" resolved "https://registry.yarnpkg.com/@octokit/request/-/request-2.4.2.tgz#87c36e820dd1e43b1629f4f35c95b00cd456320b" @@ -2487,6 +2553,20 @@ once "^1.4.0" universal-user-agent "^2.0.1" +"@octokit/request@^5.3.0", "@octokit/request@^5.4.0": + version "5.4.9" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.9.tgz#0a46f11b82351b3416d3157261ad9b1558c43365" + integrity sha512-CzwVvRyimIM1h2n9pLVYfTDmX9m+KHSgCpqPsY8F1NdEK8IaWqXhSBXsdjOBFZSpEcxNEeg4p0UO9cQ8EnOCLA== + dependencies: + "@octokit/endpoint" "^6.0.1" + "@octokit/request-error" "^2.0.0" + "@octokit/types" "^5.0.0" + deprecation "^2.0.0" + is-plain-object "^5.0.0" + node-fetch "^2.6.1" + once "^1.4.0" + universal-user-agent "^6.0.0" + "@octokit/rest@^16.23.2": version "16.23.2" resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.23.2.tgz#975e84610427c4ab6c41bec77c24aed9b7563db4" @@ -2505,6 +2585,23 @@ universal-user-agent "^2.0.0" url-template "^2.0.8" +"@octokit/rest@^18.0.6": + version "18.0.6" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.0.6.tgz#76c274f1a68f40741a131768ef483f041e7b98b6" + integrity sha512-ES4lZBKPJMX/yUoQjAZiyFjei9pJ4lTTfb9k7OtYoUzKPDLl/M8jiHqt6qeSauyU4eZGLw0sgP1WiQl9FYeM5w== + dependencies: + "@octokit/core" "^3.0.0" + "@octokit/plugin-paginate-rest" "^2.2.0" + "@octokit/plugin-request-log" "^1.0.0" + "@octokit/plugin-rest-endpoint-methods" "4.2.0" + +"@octokit/types@^5.0.0", "@octokit/types@^5.0.1", "@octokit/types@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b" + integrity sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ== + dependencies: + "@types/node" ">= 8" + "@percy/agent@^0.26.0": version "0.26.0" resolved "https://registry.yarnpkg.com/@percy/agent/-/agent-0.26.0.tgz#9f06849d752df7368198835d0b5edc16c2d69a0c" @@ -4112,16 +4209,30 @@ "@types/node" "*" "@types/webpack" "*" -"@types/lodash@^4.14.159": - version "4.14.159" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.159.tgz#61089719dc6fdd9c5cb46efc827f2571d1517065" - integrity sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg== +"@types/lodash.difference@^4.5.6": + version "4.5.6" + resolved "https://registry.yarnpkg.com/@types/lodash.difference/-/lodash.difference-4.5.6.tgz#41ec5c4e684eeacf543848a9a1b2a4856ccf9853" + integrity sha512-wXH53r+uoUCrKhmh7S5Gf6zo3vpsx/zH2R4pvkmDlmopmMTCROAUXDpPMXATGCWkCjE6ik3VZzZUxBgMjZho9Q== + dependencies: + "@types/lodash" "*" -"@types/lodash@^4.14.160": +"@types/lodash.intersection@^4.4.6": + version "4.4.6" + resolved "https://registry.yarnpkg.com/@types/lodash.intersection/-/lodash.intersection-4.4.6.tgz#0fb241badf6edbb2a7d194a70c50e950e2486e68" + integrity sha512-6ewsKax7+HgT+7mEhzXT6tIyIHc/mjCwZJnarvLbCrtW21qmDQHWbaJj4Ht4DQDBmMdnvZe8APuVlsMpZ5E5mQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*", "@types/lodash@^4.14.160": version "4.14.161" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18" integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA== +"@types/lodash@^4.14.159": + version "4.14.159" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.159.tgz#61089719dc6fdd9c5cb46efc827f2571d1517065" + integrity sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg== + "@types/log-symbols@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/log-symbols/-/log-symbols-2.0.0.tgz#7919e2ec3c8d13879bfdcab310dd7a3f7fc9466d" @@ -4269,7 +4380,7 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@8.10.54", "@types/node@>=10.17.17 <10.20.0", "@types/node@>=8.9.0", "@types/node@^12.0.2": +"@types/node@*", "@types/node@8.10.54", "@types/node@>= 8", "@types/node@>=10.17.17 <10.20.0", "@types/node@>=8.9.0", "@types/node@^12.0.2": version "10.17.26" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.26.tgz#a8a119960bff16b823be4c617da028570779bcfd" integrity sha512-myMwkO2Cr82kirHY8uknNRHEVtn0wV3DTQfkrjx17jmkstDRZ24gNUdl8AHXVyVclTYI/bNjgTPTAWvWLqXqkw== @@ -7213,26 +7324,31 @@ bach@^1.0.0: async-settle "^1.0.0" now-and-later "^2.0.0" -backport@5.5.1: - version "5.5.1" - resolved "https://registry.yarnpkg.com/backport/-/backport-5.5.1.tgz#2eeddbdc4cfc0530119bdb2b0c3c30bc7ef574dd" - integrity sha512-vQuGrxxMx9H64ywqsIYUHL8+/xvPeP0nnBa0YQt5S+XqW7etaqOoa5dFW0c77ADdqjfLlGUIvtc2i6UrmqeFUQ== +backport@5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/backport/-/backport-5.6.0.tgz#6dcc0485e5eecf66bb6f950983fd0b018217ec20" + integrity sha512-wz7Ve3uslhGUMtHuctqIEtZFItTGKRRMiNANYso0iw1ar81ILsczDGgxeOlzmmnIQFi1ZvEs6lX3cgypGfef9A== dependencies: - axios "^0.19.2" + "@octokit/rest" "^18.0.6" + "@types/lodash.difference" "^4.5.6" + "@types/lodash.intersection" "^4.4.6" + axios "^0.19.0" dedent "^0.7.0" del "^5.1.0" - find-up "^4.1.0" - inquirer "^7.3.1" + find-up "^5.0.0" + inquirer "^7.3.3" + lodash.difference "^4.5.0" lodash.flatmap "^4.5.0" + lodash.intersection "^4.4.0" lodash.isempty "^4.4.0" lodash.isstring "^4.0.1" lodash.uniq "^4.5.0" make-dir "^3.1.0" - ora "^4.0.4" + ora "^5.1.0" safe-json-stringify "^1.2.0" - strip-json-comments "^3.1.0" + strip-json-comments "^3.1.1" winston "^3.3.3" - yargs "^15.4.0" + yargs "^16.0.3" bail@^1.0.0: version "1.0.2" @@ -7306,6 +7422,11 @@ before-after-hook@^1.4.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.4.0.tgz#2b6bf23dca4f32e628fd2747c10a37c74a4b484d" integrity sha512-l5r9ir56nda3qu14nAXIlyq1MmUSs0meCIaFAh8HwkFwP1F8eToOuS3ah2VAHHcY04jaYD7FpJC5JTXHYRbkzg== +before-after-hook@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" + integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== + big-integer@^1.6.16: version "1.6.48" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" @@ -8622,10 +8743,10 @@ cli-spinners@^2.0.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.1.0.tgz#22c34b4d51f573240885b201efda4e4ec9fff3c7" integrity sha512-8B00fJOEh1HPrx4fo5eW16XmE1PcL1tGpGrxy63CXGP9nHdPBN63X75hA1zhvQuhVztJWLqV58Roj2qlNM7cAA== -cli-spinners@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.2.0.tgz#e8b988d9206c692302d8ee834e7a85c0144d8f77" - integrity sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ== +cli-spinners@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.4.0.tgz#c6256db216b878cfba4720e719cec7cf72685d7f" + integrity sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA== cli-table3@0.5.1: version "0.5.1" @@ -8744,6 +8865,15 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +cliui@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.1.tgz#a4cb67aad45cd83d8d05128fc9f4d8fbb887e6b3" + integrity sha512-rcvHOWyGyid6I1WjT/3NatKj2kDt9OdSHSXpyLXaMWFbKpGACNW8pRhhdPUq9MWUOdwn8Rz9AVETjF4105rZZQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + clone-buffer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" @@ -10639,6 +10769,11 @@ deprecation@^1.0.1: resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-1.0.1.tgz#2df79b79005752180816b7b6e079cbd80490d711" integrity sha512-ccVHpE72+tcIKaGMql33x5MAjKQIZrk+3x2GbJ7TeraUCZWHoT+KSZpoC+JQFsUBlSTXUrBaGiF0j6zVTepPLg== +deprecation@^2.0.0, deprecation@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" @@ -11755,6 +11890,11 @@ es6-weak-map@^2.0.1, es6-weak-map@^2.0.2: es6-iterator "^2.0.1" es6-symbol "^3.1.1" +escalade@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e" + integrity sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig== + escape-goat@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" @@ -13059,6 +13199,14 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + find-versions@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-2.0.0.tgz#2ad90d490f6828c1aa40292cf709ac3318210c3c" @@ -13694,7 +13842,7 @@ get-caller-file@^1.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== -get-caller-file@^2.0.1: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -16006,7 +16154,7 @@ inquirer@^6.0.0: strip-ansi "^5.1.0" through "^2.3.6" -inquirer@^7.0.0, inquirer@^7.3.1, inquirer@^7.3.3: +inquirer@^7.0.0, inquirer@^7.3.3: version "7.3.3" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== @@ -16677,6 +16825,11 @@ is-plain-object@3.0.0, is-plain-object@^3.0.0: dependencies: isobject "^4.0.0" +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-promise@^2.1, is-promise@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" @@ -18711,6 +18864,13 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + locutus@^2.0.5: version "2.0.10" resolved "https://registry.yarnpkg.com/locutus/-/locutus-2.0.10.tgz#f903619466a98a4ab76e8b87a5854b55a743b917" @@ -19011,7 +19171,7 @@ log-symbols@2.2.0, log-symbols@^2.0.0, log-symbols@^2.1.0, log-symbols@^2.2.0: dependencies: chalk "^2.0.1" -log-symbols@3.0.0, log-symbols@^3.0.0: +log-symbols@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== @@ -21460,16 +21620,16 @@ ora@^3.0.0: strip-ansi "^5.2.0" wcwidth "^1.0.1" -ora@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/ora/-/ora-4.0.4.tgz#e8da697cc5b6a47266655bf68e0fb588d29a545d" - integrity sha512-77iGeVU1cIdRhgFzCK8aw1fbtT1B/iZAvWjS+l/o1x0RShMgxHUZaD2yDpWsNCPwXg9z1ZA78Kbdvr8kBmG/Ww== +ora@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.1.0.tgz#b188cf8cd2d4d9b13fd25383bc3e5cba352c94f8" + integrity sha512-9tXIMPvjZ7hPTbk8DFq1f7Kow/HU/pQYB60JbNq+QnGwcyhWVZaQ4hM9zQDEsPxw/muLpgiHSaumUZxCAmod/w== dependencies: - chalk "^3.0.0" + chalk "^4.1.0" cli-cursor "^3.1.0" - cli-spinners "^2.2.0" + cli-spinners "^2.4.0" is-interactive "^1.0.0" - log-symbols "^3.0.0" + log-symbols "^4.0.0" mute-stream "0.0.8" strip-ansi "^6.0.0" wcwidth "^1.0.1" @@ -21640,6 +21800,13 @@ p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.3.0: dependencies: p-try "^2.0.0" +p-limit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe" + integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg== + dependencies: + p-try "^2.0.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -21661,6 +21828,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-map@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" @@ -26947,10 +27121,10 @@ strip-json-comments@^3.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== -strip-json-comments@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" - integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== strip-json-comments@~1.0.1: version "1.0.4" @@ -28688,6 +28862,11 @@ universal-user-agent@^2.0.0, universal-user-agent@^2.0.1: dependencies: os-name "^3.0.0" +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -30272,6 +30451,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -30521,6 +30709,11 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== +y18n@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.1.tgz#1ad2a7eddfa8bce7caa2e1f6b5da96c39d99d571" + integrity sha512-/jJ831jEs4vGDbYPQp4yGKDYPSCCEQ45uZWJHE1AoYBzqdZi8+LDWas0z4HrmJXmKdpFsTiowSHXdxyFhpmdMg== + yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" @@ -30582,6 +30775,11 @@ yargs-parser@^18.1.1, yargs-parser@^18.1.2, yargs-parser@^18.1.3: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^20.0.0: + version "20.2.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.0.tgz#944791ca2be2e08ddadd3d87e9de4c6484338605" + integrity sha512-2agPoRFPoIcFzOIp6656gcvsg2ohtscpw2OINr/q46+Sq41xz2OYLqx5HRHabmFU1OARIPAYH5uteICE7mn/5A== + yargs-unparser@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" @@ -30624,7 +30822,7 @@ yargs@13.3.2, yargs@^13.2.2, yargs@^13.3.0, yargs@^13.3.2: y18n "^4.0.0" yargs-parser "^13.1.2" -yargs@^15.0.2, yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.0, yargs@^15.4.1: +yargs@^15.0.2, yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.1: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== @@ -30641,6 +30839,19 @@ yargs@^15.0.2, yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.0, yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^16.0.3: + version "16.0.3" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.0.3.tgz#7a919b9e43c90f80d4a142a89795e85399a7e54c" + integrity sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA== + dependencies: + cliui "^7.0.0" + escalade "^3.0.2" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.1" + yargs-parser "^20.0.0" + yargs@^3.15.0: version "3.32.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995"