From 0993a1c32114a03251bb0dabf8662024e7f13fef Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Mon, 14 Jun 2021 15:26:13 +0200 Subject: [PATCH] [APM] Display automatic deployment annotations correctly (#102020) --- .../get_derived_service_annotations.ts | 2 +- .../test/apm_api_integration/tests/index.ts | 1 + .../tests/services/derived_annotations.ts | 181 ++++++++++++++++++ 3 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 x-pack/test/apm_api_integration/tests/services/derived_annotations.ts diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts index 028c8c042c8dc3..611f9b18a0b1a2 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts @@ -92,7 +92,7 @@ export async function getDerivedServiceAnnotations({ }, }, sort: { - '@timestamp': 'desc', + '@timestamp': 'asc', }, }, }); diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index 7c38f37093fa43..813e0e4f3cdb89 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -84,6 +84,7 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte describe('services/annotations', function () { loadTestFile(require.resolve('./services/annotations')); + loadTestFile(require.resolve('./services/derived_annotations')); }); describe('services/service_details', function () { diff --git a/x-pack/test/apm_api_integration/tests/services/derived_annotations.ts b/x-pack/test/apm_api_integration/tests/services/derived_annotations.ts new file mode 100644 index 00000000000000..2ff4eb7e733066 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/derived_annotations.ts @@ -0,0 +1,181 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { createApmApiSupertest } from '../../common/apm_api_supertest'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function annotationApiTests({ getService }: FtrProviderContext) { + const supertestRead = createApmApiSupertest(getService('supertestAsApmReadUser')); + const es = getService('es'); + + const dates = [ + new Date('2021-02-01T00:00:00.000Z'), + new Date('2021-02-01T01:00:00.000Z'), + new Date('2021-02-01T02:00:00.000Z'), + new Date('2021-02-01T03:00:00.000Z'), + ]; + + const indexName = 'apm-8.0.0-transaction'; + + registry.when( + 'Derived deployment annotations with a basic license', + { config: 'basic', archives: [] }, + () => { + describe('when there are multiple service versions', () => { + let response: APIReturnType<'GET /api/apm/services/{serviceName}/annotation/search'>; + + before(async () => { + const { body: indexExists } = await es.indices.exists({ index: indexName }); + if (indexExists) { + await es.indices.delete({ + index: indexName, + }); + } + + await es.indices.create({ + index: indexName, + body: { + mappings: { + properties: { + service: { + properties: { + name: { + type: 'keyword', + }, + version: { + type: 'keyword', + }, + environment: { + type: 'keyword', + }, + }, + }, + transaction: { + properties: { + type: { + type: 'keyword', + }, + duration: { + type: 'long', + }, + }, + }, + observer: { + properties: { + version_major: { + type: 'byte', + }, + }, + }, + processor: { + properties: { + event: { + type: 'keyword', + }, + }, + }, + }, + }, + }, + }); + + const docs = dates.flatMap((date, index) => { + const baseAnnotation = { + transaction: { + type: 'request', + duration: 1000000, + }, + + service: { + name: 'opbeans-java', + environment: 'production', + version: index + 1, + }, + observer: { + version_major: 8, + }, + processor: { + event: 'transaction', + }, + }; + return [ + { + ...baseAnnotation, + '@timestamp': date.toISOString(), + }, + { + ...baseAnnotation, + '@timestamp': new Date(date.getTime() + 30000), + }, + { + ...baseAnnotation, + '@timestamp': new Date(date.getTime() + 60000), + }, + ]; + }); + + await es.bulk({ + index: indexName, + body: docs.flatMap((doc) => [{ index: {} }, doc]), + refresh: true, + }); + + response = ( + await supertestRead({ + endpoint: 'GET /api/apm/services/{serviceName}/annotation/search', + params: { + path: { + serviceName: 'opbeans-java', + }, + query: { + start: dates[1].toISOString(), + end: dates[2].toISOString(), + environment: 'production', + }, + }, + }) + ).body; + }); + + it('annotations are displayed for the service versions in the given time range', async () => { + expect(response.annotations.length).to.be(2); + expect(response.annotations[0]['@timestamp']).to.be(dates[1].getTime()); + expect(response.annotations[1]['@timestamp']).to.be(dates[2].getTime()); + + expectSnapshot(response.annotations[0]).toMatchInline(` + Object { + "@timestamp": 1612141200000, + "id": "2", + "text": "2", + "type": "version", + } + `); + }); + + it('annotations are not displayed for the service versions outside of the given time range', () => { + expect( + response.annotations.some((annotation) => { + return ( + annotation['@timestamp'] !== dates[0].getTime() && + annotation['@timestamp'] !== dates[2].getTime() + ); + }) + ); + }); + + after(async () => { + await es.indices.delete({ + index: indexName, + }); + }); + }); + } + ); +}