Functional tests for execution context propagation in alerting and task manager plugins (#111179) (#112066)

* move execution context tests to x-pack folder

* add execution context tests for alerts and tasks

* cleanup tests

* remove obsolete test folder

* use ToolingLog instead of console.log
# Conflicts:
#	scripts/functional_tests.js
#	x-pack/scripts/functional_tests.js
This commit is contained in:
Mikhail Shustov 2021-09-14 15:43:55 +03:00 committed by GitHub
parent e2d87731de
commit 6eb230d23c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 715 additions and 405 deletions

View file

@ -17,5 +17,4 @@ require('@kbn/test').runTestsCli([
require.resolve('../test/new_visualize_flow/config.ts'),
require.resolve('../test/security_functional/config.ts'),
require.resolve('../test/functional/config.legacy.ts'),
require.resolve('../test/functional_execution_context/config.ts'),
]);

View file

@ -1,11 +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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { services as functionalServices } from '../functional/services';
export const services = functionalServices;

View file

@ -1,374 +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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { Ecs, KibanaExecutionContext } from 'kibana/server';
import Fs from 'fs/promises';
import Path from 'path';
import { isEqual } from 'lodash';
import type { FtrProviderContext } from '../ftr_provider_context';
const logFilePath = Path.resolve(__dirname, '../kibana.log');
// to avoid splitting log record containing \n symbol
const endOfLine = /(?<=})\s*\n/;
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'home']);
const retry = getService('retry');
async function assertLogContains(
description: string,
predicate: (record: Ecs) => boolean
): Promise<void> {
// logs are written to disk asynchronously. I sacrificed performance to reduce flakiness.
await retry.waitFor(description, async () => {
const logsStr = await Fs.readFile(logFilePath, 'utf-8');
const normalizedRecords = logsStr
.split(endOfLine)
.filter(Boolean)
.map((s) => JSON.parse(s));
return normalizedRecords.some(predicate);
});
}
function isExecutionContextLog(
record: string | undefined,
executionContext: KibanaExecutionContext
) {
if (!record) return false;
try {
const object = JSON.parse(record);
return isEqual(object, executionContext);
} catch (e) {
return false;
}
}
describe('Execution context service', () => {
before(async () => {
await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', {
useActualUrl: true,
});
await PageObjects.home.addSampleDataSet('flights');
await PageObjects.header.waitUntilLoadingHasFinished();
});
after(async () => {
await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', {
useActualUrl: true,
});
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.home.removeSampleDataSet('flights');
});
describe('discover app', () => {
before(async () => {
await PageObjects.common.navigateToApp('discover');
await PageObjects.header.waitUntilLoadingHasFinished();
});
it('propagates context for Discover', async () => {
await assertLogContains(
'execution context propagates to Elasticsearch via "x-opaque-id" header',
(record) => Boolean(record.http?.request?.id?.includes('kibana:application:discover'))
);
await assertLogContains('execution context propagates to Kibana logs', (record) =>
isExecutionContextLog(record?.message, {
description: 'fetch documents',
id: '',
name: 'discover',
type: 'application',
// discovery doesn't have an URL since one of from the example dataset is not saved separately
url: '/app/discover',
})
);
await assertLogContains('execution context propagates to Kibana logs', (record) =>
isExecutionContextLog(record?.message, {
description: 'fetch chart data and total hits',
id: '',
name: 'discover',
type: 'application',
url: '/app/discover',
})
);
});
});
describe('dashboard app', () => {
before(async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.loadSavedDashboard('[Flights] Global Flight Dashboard');
await PageObjects.dashboard.waitForRenderComplete();
await PageObjects.header.waitUntilLoadingHasFinished();
});
describe('propagates context for Lens visualizations', () => {
it('lnsXY', async () => {
await assertLogContains(
'execution context propagates to Elasticsearch via "x-opaque-id" header',
(record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsXY:086ac2e9-dd16-4b45-92b8-1e43ff7e3f65'
)
)
);
await assertLogContains('execution context propagates to Kibana logs', (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'lens',
name: 'lnsXY',
id: '086ac2e9-dd16-4b45-92b8-1e43ff7e3f65',
description: '[Flights] Flight count',
url: '/app/lens#/edit_by_value',
})
);
});
it('lnsMetric', async () => {
await assertLogContains(
'execution context propagates to Elasticsearch via "x-opaque-id" header',
(record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsMetric:b766e3b8-4544-46ed-99e6-9ecc4847e2a2'
)
)
);
await assertLogContains('execution context propagates to Kibana logs', (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'lens',
name: 'lnsMetric',
id: '2e33ade5-96e5-40b4-b460-493e5d4fa834',
description: '',
url: '/app/lens#/edit_by_value',
})
);
});
it('lnsDatatable', async () => {
await assertLogContains(
'execution context propagates to Elasticsearch via "x-opaque-id" header',
(record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsDatatable:fb86b32f-fb7a-45cf-9511-f366fef51bbd'
)
)
);
await assertLogContains('execution context propagates to Kibana logs', (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'lens',
name: 'lnsDatatable',
id: 'fb86b32f-fb7a-45cf-9511-f366fef51bbd',
description: 'Cities by delay, cancellation',
url: '/app/lens#/edit_by_value',
})
);
});
it('lnsPie', async () => {
await assertLogContains(
'execution context propagates to Elasticsearch via "x-opaque-id" header',
(record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsPie:5d53db36-2d5a-4adc-af7b-cec4c1a294e0'
)
)
);
await assertLogContains('execution context propagates to Kibana logs', (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'lens',
name: 'lnsPie',
id: '5d53db36-2d5a-4adc-af7b-cec4c1a294e0',
description: '[Flights] Delay Type',
url: '/app/lens#/edit_by_value',
})
);
});
});
it('propagates context for built-in Discover', async () => {
await assertLogContains(
'execution context propagates to Elasticsearch via "x-opaque-id" header',
(record) =>
Boolean(
record.http?.request?.id?.includes(
'application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;search:discover:571aaf70-4c88-11e8-b3d7-01146121b73d'
)
)
);
await assertLogContains('execution context propagates to Kibana logs', (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'search',
name: 'discover',
id: '571aaf70-4c88-11e8-b3d7-01146121b73d',
description: '[Flights] Flight Log',
url: '/app/discover#/view/571aaf70-4c88-11e8-b3d7-01146121b73d',
})
);
});
it('propagates context for TSVB visualizations', async () => {
await assertLogContains(
'execution context propagates to Elasticsearch via "x-opaque-id" header',
(record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:TSVB:bcb63b50-4c89-11e8-b3d7-01146121b73d'
)
)
);
await assertLogContains('execution context propagates to Kibana logs', (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'visualization',
name: 'TSVB',
id: 'bcb63b50-4c89-11e8-b3d7-01146121b73d',
description: '[Flights] Delays & Cancellations',
url: '/app/visualize#/edit/bcb63b50-4c89-11e8-b3d7-01146121b73d',
})
);
});
it('propagates context for Vega visualizations', async () => {
await assertLogContains(
'execution context propagates to Elasticsearch via "x-opaque-id" header',
(record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:Vega:ed78a660-53a0-11e8-acbd-0be0ad9d822b'
)
)
);
await assertLogContains('execution context propagates to Kibana logs', (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'visualization',
name: 'Vega',
id: 'ed78a660-53a0-11e8-acbd-0be0ad9d822b',
description: '[Flights] Airport Connections (Hover Over Airport)',
url: '/app/visualize#/edit/ed78a660-53a0-11e8-acbd-0be0ad9d822b',
})
);
});
it('propagates context for Tag Cloud visualization', async () => {
await assertLogContains(
'execution context propagates to Elasticsearch via "x-opaque-id" header',
(record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:Tag cloud:293b5a30-4c8f-11e8-b3d7-01146121b73d'
)
)
);
await assertLogContains('execution context propagates to Kibana logs', (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'visualization',
name: 'Tag cloud',
id: '293b5a30-4c8f-11e8-b3d7-01146121b73d',
description: '[Flights] Destination Weather',
url: '/app/visualize#/edit/293b5a30-4c8f-11e8-b3d7-01146121b73d',
})
);
});
it('propagates context for Vertical bar visualization', async () => {
await assertLogContains(
'execution context propagates to Elasticsearch via "x-opaque-id" header',
(record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:Vertical bar:9886b410-4c8b-11e8-b3d7-01146121b73d'
)
)
);
await assertLogContains('execution context propagates to Kibana logs', (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'visualization',
name: 'Vertical bar',
id: '9886b410-4c8b-11e8-b3d7-01146121b73d',
description: '[Flights] Delay Buckets',
url: '/app/visualize#/edit/9886b410-4c8b-11e8-b3d7-01146121b73d',
})
);
});
});
});
}

View file

@ -92,4 +92,5 @@ require('@kbn/test').runTestsCli([
require.resolve('../test/fleet_functional/config.ts'),
require.resolve('../test/examples/config.ts'),
require.resolve('../test/performance/config.ts'),
require.resolve('../test/functional_execution_context/config.ts'),
]);

View file

@ -1,16 +1,26 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrConfigProviderContext } from '@kbn/test';
import Path from 'path';
import { CA_CERT_PATH } from '@kbn/dev-utils';
import { FtrConfigProviderContext } from '@kbn/test';
import { logFilePath } from './test_utils';
const alertTestPlugin = Path.resolve(__dirname, './fixtures/plugins/alerts');
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(require.resolve('../functional/config'));
const functionalConfig = await readConfigFile(require.resolve('../../test/functional/config'));
const servers = {
...functionalConfig.get('servers'),
elasticsearch: {
...functionalConfig.get('servers.elasticsearch'),
protocol: 'https',
},
};
return {
...functionalConfig.getAll(),
@ -19,24 +29,31 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
junit: {
reportName: 'Execution Context Functional Tests',
},
servers,
esTestCluster: {
...functionalConfig.get('esTestCluster'),
ssl: true,
},
kbnTestServer: {
...functionalConfig.get('kbnTestServer'),
serverArgs: [
...functionalConfig.get('kbnTestServer.serverArgs'),
`--plugin-path=${alertTestPlugin}`,
`--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`,
`--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`,
'--execution_context.enabled=true',
'--logging.appenders.file.type=file',
`--logging.appenders.file.fileName=${Path.resolve(__dirname, './kibana.log')}`,
`--logging.appenders.file.fileName=${logFilePath}`,
'--logging.appenders.file.layout.type=json',
'--logging.loggers[0].name=elasticsearch.query',
'--logging.loggers[0].level=all',
// eslint-disable-next-line prettier/prettier
'--logging.loggers[0].appenders=[\"file\"]',
`--logging.loggers[0].appenders=${JSON.stringify(['file'])}`,
'--logging.loggers[1].name=execution_context',
'--logging.loggers[1].level=debug',
// eslint-disable-next-line prettier/prettier
'--logging.loggers[1].appenders=[\"file\"]',
`--logging.loggers[1].appenders=${JSON.stringify(['file'])}`,
],
},
};

View file

@ -0,0 +1,13 @@
{
"id": "alertsFixtures",
"owner": {
"name": "Core team",
"githubTeam": "kibana-core"
},
"version": "1.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack"],
"requiredPlugins": ["features", "alerting"],
"server": true,
"ui": false
}

View file

@ -0,0 +1,13 @@
{
"name": "alerts-fixtures",
"version": "1.0.0",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
},
"scripts": {
"kbn": "node ../../../../../../scripts/kbn.js",
"build": "rm -rf './target' && ../../../../../../node_modules/.bin/tsc"
},
"license": "Elastic License 2.0"
}

View file

@ -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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FixturePlugin } from './plugin';
export const plugin = () => new FixturePlugin();

View file

@ -0,0 +1,88 @@
/*
* 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 { Plugin, CoreSetup } from 'kibana/server';
import { PluginSetupContract as AlertingPluginSetup } from '../../../../../../plugins/alerting/server/plugin';
import { EncryptedSavedObjectsPluginStart } from '../../../../../../plugins/encrypted_saved_objects/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../plugins/features/server';
import { SpacesPluginStart } from '../../../../../../plugins/spaces/server';
import { SecurityPluginStart } from '../../../../../../plugins/security/server';
export interface FixtureSetupDeps {
features: FeaturesPluginSetup;
alerting: AlertingPluginSetup;
}
export interface FixtureStartDeps {
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
security?: SecurityPluginStart;
spaces?: SpacesPluginStart;
}
export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, FixtureStartDeps> {
constructor() {}
public setup(core: CoreSetup<FixtureStartDeps>, { features, alerting }: FixtureSetupDeps) {
features.registerKibanaFeature({
id: 'alertsFixture',
name: 'Alerts',
app: ['alerts', 'kibana'],
category: { id: 'foo', label: 'foo' },
alerting: ['test.executionContext'],
privileges: {
all: {
app: ['alerts', 'kibana'],
savedObject: {
all: ['alert'],
read: [],
},
alerting: {
rule: {
all: ['test.executionContext'],
},
},
ui: [],
},
read: {
app: ['alerts', 'kibana'],
savedObject: {
all: [],
read: ['alert'],
},
alerting: {
rule: {
read: ['test.executionContext'],
},
},
ui: [],
},
},
});
alerting.registerType({
id: 'test.executionContext',
name: 'Test: Query Elasticsearch server',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
producer: 'alertsFixture',
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
isExportable: true,
async executor() {
const [coreStart] = await core.getStartServices();
await coreStart.elasticsearch.client.asInternalUser.ping();
},
});
}
public start() {}
public stop() {}
}

View file

@ -1,13 +1,12 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { GenericFtrProviderContext } from '@kbn/test';
import { pageObjects } from '../functional/page_objects';
import { pageObjects } from '../../test/functional/page_objects';
import { services } from './services';
export type FtrProviderContext = GenericFtrProviderContext<typeof services, typeof pageObjects>;

View file

@ -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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { services as functionalServices } from '../../test/functional/services';
export const services = functionalServices;

View file

@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import Fs from 'fs/promises';
import Path from 'path';
import { isEqualWith } from 'lodash';
import type { Ecs, KibanaExecutionContext } from 'kibana/server';
import type { RetryService } from '../../../test/common/services/retry';
export const logFilePath = Path.resolve(__dirname, './kibana.log');
export const ANY = Symbol('any');
export function isExecutionContextLog(
record: string | undefined,
executionContext: KibanaExecutionContext
) {
if (!record) return false;
try {
const object = JSON.parse(record);
return isEqualWith(object, executionContext, function customizer(obj1: any, obj2: any) {
if (obj2 === ANY) return true;
});
} catch (e) {
return false;
}
}
// to avoid splitting log record containing \n symbol
const endOfLine = /(?<=})\s*\n/;
export async function assertLogContains({
description,
predicate,
retry,
}: {
description: string;
predicate: (record: Ecs) => boolean;
retry: RetryService;
}): Promise<void> {
// logs are written to disk asynchronously. I sacrificed performance to reduce flakiness.
await retry.waitFor(description, async () => {
const logsStr = await Fs.readFile(logFilePath, 'utf-8');
const normalizedRecords = logsStr
.split(endOfLine)
.filter(Boolean)
.map((s) => JSON.parse(s));
return normalizedRecords.some(predicate);
});
}

View file

@ -0,0 +1,381 @@
/*
* 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 type { FtrProviderContext } from '../ftr_provider_context';
import { assertLogContains, isExecutionContextLog } from '../test_utils';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'home']);
const retry = getService('retry');
describe('Browser apps', () => {
before(async () => {
await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', {
useActualUrl: true,
});
await PageObjects.home.addSampleDataSet('flights');
await PageObjects.header.waitUntilLoadingHasFinished();
});
after(async () => {
await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', {
useActualUrl: true,
});
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.home.removeSampleDataSet('flights');
});
describe('discover app', () => {
before(async () => {
await PageObjects.common.navigateToApp('discover');
await PageObjects.header.waitUntilLoadingHasFinished();
});
it('propagates context for Discover', async () => {
await assertLogContains({
description: 'execution context propagates to Elasticsearch via "x-opaque-id" header',
predicate: (record) =>
Boolean(record.http?.request?.id?.includes('kibana:application:discover')),
retry,
});
await assertLogContains({
description: 'execution context propagates to Kibana logs',
predicate: (record) =>
isExecutionContextLog(record?.message, {
description: 'fetch documents',
id: '',
name: 'discover',
type: 'application',
// discovery doesn't have an URL since one of from the example dataset is not saved separately
url: '/app/discover',
}),
retry,
});
await assertLogContains({
description: 'execution context propagates to Kibana logs',
predicate: (record) =>
isExecutionContextLog(record?.message, {
description: 'fetch chart data and total hits',
id: '',
name: 'discover',
type: 'application',
url: '/app/discover',
}),
retry,
});
});
});
describe('dashboard app', () => {
before(async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.loadSavedDashboard('[Flights] Global Flight Dashboard');
await PageObjects.dashboard.waitForRenderComplete();
await PageObjects.header.waitUntilLoadingHasFinished();
});
describe('propagates context for Lens visualizations', () => {
it('lnsXY', async () => {
await assertLogContains({
description: 'execution context propagates to Elasticsearch via "x-opaque-id" header',
predicate: (record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsXY:086ac2e9-dd16-4b45-92b8-1e43ff7e3f65'
)
),
retry,
});
await assertLogContains({
description: 'execution context propagates to Kibana logs',
predicate: (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'lens',
name: 'lnsXY',
id: '086ac2e9-dd16-4b45-92b8-1e43ff7e3f65',
description: '[Flights] Flight count',
url: '/app/lens#/edit_by_value',
}),
retry,
});
});
it('lnsMetric', async () => {
await assertLogContains({
description: 'execution context propagates to Elasticsearch via "x-opaque-id" header',
predicate: (record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsMetric:b766e3b8-4544-46ed-99e6-9ecc4847e2a2'
)
),
retry,
});
await assertLogContains({
description: 'execution context propagates to Kibana logs',
predicate: (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'lens',
name: 'lnsMetric',
id: '2e33ade5-96e5-40b4-b460-493e5d4fa834',
description: '',
url: '/app/lens#/edit_by_value',
}),
retry,
});
});
it('lnsDatatable', async () => {
await assertLogContains({
description: 'execution context propagates to Elasticsearch via "x-opaque-id" header',
predicate: (record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsDatatable:fb86b32f-fb7a-45cf-9511-f366fef51bbd'
)
),
retry,
});
await assertLogContains({
description: 'execution context propagates to Kibana logs',
predicate: (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'lens',
name: 'lnsDatatable',
id: 'fb86b32f-fb7a-45cf-9511-f366fef51bbd',
description: 'Cities by delay, cancellation',
url: '/app/lens#/edit_by_value',
}),
retry,
});
});
it('lnsPie', async () => {
await assertLogContains({
description: 'execution context propagates to Elasticsearch via "x-opaque-id" header',
predicate: (record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsPie:5d53db36-2d5a-4adc-af7b-cec4c1a294e0'
)
),
retry,
});
await assertLogContains({
description: 'execution context propagates to Kibana logs',
predicate: (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'lens',
name: 'lnsPie',
id: '5d53db36-2d5a-4adc-af7b-cec4c1a294e0',
description: '[Flights] Delay Type',
url: '/app/lens#/edit_by_value',
}),
retry,
});
});
});
it('propagates context for built-in Discover', async () => {
await assertLogContains({
description: 'execution context propagates to Elasticsearch via "x-opaque-id" header',
predicate: (record) =>
Boolean(
record.http?.request?.id?.includes(
'application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;search:discover:571aaf70-4c88-11e8-b3d7-01146121b73d'
)
),
retry,
});
await assertLogContains({
description: 'execution context propagates to Kibana logs',
predicate: (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'search',
name: 'discover',
id: '571aaf70-4c88-11e8-b3d7-01146121b73d',
description: '[Flights] Flight Log',
url: '/app/discover#/view/571aaf70-4c88-11e8-b3d7-01146121b73d',
}),
retry,
});
});
it('propagates context for TSVB visualizations', async () => {
await assertLogContains({
description: 'execution context propagates to Elasticsearch via "x-opaque-id" header',
predicate: (record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:TSVB:bcb63b50-4c89-11e8-b3d7-01146121b73d'
)
),
retry,
});
await assertLogContains({
description: 'execution context propagates to Kibana logs',
predicate: (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'visualization',
name: 'TSVB',
id: 'bcb63b50-4c89-11e8-b3d7-01146121b73d',
description: '[Flights] Delays & Cancellations',
url: '/app/visualize#/edit/bcb63b50-4c89-11e8-b3d7-01146121b73d',
}),
retry,
});
});
it('propagates context for Vega visualizations', async () => {
await assertLogContains({
description: 'execution context propagates to Elasticsearch via "x-opaque-id" header',
predicate: (record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:Vega:ed78a660-53a0-11e8-acbd-0be0ad9d822b'
)
),
retry,
});
await assertLogContains({
description: 'execution context propagates to Kibana logs',
predicate: (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'visualization',
name: 'Vega',
id: 'ed78a660-53a0-11e8-acbd-0be0ad9d822b',
description: '[Flights] Airport Connections (Hover Over Airport)',
url: '/app/visualize#/edit/ed78a660-53a0-11e8-acbd-0be0ad9d822b',
}),
retry,
});
});
it('propagates context for Tag Cloud visualization', async () => {
await assertLogContains({
description: 'execution context propagates to Elasticsearch via "x-opaque-id" header',
predicate: (record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:Tag cloud:293b5a30-4c8f-11e8-b3d7-01146121b73d'
)
),
retry,
});
await assertLogContains({
description: 'execution context propagates to Kibana logs',
predicate: (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'visualization',
name: 'Tag cloud',
id: '293b5a30-4c8f-11e8-b3d7-01146121b73d',
description: '[Flights] Destination Weather',
url: '/app/visualize#/edit/293b5a30-4c8f-11e8-b3d7-01146121b73d',
}),
retry,
});
});
it('propagates context for Vertical bar visualization', async () => {
await assertLogContains({
description: 'execution context propagates to Elasticsearch via "x-opaque-id" header',
predicate: (record) =>
Boolean(
record.http?.request?.id?.includes(
'kibana:application:dashboard:7adfa750-4c81-11e8-b3d7-01146121b73d;visualization:Vertical bar:9886b410-4c8b-11e8-b3d7-01146121b73d'
)
),
retry,
});
await assertLogContains({
description: 'execution context propagates to Kibana logs',
predicate: (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'application',
name: 'dashboard',
id: '7adfa750-4c81-11e8-b3d7-01146121b73d',
description: '[Flights] Global Flight Dashboard',
url: '/view/7adfa750-4c81-11e8-b3d7-01146121b73d',
},
type: 'visualization',
name: 'Vertical bar',
id: '9886b410-4c8b-11e8-b3d7-01146121b73d',
description: '[Flights] Delay Buckets',
url: '/app/visualize#/edit/9886b410-4c8b-11e8-b3d7-01146121b73d',
}),
retry,
});
});
});
});
}

View file

@ -1,9 +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
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../ftr_provider_context';
@ -11,6 +10,7 @@ import { FtrProviderContext } from '../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Execution context', function () {
this.tags('ciGroup1');
loadTestFile(require.resolve('./execution_context'));
loadTestFile(require.resolve('./browser'));
loadTestFile(require.resolve('./server'));
});
}

View file

@ -0,0 +1,112 @@
/*
* 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 type { FtrProviderContext } from '../ftr_provider_context';
import { assertLogContains, isExecutionContextLog, ANY } from '../test_utils';
function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export default function ({ getService }: FtrProviderContext) {
const retry = getService('retry');
const supertest = getService('supertest');
const log = getService('log');
async function waitForStatus(
id: string,
statuses: Set<string>,
waitMillis: number = 10000
): Promise<Record<string, any>> {
if (waitMillis < 0) {
expect().fail(`waiting for alert ${id} statuses ${Array.from(statuses)} timed out`);
}
const response = await supertest.get(`/api/alerting/rule/${id}`);
expect(response.status).to.eql(200);
const { status } = response.body.execution_status;
if (statuses.has(status)) return response.body.execution_status;
log.debug(
`waitForStatus(${Array.from(statuses)} for id:${id}): got ${JSON.stringify(
response.body.execution_status
)}, retrying`
);
const WaitForStatusIncrement = 500;
await delay(WaitForStatusIncrement);
return await waitForStatus(id, statuses, waitMillis - WaitForStatusIncrement);
}
describe('Server-side apps', () => {
it('propagates context for Task and Alerts', async () => {
const { body: createdAlert } = await supertest
.post('/api/alerting/rule')
.set('kbn-xsrf', 'true')
.send({
enabled: true,
name: 'abc',
tags: ['foo'],
rule_type_id: 'test.executionContext',
consumer: 'alertsFixture',
schedule: { interval: '3s' },
throttle: '20s',
actions: [],
params: {},
notify_when: 'onThrottleInterval',
})
.expect(200);
const alertId = createdAlert.id;
await waitForStatus(alertId, new Set(['ok']), 90_000);
await assertLogContains({
description:
'task manager execution context propagates to Elasticsearch via "x-opaque-id" header',
predicate: (record) =>
Boolean(
// exclude part with taskId
record.http?.request?.id?.includes(
`kibana:task manager:run alerting:test.executionContext:`
)
),
retry,
});
await assertLogContains({
description:
'alerting execution context propagates to Elasticsearch via "x-opaque-id" header',
predicate: (record) =>
Boolean(
record.http?.request?.id?.includes(`alert:execute test.executionContext:${alertId}`)
),
retry,
});
await assertLogContains({
description: 'execution context propagates to Kibana logs',
predicate: (record) =>
isExecutionContextLog(record?.message, {
parent: {
type: 'task manager',
name: 'run alerting:test.executionContext',
// @ts-expect-error. it accepts strings only
id: ANY,
description: 'run task',
},
type: 'alert',
name: 'execute test.executionContext',
id: alertId,
description: 'execute [test.executionContext] with name [abc] in [default] namespace',
}),
retry,
});
});
});
}