mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[APM] Migrate `/data_view` to deployment agnostic test (#199296)](https://github.com/elastic/kibana/pull/199296) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Sergi Romeu","email":"sergi.romeu@elastic.co"},"sourceCommit":{"committedDate":"2024-11-12T10:41:34Z","message":"[APM] Migrate `/data_view` to deployment agnostic test (#199296)\n\n## Summary\n\nCloses https://github.com/elastic/kibana/issues/198965\nPart of https://github.com/elastic/kibana/issues/193245\n\nThis PR contains the changes to migrate `data_view` test folder to\nDeployment-agnostic testing strategy.\n\n### How to test\n\n- Serverless\n\n```\nnode scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts\nnode scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts --grep=\"APM\"\n```\n\nIt's recommended to be run against\n[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)\n\n- Stateful\n```\nnode scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts\nnode scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts --grep=\"APM\"\n```\n\n## Checks\n\n- [x] (OPTIONAL, only if a test has been unskipped) Run flaky test suite\n- [x] local run for serverless\n- [x] local run for stateful\n- [x] MKI run for serverless","sha":"803738fa0c8fea3cdfc4760c7b3dd667637de724","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-infra_services"],"title":"[APM] Migrate `/data_view` to deployment agnostic test","number":199296,"url":"https://github.com/elastic/kibana/pull/199296","mergeCommit":{"message":"[APM] Migrate `/data_view` to deployment agnostic test (#199296)\n\n## Summary\n\nCloses https://github.com/elastic/kibana/issues/198965\nPart of https://github.com/elastic/kibana/issues/193245\n\nThis PR contains the changes to migrate `data_view` test folder to\nDeployment-agnostic testing strategy.\n\n### How to test\n\n- Serverless\n\n```\nnode scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts\nnode scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts --grep=\"APM\"\n```\n\nIt's recommended to be run against\n[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)\n\n- Stateful\n```\nnode scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts\nnode scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts --grep=\"APM\"\n```\n\n## Checks\n\n- [x] (OPTIONAL, only if a test has been unskipped) Run flaky test suite\n- [x] local run for serverless\n- [x] local run for stateful\n- [x] MKI run for serverless","sha":"803738fa0c8fea3cdfc4760c7b3dd667637de724"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/199296","number":199296,"mergeCommit":{"message":"[APM] Migrate `/data_view` to deployment agnostic test (#199296)\n\n## Summary\n\nCloses https://github.com/elastic/kibana/issues/198965\nPart of https://github.com/elastic/kibana/issues/193245\n\nThis PR contains the changes to migrate `data_view` test folder to\nDeployment-agnostic testing strategy.\n\n### How to test\n\n- Serverless\n\n```\nnode scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts\nnode scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts --grep=\"APM\"\n```\n\nIt's recommended to be run against\n[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)\n\n- Stateful\n```\nnode scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts\nnode scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts --grep=\"APM\"\n```\n\n## Checks\n\n- [x] (OPTIONAL, only if a test has been unskipped) Run flaky test suite\n- [x] local run for serverless\n- [x] local run for stateful\n- [x] MKI run for serverless","sha":"803738fa0c8fea3cdfc4760c7b3dd667637de724"}}]}] BACKPORT--> Co-authored-by: Sergi Romeu <sergi.romeu@elastic.co>
This commit is contained in:
parent
ee0934ba8b
commit
8fe516a539
4 changed files with 306 additions and 268 deletions
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
describe('data_view', () => {
|
||||||
|
loadTestFile(require.resolve('./static.spec.ts'));
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,291 @@
|
||||||
|
/*
|
||||||
|
* 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 { apm, timerange } from '@kbn/apm-synthtrace-client';
|
||||||
|
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||||
|
import expect from '@kbn/expect';
|
||||||
|
import { DataView } from '@kbn/data-views-plugin/common';
|
||||||
|
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||||
|
import request from 'superagent';
|
||||||
|
import { getStaticDataViewId } from '@kbn/apm-data-view';
|
||||||
|
import { SupertestWithRoleScope } from '../../../../services/role_scoped_supertest';
|
||||||
|
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
|
||||||
|
import {
|
||||||
|
SupertestReturnType,
|
||||||
|
ApmApiError,
|
||||||
|
} from '../../../../../../apm_api_integration/common/apm_api_supertest';
|
||||||
|
|
||||||
|
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
|
const apmApiClient = getService('apmApi');
|
||||||
|
const roleScopedSupertest = getService('roleScopedSupertest');
|
||||||
|
const synthtraceService = getService('synthtrace');
|
||||||
|
const logger = getService('log');
|
||||||
|
const dataViewPattern =
|
||||||
|
'traces-apm*,apm-*,traces-*.otel-*,logs-apm*,apm-*,logs-*.otel-*,metrics-apm*,apm-*,metrics-*.otel-*';
|
||||||
|
|
||||||
|
function createDataViewWithWriteUser({ spaceId }: { spaceId: string }) {
|
||||||
|
return apmApiClient.writeUser({
|
||||||
|
endpoint: 'POST /internal/apm/data_view/static',
|
||||||
|
spaceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDataViewWithReadUser({ spaceId }: { spaceId: string }) {
|
||||||
|
return apmApiClient.readUser({
|
||||||
|
endpoint: 'POST /internal/apm/data_view/static',
|
||||||
|
spaceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Data view static', () => {
|
||||||
|
let supertestEditorWithApiKey: SupertestWithRoleScope;
|
||||||
|
let supertestEditorWithCookieCredentials: SupertestWithRoleScope;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
supertestEditorWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('editor', {
|
||||||
|
withInternalHeaders: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
supertestEditorWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope(
|
||||||
|
'editor',
|
||||||
|
{
|
||||||
|
useCookieHeader: true,
|
||||||
|
withInternalHeaders: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await supertestEditorWithApiKey.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
function deleteDataView(spaceId: string) {
|
||||||
|
return supertestEditorWithApiKey.delete(
|
||||||
|
`/s/${spaceId}/api/saved_objects/index-pattern/${getStaticDataViewId(spaceId)}?force=true`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDataView({ spaceId }: { spaceId: string }) {
|
||||||
|
const spacePrefix = spaceId !== 'default' ? `/s/${spaceId}` : '';
|
||||||
|
return supertestEditorWithApiKey.get(
|
||||||
|
`${spacePrefix}/api/saved_objects/index-pattern/${getStaticDataViewId(spaceId)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDataViewSuggestions(field: string) {
|
||||||
|
return supertestEditorWithCookieCredentials
|
||||||
|
.post(`/internal/kibana/suggestions/values/${dataViewPattern}`)
|
||||||
|
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
|
||||||
|
.send({ query: '', field, method: 'terms_agg' });
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('no mappings exist', () => {
|
||||||
|
let response: SupertestReturnType<'POST /internal/apm/data_view/static'>;
|
||||||
|
before(async () => {
|
||||||
|
response = await createDataViewWithWriteUser({ spaceId: 'default' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not create data view', async () => {
|
||||||
|
expect(response.status).to.be(200);
|
||||||
|
expect(response.body).to.eql({
|
||||||
|
created: false,
|
||||||
|
reason: 'No APM data',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot fetch data view', async () => {
|
||||||
|
const res = await getDataView({ spaceId: 'default' });
|
||||||
|
expect(res.status).to.be(404);
|
||||||
|
expect(res.body.message).to.eql(
|
||||||
|
'Saved object [index-pattern/apm_static_data_view_id_default] not found'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when mappings and APM data exists', () => {
|
||||||
|
let synthtrace: ApmSynthtraceEsClient;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
synthtrace = await synthtraceService.createApmSynthtraceEsClient();
|
||||||
|
await generateApmData(synthtrace);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await synthtrace.clean();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
try {
|
||||||
|
await Promise.all([deleteDataView('default'), deleteDataView('foo')]);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`Could not delete data views ${e.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when creating data view with write user', () => {
|
||||||
|
let response: SupertestReturnType<'POST /internal/apm/data_view/static'>;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
response = await createDataViewWithWriteUser({ spaceId: 'default' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('successfully creates the apm data view', async () => {
|
||||||
|
expect(response.status).to.be(200);
|
||||||
|
|
||||||
|
const dataView = (response.body as { dataView: DataView }).dataView;
|
||||||
|
|
||||||
|
expect(dataView.id).to.be('apm_static_data_view_id_default');
|
||||||
|
expect(dataView.name).to.be('APM');
|
||||||
|
expect(dataView.title).to.be(
|
||||||
|
'traces-apm*,apm-*,traces-*.otel-*,logs-apm*,apm-*,logs-*.otel-*,metrics-apm*,apm-*,metrics-*.otel-*'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when fetching the data view', () => {
|
||||||
|
let dataViewResponse: request.Response;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await createDataViewWithWriteUser({ spaceId: 'default' });
|
||||||
|
dataViewResponse = await getDataView({ spaceId: 'default' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return 200', () => {
|
||||||
|
expect(dataViewResponse.status).to.be(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has correct id', () => {
|
||||||
|
expect(dataViewResponse.body.id).to.be('apm_static_data_view_id_default');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has correct title', () => {
|
||||||
|
expect(dataViewResponse.body.attributes.title).to.be(dataViewPattern);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has correct attributes', () => {
|
||||||
|
expect(dataViewResponse.body.attributes.fieldFormatMap).to.be(
|
||||||
|
JSON.stringify({
|
||||||
|
'trace.id': {
|
||||||
|
id: 'url',
|
||||||
|
params: {
|
||||||
|
urlTemplate: 'apm/link-to/trace/{{value}}',
|
||||||
|
labelTemplate: '{{value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'transaction.id': {
|
||||||
|
id: 'url',
|
||||||
|
params: {
|
||||||
|
urlTemplate: 'apm/link-to/transaction/{{value}}',
|
||||||
|
labelTemplate: '{{value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'transaction.duration.us': {
|
||||||
|
id: 'duration',
|
||||||
|
params: {
|
||||||
|
inputFormat: 'microseconds',
|
||||||
|
outputFormat: 'asMilliseconds',
|
||||||
|
showSuffix: true,
|
||||||
|
useShortSuffix: true,
|
||||||
|
outputPrecision: 2,
|
||||||
|
includeSpaceWithSuffix: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// this test ensures that the default APM Data View doesn't interfere with suggestions returned in the kuery bar (this has been a problem in the past)
|
||||||
|
it('can get suggestions for `trace.id`', async () => {
|
||||||
|
const suggestions = await getDataViewSuggestions('trace.id');
|
||||||
|
expect(suggestions.body.length).to.be(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when creating data view via read user', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
try {
|
||||||
|
await createDataViewWithReadUser({ spaceId: 'default' });
|
||||||
|
} catch (e) {
|
||||||
|
const err = e as ApmApiError;
|
||||||
|
const responseBody = err.res.body;
|
||||||
|
expect(err.res.status).to.eql(403);
|
||||||
|
expect(responseBody.statusCode).to.eql(403);
|
||||||
|
expect(responseBody.error).to.eql('Forbidden');
|
||||||
|
expect(responseBody.message).to.eql('Unable to create index-pattern');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when creating data view twice', () => {
|
||||||
|
it('returns 200 response with reason, if data view already exists', async () => {
|
||||||
|
await createDataViewWithWriteUser({ spaceId: 'default' });
|
||||||
|
const res = await createDataViewWithWriteUser({ spaceId: 'default' });
|
||||||
|
|
||||||
|
expect(res.status).to.be(200);
|
||||||
|
expect(res.body).to.eql({
|
||||||
|
created: false,
|
||||||
|
reason: 'Dataview already exists in the active space and does not need to be updated',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when creating data view in "default" space', () => {
|
||||||
|
it('can be retrieved from the "default" space', async () => {
|
||||||
|
await createDataViewWithWriteUser({ spaceId: 'default' });
|
||||||
|
const res = await getDataView({ spaceId: 'default' });
|
||||||
|
expect(res.body.id).to.eql('apm_static_data_view_id_default');
|
||||||
|
expect(res.body.namespaces).to.eql(['default']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot be retrieved from the "foo" space', async () => {
|
||||||
|
await createDataViewWithWriteUser({ spaceId: 'default' });
|
||||||
|
const res = await getDataView({ spaceId: 'foo' });
|
||||||
|
expect(res.body.statusCode).to.be(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when creating data view in "foo" space', () => {
|
||||||
|
it('can be retrieved from the "foo" space', async () => {
|
||||||
|
await createDataViewWithWriteUser({ spaceId: 'foo' });
|
||||||
|
const res = await getDataView({ spaceId: 'foo' });
|
||||||
|
expect(res.body.id).to.eql('apm_static_data_view_id_foo');
|
||||||
|
expect(res.body.namespaces).to.eql(['foo']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot be retrieved from the "default" space', async () => {
|
||||||
|
await createDataViewWithWriteUser({ spaceId: 'foo' });
|
||||||
|
const res = await getDataView({ spaceId: 'default' });
|
||||||
|
expect(res.body.statusCode).to.be(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateApmData(synthtrace: ApmSynthtraceEsClient) {
|
||||||
|
const range = timerange(
|
||||||
|
new Date('2021-10-01T00:00:00.000Z').getTime(),
|
||||||
|
new Date('2021-10-01T00:01:00.000Z').getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
const instance = apm
|
||||||
|
.service({ name: 'my-service', environment: 'production', agentName: 'go' })
|
||||||
|
.instance('my-instance');
|
||||||
|
|
||||||
|
return synthtrace.index([
|
||||||
|
range
|
||||||
|
.interval('1s')
|
||||||
|
.rate(1)
|
||||||
|
.generator((timestamp) =>
|
||||||
|
instance
|
||||||
|
.transaction({ transactionName: 'GET /api' })
|
||||||
|
.timestamp(timestamp)
|
||||||
|
.duration(30)
|
||||||
|
.success()
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ export default function apmApiIntegrationTests({
|
||||||
loadTestFile(require.resolve('./agent_explorer'));
|
loadTestFile(require.resolve('./agent_explorer'));
|
||||||
loadTestFile(require.resolve('./custom_dashboards'));
|
loadTestFile(require.resolve('./custom_dashboards'));
|
||||||
loadTestFile(require.resolve('./dependencies'));
|
loadTestFile(require.resolve('./dependencies'));
|
||||||
|
loadTestFile(require.resolve('./data_view'));
|
||||||
loadTestFile(require.resolve('./correlations'));
|
loadTestFile(require.resolve('./correlations'));
|
||||||
loadTestFile(require.resolve('./entities'));
|
loadTestFile(require.resolve('./entities'));
|
||||||
loadTestFile(require.resolve('./cold_start'));
|
loadTestFile(require.resolve('./cold_start'));
|
||||||
|
|
|
@ -1,268 +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
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { apm, timerange } from '@kbn/apm-synthtrace-client';
|
|
||||||
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
|
||||||
import expect from '@kbn/expect';
|
|
||||||
import { DataView } from '@kbn/data-views-plugin/common';
|
|
||||||
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
|
||||||
import request from 'superagent';
|
|
||||||
import { getStaticDataViewId } from '@kbn/apm-data-view';
|
|
||||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
|
||||||
import { SupertestReturnType, ApmApiError } from '../../common/apm_api_supertest';
|
|
||||||
|
|
||||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
|
||||||
const registry = getService('registry');
|
|
||||||
const apmApiClient = getService('apmApiClient');
|
|
||||||
const supertest = getService('supertest');
|
|
||||||
const synthtrace = getService('apmSynthtraceEsClient');
|
|
||||||
const logger = getService('log');
|
|
||||||
const dataViewPattern =
|
|
||||||
'traces-apm*,apm-*,traces-*.otel-*,logs-apm*,apm-*,logs-*.otel-*,metrics-apm*,apm-*,metrics-*.otel-*';
|
|
||||||
|
|
||||||
function createDataViewWithWriteUser({ spaceId }: { spaceId: string }) {
|
|
||||||
return apmApiClient.writeUser({
|
|
||||||
endpoint: 'POST /internal/apm/data_view/static',
|
|
||||||
spaceId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDataViewWithReadUser({ spaceId }: { spaceId: string }) {
|
|
||||||
return apmApiClient.readUser({
|
|
||||||
endpoint: 'POST /internal/apm/data_view/static',
|
|
||||||
spaceId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteDataView(spaceId: string) {
|
|
||||||
return supertest
|
|
||||||
.delete(
|
|
||||||
`/s/${spaceId}/api/saved_objects/index-pattern/${getStaticDataViewId(spaceId)}?force=true`
|
|
||||||
)
|
|
||||||
.set('kbn-xsrf', 'foo');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDataView({ spaceId }: { spaceId: string }) {
|
|
||||||
const spacePrefix = spaceId !== 'default' ? `/s/${spaceId}` : '';
|
|
||||||
return supertest.get(
|
|
||||||
`${spacePrefix}/api/saved_objects/index-pattern/${getStaticDataViewId(spaceId)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDataViewSuggestions(field: string) {
|
|
||||||
return supertest
|
|
||||||
.post(`/internal/kibana/suggestions/values/${dataViewPattern}`)
|
|
||||||
.set('kbn-xsrf', 'foo')
|
|
||||||
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
|
|
||||||
.send({ query: '', field, method: 'terms_agg' });
|
|
||||||
}
|
|
||||||
|
|
||||||
registry.when('no mappings exist', { config: 'basic', archives: [] }, () => {
|
|
||||||
let response: SupertestReturnType<'POST /internal/apm/data_view/static'>;
|
|
||||||
before(async () => {
|
|
||||||
response = await createDataViewWithWriteUser({ spaceId: 'default' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not create data view', async () => {
|
|
||||||
expect(response.status).to.be(200);
|
|
||||||
expect(response.body).to.eql({
|
|
||||||
created: false,
|
|
||||||
reason: 'No APM data',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cannot fetch data view', async () => {
|
|
||||||
const res = await getDataView({ spaceId: 'default' });
|
|
||||||
expect(res.status).to.be(404);
|
|
||||||
expect(res.body.message).to.eql(
|
|
||||||
'Saved object [index-pattern/apm_static_data_view_id_default] not found'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// FLAKY: https://github.com/elastic/kibana/issues/177120
|
|
||||||
registry.when('mappings and APM data exists', { config: 'basic', archives: [] }, () => {
|
|
||||||
// eslint-disable-next-line mocha/no-sibling-hooks
|
|
||||||
before(async () => {
|
|
||||||
await generateApmData(synthtrace);
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await synthtrace.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
try {
|
|
||||||
await Promise.all([deleteDataView('default'), deleteDataView('foo')]);
|
|
||||||
} catch (e) {
|
|
||||||
logger.error(`Could not delete data views ${e.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when creating data view with write user', () => {
|
|
||||||
let response: SupertestReturnType<'POST /internal/apm/data_view/static'>;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
response = await createDataViewWithWriteUser({ spaceId: 'default' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('successfully creates the apm data view', async () => {
|
|
||||||
expect(response.status).to.be(200);
|
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
const dataView = response.body.dataView as DataView;
|
|
||||||
|
|
||||||
expect(dataView.id).to.be('apm_static_data_view_id_default');
|
|
||||||
expect(dataView.name).to.be('APM');
|
|
||||||
expect(dataView.title).to.be(
|
|
||||||
'traces-apm*,apm-*,traces-*.otel-*,logs-apm*,apm-*,logs-*.otel-*,metrics-apm*,apm-*,metrics-*.otel-*'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when fetching the data view', () => {
|
|
||||||
let dataViewResponse: request.Response;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
await createDataViewWithWriteUser({ spaceId: 'default' });
|
|
||||||
dataViewResponse = await getDataView({ spaceId: 'default' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('return 200', () => {
|
|
||||||
expect(dataViewResponse.status).to.be(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has correct id', () => {
|
|
||||||
expect(dataViewResponse.body.id).to.be('apm_static_data_view_id_default');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has correct title', () => {
|
|
||||||
expect(dataViewResponse.body.attributes.title).to.be(dataViewPattern);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has correct attributes', () => {
|
|
||||||
expect(dataViewResponse.body.attributes.fieldFormatMap).to.be(
|
|
||||||
JSON.stringify({
|
|
||||||
'trace.id': {
|
|
||||||
id: 'url',
|
|
||||||
params: {
|
|
||||||
urlTemplate: 'apm/link-to/trace/{{value}}',
|
|
||||||
labelTemplate: '{{value}}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'transaction.id': {
|
|
||||||
id: 'url',
|
|
||||||
params: {
|
|
||||||
urlTemplate: 'apm/link-to/transaction/{{value}}',
|
|
||||||
labelTemplate: '{{value}}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'transaction.duration.us': {
|
|
||||||
id: 'duration',
|
|
||||||
params: {
|
|
||||||
inputFormat: 'microseconds',
|
|
||||||
outputFormat: 'asMilliseconds',
|
|
||||||
showSuffix: true,
|
|
||||||
useShortSuffix: true,
|
|
||||||
outputPrecision: 2,
|
|
||||||
includeSpaceWithSuffix: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// this test ensures that the default APM Data View doesn't interfere with suggestions returned in the kuery bar (this has been a problem in the past)
|
|
||||||
it('can get suggestions for `trace.id`', async () => {
|
|
||||||
const suggestions = await getDataViewSuggestions('trace.id');
|
|
||||||
expect(suggestions.body.length).to.be(10);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when creating data view via read user', () => {
|
|
||||||
it('throws an error', async () => {
|
|
||||||
try {
|
|
||||||
await createDataViewWithReadUser({ spaceId: 'default' });
|
|
||||||
} catch (e) {
|
|
||||||
const err = e as ApmApiError;
|
|
||||||
const responseBody = err.res.body;
|
|
||||||
expect(err.res.status).to.eql(403);
|
|
||||||
expect(responseBody.statusCode).to.eql(403);
|
|
||||||
expect(responseBody.error).to.eql('Forbidden');
|
|
||||||
expect(responseBody.message).to.eql('Unable to create index-pattern');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when creating data view twice', () => {
|
|
||||||
it('returns 200 response with reason, if data view already exists', async () => {
|
|
||||||
await createDataViewWithWriteUser({ spaceId: 'default' });
|
|
||||||
const res = await createDataViewWithWriteUser({ spaceId: 'default' });
|
|
||||||
|
|
||||||
expect(res.status).to.be(200);
|
|
||||||
expect(res.body).to.eql({
|
|
||||||
created: false,
|
|
||||||
reason: 'Dataview already exists in the active space and does not need to be updated',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when creating data view in "default" space', () => {
|
|
||||||
it('can be retrieved from the "default" space', async () => {
|
|
||||||
await createDataViewWithWriteUser({ spaceId: 'default' });
|
|
||||||
const res = await getDataView({ spaceId: 'default' });
|
|
||||||
expect(res.body.id).to.eql('apm_static_data_view_id_default');
|
|
||||||
expect(res.body.namespaces).to.eql(['default']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cannot be retrieved from the "foo" space', async () => {
|
|
||||||
await createDataViewWithWriteUser({ spaceId: 'default' });
|
|
||||||
const res = await getDataView({ spaceId: 'foo' });
|
|
||||||
expect(res.body.statusCode).to.be(404);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when creating data view in "foo" space', () => {
|
|
||||||
it('can be retrieved from the "foo" space', async () => {
|
|
||||||
await createDataViewWithWriteUser({ spaceId: 'foo' });
|
|
||||||
const res = await getDataView({ spaceId: 'foo' });
|
|
||||||
expect(res.body.id).to.eql('apm_static_data_view_id_foo');
|
|
||||||
expect(res.body.namespaces).to.eql(['foo']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cannot be retrieved from the "default" space', async () => {
|
|
||||||
await createDataViewWithWriteUser({ spaceId: 'foo' });
|
|
||||||
const res = await getDataView({ spaceId: 'default' });
|
|
||||||
expect(res.body.statusCode).to.be(404);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateApmData(synthtrace: ApmSynthtraceEsClient) {
|
|
||||||
const range = timerange(
|
|
||||||
new Date('2021-10-01T00:00:00.000Z').getTime(),
|
|
||||||
new Date('2021-10-01T00:01:00.000Z').getTime()
|
|
||||||
);
|
|
||||||
|
|
||||||
const instance = apm
|
|
||||||
.service({ name: 'my-service', environment: 'production', agentName: 'go' })
|
|
||||||
.instance('my-instance');
|
|
||||||
|
|
||||||
return synthtrace.index([
|
|
||||||
range
|
|
||||||
.interval('1s')
|
|
||||||
.rate(1)
|
|
||||||
.generator((timestamp) =>
|
|
||||||
instance
|
|
||||||
.transaction({ transactionName: 'GET /api' })
|
|
||||||
.timestamp(timestamp)
|
|
||||||
.duration(30)
|
|
||||||
.success()
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue