throw new Error('Not implemented.');
},
}),
- },
+ }),
...partialDeps,
};
const service = new UrlService(deps);
diff --git a/src/plugins/share/common/url_service/locators/locator.ts b/src/plugins/share/common/url_service/locators/locator.ts
index fc970e2c7a490..2d33f701df595 100644
--- a/src/plugins/share/common/url_service/locators/locator.ts
+++ b/src/plugins/share/common/url_service/locators/locator.ts
@@ -67,13 +67,15 @@ export class Locator implements LocatorPublic
{
state: P,
references: SavedObjectReference[]
): P => {
- return this.definition.inject ? this.definition.inject(state, references) : state;
+ if (!this.definition.inject) return state;
+ return this.definition.inject(state, references);
};
public readonly extract: PersistableState
['extract'] = (
state: P
): { state: P; references: SavedObjectReference[] } => {
- return this.definition.extract ? this.definition.extract(state) : { state, references: [] };
+ if (!this.definition.extract) return { state, references: [] };
+ return this.definition.extract(state);
};
// LocatorPublic
----------------------------------------------------------
diff --git a/src/plugins/share/common/url_service/locators/locator_client.ts b/src/plugins/share/common/url_service/locators/locator_client.ts
index 587083551aa6d..7dd69165be5dd 100644
--- a/src/plugins/share/common/url_service/locators/locator_client.ts
+++ b/src/plugins/share/common/url_service/locators/locator_client.ts
@@ -7,9 +7,12 @@
*/
import type { SerializableRecord } from '@kbn/utility-types';
+import { MigrateFunctionsObject } from 'src/plugins/kibana_utils/common';
+import { SavedObjectReference } from 'kibana/server';
import type { LocatorDependencies } from './locator';
-import type { LocatorDefinition, LocatorPublic, ILocatorClient } from './types';
+import type { LocatorDefinition, LocatorPublic, ILocatorClient, LocatorData } from './types';
import { Locator } from './locator';
+import { LocatorMigrationFunction, LocatorsMigrationMap } from '.';
export type LocatorClientDependencies = LocatorDependencies;
@@ -44,4 +47,91 @@ export class LocatorClient implements ILocatorClient {
public get
(id: string): undefined | LocatorPublic
{
return this.locators.get(id);
}
+
+ protected getOrThrow
(id: string): LocatorPublic
{
+ const locator = this.locators.get(id);
+ if (!locator) throw new Error(`Locator [ID = "${id}"] is not registered.`);
+ return locator;
+ }
+
+ public migrations(): { [locatorId: string]: MigrateFunctionsObject } {
+ const migrations: { [locatorId: string]: MigrateFunctionsObject } = {};
+
+ for (const locator of this.locators.values()) {
+ migrations[locator.id] = locator.migrations;
+ }
+
+ return migrations;
+ }
+
+ // PersistableStateService ----------------------------------------------------------
+
+ public telemetry(
+ state: LocatorData,
+ collector: Record
+ ): Record {
+ for (const locator of this.locators.values()) {
+ collector = locator.telemetry(state.state, collector);
+ }
+
+ return collector;
+ }
+
+ public inject(state: LocatorData, references: SavedObjectReference[]): LocatorData {
+ const locator = this.getOrThrow(state.id);
+ const filteredReferences = references
+ .filter((ref) => ref.name.startsWith('params:'))
+ .map((ref) => ({
+ ...ref,
+ name: ref.name.substr('params:'.length),
+ }));
+ return {
+ ...state,
+ state: locator.inject(state.state, filteredReferences),
+ };
+ }
+
+ public extract(state: LocatorData): { state: LocatorData; references: SavedObjectReference[] } {
+ const locator = this.getOrThrow(state.id);
+ const extracted = locator.extract(state.state);
+ return {
+ state: {
+ ...state,
+ state: extracted.state,
+ },
+ references: extracted.references.map((ref) => ({
+ ...ref,
+ name: 'params:' + ref.name,
+ })),
+ };
+ }
+
+ public readonly getAllMigrations = (): LocatorsMigrationMap => {
+ const locatorParamsMigrations = this.migrations();
+ const locatorMigrations: LocatorsMigrationMap = {};
+ const versions = new Set();
+
+ for (const migrationMap of Object.values(locatorParamsMigrations))
+ for (const version of Object.keys(migrationMap)) versions.add(version);
+
+ for (const version of versions.values()) {
+ const migration: LocatorMigrationFunction = (locator) => {
+ const locatorMigrationsMap = locatorParamsMigrations[locator.id];
+ if (!locatorMigrationsMap) return locator;
+
+ const migrationFunction = locatorMigrationsMap[version];
+ if (!migrationFunction) return locator;
+
+ return {
+ ...locator,
+ version,
+ state: migrationFunction(locator.state),
+ };
+ };
+
+ locatorMigrations[version] = migration;
+ }
+
+ return locatorMigrations;
+ };
}
diff --git a/src/plugins/share/common/url_service/locators/types.ts b/src/plugins/share/common/url_service/locators/types.ts
index ab0efa9b2375a..c64dc588aaf22 100644
--- a/src/plugins/share/common/url_service/locators/types.ts
+++ b/src/plugins/share/common/url_service/locators/types.ts
@@ -8,13 +8,18 @@
import type { SerializableRecord } from '@kbn/utility-types';
import { DependencyList } from 'react';
-import { PersistableState } from 'src/plugins/kibana_utils/common';
+import {
+ MigrateFunction,
+ PersistableState,
+ PersistableStateService,
+ VersionedState,
+} from 'src/plugins/kibana_utils/common';
import type { FormatSearchParamsOptions } from './redirect';
/**
* URL locator registry.
*/
-export interface ILocatorClient {
+export interface ILocatorClient extends PersistableStateService {
/**
* Create and register a new locator.
*
@@ -141,3 +146,22 @@ export interface KibanaLocation {
*/
state: S;
}
+
+/**
+ * Represents a serializable state of a locator. Includes locator ID, version
+ * and its params.
+ */
+export interface LocatorData
+ extends VersionedState,
+ SerializableRecord {
+ /**
+ * Locator ID.
+ */
+ id: string;
+}
+
+export interface LocatorsMigrationMap {
+ [semver: string]: LocatorMigrationFunction;
+}
+
+export type LocatorMigrationFunction = MigrateFunction;
diff --git a/src/plugins/share/common/url_service/mocks.ts b/src/plugins/share/common/url_service/mocks.ts
index dd86e2398589e..24ba226818427 100644
--- a/src/plugins/share/common/url_service/mocks.ts
+++ b/src/plugins/share/common/url_service/mocks.ts
@@ -18,7 +18,7 @@ export class MockUrlService extends UrlService {
getUrl: async ({ app, path }, { absolute }) => {
return `${absolute ? 'http://localhost:8888' : ''}/app/${app}${path}`;
},
- shortUrls: {
+ shortUrls: () => ({
get: () => ({
create: async () => {
throw new Error('Not implemented.');
@@ -33,7 +33,7 @@ export class MockUrlService extends UrlService {
throw new Error('Not implemented.');
},
}),
- },
+ }),
});
}
}
diff --git a/src/plugins/share/common/url_service/short_urls/types.ts b/src/plugins/share/common/url_service/short_urls/types.ts
index db744a25f9f79..698ffe7b8421b 100644
--- a/src/plugins/share/common/url_service/short_urls/types.ts
+++ b/src/plugins/share/common/url_service/short_urls/types.ts
@@ -6,9 +6,8 @@
* Side Public License, v 1.
*/
-import { SerializableRecord } from '@kbn/utility-types';
-import { VersionedState } from 'src/plugins/kibana_utils/common';
-import { LocatorPublic } from '../locators';
+import type { SerializableRecord } from '@kbn/utility-types';
+import type { LocatorPublic, ILocatorClient, LocatorData } from '../locators';
/**
* A factory for Short URL Service. We need this factory as the dependency
@@ -21,6 +20,10 @@ export interface IShortUrlClientFactory {
get(dependencies: D): IShortUrlClient;
}
+export type IShortUrlClientFactoryProvider = (params: {
+ locators: ILocatorClient;
+}) => IShortUrlClientFactory;
+
/**
* CRUD-like API for short URLs.
*/
@@ -128,14 +131,4 @@ export interface ShortUrlData;
}
-/**
- * Represents a serializable state of a locator. Includes locator ID, version
- * and its params.
- */
-export interface LocatorData
- extends VersionedState {
- /**
- * Locator ID.
- */
- id: string;
-}
+export type { LocatorData };
diff --git a/src/plugins/share/common/url_service/url_service.ts b/src/plugins/share/common/url_service/url_service.ts
index dedb81720865d..24e2ea0b62379 100644
--- a/src/plugins/share/common/url_service/url_service.ts
+++ b/src/plugins/share/common/url_service/url_service.ts
@@ -7,10 +7,10 @@
*/
import { LocatorClient, LocatorClientDependencies } from './locators';
-import { IShortUrlClientFactory } from './short_urls';
+import { IShortUrlClientFactoryProvider, IShortUrlClientFactory } from './short_urls';
export interface UrlServiceDependencies extends LocatorClientDependencies {
- shortUrls: IShortUrlClientFactory;
+ shortUrls: IShortUrlClientFactoryProvider;
}
/**
@@ -26,6 +26,8 @@ export class UrlService {
constructor(protected readonly deps: UrlServiceDependencies) {
this.locators = new LocatorClient(deps);
- this.shortUrls = deps.shortUrls;
+ this.shortUrls = deps.shortUrls({
+ locators: this.locators,
+ });
}
}
diff --git a/src/plugins/share/public/mocks.ts b/src/plugins/share/public/mocks.ts
index 73df7257290f0..33cdf141de9f3 100644
--- a/src/plugins/share/public/mocks.ts
+++ b/src/plugins/share/public/mocks.ts
@@ -18,7 +18,7 @@ const url = new UrlService({
getUrl: async ({ app, path }, { absolute }) => {
return `${absolute ? 'http://localhost:8888' : ''}/app/${app}${path}`;
},
- shortUrls: {
+ shortUrls: () => ({
get: () => ({
create: async () => {
throw new Error('Not implemented');
@@ -33,7 +33,7 @@ const url = new UrlService({
throw new Error('Not implemented.');
},
}),
- },
+ }),
});
const createSetupContract = (): Setup => {
diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts
index 103fbb50bb95f..fd8a5fd7541a6 100644
--- a/src/plugins/share/public/plugin.ts
+++ b/src/plugins/share/public/plugin.ts
@@ -104,7 +104,7 @@ export class SharePlugin implements Plugin {
});
return url;
},
- shortUrls: {
+ shortUrls: () => ({
get: () => ({
create: async () => {
throw new Error('Not implemented');
@@ -119,7 +119,7 @@ export class SharePlugin implements Plugin {
throw new Error('Not implemented.');
},
}),
- },
+ }),
});
this.url.locators.create(new LegacyShortUrlLocatorDefinition());
diff --git a/src/plugins/share/server/plugin.ts b/src/plugins/share/server/plugin.ts
index f0e4abf9eb589..d79588420fe87 100644
--- a/src/plugins/share/server/plugin.ts
+++ b/src/plugins/share/server/plugin.ts
@@ -9,11 +9,14 @@
import { i18n } from '@kbn/i18n';
import { schema } from '@kbn/config-schema';
import { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server';
-import { url } from './saved_objects';
import { CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING } from '../common/constants';
import { UrlService } from '../common/url_service';
-import { ServerUrlService, ServerShortUrlClientFactory } from './url_service';
-import { registerUrlServiceRoutes } from './url_service/http/register_url_service_routes';
+import {
+ ServerUrlService,
+ ServerShortUrlClientFactory,
+ registerUrlServiceRoutes,
+ registerUrlServiceSavedObjectType,
+} from './url_service';
import { LegacyShortUrlLocatorDefinition } from '../common/url_service/locators/legacy_short_url_locator';
/** @public */
@@ -44,18 +47,17 @@ export class SharePlugin implements Plugin {
getUrl: async () => {
throw new Error('Locator .getUrl() currently is not supported on the server.');
},
- shortUrls: new ServerShortUrlClientFactory({
- currentVersion: this.version,
- }),
+ shortUrls: ({ locators }) =>
+ new ServerShortUrlClientFactory({
+ currentVersion: this.version,
+ locators,
+ }),
});
-
this.url.locators.create(new LegacyShortUrlLocatorDefinition());
- const router = core.http.createRouter();
-
- registerUrlServiceRoutes(core, router, this.url);
+ registerUrlServiceSavedObjectType(core.savedObjects, this.url);
+ registerUrlServiceRoutes(core, core.http.createRouter(), this.url);
- core.savedObjects.registerType(url);
core.uiSettings.register({
[CSV_SEPARATOR_SETTING]: {
name: i18n.translate('share.advancedSettings.csv.separatorTitle', {
diff --git a/src/plugins/share/server/saved_objects/url.ts b/src/plugins/share/server/saved_objects/url.ts
deleted file mode 100644
index 6288e87f629f5..0000000000000
--- a/src/plugins/share/server/saved_objects/url.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
- * 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 { SavedObjectsType } from 'kibana/server';
-
-export const url: SavedObjectsType = {
- name: 'url',
- namespaceType: 'single',
- hidden: false,
- management: {
- icon: 'link',
- defaultSearchField: 'url',
- importableAndExportable: true,
- getTitle(obj) {
- return `/goto/${encodeURIComponent(obj.id)}`;
- },
- getInAppUrl(obj) {
- return {
- path: '/goto/' + encodeURIComponent(obj.id),
- uiCapabilitiesPath: '',
- };
- },
- },
- mappings: {
- properties: {
- slug: {
- type: 'text',
- fields: {
- keyword: {
- type: 'keyword',
- },
- },
- },
- accessCount: {
- type: 'long',
- },
- accessDate: {
- type: 'date',
- },
- createDate: {
- type: 'date',
- },
- // Legacy field - contains already pre-formatted final URL.
- // This is here to support old saved objects that have this field.
- // TODO: Remove this field and execute a migration to the new format.
- url: {
- type: 'text',
- fields: {
- keyword: {
- type: 'keyword',
- ignore_above: 2048,
- },
- },
- },
- // Information needed to load and execute a locator.
- locatorJSON: {
- type: 'text',
- index: false,
- },
- },
- },
-};
diff --git a/src/plugins/share/server/url_service/index.ts b/src/plugins/share/server/url_service/index.ts
index 068a5289d42ed..62d1329371736 100644
--- a/src/plugins/share/server/url_service/index.ts
+++ b/src/plugins/share/server/url_service/index.ts
@@ -8,3 +8,5 @@
export * from './types';
export * from './short_urls';
+export { registerUrlServiceRoutes } from './http/register_url_service_routes';
+export { registerUrlServiceSavedObjectType } from './saved_objects/register_url_service_saved_object_type';
diff --git a/src/plugins/share/server/url_service/saved_objects/register_url_service_saved_object_type.test.ts b/src/plugins/share/server/url_service/saved_objects/register_url_service_saved_object_type.test.ts
new file mode 100644
index 0000000000000..651169f6101a9
--- /dev/null
+++ b/src/plugins/share/server/url_service/saved_objects/register_url_service_saved_object_type.test.ts
@@ -0,0 +1,144 @@
+/*
+ * 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 { SerializableRecord } from '@kbn/utility-types';
+import type {
+ SavedObjectMigrationMap,
+ SavedObjectsType,
+ SavedObjectUnsanitizedDoc,
+} from 'kibana/server';
+import { ServerShortUrlClientFactory } from '..';
+import { UrlService, LocatorDefinition } from '../../../common/url_service';
+import { LegacyShortUrlLocatorDefinition } from '../../../common/url_service/locators/legacy_short_url_locator';
+import { MemoryShortUrlStorage } from '../short_urls/storage/memory_short_url_storage';
+import { ShortUrlSavedObjectAttributes } from '../short_urls/storage/saved_object_short_url_storage';
+import { registerUrlServiceSavedObjectType } from './register_url_service_saved_object_type';
+
+const setup = () => {
+ const currentVersion = '7.7.7';
+ const service = new UrlService({
+ getUrl: () => {
+ throw new Error('Not implemented.');
+ },
+ navigate: () => {
+ throw new Error('Not implemented.');
+ },
+ shortUrls: ({ locators }) =>
+ new ServerShortUrlClientFactory({
+ currentVersion,
+ locators,
+ }),
+ });
+ const definition = new LegacyShortUrlLocatorDefinition();
+ const locator = service.locators.create(definition);
+ const storage = new MemoryShortUrlStorage();
+ const client = service.shortUrls.get({ storage });
+
+ let type: SavedObjectsType;
+ registerUrlServiceSavedObjectType(
+ {
+ registerType: (urlSavedObjectType) => {
+ type = urlSavedObjectType;
+ },
+ },
+ service
+ );
+
+ return {
+ type: type!,
+ client,
+ service,
+ storage,
+ locator,
+ definition,
+ currentVersion,
+ };
+};
+
+describe('migrations', () => {
+ test('returns empty migrations object if there are no migrations', () => {
+ const { type } = setup();
+
+ expect((type.migrations as () => SavedObjectMigrationMap)()).toEqual({});
+ });
+
+ test('migrates locator to the latest version', () => {
+ interface FooLocatorParamsOld extends SerializableRecord {
+ color: string;
+ indexPattern: string;
+ }
+
+ interface FooLocatorParams extends SerializableRecord {
+ color: string;
+ indexPatterns: string[];
+ }
+
+ class FooLocatorDefinition implements LocatorDefinition {
+ public readonly id = 'FOO_LOCATOR';
+
+ public async getLocation() {
+ return {
+ app: 'foo',
+ path: '',
+ state: {},
+ };
+ }
+
+ migrations = {
+ '8.0.0': ({ indexPattern, ...rest }: FooLocatorParamsOld): FooLocatorParams => ({
+ ...rest,
+ indexPatterns: [indexPattern],
+ }),
+ };
+ }
+
+ const { type, service } = setup();
+
+ service.locators.create(new FooLocatorDefinition());
+
+ const migrationFunction = (type.migrations as () => SavedObjectMigrationMap)()['8.0.0'];
+
+ expect(typeof migrationFunction).toBe('function');
+
+ const doc1: SavedObjectUnsanitizedDoc = {
+ id: 'foo',
+ attributes: {
+ accessCount: 0,
+ accessDate: 0,
+ createDate: 0,
+ locatorJSON: JSON.stringify({
+ id: 'FOO_LOCATOR',
+ version: '7.7.7',
+ state: {
+ color: 'red',
+ indexPattern: 'myIndex',
+ },
+ }),
+ url: '',
+ },
+ type: 'url',
+ };
+
+ const doc2 = migrationFunction(doc1, {} as any);
+
+ expect(doc2.id).toBe('foo');
+ expect(doc2.type).toBe('url');
+ expect(doc2.attributes.accessCount).toBe(0);
+ expect(doc2.attributes.accessDate).toBe(0);
+ expect(doc2.attributes.createDate).toBe(0);
+ expect(doc2.attributes.url).toBe('');
+ expect(JSON.parse(doc2.attributes.locatorJSON)).toEqual({
+ id: 'FOO_LOCATOR',
+ version: '8.0.0',
+ state: {
+ color: 'red',
+ indexPatterns: ['myIndex'],
+ },
+ });
+ });
+});
diff --git a/src/plugins/share/server/url_service/saved_objects/register_url_service_saved_object_type.ts b/src/plugins/share/server/url_service/saved_objects/register_url_service_saved_object_type.ts
new file mode 100644
index 0000000000000..b2fcefcc767cf
--- /dev/null
+++ b/src/plugins/share/server/url_service/saved_objects/register_url_service_saved_object_type.ts
@@ -0,0 +1,97 @@
+/*
+ * 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 {
+ SavedObjectMigrationMap,
+ SavedObjectsServiceSetup,
+ SavedObjectsType,
+} from 'kibana/server';
+import type { LocatorData } from 'src/plugins/share/common/url_service';
+import type { ServerUrlService } from '..';
+
+export const registerUrlServiceSavedObjectType = (
+ so: Pick,
+ service: ServerUrlService
+) => {
+ const urlSavedObjectType: SavedObjectsType = {
+ name: 'url',
+ namespaceType: 'single',
+ hidden: false,
+ management: {
+ icon: 'link',
+ defaultSearchField: 'url',
+ importableAndExportable: true,
+ getTitle(obj) {
+ return `/goto/${encodeURIComponent(obj.id)}`;
+ },
+ getInAppUrl(obj) {
+ return {
+ path: '/goto/' + encodeURIComponent(obj.id),
+ uiCapabilitiesPath: '',
+ };
+ },
+ },
+ mappings: {
+ properties: {
+ slug: {
+ type: 'text',
+ fields: {
+ keyword: {
+ type: 'keyword',
+ },
+ },
+ },
+ accessCount: {
+ type: 'long',
+ },
+ accessDate: {
+ type: 'date',
+ },
+ createDate: {
+ type: 'date',
+ },
+ // Legacy field - contains already pre-formatted final URL.
+ // This is here to support old saved objects that have this field.
+ // TODO: Remove this field and execute a migration to the new format.
+ url: {
+ type: 'text',
+ fields: {
+ keyword: {
+ type: 'keyword',
+ ignore_above: 2048,
+ },
+ },
+ },
+ // Information needed to load and execute a locator.
+ locatorJSON: {
+ type: 'text',
+ index: false,
+ },
+ },
+ },
+ migrations: () => {
+ const locatorMigrations = service.locators.getAllMigrations();
+ const savedObjectLocatorMigrations: SavedObjectMigrationMap = {};
+
+ for (const [version, locatorMigration] of Object.entries(locatorMigrations)) {
+ savedObjectLocatorMigrations[version] = (doc) => {
+ const locator = JSON.parse(doc.attributes.locatorJSON) as LocatorData;
+ doc.attributes = {
+ ...doc.attributes,
+ locatorJSON: JSON.stringify(locatorMigration(locator)),
+ };
+ return doc;
+ };
+ }
+
+ return savedObjectLocatorMigrations;
+ },
+ };
+
+ so.registerType(urlSavedObjectType);
+};
diff --git a/src/plugins/share/server/url_service/short_urls/short_url_client.test.ts b/src/plugins/share/server/url_service/short_urls/short_url_client.test.ts
index ac684eb03a9d5..503748a2b1cad 100644
--- a/src/plugins/share/server/url_service/short_urls/short_url_client.test.ts
+++ b/src/plugins/share/server/url_service/short_urls/short_url_client.test.ts
@@ -7,9 +7,11 @@
*/
import { ServerShortUrlClientFactory } from './short_url_client_factory';
-import { UrlService } from '../../../common/url_service';
+import { UrlService, LocatorDefinition } from '../../../common/url_service';
import { LegacyShortUrlLocatorDefinition } from '../../../common/url_service/locators/legacy_short_url_locator';
import { MemoryShortUrlStorage } from './storage/memory_short_url_storage';
+import { SerializableRecord } from '@kbn/utility-types';
+import { SavedObjectReference } from 'kibana/server';
const setup = () => {
const currentVersion = '1.2.3';
@@ -20,9 +22,11 @@ const setup = () => {
navigate: () => {
throw new Error('Not implemented.');
},
- shortUrls: new ServerShortUrlClientFactory({
- currentVersion,
- }),
+ shortUrls: ({ locators }) =>
+ new ServerShortUrlClientFactory({
+ currentVersion,
+ locators,
+ }),
});
const definition = new LegacyShortUrlLocatorDefinition();
const locator = service.locators.create(definition);
@@ -177,4 +181,111 @@ describe('ServerShortUrlClient', () => {
);
});
});
+
+ describe('Persistable State', () => {
+ interface FooLocatorParams extends SerializableRecord {
+ dashboardId: string;
+ indexPatternId: string;
+ }
+
+ class FooLocatorDefinition implements LocatorDefinition {
+ public readonly id = 'FOO_LOCATOR';
+
+ public readonly getLocation = async () => ({
+ app: 'foo_app',
+ path: '/foo/path',
+ state: {},
+ });
+
+ public readonly extract = (
+ state: FooLocatorParams
+ ): { state: FooLocatorParams; references: SavedObjectReference[] } => ({
+ state,
+ references: [
+ {
+ id: state.dashboardId,
+ type: 'dashboard',
+ name: 'dashboardId',
+ },
+ {
+ id: state.indexPatternId,
+ type: 'index_pattern',
+ name: 'indexPatternId',
+ },
+ ],
+ });
+
+ public readonly inject = (
+ state: FooLocatorParams,
+ references: SavedObjectReference[]
+ ): FooLocatorParams => {
+ const dashboard = references.find(
+ (ref) => ref.type === 'dashboard' && ref.name === 'dashboardId'
+ );
+ const indexPattern = references.find(
+ (ref) => ref.type === 'index_pattern' && ref.name === 'indexPatternId'
+ );
+
+ return {
+ ...state,
+ dashboardId: dashboard ? dashboard.id : '',
+ indexPatternId: indexPattern ? indexPattern.id : '',
+ };
+ };
+ }
+
+ test('extracts and persists references', async () => {
+ const { service, client, storage } = setup();
+ const locator = service.locators.create(new FooLocatorDefinition());
+ const shortUrl = await client.create({
+ locator,
+ params: {
+ dashboardId: '123',
+ indexPatternId: '456',
+ },
+ });
+ const record = await storage.getById(shortUrl.data.id);
+
+ expect(record.references).toEqual([
+ {
+ id: '123',
+ type: 'dashboard',
+ name: 'locator:params:dashboardId',
+ },
+ {
+ id: '456',
+ type: 'index_pattern',
+ name: 'locator:params:indexPatternId',
+ },
+ ]);
+ });
+
+ test('injects references', async () => {
+ const { service, client, storage } = setup();
+ const locator = service.locators.create(new FooLocatorDefinition());
+ const shortUrl1 = await client.create({
+ locator,
+ params: {
+ dashboardId: '3',
+ indexPatternId: '5',
+ },
+ });
+ const record1 = await storage.getById(shortUrl1.data.id);
+
+ record1.data.locator.state = {};
+
+ await storage.update(record1.data.id, record1.data);
+
+ const record2 = await storage.getById(shortUrl1.data.id);
+
+ expect(record2.data.locator.state).toEqual({});
+
+ const shortUrl2 = await client.get(shortUrl1.data.id);
+
+ expect(shortUrl2.data.locator.state).toEqual({
+ dashboardId: '3',
+ indexPatternId: '5',
+ });
+ });
+ });
});
diff --git a/src/plugins/share/server/url_service/short_urls/short_url_client.ts b/src/plugins/share/server/url_service/short_urls/short_url_client.ts
index caaa76bef172d..1efece073d955 100644
--- a/src/plugins/share/server/url_service/short_urls/short_url_client.ts
+++ b/src/plugins/share/server/url_service/short_urls/short_url_client.ts
@@ -7,8 +7,17 @@
*/
import type { SerializableRecord } from '@kbn/utility-types';
+import { SavedObjectReference } from 'kibana/server';
import { generateSlug } from 'random-word-slugs';
-import type { IShortUrlClient, ShortUrl, ShortUrlCreateParams } from '../../../common/url_service';
+import { ShortUrlRecord } from '.';
+import type {
+ IShortUrlClient,
+ ShortUrl,
+ ShortUrlCreateParams,
+ ILocatorClient,
+ ShortUrlData,
+ LocatorData,
+} from '../../../common/url_service';
import type { ShortUrlStorage } from './types';
import { validateSlug } from './util';
@@ -36,6 +45,11 @@ export interface ServerShortUrlClientDependencies {
* Storage provider for short URLs.
*/
storage: ShortUrlStorage;
+
+ /**
+ * The locators service.
+ */
+ locators: ILocatorClient;
}
export class ServerShortUrlClient implements IShortUrlClient {
@@ -64,44 +78,80 @@ export class ServerShortUrlClient implements IShortUrlClient {
}
}
+ const extracted = this.extractReferences({
+ id: locator.id,
+ version: currentVersion,
+ state: params,
+ });
const now = Date.now();
- const data = await storage.create({
- accessCount: 0,
- accessDate: now,
- createDate: now,
- slug,
- locator: {
- id: locator.id,
- version: currentVersion,
- state: params,
+
+ const data = await storage.create(
+ {
+ accessCount: 0,
+ accessDate: now,
+ createDate: now,
+ slug,
+ locator: extracted.state as LocatorData
,
},
- });
+ { references: extracted.references }
+ );
return {
data,
};
}
- public async get(id: string): Promise {
- const { storage } = this.dependencies;
- const data = await storage.getById(id);
+ private extractReferences(locatorData: LocatorData): {
+ state: LocatorData;
+ references: SavedObjectReference[];
+ } {
+ const { locators } = this.dependencies;
+ const { state, references } = locators.extract(locatorData);
+ return {
+ state,
+ references: references.map((ref) => ({
+ ...ref,
+ name: 'locator:' + ref.name,
+ })),
+ };
+ }
+ private injectReferences({ data, references }: ShortUrlRecord): ShortUrlData {
+ const { locators } = this.dependencies;
+ const locatorReferences = references
+ .filter((ref) => ref.name.startsWith('locator:'))
+ .map((ref) => ({
+ ...ref,
+ name: ref.name.substr('locator:'.length),
+ }));
return {
- data,
+ ...data,
+ locator: locators.inject(data.locator, locatorReferences),
};
}
- public async delete(id: string): Promise {
+ public async get(id: string): Promise {
const { storage } = this.dependencies;
- await storage.delete(id);
+ const record = await storage.getById(id);
+ const data = this.injectReferences(record);
+
+ return {
+ data,
+ };
}
public async resolve(slug: string): Promise {
const { storage } = this.dependencies;
- const data = await storage.getBySlug(slug);
+ const record = await storage.getBySlug(slug);
+ const data = this.injectReferences(record);
return {
data,
};
}
+
+ public async delete(id: string): Promise {
+ const { storage } = this.dependencies;
+ await storage.delete(id);
+ }
}
diff --git a/src/plugins/share/server/url_service/short_urls/short_url_client_factory.ts b/src/plugins/share/server/url_service/short_urls/short_url_client_factory.ts
index 696233b7a1ca5..63456c36daa68 100644
--- a/src/plugins/share/server/url_service/short_urls/short_url_client_factory.ts
+++ b/src/plugins/share/server/url_service/short_urls/short_url_client_factory.ts
@@ -8,7 +8,7 @@
import { SavedObjectsClientContract } from 'kibana/server';
import { ShortUrlStorage } from './types';
-import type { IShortUrlClientFactory } from '../../../common/url_service';
+import type { IShortUrlClientFactory, ILocatorClient } from '../../../common/url_service';
import { ServerShortUrlClient } from './short_url_client';
import { SavedObjectShortUrlStorage } from './storage/saved_object_short_url_storage';
@@ -20,6 +20,11 @@ export interface ServerShortUrlClientFactoryDependencies {
* Current version of Kibana, e.g. 7.15.0.
*/
currentVersion: string;
+
+ /**
+ * Locators service.
+ */
+ locators: ILocatorClient;
}
export interface ServerShortUrlClientFactoryCreateParams {
@@ -39,9 +44,11 @@ export class ServerShortUrlClientFactory
savedObjects: params.savedObjects!,
savedObjectType: 'url',
});
+ const { currentVersion, locators } = this.dependencies;
const client = new ServerShortUrlClient({
storage,
- currentVersion: this.dependencies.currentVersion,
+ currentVersion,
+ locators,
});
return client;
diff --git a/src/plugins/share/server/url_service/short_urls/storage/memory_short_url_storage.test.ts b/src/plugins/share/server/url_service/short_urls/storage/memory_short_url_storage.test.ts
index d178e0b81786c..5d1b0bfa0bf55 100644
--- a/src/plugins/share/server/url_service/short_urls/storage/memory_short_url_storage.test.ts
+++ b/src/plugins/share/server/url_service/short_urls/storage/memory_short_url_storage.test.ts
@@ -41,6 +41,46 @@ describe('.create()', () => {
});
});
+describe('.update()', () => {
+ test('can update an existing short URL', async () => {
+ const storage = new MemoryShortUrlStorage();
+ const now = Date.now();
+ const url1 = await storage.create({
+ accessCount: 0,
+ createDate: now,
+ accessDate: now,
+ locator: {
+ id: 'TEST_LOCATOR',
+ version: '7.11',
+ state: {
+ foo: 'bar',
+ },
+ },
+ slug: 'test-slug',
+ });
+
+ await storage.update(url1.id, {
+ accessCount: 1,
+ });
+
+ const url2 = await storage.getById(url1.id);
+
+ expect(url1.accessCount).toBe(0);
+ expect(url2.data.accessCount).toBe(1);
+ });
+
+ test('throws when URL does not exist', async () => {
+ const storage = new MemoryShortUrlStorage();
+ const [, error] = await of(
+ storage.update('DOES_NOT_EXIST', {
+ accessCount: 1,
+ })
+ );
+
+ expect(error).toBeInstanceOf(Error);
+ });
+});
+
describe('.getById()', () => {
test('can fetch by ID a newly created short URL', async () => {
const storage = new MemoryShortUrlStorage();
@@ -58,7 +98,7 @@ describe('.getById()', () => {
},
slug: 'test-slug',
});
- const url2 = await storage.getById(url1.id);
+ const url2 = (await storage.getById(url1.id)).data;
expect(url2.accessCount).toBe(0);
expect(url1.createDate).toBe(now);
@@ -112,7 +152,7 @@ describe('.getBySlug()', () => {
},
slug: 'test-slug',
});
- const url2 = await storage.getBySlug('test-slug');
+ const url2 = (await storage.getBySlug('test-slug')).data;
expect(url2.accessCount).toBe(0);
expect(url1.createDate).toBe(now);
diff --git a/src/plugins/share/server/url_service/short_urls/storage/memory_short_url_storage.ts b/src/plugins/share/server/url_service/short_urls/storage/memory_short_url_storage.ts
index 40d76a91154ba..fafd00344eecd 100644
--- a/src/plugins/share/server/url_service/short_urls/storage/memory_short_url_storage.ts
+++ b/src/plugins/share/server/url_service/short_urls/storage/memory_short_url_storage.ts
@@ -9,35 +9,54 @@
import { v4 as uuidv4 } from 'uuid';
import type { SerializableRecord } from '@kbn/utility-types';
import { ShortUrlData } from 'src/plugins/share/common/url_service/short_urls/types';
-import { ShortUrlStorage } from '../types';
+import { SavedObjectReference } from 'kibana/server';
+import { ShortUrlStorage, ShortUrlRecord } from '../types';
+
+const clone = (obj: P): P => JSON.parse(JSON.stringify(obj)) as P;
export class MemoryShortUrlStorage implements ShortUrlStorage {
- private urls = new Map();
+ private urls = new Map();
public async create(
- data: Omit, 'id'>
+ data: Omit, 'id'>,
+ { references = [] }: { references?: SavedObjectReference[] } = {}
): Promise> {
const id = uuidv4();
- const url: ShortUrlData = { ...data, id };
+ const url: ShortUrlRecord
= {
+ data: { ...data, id },
+ references,
+ };
this.urls.set(id, url);
- return url;
+
+ return clone(url.data);
+ }
+
+ public async update
(
+ id: string,
+ data: Partial, 'id'>>,
+ { references }: { references?: SavedObjectReference[] } = {}
+ ): Promise {
+ const so = await this.getById(id);
+ Object.assign(so.data, data);
+ if (references) so.references = references;
+ this.urls.set(id, so);
}
public async getById(
id: string
- ): Promise> {
+ ): Promise> {
if (!this.urls.has(id)) {
throw new Error(`No short url with id "${id}"`);
}
- return this.urls.get(id)! as ShortUrlData;
+ return clone(this.urls.get(id)! as ShortUrlRecord
);
}
public async getBySlug
(
slug: string
- ): Promise> {
+ ): Promise> {
for (const url of this.urls.values()) {
- if (url.slug === slug) {
- return url as ShortUrlData;
+ if (url.data.slug === slug) {
+ return clone(url as ShortUrlRecord
);
}
}
throw new Error(`No short url with slug "${slug}".`);
@@ -45,7 +64,7 @@ export class MemoryShortUrlStorage implements ShortUrlStorage {
public async exists(slug: string): Promise {
for (const url of this.urls.values()) {
- if (url.slug === slug) {
+ if (url.data.slug === slug) {
return true;
}
}
diff --git a/src/plugins/share/server/url_service/short_urls/storage/saved_object_short_url_storage.ts b/src/plugins/share/server/url_service/short_urls/storage/saved_object_short_url_storage.ts
index c66db6d82cdbd..792dfabde3cab 100644
--- a/src/plugins/share/server/url_service/short_urls/storage/saved_object_short_url_storage.ts
+++ b/src/plugins/share/server/url_service/short_urls/storage/saved_object_short_url_storage.ts
@@ -7,7 +7,8 @@
*/
import type { SerializableRecord } from '@kbn/utility-types';
-import { SavedObject, SavedObjectsClientContract } from 'kibana/server';
+import { SavedObject, SavedObjectReference, SavedObjectsClientContract } from 'kibana/server';
+import { ShortUrlRecord } from '..';
import { LEGACY_SHORT_URL_LOCATOR_ID } from '../../../../common/url_service/locators/legacy_short_url_locator';
import { ShortUrlData } from '../../../../common/url_service/short_urls/types';
import { ShortUrlStorage } from '../types';
@@ -85,12 +86,15 @@ const createShortUrlData = (
};
const createAttributes =
(
- data: Omit, 'id'>
+ data: Partial, 'id'>>
): ShortUrlSavedObjectAttributes => {
- const { locator, ...rest } = data;
+ const { accessCount = 0, accessDate = 0, createDate = 0, slug = '', locator } = data;
const attributes: ShortUrlSavedObjectAttributes = {
- ...rest,
- locatorJSON: JSON.stringify(locator),
+ accessCount,
+ accessDate,
+ createDate,
+ slug,
+ locatorJSON: locator ? JSON.stringify(locator) : '',
url: '',
};
@@ -106,30 +110,49 @@ export class SavedObjectShortUrlStorage implements ShortUrlStorage {
constructor(private readonly dependencies: SavedObjectShortUrlStorageDependencies) {}
public async create(
- data: Omit, 'id'>
+ data: Omit, 'id'>,
+ { references }: { references?: SavedObjectReference[] } = {}
): Promise> {
const { savedObjects, savedObjectType } = this.dependencies;
const attributes = createAttributes(data);
const savedObject = await savedObjects.create(savedObjectType, attributes, {
refresh: true,
+ references,
});
return createShortUrlData(savedObject);
}
+ public async update
(
+ id: string,
+ data: Partial, 'id'>>,
+ { references }: { references?: SavedObjectReference[] } = {}
+ ): Promise {
+ const { savedObjects, savedObjectType } = this.dependencies;
+ const attributes = createAttributes(data);
+
+ await savedObjects.update(savedObjectType, id, attributes, {
+ refresh: true,
+ references,
+ });
+ }
+
public async getById(
id: string
- ): Promise> {
+ ): Promise> {
const { savedObjects, savedObjectType } = this.dependencies;
const savedObject = await savedObjects.get(savedObjectType, id);
- return createShortUrlData(savedObject);
+ return {
+ data: createShortUrlData
(savedObject),
+ references: savedObject.references,
+ };
}
public async getBySlug
(
slug: string
- ): Promise> {
+ ): Promise> {
const { savedObjects } = this.dependencies;
const search = `(attributes.slug:"${escapeSearchReservedChars(slug)}")`;
const result = await savedObjects.find({
@@ -143,7 +166,10 @@ export class SavedObjectShortUrlStorage implements ShortUrlStorage {
const savedObject = result.saved_objects[0] as ShortUrlSavedObject;
- return createShortUrlData(savedObject);
+ return {
+ data: createShortUrlData
(savedObject),
+ references: savedObject.references,
+ };
}
public async exists(slug: string): Promise {
diff --git a/src/plugins/share/server/url_service/short_urls/types.ts b/src/plugins/share/server/url_service/short_urls/types.ts
index 7aab70ca49519..9a9d9006eb371 100644
--- a/src/plugins/share/server/url_service/short_urls/types.ts
+++ b/src/plugins/share/server/url_service/short_urls/types.ts
@@ -7,6 +7,7 @@
*/
import type { SerializableRecord } from '@kbn/utility-types';
+import { SavedObjectReference } from 'kibana/server';
import { ShortUrlData } from '../../../common/url_service/short_urls/types';
/**
@@ -17,20 +18,32 @@ export interface ShortUrlStorage {
* Create and store a new short URL entry.
*/
create(
- data: Omit, 'id'>
+ data: Omit, 'id'>,
+ options?: { references?: SavedObjectReference[] }
): Promise>;
+ /**
+ * Update an existing short URL entry.
+ */
+ update(
+ id: string,
+ data: Partial, 'id'>>,
+ options?: { references?: SavedObjectReference[] }
+ ): Promise;
+
/**
* Fetch a short URL entry by ID.
*/
- getById(id: string): Promise>;
+ getById(
+ id: string
+ ): Promise>;
/**
* Fetch a short URL entry by slug.
*/
getBySlug(
slug: string
- ): Promise>;
+ ): Promise>;
/**
* Checks if a short URL exists by slug.
@@ -42,3 +55,8 @@ export interface ShortUrlStorage {
*/
delete(id: string): Promise;
}
+
+export interface ShortUrlRecord {
+ data: ShortUrlData;
+ references: SavedObjectReference[];
+}
diff --git a/src/plugins/vis_default_editor/kibana.json b/src/plugins/vis_default_editor/kibana.json
index e85c5713eb82c..efed1eab1e494 100644
--- a/src/plugins/vis_default_editor/kibana.json
+++ b/src/plugins/vis_default_editor/kibana.json
@@ -3,7 +3,7 @@
"version": "kibana",
"ui": true,
"optionalPlugins": ["visualize"],
- "requiredBundles": ["kibanaUtils", "kibanaReact", "data", "fieldFormats"],
+ "requiredBundles": ["kibanaUtils", "kibanaReact", "data", "fieldFormats", "discover"],
"owner": {
"name": "Vis Editors",
"githubTeam": "kibana-vis-editors"
diff --git a/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx b/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx
index dab982e5a8070..f1eebbbdf2116 100644
--- a/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx
+++ b/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx
@@ -26,7 +26,7 @@ import {
} from 'src/plugins/visualizations/public';
import type { Schema } from 'src/plugins/visualizations/public';
import { TimeRange } from 'src/plugins/data/public';
-import { SavedObject } from 'src/plugins/saved_objects/public';
+import { SavedSearch } from 'src/plugins/discover/public';
import { DefaultEditorNavBar } from './navbar';
import { DefaultEditorControls } from './controls';
import { setStateParamValue, useEditorReducer, useEditorFormState, discardChanges } from './state';
@@ -42,7 +42,7 @@ interface DefaultEditorSideBarProps {
vis: Vis;
isLinkedSearch: boolean;
eventEmitter: EventEmitter;
- savedSearch?: SavedObject;
+ savedSearch?: SavedSearch;
timeRange: TimeRange;
}
diff --git a/src/plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx b/src/plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx
index cab27d53b827d..2740f4ff50b4e 100644
--- a/src/plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx
+++ b/src/plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx
@@ -25,18 +25,18 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { Vis } from 'src/plugins/visualizations/public';
-import { SavedObject } from 'src/plugins/saved_objects/public';
+import { SavedSearch, getSavedSearchUrl } from '../../../../discover/public';
import { ApplicationStart } from '../../../../../core/public';
import { useKibana } from '../../../../kibana_react/public';
interface LinkedSearchProps {
- savedSearch: SavedObject;
+ savedSearch: SavedSearch;
eventEmitter: EventEmitter;
}
interface SidebarTitleProps {
isLinkedSearch: boolean;
- savedSearch?: SavedObject;
+ savedSearch?: SavedSearch;
vis: Vis;
eventEmitter: EventEmitter;
}
@@ -55,7 +55,7 @@ export function LinkedSearch({ savedSearch, eventEmitter }: LinkedSearchProps) {
}, [eventEmitter]);
const onClickViewInDiscover = useCallback(() => {
application.navigateToApp('discover', {
- path: `#/view/${savedSearch.id}`,
+ path: getSavedSearchUrl(savedSearch.id),
});
}, [application, savedSearch.id]);
diff --git a/src/plugins/vis_types/pie/public/utils/get_layers.ts b/src/plugins/vis_types/pie/public/utils/get_layers.ts
index 6ecef858619b5..c9d8da15b78f6 100644
--- a/src/plugins/vis_types/pie/public/utils/get_layers.ts
+++ b/src/plugins/vis_types/pie/public/utils/get_layers.ts
@@ -133,7 +133,6 @@ export const getLayers = (
syncColors: boolean
): PartitionLayer[] => {
const fillLabel: Partial = {
- textInvertible: true,
valueFont: {
fontWeight: 700,
},
diff --git a/src/plugins/vis_types/timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_types/timelion/public/components/timelion_vis_component.tsx
index d7b7bb14723d7..e6d2638bedf48 100644
--- a/src/plugins/vis_types/timelion/public/components/timelion_vis_component.tsx
+++ b/src/plugins/vis_types/timelion/public/components/timelion_vis_component.tsx
@@ -64,6 +64,8 @@ const DefaultYAxis = () => (
id="left"
domain={withStaticPadding({
fit: false,
+ min: NaN,
+ max: NaN,
})}
position={Position.Left}
groupId={`${MAIN_GROUP_ID}`}
diff --git a/src/plugins/vis_types/timelion/public/helpers/panel_utils.ts b/src/plugins/vis_types/timelion/public/helpers/panel_utils.ts
index 3c76b95bd05ca..98be5efc55a26 100644
--- a/src/plugins/vis_types/timelion/public/helpers/panel_utils.ts
+++ b/src/plugins/vis_types/timelion/public/helpers/panel_utils.ts
@@ -88,8 +88,8 @@ const adaptYaxisParams = (yaxis: IAxis) => {
tickFormat: y.tickFormatter,
domain: withStaticPadding({
fit: y.min === undefined && y.max === undefined,
- min: y.min,
- max: y.max,
+ min: y.min ?? NaN,
+ max: y.max ?? NaN,
}),
};
};
@@ -118,6 +118,8 @@ export const extractAllYAxis = (series: Series[]) => {
groupId,
domain: withStaticPadding({
fit: false,
+ min: NaN,
+ max: NaN,
}),
id: (yaxis?.position || Position.Left) + index,
position: Position.Left,
diff --git a/src/plugins/vis_types/vega/public/data_model/vega_parser.test.js b/src/plugins/vis_types/vega/public/data_model/vega_parser.test.js
index cfeed174307ac..13c17b8f4c38f 100644
--- a/src/plugins/vis_types/vega/public/data_model/vega_parser.test.js
+++ b/src/plugins/vis_types/vega/public/data_model/vega_parser.test.js
@@ -81,6 +81,20 @@ describe(`VegaParser.parseAsync`, () => {
})
)
);
+
+ test(`should return a specific error in case of $schema URL not valid`, async () => {
+ const vp = new VegaParser({
+ $schema: 'https://vega.github.io/schema/vega-lite/v4.jsonanythingtobreakthis',
+ mark: 'circle',
+ encoding: { row: { field: 'a' } },
+ });
+
+ await vp.parseAsync();
+
+ expect(vp.error).toBe(
+ 'The URL for the JSON "$schema" is incorrect. Correct the URL, then click Update.'
+ );
+ });
});
describe(`VegaParser._setDefaultValue`, () => {
diff --git a/src/plugins/vis_types/vega/public/data_model/vega_parser.ts b/src/plugins/vis_types/vega/public/data_model/vega_parser.ts
index 9000fed7f6116..bf2a6be25c71a 100644
--- a/src/plugins/vis_types/vega/public/data_model/vega_parser.ts
+++ b/src/plugins/vis_types/vega/public/data_model/vega_parser.ts
@@ -553,25 +553,37 @@ The URL is an identifier only. Kibana and your browser will never access this UR
* @private
*/
private parseSchema(spec: VegaSpec) {
- const schema = schemaParser(spec.$schema);
- const isVegaLite = schema.library === 'vega-lite';
- const libVersion = isVegaLite ? vegaLiteVersion : vegaVersion;
+ try {
+ const schema = schemaParser(spec.$schema);
+ const isVegaLite = schema.library === 'vega-lite';
+ const libVersion = isVegaLite ? vegaLiteVersion : vegaVersion;
- if (versionCompare(schema.version, libVersion) > 0) {
- this._onWarning(
- i18n.translate('visTypeVega.vegaParser.notValidLibraryVersionForInputSpecWarningMessage', {
+ if (versionCompare(schema.version, libVersion) > 0) {
+ this._onWarning(
+ i18n.translate(
+ 'visTypeVega.vegaParser.notValidLibraryVersionForInputSpecWarningMessage',
+ {
+ defaultMessage:
+ 'The input spec uses {schemaLibrary} {schemaVersion}, but current version of {schemaLibrary} is {libraryVersion}.',
+ values: {
+ schemaLibrary: schema.library,
+ schemaVersion: schema.version,
+ libraryVersion: libVersion,
+ },
+ }
+ )
+ );
+ }
+
+ return { isVegaLite, libVersion };
+ } catch (e) {
+ throw Error(
+ i18n.translate('visTypeVega.vegaParser.notValidSchemaForInputSpec', {
defaultMessage:
- 'The input spec uses {schemaLibrary} {schemaVersion}, but current version of {schemaLibrary} is {libraryVersion}.',
- values: {
- schemaLibrary: schema.library,
- schemaVersion: schema.version,
- libraryVersion: libVersion,
- },
+ 'The URL for the JSON "$schema" is incorrect. Correct the URL, then click Update.',
})
);
}
-
- return { isVegaLite, libVersion };
}
/**
diff --git a/src/plugins/vis_types/xy/public/components/xy_settings.tsx b/src/plugins/vis_types/xy/public/components/xy_settings.tsx
index 5e02b65822d6c..74aff7535c2d8 100644
--- a/src/plugins/vis_types/xy/public/components/xy_settings.tsx
+++ b/src/plugins/vis_types/xy/public/components/xy_settings.tsx
@@ -71,7 +71,6 @@ function getValueLabelsStyling() {
return {
displayValue: {
fontSize: { min: VALUE_LABELS_MIN_FONTSIZE, max: VALUE_LABELS_MAX_FONTSIZE },
- fill: { textInverted: false, textContrast: true },
alignment: { horizontal: HorizontalAlignment.Center, vertical: VerticalAlignment.Middle },
},
};
diff --git a/src/plugins/vis_types/xy/public/config/get_axis.ts b/src/plugins/vis_types/xy/public/config/get_axis.ts
index b5cc96830e46a..09495725296cd 100644
--- a/src/plugins/vis_types/xy/public/config/get_axis.ts
+++ b/src/plugins/vis_types/xy/public/config/get_axis.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { identity, isNil } from 'lodash';
+import { identity } from 'lodash';
import { AxisSpec, TickFormatter, YDomainRange, ScaleType as ECScaleType } from '@elastic/charts';
@@ -171,17 +171,5 @@ function getAxisDomain(
const fit = defaultYExtents;
const padding = boundsMargin || undefined;
- if (!isNil(min) && !isNil(max)) {
- return { fit, padding, min, max };
- }
-
- if (!isNil(min)) {
- return { fit, padding, min };
- }
-
- if (!isNil(max)) {
- return { fit, padding, max };
- }
-
- return { fit, padding };
+ return { fit, padding, min: min ?? NaN, max: max ?? NaN };
}
diff --git a/src/plugins/vis_types/xy/public/utils/domain.ts b/src/plugins/vis_types/xy/public/utils/domain.ts
index fa8dd74e3942a..5b1310863979a 100644
--- a/src/plugins/vis_types/xy/public/utils/domain.ts
+++ b/src/plugins/vis_types/xy/public/utils/domain.ts
@@ -33,6 +33,8 @@ export const getXDomain = (params: Aspect['params']): DomainRange => {
return {
minInterval,
+ min: NaN,
+ max: NaN,
};
};
@@ -74,9 +76,9 @@ export const getAdjustedDomain = (
};
}
- return 'interval' in params
- ? {
- minInterval: params.interval,
- }
- : {};
+ return {
+ minInterval: 'interval' in params ? params.interval : undefined,
+ min: NaN,
+ max: NaN,
+ };
};
diff --git a/src/plugins/vis_types/xy/public/utils/render_all_series.test.mocks.ts b/src/plugins/vis_types/xy/public/utils/render_all_series.test.mocks.ts
index 5fe1b03dd8b93..c14e313b1e7a4 100644
--- a/src/plugins/vis_types/xy/public/utils/render_all_series.test.mocks.ts
+++ b/src/plugins/vis_types/xy/public/utils/render_all_series.test.mocks.ts
@@ -112,7 +112,10 @@ export const getVisConfig = (): VisConfig => {
mode: AxisMode.Normal,
type: 'linear',
},
- domain: {},
+ domain: {
+ min: NaN,
+ max: NaN,
+ },
integersOnly: false,
},
],
@@ -246,7 +249,10 @@ export const getVisConfigMutipleYaxis = (): VisConfig => {
mode: AxisMode.Normal,
type: 'linear',
},
- domain: {},
+ domain: {
+ min: NaN,
+ max: NaN,
+ },
integersOnly: false,
},
],
@@ -435,7 +441,10 @@ export const getVisConfigPercentiles = (): VisConfig => {
mode: AxisMode.Normal,
type: 'linear',
},
- domain: {},
+ domain: {
+ min: NaN,
+ max: NaN,
+ },
integersOnly: false,
},
],
diff --git a/src/plugins/vis_types/xy/public/vis_component.tsx b/src/plugins/vis_types/xy/public/vis_component.tsx
index f4d566f49602e..515ad3e7eaf6f 100644
--- a/src/plugins/vis_types/xy/public/vis_component.tsx
+++ b/src/plugins/vis_types/xy/public/vis_component.tsx
@@ -19,6 +19,7 @@ import {
ScaleType,
AccessorFn,
Accessor,
+ XYBrushEvent,
} from '@elastic/charts';
import { compact } from 'lodash';
@@ -131,7 +132,10 @@ const VisComponent = (props: VisComponentProps) => {
): BrushEndListener | undefined => {
if (xAccessor !== null && isInterval) {
return (brushArea) => {
- const event = getBrushFromChartBrushEventFn(visData, xAccessor)(brushArea);
+ const event = getBrushFromChartBrushEventFn(
+ visData,
+ xAccessor
+ )(brushArea as XYBrushEvent);
props.fireEvent(event);
};
}
diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts
index 47f544ce2f5d3..87095f5c389ed 100644
--- a/src/plugins/visualizations/public/plugin.ts
+++ b/src/plugins/visualizations/public/plugin.ts
@@ -23,9 +23,9 @@ import {
setAggs,
setChrome,
setOverlays,
- setSavedSearchLoader,
setEmbeddable,
setDocLinks,
+ setSpaces,
} from './services';
import {
VISUALIZE_EMBEDDABLE_TYPE,
@@ -51,8 +51,6 @@ import {
findListItems,
} from './utils/saved_visualize_utils';
-import { createSavedSearchesLoader } from '../../discover/public';
-
import type {
PluginInitializerContext,
CoreSetup,
@@ -191,6 +189,11 @@ export class VisualizationsPlugin
setAggs(data.search.aggs);
setOverlays(core.overlays);
setChrome(core.chrome);
+
+ if (spaces) {
+ setSpaces(spaces);
+ }
+
const savedVisualizationsLoader = createSavedVisLoader({
savedObjectsClient: core.savedObjects.client,
indexPatterns: data.indexPatterns,
@@ -198,11 +201,7 @@ export class VisualizationsPlugin
visualizationTypes: types,
});
setSavedVisualizationsLoader(savedVisualizationsLoader);
- const savedSearchLoader = createSavedSearchesLoader({
- savedObjectsClient: core.savedObjects.client,
- savedObjects,
- });
- setSavedSearchLoader(savedSearchLoader);
+
return {
...types,
showNewVisModal,
diff --git a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts
index fbd8e414c2738..aa8183eb8da39 100644
--- a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts
+++ b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts
@@ -16,8 +16,8 @@
import type { SavedObjectsStart, SavedObject } from '../../../../plugins/saved_objects/public';
// @ts-ignore
import { updateOldState } from '../legacy/vis_update_state';
+import { __LEGACY } from '../../../discover/public';
import { extractReferences, injectReferences } from '../utils/saved_visualization_references';
-import { createSavedSearchesLoader } from '../../../discover/public';
import type { SavedObjectsClientContract } from '../../../../core/public';
import type { IndexPatternsContract } from '../../../../plugins/data/public';
import type { ISavedVis } from '../types';
@@ -30,7 +30,7 @@ export interface SavedVisServices {
/** @deprecated **/
export function createSavedVisClass(services: SavedVisServices) {
- const savedSearch = createSavedSearchesLoader(services);
+ const savedSearch = __LEGACY.createSavedSearchesLoader(services);
class SavedVis extends services.savedObjects.SavedObjectClass {
public static type: string = 'visualization';
diff --git a/src/plugins/visualizations/public/services.ts b/src/plugins/visualizations/public/services.ts
index f1ab9077cd207..ed18884d9dc83 100644
--- a/src/plugins/visualizations/public/services.ts
+++ b/src/plugins/visualizations/public/services.ts
@@ -18,13 +18,14 @@ import type {
} from '../../../core/public';
import type { TypesStart } from './vis_types';
import { createGetterSetter } from '../../../plugins/kibana_utils/public';
-import { DataPublicPluginStart, TimefilterContract } from '../../../plugins/data/public';
-import { UsageCollectionSetup } from '../../../plugins/usage_collection/public';
-import { ExpressionsStart } from '../../../plugins/expressions/public';
-import { UiActionsStart } from '../../../plugins/ui_actions/public';
-import { SavedVisualizationsLoader } from './saved_visualizations';
-import { SavedObjectLoader } from '../../saved_objects/public';
-import { EmbeddableStart } from '../../embeddable/public';
+import type { DataPublicPluginStart, TimefilterContract } from '../../../plugins/data/public';
+import type { UsageCollectionSetup } from '../../../plugins/usage_collection/public';
+import type { ExpressionsStart } from '../../../plugins/expressions/public';
+import type { UiActionsStart } from '../../../plugins/ui_actions/public';
+import type { SavedVisualizationsLoader } from './saved_visualizations';
+import type { EmbeddableStart } from '../../embeddable/public';
+
+import type { SpacesPluginStart } from '../../../../x-pack/plugins/spaces/public';
export const [getUISettings, setUISettings] = createGetterSetter('UISettings');
@@ -47,8 +48,10 @@ export const [getTimeFilter, setTimeFilter] = createGetterSetter('Search');
-export const [getUsageCollector, setUsageCollector] =
- createGetterSetter('UsageCollection');
+export const [getUsageCollector, setUsageCollector] = createGetterSetter(
+ 'UsageCollection',
+ false
+);
export const [getExpressions, setExpressions] = createGetterSetter('Expressions');
@@ -64,5 +67,4 @@ export const [getOverlays, setOverlays] = createGetterSetter('Over
export const [getChrome, setChrome] = createGetterSetter('Chrome');
-export const [getSavedSearchLoader, setSavedSearchLoader] =
- createGetterSetter('savedSearchLoader');
+export const [getSpaces, setSpaces] = createGetterSetter('Spaces', false);
diff --git a/src/plugins/visualizations/public/vis.ts b/src/plugins/visualizations/public/vis.ts
index dfab4ecfc3cd8..2a1e7f2c8c673 100644
--- a/src/plugins/visualizations/public/vis.ts
+++ b/src/plugins/visualizations/public/vis.ts
@@ -21,17 +21,19 @@ import { Assign } from '@kbn/utility-types';
import { i18n } from '@kbn/i18n';
import { PersistedState } from './persisted_state';
-import { getTypes, getAggs, getSearch, getSavedSearchLoader } from './services';
+import { getTypes, getAggs, getSearch, getSavedObjects, getSpaces } from './services';
import {
IAggConfigs,
IndexPattern,
ISearchSource,
AggConfigSerialized,
SearchSourceFields,
-} from '../../../plugins/data/public';
+} from '../../data/public';
import { BaseVisType } from './vis_types';
import { VisParams } from '../common/types';
+import { getSavedSearch, throwErrorOnSavedSearchUrlConflict } from '../../discover/public';
+
export interface SerializedVisData {
expression?: string;
aggs: AggConfigSerialized[];
@@ -58,14 +60,20 @@ export interface VisData {
}
const getSearchSource = async (inputSearchSource: ISearchSource, savedSearchId?: string) => {
- const searchSource = inputSearchSource.createCopy();
if (savedSearchId) {
- const savedSearch = await getSavedSearchLoader().get(savedSearchId);
+ const savedSearch = await getSavedSearch(savedSearchId, {
+ search: getSearch(),
+ savedObjectsClient: getSavedObjects().client,
+ spaces: getSpaces(),
+ });
+
+ await throwErrorOnSavedSearchUrlConflict(savedSearch);
- searchSource.setParent(savedSearch.searchSource);
+ if (savedSearch?.searchSource) {
+ inputSearchSource.setParent(savedSearch.searchSource);
+ }
}
- searchSource.setField('size', 0);
- return searchSource;
+ return inputSearchSource;
};
type PartialVisState = Assign }>;
diff --git a/src/plugins/visualize/public/application/types.ts b/src/plugins/visualize/public/application/types.ts
index 4debd9a4a7b7d..e77520c962d88 100644
--- a/src/plugins/visualize/public/application/types.ts
+++ b/src/plugins/visualize/public/application/types.ts
@@ -8,7 +8,6 @@
import type { EventEmitter } from 'events';
import type { History } from 'history';
-
import type { SerializableRecord } from '@kbn/utility-types';
import type {
@@ -38,7 +37,7 @@ import type {
import type { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public';
import type { Query, Filter, DataPublicPluginStart, TimeRange } from 'src/plugins/data/public';
import type { SharePluginStart } from 'src/plugins/share/public';
-import type { SavedObjectsStart, SavedObject } from 'src/plugins/saved_objects/public';
+import type { SavedObjectsStart } from 'src/plugins/saved_objects/public';
import type { EmbeddableStart, EmbeddableStateTransfer } from 'src/plugins/embeddable/public';
import type { UrlForwardingStart } from 'src/plugins/url_forwarding/public';
import type { PresentationUtilPluginStart } from 'src/plugins/presentation_util/public';
@@ -46,6 +45,7 @@ import type { SpacesPluginStart } from '../../../../../x-pack/plugins/spaces/pub
import type { DashboardStart } from '../../../dashboard/public';
import type { SavedObjectsTaggingApi } from '../../../saved_objects_tagging_oss/public';
import type { UsageCollectionStart } from '../../../usage_collection/public';
+import type { SavedSearch } from '../../../discover/public';
import { PureVisState } from '../../common/types';
@@ -108,20 +108,15 @@ export interface VisualizeServices extends CoreStart {
spaces?: SpacesPluginStart;
}
-export interface SavedVisInstance {
- vis: Vis;
- savedVis: VisSavedObject;
- savedSearch?: SavedObject;
- embeddableHandler: VisualizeEmbeddableContract;
-}
-
-export interface ByValueVisInstance {
+export interface VisInstance {
vis: Vis;
savedVis: VisSavedObject;
- savedSearch?: SavedObject;
+ savedSearch?: SavedSearch;
embeddableHandler: VisualizeEmbeddableContract;
}
+export type SavedVisInstance = VisInstance;
+export type ByValueVisInstance = VisInstance;
export type VisualizeEditorVisInstance = SavedVisInstance | ByValueVisInstance;
export type VisEditorConstructor = new (
@@ -142,7 +137,7 @@ export interface EditorRenderProps {
filters: Filter[];
timeRange: TimeRange;
query?: Query;
- savedSearch?: SavedObject;
+ savedSearch?: SavedSearch;
uiState: PersistedState;
/**
* Flag to determine if visualiztion is linked to the saved search
diff --git a/src/plugins/visualize/public/application/utils/get_visualization_instance.test.ts b/src/plugins/visualize/public/application/utils/get_visualization_instance.test.ts
index 209516793d69d..777ba244c06a1 100644
--- a/src/plugins/visualize/public/application/utils/get_visualization_instance.test.ts
+++ b/src/plugins/visualize/public/application/utils/get_visualization_instance.test.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { createSavedSearchesLoader } from '../../../../discover/public';
+import { getSavedSearch } from '../../../../discover/public';
import type {
VisualizeInput,
VisSavedObject,
@@ -21,13 +21,13 @@ import { createVisualizeServicesMock } from './mocks';
import { VisualizeServices } from '../types';
import { BehaviorSubject } from 'rxjs';
-const mockSavedSearchObj = {};
-const mockGetSavedSearch = jest.fn(() => mockSavedSearchObj);
-
jest.mock('../../../../discover/public', () => ({
- createSavedSearchesLoader: jest.fn(() => ({
- get: mockGetSavedSearch,
- })),
+ getSavedSearch: jest.fn().mockResolvedValue({
+ id: 'savedSearch',
+ title: 'savedSearchTitle',
+ searchSource: {},
+ }),
+ throwErrorOnSavedSearchUrlConflict: jest.fn(),
}));
let savedVisMock: VisSavedObject;
@@ -116,9 +116,14 @@ describe('getVisualizationInstance', () => {
visMock.data.savedSearchId = 'saved_search_id';
const { savedSearch } = await getVisualizationInstance(mockServices, 'saved_vis_id');
- expect(createSavedSearchesLoader).toHaveBeenCalled();
- expect(mockGetSavedSearch).toHaveBeenCalledWith(visMock.data.savedSearchId);
- expect(savedSearch).toBe(mockSavedSearchObj);
+ expect(getSavedSearch).toHaveBeenCalled();
+ expect(savedSearch).toMatchInlineSnapshot(`
+ Object {
+ "id": "savedSearch",
+ "searchSource": Object {},
+ "title": "savedSearchTitle",
+ }
+ `);
});
test('should subscribe on embeddable handler updates and send toasts on errors', async () => {
diff --git a/src/plugins/visualize/public/application/utils/get_visualization_instance.ts b/src/plugins/visualize/public/application/utils/get_visualization_instance.ts
index faf25ff28cec0..876501d5f099b 100644
--- a/src/plugins/visualize/public/application/utils/get_visualization_instance.ts
+++ b/src/plugins/visualize/public/application/utils/get_visualization_instance.ts
@@ -14,10 +14,13 @@ import {
VisualizeInput,
} from 'src/plugins/visualizations/public';
import { SearchSourceFields } from 'src/plugins/data/public';
-import { SavedObject } from 'src/plugins/saved_objects/public';
import { cloneDeep } from 'lodash';
import { ExpressionValueError } from 'src/plugins/expressions/public';
-import { createSavedSearchesLoader } from '../../../../discover/public';
+import {
+ getSavedSearch,
+ SavedSearch,
+ throwErrorOnSavedSearchUrlConflict,
+} from '../../../../discover/public';
import { SavedFieldNotFound, SavedFieldTypeInvalidForAgg } from '../../../../kibana_utils/common';
import { VisualizeServices } from '../types';
@@ -33,8 +36,7 @@ const createVisualizeEmbeddableAndLinkSavedSearch = async (
vis: Vis,
visualizeServices: VisualizeServices
) => {
- const { data, createVisEmbeddableFromObject, savedObjects, savedObjectsPublic } =
- visualizeServices;
+ const { data, createVisEmbeddableFromObject, savedObjects, spaces } = visualizeServices;
const embeddableHandler = (await createVisEmbeddableFromObject(vis, {
id: '',
timeRange: data.query.timefilter.timefilter.getTime(),
@@ -50,13 +52,16 @@ const createVisualizeEmbeddableAndLinkSavedSearch = async (
}
});
- let savedSearch: SavedObject | undefined;
+ let savedSearch: SavedSearch | undefined;
if (vis.data.savedSearchId) {
- savedSearch = await createSavedSearchesLoader({
+ savedSearch = await getSavedSearch(vis.data.savedSearchId, {
+ search: data.search,
savedObjectsClient: savedObjects.client,
- savedObjects: savedObjectsPublic,
- }).get(vis.data.savedSearchId);
+ spaces,
+ });
+
+ await throwErrorOnSavedSearchUrlConflict(savedSearch);
}
return { savedSearch, embeddableHandler };
diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts
index c9df6a6ec57d8..7ff3434286b6b 100644
--- a/src/plugins/visualize/public/plugin.ts
+++ b/src/plugins/visualize/public/plugin.ts
@@ -28,7 +28,6 @@ import {
createKbnUrlStateStorage,
withNotifyOnErrors,
} from '../../kibana_utils/public';
-import type { SpacesPluginStart } from '../../../../x-pack/plugins/spaces/public';
import { VisualizeConstants } from './application/visualize_constants';
import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../data/public';
@@ -45,6 +44,7 @@ import type { EmbeddableStart } from '../../embeddable/public';
import type { DashboardStart } from '../../dashboard/public';
import type { SavedObjectTaggingOssPluginStart } from '../../saved_objects_tagging_oss/public';
import type { UsageCollectionStart } from '../../usage_collection/public';
+import type { SpacesApi } from '../../../../x-pack/plugins/spaces/public';
import { setVisEditorsRegistry, setUISettings, setUsageCollector } from './services';
import { createVisEditorsRegistry, VisEditorsRegistry } from './vis_editors_registry';
@@ -62,7 +62,7 @@ export interface VisualizePluginStartDependencies {
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
presentationUtil: PresentationUtilPluginStart;
usageCollection?: UsageCollectionStart;
- spaces: SpacesPluginStart;
+ spaces?: SpacesApi;
}
export interface VisualizePluginSetupDependencies {
diff --git a/src/plugins/visualize/public/services.ts b/src/plugins/visualize/public/services.ts
index 7efb054fc636a..3f91762c60780 100644
--- a/src/plugins/visualize/public/services.ts
+++ b/src/plugins/visualize/public/services.ts
@@ -13,8 +13,10 @@ import type { UsageCollectionStart } from '../../usage_collection/public';
export const [getUISettings, setUISettings] = createGetterSetter('UISettings');
-export const [getUsageCollector, setUsageCollector] =
- createGetterSetter('UsageCollection');
+export const [getUsageCollector, setUsageCollector] = createGetterSetter(
+ 'UsageCollection',
+ false
+);
export const [getVisEditorsRegistry, setVisEditorsRegistry] =
createGetterSetter('VisEditorsRegistry');
diff --git a/test/examples/embeddables/dashboard.ts b/test/examples/embeddables/dashboard.ts
index 77ad5a5da9eeb..b97905ca9ce6a 100644
--- a/test/examples/embeddables/dashboard.ts
+++ b/test/examples/embeddables/dashboard.ts
@@ -95,10 +95,10 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
const esArchiver = getService('esArchiver');
const testSubjects = getService('testSubjects');
const pieChart = getService('pieChart');
- const browser = getService('browser');
const dashboardExpect = getService('dashboardExpect');
const elasticChart = getService('elasticChart');
const PageObjects = getPageObjects(['common', 'visChart']);
+ const monacoEditor = getService('monacoEditor');
describe('dashboard container', () => {
before(async () => {
@@ -128,17 +128,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
});
async function updateInput(input: string) {
- const editorWrapper = await (
- await testSubjects.find('dashboardEmbeddableByValueInputEditor')
- ).findByClassName('ace_editor');
- const editorId = await editorWrapper.getAttribute('id');
- await browser.execute(
- (_editorId: string, _input: string) => {
- return (window as any).ace.edit(_editorId).setValue(_input);
- },
- editorId,
- input
- );
+ await monacoEditor.setCodeEditorValue(input);
await testSubjects.click('dashboardEmbeddableByValueInputSubmit');
}
}
diff --git a/test/functional/apps/dashboard/dashboard_state.ts b/test/functional/apps/dashboard/dashboard_state.ts
index 45ba62749dd77..0cc0fa4806482 100644
--- a/test/functional/apps/dashboard/dashboard_state.ts
+++ b/test/functional/apps/dashboard/dashboard_state.ts
@@ -7,6 +7,7 @@
*/
import expect from '@kbn/expect';
+import chroma from 'chroma-js';
import { PIE_CHART_VIS_NAME, AREA_CHART_VIS_NAME } from '../../page_objects/dashboard_page';
import { DEFAULT_PANEL_WIDTH } from '../../../../src/plugins/dashboard/public/application/embeddable/dashboard_constants';
@@ -264,14 +265,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.header.waitUntilLoadingHasFinished();
await retry.try(async () => {
- const allPieSlicesColor = await pieChart.getAllPieSliceStyles('80,000');
- let whitePieSliceCounts = 0;
- allPieSlicesColor.forEach((style) => {
- if (style.indexOf('rgb(255, 255, 255)') > -1) {
- whitePieSliceCounts++;
- }
- });
-
+ const allPieSlicesColor = await pieChart.getAllPieSliceColor('80,000');
+ const whitePieSliceCounts = allPieSlicesColor.reduce((count, color) => {
+ // converting the color to a common format, testing the color, not the string format
+ return chroma(color).hex().toUpperCase() === '#FFFFFF' ? count + 1 : count;
+ }, 0);
expect(whitePieSliceCounts).to.be(1);
});
});
diff --git a/test/functional/apps/saved_objects_management/inspect_saved_objects.ts b/test/functional/apps/saved_objects_management/inspect_saved_objects.ts
index 839c262acffa0..7fff2cc001844 100644
--- a/test/functional/apps/saved_objects_management/inspect_saved_objects.ts
+++ b/test/functional/apps/saved_objects_management/inspect_saved_objects.ts
@@ -32,7 +32,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
return bools.every((currBool) => currBool === true);
};
- describe('saved objects edition page', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/68400
+ describe.skip('saved objects edition page', () => {
beforeEach(async () => {
await esArchiver.load(
'test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object'
diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts
index 2645148467d58..b929a78072868 100644
--- a/test/functional/page_objects/settings_page.ts
+++ b/test/functional/page_objects/settings_page.ts
@@ -21,6 +21,7 @@ export class SettingsPageObject extends FtrService {
private readonly header = this.ctx.getPageObject('header');
private readonly common = this.ctx.getPageObject('common');
private readonly savedObjects = this.ctx.getPageObject('savedObjects');
+ private readonly monacoEditor = this.ctx.getService('monacoEditor');
async clickNavigation() {
await this.find.clickDisplayedByCssSelector('.app-link:nth-child(5) a');
@@ -725,14 +726,7 @@ export class SettingsPageObject extends FtrService {
async setScriptedFieldScript(script: string) {
this.log.debug('set scripted field script = ' + script);
- const aceEditorCssSelector = '[data-test-subj="editorFieldScript"] .ace_editor';
- const editor = await this.find.byCssSelector(aceEditorCssSelector);
- await editor.click();
- const existingText = await editor.getVisibleText();
- for (let i = 0; i < existingText.length; i++) {
- await this.browser.pressKeys(this.browser.keys.BACK_SPACE);
- }
- await this.browser.pressKeys(...script.split(''));
+ await this.monacoEditor.setCodeEditorValue(script);
}
async openScriptedFieldHelp(activeTab: string) {
diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts
index d2e4091f93577..b0e9e21d07b0b 100644
--- a/test/functional/page_objects/visualize_chart_page.ts
+++ b/test/functional/page_objects/visualize_chart_page.ts
@@ -7,7 +7,7 @@
*/
import { Position } from '@elastic/charts';
-import Color from 'color';
+import chroma from 'chroma-js';
import { FtrService } from '../ftr_provider_context';
@@ -181,17 +181,17 @@ export class VisualizeChartPageObject extends FtrService {
return items.some(({ color: c }) => c === color);
}
- public async doesSelectedLegendColorExistForPie(color: string) {
+ public async doesSelectedLegendColorExistForPie(matchingColor: string) {
if (await this.isNewLibraryChart(pieChartSelector)) {
+ const hexMatchingColor = chroma(matchingColor).hex().toUpperCase();
const slices =
(await this.getEsChartDebugState(pieChartSelector))?.partition?.[0]?.partitions ?? [];
- return slices.some(({ color: c }) => {
- const rgbColor = new Color(color).rgb().toString();
- return c === rgbColor;
+ return slices.some(({ color }) => {
+ return hexMatchingColor === chroma(color).hex().toUpperCase();
});
}
- return await this.testSubjects.exists(`legendSelectedColor-${color}`);
+ return await this.testSubjects.exists(`legendSelectedColor-${matchingColor}`);
}
public async expectError() {
diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts
index 5bf99b4bf1136..653058959b839 100644
--- a/test/functional/services/remote/remote.ts
+++ b/test/functional/services/remote/remote.ts
@@ -92,7 +92,7 @@ export async function RemoteProvider({ getService }: FtrProviderContext) {
.subscribe({
next({ message, level }) {
const msg = message.replace(/\\n/g, '\n');
- log[level === 'SEVERE' || level === 'error' ? 'error' : 'debug'](
+ log[level === 'SEVERE' || level === 'error' ? 'warning' : 'debug'](
`browser[${level}] ${msg}`
);
},
diff --git a/test/functional/services/visualizations/pie_chart.ts b/test/functional/services/visualizations/pie_chart.ts
index 7c925318f0211..ff0c24e2830cf 100644
--- a/test/functional/services/visualizations/pie_chart.ts
+++ b/test/functional/services/visualizations/pie_chart.ts
@@ -7,6 +7,7 @@
*/
import expect from '@kbn/expect';
+import { isNil } from 'lodash';
import { FtrService } from '../../ftr_provider_context';
const pieChartSelector = 'visTypePieChart';
@@ -100,8 +101,8 @@ export class PieChartService extends FtrService {
return await pieSlice.getAttribute('style');
}
- async getAllPieSliceStyles(name: string) {
- this.log.debug(`VisualizePage.getAllPieSliceStyles(${name})`);
+ async getAllPieSliceColor(name: string) {
+ this.log.debug(`VisualizePage.getAllPieSliceColor(${name})`);
if (await this.visChart.isNewLibraryChart(pieChartSelector)) {
const slices =
(await this.visChart.getEsChartDebugState(pieChartSelector))?.partition?.[0]?.partitions ??
@@ -112,9 +113,22 @@ export class PieChartService extends FtrService {
return selectedSlice.map((slice) => slice.color);
}
const pieSlices = await this.getAllPieSlices(name);
- return await Promise.all(
+ const slicesStyles = await Promise.all(
pieSlices.map(async (pieSlice) => await pieSlice.getAttribute('style'))
);
+ return slicesStyles
+ .map(
+ (styles) =>
+ styles.split(';').reduce>((styleAsObj, style) => {
+ const stylePair = style.split(':');
+ if (stylePair.length !== 2) {
+ return styleAsObj;
+ }
+ styleAsObj[stylePair[0].trim()] = stylePair[1].trim();
+ return styleAsObj;
+ }, {}).fill // in vislib the color is available on the `fill` style prop
+ )
+ .filter((d) => !isNil(d));
}
async getPieChartData() {
diff --git a/x-pack/examples/reporting_example/public/components/app.tsx b/x-pack/examples/reporting_example/public/components/app.tsx
index a34cd0ab518de..3e2f08fc89c7b 100644
--- a/x-pack/examples/reporting_example/public/components/app.tsx
+++ b/x-pack/examples/reporting_example/public/components/app.tsx
@@ -244,7 +244,12 @@ export const ReportingExampleApp = ({
)}
{logos.map((item, index) => (
-
+
}
title={`Elastic ${item}`}
diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md
index b19e89a599840..0d66c9d30f8b9 100644
--- a/x-pack/plugins/actions/README.md
+++ b/x-pack/plugins/actions/README.md
@@ -33,29 +33,36 @@ Table of Contents
- [actionsClient.execute(options)](#actionsclientexecuteoptions)
- [Example](#example-2)
- [Built-in Action Types](#built-in-action-types)
- - [ServiceNow](#servicenow)
+ - [ServiceNow ITSM](#servicenow-itsm)
- [`params`](#params)
- [`subActionParams (pushToService)`](#subactionparams-pushtoservice)
- [`subActionParams (getFields)`](#subactionparams-getfields)
- [`subActionParams (getIncident)`](#subactionparams-getincident)
- [`subActionParams (getChoices)`](#subactionparams-getchoices)
- - [Jira](#jira)
+ - [ServiceNow Sec Ops](#servicenow-sec-ops)
- [`params`](#params-1)
- [`subActionParams (pushToService)`](#subactionparams-pushtoservice-1)
+ - [`subActionParams (getFields)`](#subactionparams-getfields-1)
- [`subActionParams (getIncident)`](#subactionparams-getincident-1)
+ - [`subActionParams (getChoices)`](#subactionparams-getchoices-1)
+ - [| fields | An array of fields. Example: `[priority, category]`. | string[] |](#-fields----an-array-of-fields-example-priority-category--string-)
+ - [Jira](#jira)
+ - [`params`](#params-2)
+ - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-2)
+ - [`subActionParams (getIncident)`](#subactionparams-getincident-2)
- [`subActionParams (issueTypes)`](#subactionparams-issuetypes)
- [`subActionParams (fieldsByIssueType)`](#subactionparams-fieldsbyissuetype)
- [`subActionParams (issues)`](#subactionparams-issues)
- [`subActionParams (issue)`](#subactionparams-issue)
- - [`subActionParams (getFields)`](#subactionparams-getfields-1)
- - [IBM Resilient](#ibm-resilient)
- - [`params`](#params-2)
- - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-2)
- [`subActionParams (getFields)`](#subactionparams-getfields-2)
+ - [IBM Resilient](#ibm-resilient)
+ - [`params`](#params-3)
+ - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-3)
+ - [`subActionParams (getFields)`](#subactionparams-getfields-3)
- [`subActionParams (incidentTypes)`](#subactionparams-incidenttypes)
- [`subActionParams (severity)`](#subactionparams-severity)
- [Swimlane](#swimlane)
- - [`params`](#params-3)
+ - [`params`](#params-4)
- [| severity | The severity of the incident. | string _(optional)_ |](#-severity-----the-severity-of-the-incident-----string-optional-)
- [Command Line Utility](#command-line-utility)
- [Developing New Action Types](#developing-new-action-types)
@@ -246,9 +253,9 @@ Kibana ships with a set of built-in action types. See [Actions and connector typ
In addition to the documented configurations, several built in action type offer additional `params` configurations.
-## ServiceNow
+## ServiceNow ITSM
-The [ServiceNow user documentation `params`](https://www.elastic.co/guide/en/kibana/master/servicenow-action-type.html) lists configuration properties for the `pushToService` subaction. In addition, several other subaction types are available.
+The [ServiceNow ITSM user documentation `params`](https://www.elastic.co/guide/en/kibana/master/servicenow-action-type.html) lists configuration properties for the `pushToService` subaction. In addition, several other subaction types are available.
### `params`
| Property | Description | Type |
@@ -265,16 +272,18 @@ The [ServiceNow user documentation `params`](https://www.elastic.co/guide/en/kib
The following table describes the properties of the `incident` object.
-| Property | Description | Type |
-| ----------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------- |
-| short_description | The title of the incident. | string |
-| description | The description of the incident. | string _(optional)_ |
-| externalId | The ID of the incident in ServiceNow. If present, the incident is updated. Otherwise, a new incident is created. | string _(optional)_ |
-| severity | The severity in ServiceNow. | string _(optional)_ |
-| urgency | The urgency in ServiceNow. | string _(optional)_ |
-| impact | The impact in ServiceNow. | string _(optional)_ |
-| category | The category in ServiceNow. | string _(optional)_ |
-| subcategory | The subcategory in ServiceNow. | string _(optional)_ |
+| Property | Description | Type |
+| ------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------- |
+| short_description | The title of the incident. | string |
+| description | The description of the incident. | string _(optional)_ |
+| externalId | The ID of the incident in ServiceNow. If present, the incident is updated. Otherwise, a new incident is created. | string _(optional)_ |
+| severity | The severity in ServiceNow. | string _(optional)_ |
+| urgency | The urgency in ServiceNow. | string _(optional)_ |
+| impact | The impact in ServiceNow. | string _(optional)_ |
+| category | The category in ServiceNow. | string _(optional)_ |
+| subcategory | The subcategory in ServiceNow. | string _(optional)_ |
+| correlation_id | The correlation id of the incident. | string _(optional)_ |
+| correlation_display | The correlation display of the ServiceNow. | string _(optional)_ |
#### `subActionParams (getFields)`
@@ -289,12 +298,64 @@ No parameters for the `getFields` subaction. Provide an empty object `{}`.
#### `subActionParams (getChoices)`
-| Property | Description | Type |
-| -------- | ------------------------------------------------------------ | -------- |
-| fields | An array of fields. Example: `[priority, category, impact]`. | string[] |
+| Property | Description | Type |
+| -------- | -------------------------------------------------- | -------- |
+| fields | An array of fields. Example: `[category, impact]`. | string[] |
---
+## ServiceNow Sec Ops
+
+The [ServiceNow SecOps user documentation `params`](https://www.elastic.co/guide/en/kibana/master/servicenow-sir-action-type.html) lists configuration properties for the `pushToService` subaction. In addition, several other subaction types are available.
+
+### `params`
+
+| Property | Description | Type |
+| --------------- | -------------------------------------------------------------------------------------------------- | ------ |
+| subAction | The subaction to perform. It can be `pushToService`, `getFields`, `getIncident`, and `getChoices`. | string |
+| subActionParams | The parameters of the subaction. | object |
+
+#### `subActionParams (pushToService)`
+
+| Property | Description | Type |
+| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------- |
+| incident | The ServiceNow security incident. | object |
+| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }`. | object[] _(optional)_ |
+
+The following table describes the properties of the `incident` object.
+
+| Property | Description | Type |
+| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
+| short_description | The title of the security incident. | string |
+| description | The description of the security incident. | string _(optional)_ |
+| externalId | The ID of the security incident in ServiceNow. If present, the security incident is updated. Otherwise, a new security incident is created. | string _(optional)_ |
+| priority | The priority in ServiceNow. | string _(optional)_ |
+| dest_ip | A list of destination IPs related to the security incident. The IPs will be added as observables to the security incident. | (string \| string[]) _(optional)_ |
+| source_ip | A list of source IPs related to the security incident. The IPs will be added as observables to the security incident. | (string \| string[]) _(optional)_ |
+| malware_hash | A list of malware hashes related to the security incident. The hashes will be added as observables to the security incident. | (string \| string[]) _(optional)_ |
+| malware_url | A list of malware URLs related to the security incident. The URLs will be added as observables to the security incident. | (string \| string[]) _(optional)_ |
+| category | The category in ServiceNow. | string _(optional)_ |
+| subcategory | The subcategory in ServiceNow. | string _(optional)_ |
+| correlation_id | The correlation id of the security incident. | string _(optional)_ |
+| correlation_display | The correlation display of the security incident. | string _(optional)_ |
+
+#### `subActionParams (getFields)`
+
+No parameters for the `getFields` subaction. Provide an empty object `{}`.
+
+#### `subActionParams (getIncident)`
+
+| Property | Description | Type |
+| ---------- | ---------------------------------------------- | ------ |
+| externalId | The ID of the security incident in ServiceNow. | string |
+
+
+#### `subActionParams (getChoices)`
+
+| Property | Description | Type |
+| -------- | ---------------------------------------------------- | -------- |
+| fields | An array of fields. Example: `[priority, category]`. | string[] |
+---
## Jira
The [Jira user documentation `params`](https://www.elastic.co/guide/en/kibana/master/jira-action-type.html) lists configuration properties for the `pushToService` subaction. In addition, several other subaction types are available.
diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts
index 5d83b658111e4..7710ff79d08b4 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts
@@ -143,7 +143,7 @@ export function getActionType({
}),
validate: {
config: schema.object(configSchemaProps, {
- validate: curry(valdiateActionTypeConfig)(configurationUtilities),
+ validate: curry(validateActionTypeConfig)(configurationUtilities),
}),
secrets: SecretsSchema,
params: ParamsSchema,
@@ -152,7 +152,7 @@ export function getActionType({
};
}
-function valdiateActionTypeConfig(
+function validateActionTypeConfig(
configurationUtilities: ActionsConfigurationUtilities,
configObject: ActionTypeConfigType
) {
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts
index 8d24e48d4d515..e1f66263729e2 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts
@@ -25,6 +25,7 @@ describe('api', () => {
const res = await api.pushToService({
externalService,
params,
+ config: {},
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
@@ -57,6 +58,7 @@ describe('api', () => {
const res = await api.pushToService({
externalService,
params,
+ config: {},
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
@@ -78,6 +80,7 @@ describe('api', () => {
await api.pushToService({
externalService,
params,
+ config: {},
secrets: { username: 'elastic', password: 'elastic' },
logger: mockedLogger,
commentFieldKey: 'comments',
@@ -93,6 +96,9 @@ describe('api', () => {
caller_id: 'elastic',
description: 'Incident description',
short_description: 'Incident title',
+ correlation_display: 'Alerting',
+ correlation_id: 'ruleId',
+ opened_by: 'elastic',
},
});
expect(externalService.updateIncident).not.toHaveBeenCalled();
@@ -103,6 +109,7 @@ describe('api', () => {
await api.pushToService({
externalService,
params,
+ config: {},
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
@@ -118,6 +125,8 @@ describe('api', () => {
comments: 'A comment',
description: 'Incident description',
short_description: 'Incident title',
+ correlation_display: 'Alerting',
+ correlation_id: 'ruleId',
},
incidentId: 'incident-1',
});
@@ -132,6 +141,8 @@ describe('api', () => {
comments: 'Another comment',
description: 'Incident description',
short_description: 'Incident title',
+ correlation_display: 'Alerting',
+ correlation_id: 'ruleId',
},
incidentId: 'incident-1',
});
@@ -142,6 +153,7 @@ describe('api', () => {
await api.pushToService({
externalService,
params,
+ config: {},
secrets: {},
logger: mockedLogger,
commentFieldKey: 'work_notes',
@@ -157,6 +169,8 @@ describe('api', () => {
work_notes: 'A comment',
description: 'Incident description',
short_description: 'Incident title',
+ correlation_display: 'Alerting',
+ correlation_id: 'ruleId',
},
incidentId: 'incident-1',
});
@@ -171,6 +185,8 @@ describe('api', () => {
work_notes: 'Another comment',
description: 'Incident description',
short_description: 'Incident title',
+ correlation_display: 'Alerting',
+ correlation_id: 'ruleId',
},
incidentId: 'incident-1',
});
@@ -182,6 +198,7 @@ describe('api', () => {
const res = await api.pushToService({
externalService,
params: apiParams,
+ config: {},
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
@@ -210,6 +227,7 @@ describe('api', () => {
const res = await api.pushToService({
externalService,
params,
+ config: {},
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
@@ -228,6 +246,7 @@ describe('api', () => {
await api.pushToService({
externalService,
params,
+ config: {},
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
@@ -243,6 +262,8 @@ describe('api', () => {
subcategory: 'os',
description: 'Incident description',
short_description: 'Incident title',
+ correlation_display: 'Alerting',
+ correlation_id: 'ruleId',
},
});
expect(externalService.createIncident).not.toHaveBeenCalled();
@@ -253,6 +274,7 @@ describe('api', () => {
await api.pushToService({
externalService,
params,
+ config: {},
secrets: {},
logger: mockedLogger,
commentFieldKey: 'comments',
@@ -267,6 +289,8 @@ describe('api', () => {
subcategory: 'os',
description: 'Incident description',
short_description: 'Incident title',
+ correlation_display: 'Alerting',
+ correlation_id: 'ruleId',
},
incidentId: 'incident-3',
});
@@ -281,6 +305,8 @@ describe('api', () => {
comments: 'A comment',
description: 'Incident description',
short_description: 'Incident title',
+ correlation_display: 'Alerting',
+ correlation_id: 'ruleId',
},
incidentId: 'incident-2',
});
@@ -291,6 +317,7 @@ describe('api', () => {
await api.pushToService({
externalService,
params,
+ config: {},
secrets: {},
logger: mockedLogger,
commentFieldKey: 'work_notes',
@@ -305,6 +332,8 @@ describe('api', () => {
subcategory: 'os',
description: 'Incident description',
short_description: 'Incident title',
+ correlation_display: 'Alerting',
+ correlation_id: 'ruleId',
},
incidentId: 'incident-3',
});
@@ -319,6 +348,8 @@ describe('api', () => {
work_notes: 'A comment',
description: 'Incident description',
short_description: 'Incident title',
+ correlation_display: 'Alerting',
+ correlation_id: 'ruleId',
},
incidentId: 'incident-2',
});
@@ -344,4 +375,23 @@ describe('api', () => {
expect(res).toEqual(serviceNowChoices);
});
});
+
+ describe('getIncident', () => {
+ test('it gets the incident correctly', async () => {
+ const res = await api.getIncident({
+ externalService,
+ params: {
+ externalId: 'incident-1',
+ },
+ });
+ expect(res).toEqual({
+ description: 'description from servicenow',
+ id: 'incident-1',
+ pushedDate: '2020-03-10T12:24:20.000Z',
+ short_description: 'title from servicenow',
+ title: 'INC01',
+ url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123',
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts
index 4120c07c32303..88cdfd069cf1b 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts
@@ -6,7 +6,7 @@
*/
import {
- ExternalServiceApi,
+ ExternalServiceAPI,
GetChoicesHandlerArgs,
GetChoicesResponse,
GetCommonFieldsHandlerArgs,
@@ -19,7 +19,11 @@ import {
} from './types';
const handshakeHandler = async ({ externalService, params }: HandshakeApiHandlerArgs) => {};
-const getIncidentHandler = async ({ externalService, params }: GetIncidentApiHandlerArgs) => {};
+const getIncidentHandler = async ({ externalService, params }: GetIncidentApiHandlerArgs) => {
+ const { externalId: id } = params;
+ const res = await externalService.getIncident(id);
+ return res;
+};
const pushToServiceHandler = async ({
externalService,
@@ -42,6 +46,7 @@ const pushToServiceHandler = async ({
incident: {
...incident,
caller_id: secrets.username,
+ opened_by: secrets.username,
},
});
}
@@ -84,7 +89,7 @@ const getChoicesHandler = async ({
return res;
};
-export const api: ExternalServiceApi = {
+export const api: ExternalServiceAPI = {
getChoices: getChoicesHandler,
getFields: getFieldsHandler,
getIncident: getIncidentHandler,
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api_sir.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api_sir.test.ts
new file mode 100644
index 0000000000000..358af7cd2e9ef
--- /dev/null
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api_sir.test.ts
@@ -0,0 +1,286 @@
+/*
+ * 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 { Logger } from '../../../../../../src/core/server';
+import { externalServiceSIRMock, sirParams } from './mocks';
+import { ExternalServiceSIR, ObservableTypes } from './types';
+import { apiSIR, combineObservables, formatObservables, prepareParams } from './api_sir';
+let mockedLogger: jest.Mocked;
+
+describe('api_sir', () => {
+ let externalService: jest.Mocked;
+
+ beforeEach(() => {
+ externalService = externalServiceSIRMock.create();
+ jest.clearAllMocks();
+ });
+
+ describe('combineObservables', () => {
+ test('it returns an empty array when both arguments are an empty array', async () => {
+ expect(combineObservables([], [])).toEqual([]);
+ });
+
+ test('it returns an empty array when both arguments are an empty string', async () => {
+ expect(combineObservables('', '')).toEqual([]);
+ });
+
+ test('it returns an empty array when a="" and b=[]', async () => {
+ expect(combineObservables('', [])).toEqual([]);
+ });
+
+ test('it returns an empty array when a=[] and b=""', async () => {
+ expect(combineObservables([], '')).toEqual([]);
+ });
+
+ test('it returns a if b is empty', async () => {
+ expect(combineObservables('a', '')).toEqual(['a']);
+ });
+
+ test('it returns b if a is empty', async () => {
+ expect(combineObservables([], ['b'])).toEqual(['b']);
+ });
+
+ test('it combines two strings', async () => {
+ expect(combineObservables('a,b', 'c,d')).toEqual(['a', 'b', 'c', 'd']);
+ });
+
+ test('it combines two arrays', async () => {
+ expect(combineObservables(['a'], ['b'])).toEqual(['a', 'b']);
+ });
+
+ test('it combines a string with an array', async () => {
+ expect(combineObservables('a', ['b'])).toEqual(['a', 'b']);
+ });
+
+ test('it combines an array with a string ', async () => {
+ expect(combineObservables(['a'], 'b')).toEqual(['a', 'b']);
+ });
+
+ test('it combines a "," concatenated string', async () => {
+ expect(combineObservables(['a'], 'b,c,d')).toEqual(['a', 'b', 'c', 'd']);
+ expect(combineObservables('b,c,d', ['a'])).toEqual(['b', 'c', 'd', 'a']);
+ });
+
+ test('it combines a "|" concatenated string', async () => {
+ expect(combineObservables(['a'], 'b|c|d')).toEqual(['a', 'b', 'c', 'd']);
+ expect(combineObservables('b|c|d', ['a'])).toEqual(['b', 'c', 'd', 'a']);
+ });
+
+ test('it combines a space concatenated string', async () => {
+ expect(combineObservables(['a'], 'b c d')).toEqual(['a', 'b', 'c', 'd']);
+ expect(combineObservables('b c d', ['a'])).toEqual(['b', 'c', 'd', 'a']);
+ });
+
+ test('it combines a "\\n" concatenated string', async () => {
+ expect(combineObservables(['a'], 'b\nc\nd')).toEqual(['a', 'b', 'c', 'd']);
+ expect(combineObservables('b\nc\nd', ['a'])).toEqual(['b', 'c', 'd', 'a']);
+ });
+
+ test('it combines a "\\r" concatenated string', async () => {
+ expect(combineObservables(['a'], 'b\rc\rd')).toEqual(['a', 'b', 'c', 'd']);
+ expect(combineObservables('b\rc\rd', ['a'])).toEqual(['b', 'c', 'd', 'a']);
+ });
+
+ test('it combines a "\\t" concatenated string', async () => {
+ expect(combineObservables(['a'], 'b\tc\td')).toEqual(['a', 'b', 'c', 'd']);
+ expect(combineObservables('b\tc\td', ['a'])).toEqual(['b', 'c', 'd', 'a']);
+ });
+
+ test('it combines two strings with different delimiter', async () => {
+ expect(combineObservables('a|b|c', 'd e f')).toEqual(['a', 'b', 'c', 'd', 'e', 'f']);
+ });
+ });
+
+ describe('formatObservables', () => {
+ test('it formats array observables correctly', async () => {
+ const expectedTypes: Array<[ObservableTypes, string]> = [
+ [ObservableTypes.ip4, 'ipv4-addr'],
+ [ObservableTypes.sha256, 'SHA256'],
+ [ObservableTypes.url, 'URL'],
+ ];
+
+ for (const type of expectedTypes) {
+ expect(formatObservables(['a', 'b', 'c'], type[0])).toEqual([
+ { type: type[1], value: 'a' },
+ { type: type[1], value: 'b' },
+ { type: type[1], value: 'c' },
+ ]);
+ }
+ });
+
+ test('it removes duplicates from array observables correctly', async () => {
+ expect(formatObservables(['a', 'a', 'c'], ObservableTypes.ip4)).toEqual([
+ { type: 'ipv4-addr', value: 'a' },
+ { type: 'ipv4-addr', value: 'c' },
+ ]);
+ });
+
+ test('it formats an empty array correctly', async () => {
+ expect(formatObservables([], ObservableTypes.ip4)).toEqual([]);
+ });
+
+ test('it removes empty observables correctly', async () => {
+ expect(formatObservables(['a', '', 'c'], ObservableTypes.ip4)).toEqual([
+ { type: 'ipv4-addr', value: 'a' },
+ { type: 'ipv4-addr', value: 'c' },
+ ]);
+ });
+ });
+
+ describe('prepareParams', () => {
+ test('it prepares the params correctly when the connector is legacy', async () => {
+ expect(prepareParams(true, sirParams)).toEqual({
+ ...sirParams,
+ incident: {
+ ...sirParams.incident,
+ dest_ip: '192.168.1.1,192.168.1.3',
+ source_ip: '192.168.1.2,192.168.1.4',
+ malware_hash: '5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9',
+ malware_url: 'https://example.com',
+ },
+ });
+ });
+
+ test('it prepares the params correctly when the connector is not legacy', async () => {
+ expect(prepareParams(false, sirParams)).toEqual({
+ ...sirParams,
+ incident: {
+ ...sirParams.incident,
+ dest_ip: null,
+ source_ip: null,
+ malware_hash: null,
+ malware_url: null,
+ },
+ });
+ });
+
+ test('it prepares the params correctly when the connector is legacy and the observables are undefined', async () => {
+ const {
+ dest_ip: destIp,
+ source_ip: sourceIp,
+ malware_hash: malwareHash,
+ malware_url: malwareURL,
+ ...incidentWithoutObservables
+ } = sirParams.incident;
+
+ expect(
+ prepareParams(true, {
+ ...sirParams,
+ // @ts-expect-error
+ incident: incidentWithoutObservables,
+ })
+ ).toEqual({
+ ...sirParams,
+ incident: {
+ ...sirParams.incident,
+ dest_ip: null,
+ source_ip: null,
+ malware_hash: null,
+ malware_url: null,
+ },
+ });
+ });
+ });
+
+ describe('pushToService', () => {
+ test('it creates an incident correctly', async () => {
+ const params = { ...sirParams, incident: { ...sirParams.incident, externalId: null } };
+ const res = await apiSIR.pushToService({
+ externalService,
+ params,
+ config: { isLegacy: false },
+ secrets: {},
+ logger: mockedLogger,
+ commentFieldKey: 'work_notes',
+ });
+
+ expect(res).toEqual({
+ id: 'incident-1',
+ title: 'INC01',
+ pushedDate: '2020-03-10T12:24:20.000Z',
+ url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123',
+ comments: [
+ {
+ commentId: 'case-comment-1',
+ pushedDate: '2020-03-10T12:24:20.000Z',
+ },
+ {
+ commentId: 'case-comment-2',
+ pushedDate: '2020-03-10T12:24:20.000Z',
+ },
+ ],
+ });
+ });
+
+ test('it adds observables correctly', async () => {
+ const params = { ...sirParams, incident: { ...sirParams.incident, externalId: null } };
+ await apiSIR.pushToService({
+ externalService,
+ params,
+ config: { isLegacy: false },
+ secrets: {},
+ logger: mockedLogger,
+ commentFieldKey: 'work_notes',
+ });
+
+ expect(externalService.bulkAddObservableToIncident).toHaveBeenCalledWith(
+ [
+ { type: 'ipv4-addr', value: '192.168.1.1' },
+ { type: 'ipv4-addr', value: '192.168.1.3' },
+ { type: 'ipv4-addr', value: '192.168.1.2' },
+ { type: 'ipv4-addr', value: '192.168.1.4' },
+ {
+ type: 'SHA256',
+ value: '5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9',
+ },
+ { type: 'URL', value: 'https://example.com' },
+ ],
+ // createIncident mock returns this incident id
+ 'incident-1'
+ );
+ });
+
+ test('it does not call bulkAddObservableToIncident if it a legacy connector', async () => {
+ const params = { ...sirParams, incident: { ...sirParams.incident, externalId: null } };
+ await apiSIR.pushToService({
+ externalService,
+ params,
+ config: { isLegacy: true },
+ secrets: {},
+ logger: mockedLogger,
+ commentFieldKey: 'work_notes',
+ });
+
+ expect(externalService.bulkAddObservableToIncident).not.toHaveBeenCalled();
+ });
+
+ test('it does not call bulkAddObservableToIncident if there are no observables', async () => {
+ const params = {
+ ...sirParams,
+ incident: {
+ ...sirParams.incident,
+ dest_ip: null,
+ source_ip: null,
+ malware_hash: null,
+ malware_url: null,
+ externalId: null,
+ },
+ };
+
+ await apiSIR.pushToService({
+ externalService,
+ params,
+ config: { isLegacy: false },
+ secrets: {},
+ logger: mockedLogger,
+ commentFieldKey: 'work_notes',
+ });
+
+ expect(externalService.bulkAddObservableToIncident).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api_sir.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api_sir.ts
new file mode 100644
index 0000000000000..326bb79a0e708
--- /dev/null
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api_sir.ts
@@ -0,0 +1,154 @@
+/*
+ * 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 { isEmpty, isString } from 'lodash';
+
+import {
+ ExecutorSubActionPushParamsSIR,
+ ExternalServiceAPI,
+ ExternalServiceSIR,
+ ObservableTypes,
+ PushToServiceApiHandlerArgs,
+ PushToServiceApiParamsSIR,
+ PushToServiceResponse,
+} from './types';
+
+import { api } from './api';
+
+const SPLIT_REGEX = /[ ,|\r\n\t]+/;
+
+export const formatObservables = (observables: string[], type: ObservableTypes) => {
+ /**
+ * ServiceNow accepted formats are: comma, new line, tab, or pipe separators.
+ * Before the application the observables were being sent to ServiceNow as a concatenated string with
+ * delimiter. With the application the format changed to an array of observables.
+ */
+ const uniqueObservables = new Set(observables);
+ return [...uniqueObservables].filter((obs) => !isEmpty(obs)).map((obs) => ({ value: obs, type }));
+};
+
+const obsAsArray = (obs: string | string[]): string[] => {
+ if (isEmpty(obs)) {
+ return [];
+ }
+
+ if (isString(obs)) {
+ return obs.split(SPLIT_REGEX);
+ }
+
+ return obs;
+};
+
+export const combineObservables = (a: string | string[], b: string | string[]): string[] => {
+ const first = obsAsArray(a);
+ const second = obsAsArray(b);
+
+ return [...first, ...second];
+};
+
+const observablesToString = (obs: string | string[] | null | undefined): string | null => {
+ if (Array.isArray(obs)) {
+ return obs.join(',');
+ }
+
+ return obs ?? null;
+};
+
+export const prepareParams = (
+ isLegacy: boolean,
+ params: PushToServiceApiParamsSIR
+): PushToServiceApiParamsSIR => {
+ if (isLegacy) {
+ /**
+ * The schema has change to accept an array of observables
+ * or a string. In the case of a legacy connector we need to
+ * convert the observables to a string
+ */
+ return {
+ ...params,
+ incident: {
+ ...params.incident,
+ dest_ip: observablesToString(params.incident.dest_ip),
+ malware_hash: observablesToString(params.incident.malware_hash),
+ malware_url: observablesToString(params.incident.malware_url),
+ source_ip: observablesToString(params.incident.source_ip),
+ },
+ };
+ }
+
+ /**
+ * For non legacy connectors the observables
+ * will be added in a different call.
+ * They need to be set to null when sending the fields
+ * to ServiceNow
+ */
+ return {
+ ...params,
+ incident: {
+ ...params.incident,
+ dest_ip: null,
+ malware_hash: null,
+ malware_url: null,
+ source_ip: null,
+ },
+ };
+};
+
+const pushToServiceHandler = async ({
+ externalService,
+ params,
+ config,
+ secrets,
+ commentFieldKey,
+ logger,
+}: PushToServiceApiHandlerArgs): Promise => {
+ const res = await api.pushToService({
+ externalService,
+ params: prepareParams(!!config.isLegacy, params as PushToServiceApiParamsSIR),
+ config,
+ secrets,
+ commentFieldKey,
+ logger,
+ });
+
+ const {
+ incident: {
+ dest_ip: destIP,
+ malware_hash: malwareHash,
+ malware_url: malwareUrl,
+ source_ip: sourceIP,
+ },
+ } = params as ExecutorSubActionPushParamsSIR;
+
+ /**
+ * Add bulk observables is only available for new connectors
+ * Old connectors gonna add their observables
+ * through the pushToService call.
+ */
+
+ if (!config.isLegacy) {
+ const sirExternalService = externalService as ExternalServiceSIR;
+
+ const obsWithType: Array<[string[], ObservableTypes]> = [
+ [combineObservables(destIP ?? [], sourceIP ?? []), ObservableTypes.ip4],
+ [obsAsArray(malwareHash ?? []), ObservableTypes.sha256],
+ [obsAsArray(malwareUrl ?? []), ObservableTypes.url],
+ ];
+
+ const observables = obsWithType.map(([obs, type]) => formatObservables(obs, type)).flat();
+ if (observables.length > 0) {
+ await sirExternalService.bulkAddObservableToIncident(observables, res.id);
+ }
+ }
+
+ return res;
+};
+
+export const apiSIR: ExternalServiceAPI = {
+ ...api,
+ pushToService: pushToServiceHandler,
+};
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.test.ts
new file mode 100644
index 0000000000000..babd360cbcb82
--- /dev/null
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.test.ts
@@ -0,0 +1,40 @@
+/*
+ * 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 { snExternalServiceConfig } from './config';
+
+/**
+ * The purpose of this test is to
+ * prevent developers from accidentally
+ * change important configuration values
+ * such as the scope or the import set table
+ * of our ServiceNow application
+ */
+
+describe('config', () => {
+ test('ITSM: the config are correct', async () => {
+ const snConfig = snExternalServiceConfig['.servicenow'];
+ expect(snConfig).toEqual({
+ importSetTable: 'x_elas2_inc_int_elastic_incident',
+ appScope: 'x_elas2_inc_int',
+ table: 'incident',
+ useImportAPI: true,
+ commentFieldKey: 'work_notes',
+ });
+ });
+
+ test('SIR: the config are correct', async () => {
+ const snConfig = snExternalServiceConfig['.servicenow-sir'];
+ expect(snConfig).toEqual({
+ importSetTable: 'x_elas2_sir_int_elastic_si_incident',
+ appScope: 'x_elas2_sir_int',
+ table: 'sn_si_incident',
+ useImportAPI: true,
+ commentFieldKey: 'work_notes',
+ });
+ });
+});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts
new file mode 100644
index 0000000000000..37e4c6994b403
--- /dev/null
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 {
+ ENABLE_NEW_SN_ITSM_CONNECTOR,
+ ENABLE_NEW_SN_SIR_CONNECTOR,
+} from '../../constants/connectors';
+import { SNProductsConfig } from './types';
+
+export const serviceNowITSMTable = 'incident';
+export const serviceNowSIRTable = 'sn_si_incident';
+
+export const ServiceNowITSMActionTypeId = '.servicenow';
+export const ServiceNowSIRActionTypeId = '.servicenow-sir';
+
+export const snExternalServiceConfig: SNProductsConfig = {
+ '.servicenow': {
+ importSetTable: 'x_elas2_inc_int_elastic_incident',
+ appScope: 'x_elas2_inc_int',
+ table: 'incident',
+ useImportAPI: ENABLE_NEW_SN_ITSM_CONNECTOR,
+ commentFieldKey: 'work_notes',
+ },
+ '.servicenow-sir': {
+ importSetTable: 'x_elas2_sir_int_elastic_si_incident',
+ appScope: 'x_elas2_sir_int',
+ table: 'sn_si_incident',
+ useImportAPI: ENABLE_NEW_SN_SIR_CONNECTOR,
+ commentFieldKey: 'work_notes',
+ },
+};
+
+export const FIELD_PREFIX = 'u_';
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts
index f2b500df6ccb3..29907381d45da 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts
@@ -18,7 +18,7 @@ import {
import { ActionsConfigurationUtilities } from '../../actions_config';
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../../types';
import { createExternalService } from './service';
-import { api } from './api';
+import { api as commonAPI } from './api';
import * as i18n from './translations';
import { Logger } from '../../../../../../src/core/server';
import {
@@ -30,7 +30,25 @@ import {
ExecutorSubActionCommonFieldsParams,
ServiceNowExecutorResultData,
ExecutorSubActionGetChoicesParams,
+ ServiceFactory,
+ ExternalServiceAPI,
} from './types';
+import {
+ ServiceNowITSMActionTypeId,
+ serviceNowITSMTable,
+ ServiceNowSIRActionTypeId,
+ serviceNowSIRTable,
+ snExternalServiceConfig,
+} from './config';
+import { createExternalServiceSIR } from './service_sir';
+import { apiSIR } from './api_sir';
+
+export {
+ ServiceNowITSMActionTypeId,
+ serviceNowITSMTable,
+ ServiceNowSIRActionTypeId,
+ serviceNowSIRTable,
+};
export type ActionParamsType =
| TypeOf
@@ -41,12 +59,6 @@ interface GetActionTypeParams {
configurationUtilities: ActionsConfigurationUtilities;
}
-const serviceNowITSMTable = 'incident';
-const serviceNowSIRTable = 'sn_si_incident';
-
-export const ServiceNowITSMActionTypeId = '.servicenow';
-export const ServiceNowSIRActionTypeId = '.servicenow-sir';
-
export type ServiceNowActionType = ActionType<
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
@@ -79,8 +91,9 @@ export function getServiceNowITSMActionType(params: GetActionTypeParams): Servic
executor: curry(executor)({
logger,
configurationUtilities,
- table: serviceNowITSMTable,
- commentFieldKey: 'work_notes',
+ actionTypeId: ServiceNowITSMActionTypeId,
+ createService: createExternalService,
+ api: commonAPI,
}),
};
}
@@ -103,8 +116,9 @@ export function getServiceNowSIRActionType(params: GetActionTypeParams): Service
executor: curry(executor)({
logger,
configurationUtilities,
- table: serviceNowSIRTable,
- commentFieldKey: 'work_notes',
+ actionTypeId: ServiceNowSIRActionTypeId,
+ createService: createExternalServiceSIR,
+ api: apiSIR,
}),
};
}
@@ -115,28 +129,31 @@ async function executor(
{
logger,
configurationUtilities,
- table,
- commentFieldKey = 'comments',
+ actionTypeId,
+ createService,
+ api,
}: {
logger: Logger;
configurationUtilities: ActionsConfigurationUtilities;
- table: string;
- commentFieldKey?: string;
+ actionTypeId: string;
+ createService: ServiceFactory;
+ api: ExternalServiceAPI;
},
execOptions: ServiceNowActionTypeExecutorOptions
): Promise> {
const { actionId, config, params, secrets } = execOptions;
const { subAction, subActionParams } = params;
+ const externalServiceConfig = snExternalServiceConfig[actionTypeId];
let data: ServiceNowExecutorResultData | null = null;
- const externalService = createExternalService(
- table,
+ const externalService = createService(
{
config,
secrets,
},
logger,
- configurationUtilities
+ configurationUtilities,
+ externalServiceConfig
);
if (!api[subAction]) {
@@ -156,9 +173,10 @@ async function executor(
data = await api.pushToService({
externalService,
params: pushToServiceParams,
+ config,
secrets,
logger,
- commentFieldKey,
+ commentFieldKey: externalServiceConfig.commentFieldKey,
});
logger.debug(`response push to service for incident id: ${data.id}`);
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts
index 909200472be33..3629fb33915ae 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts
@@ -5,7 +5,14 @@
* 2.0.
*/
-import { ExternalService, ExecutorSubActionPushParams } from './types';
+import {
+ ExternalService,
+ ExecutorSubActionPushParams,
+ PushToServiceApiParamsSIR,
+ ExternalServiceSIR,
+ Observable,
+ ObservableTypes,
+} from './types';
export const serviceNowCommonFields = [
{
@@ -74,6 +81,10 @@ const createMock = (): jest.Mocked => {
getFields: jest.fn().mockImplementation(() => Promise.resolve(serviceNowCommonFields)),
getIncident: jest.fn().mockImplementation(() =>
Promise.resolve({
+ id: 'incident-1',
+ title: 'INC01',
+ pushedDate: '2020-03-10T12:24:20.000Z',
+ url: 'https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=123',
short_description: 'title from servicenow',
description: 'description from servicenow',
})
@@ -95,16 +106,60 @@ const createMock = (): jest.Mocked => {
})
),
findIncidents: jest.fn(),
+ getApplicationInformation: jest.fn().mockImplementation(() =>
+ Promise.resolve({
+ name: 'Elastic',
+ scope: 'x_elas2_inc_int',
+ version: '1.0.0',
+ })
+ ),
+ checkIfApplicationIsInstalled: jest.fn(),
+ getUrl: jest.fn().mockImplementation(() => 'https://instance.service-now.com'),
+ checkInstance: jest.fn(),
};
return service;
};
-const externalServiceMock = {
+const createSIRMock = (): jest.Mocked => {
+ const service = {
+ ...createMock(),
+ addObservableToIncident: jest.fn().mockImplementation(() =>
+ Promise.resolve({
+ value: 'https://example.com',
+ observable_sys_id: '3',
+ })
+ ),
+ bulkAddObservableToIncident: jest.fn().mockImplementation(() =>
+ Promise.resolve([
+ {
+ value: '5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9',
+ observable_sys_id: '1',
+ },
+ {
+ value: '127.0.0.1',
+ observable_sys_id: '2',
+ },
+ {
+ value: 'https://example.com',
+ observable_sys_id: '3',
+ },
+ ])
+ ),
+ };
+
+ return service;
+};
+
+export const externalServiceMock = {
create: createMock,
};
-const executorParams: ExecutorSubActionPushParams = {
+export const externalServiceSIRMock = {
+ create: createSIRMock,
+};
+
+export const executorParams: ExecutorSubActionPushParams = {
incident: {
externalId: 'incident-3',
short_description: 'Incident title',
@@ -114,6 +169,8 @@ const executorParams: ExecutorSubActionPushParams = {
impact: '3',
category: 'software',
subcategory: 'os',
+ correlation_id: 'ruleId',
+ correlation_display: 'Alerting',
},
comments: [
{
@@ -127,6 +184,46 @@ const executorParams: ExecutorSubActionPushParams = {
],
};
-const apiParams = executorParams;
+export const sirParams: PushToServiceApiParamsSIR = {
+ incident: {
+ externalId: 'incident-3',
+ short_description: 'Incident title',
+ description: 'Incident description',
+ dest_ip: ['192.168.1.1', '192.168.1.3'],
+ source_ip: ['192.168.1.2', '192.168.1.4'],
+ malware_hash: ['5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9'],
+ malware_url: ['https://example.com'],
+ category: 'software',
+ subcategory: 'os',
+ correlation_id: 'ruleId',
+ correlation_display: 'Alerting',
+ priority: '1',
+ },
+ comments: [
+ {
+ commentId: 'case-comment-1',
+ comment: 'A comment',
+ },
+ {
+ commentId: 'case-comment-2',
+ comment: 'Another comment',
+ },
+ ],
+};
+
+export const observables: Observable[] = [
+ {
+ value: '5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9',
+ type: ObservableTypes.sha256,
+ },
+ {
+ value: '127.0.0.1',
+ type: ObservableTypes.ip4,
+ },
+ {
+ value: 'https://example.com',
+ type: ObservableTypes.url,
+ },
+];
-export { externalServiceMock, executorParams, apiParams };
+export const apiParams = executorParams;
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts
index 6fec30803d6d7..dab68bb9d3e9d 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts
@@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema';
export const ExternalIncidentServiceConfiguration = {
apiUrl: schema.string(),
+ isLegacy: schema.boolean({ defaultValue: false }),
};
export const ExternalIncidentServiceConfigurationSchema = schema.object(
@@ -39,6 +40,8 @@ const CommonAttributes = {
externalId: schema.nullable(schema.string()),
category: schema.nullable(schema.string()),
subcategory: schema.nullable(schema.string()),
+ correlation_id: schema.nullable(schema.string()),
+ correlation_display: schema.nullable(schema.string()),
};
// Schema for ServiceNow Incident Management (ITSM)
@@ -56,10 +59,22 @@ export const ExecutorSubActionPushParamsSchemaITSM = schema.object({
export const ExecutorSubActionPushParamsSchemaSIR = schema.object({
incident: schema.object({
...CommonAttributes,
- dest_ip: schema.nullable(schema.string()),
- malware_hash: schema.nullable(schema.string()),
- malware_url: schema.nullable(schema.string()),
- source_ip: schema.nullable(schema.string()),
+ dest_ip: schema.oneOf(
+ [schema.nullable(schema.string()), schema.nullable(schema.arrayOf(schema.string()))],
+ { defaultValue: null }
+ ),
+ malware_hash: schema.oneOf(
+ [schema.nullable(schema.string()), schema.nullable(schema.arrayOf(schema.string()))],
+ { defaultValue: null }
+ ),
+ malware_url: schema.oneOf(
+ [schema.nullable(schema.string()), schema.nullable(schema.arrayOf(schema.string()))],
+ { defaultValue: null }
+ ),
+ source_ip: schema.oneOf(
+ [schema.nullable(schema.string()), schema.nullable(schema.arrayOf(schema.string()))],
+ { defaultValue: null }
+ ),
priority: schema.nullable(schema.string()),
}),
comments: CommentsSchema,
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts
index 37bfb662508a2..b8499b01e6a02 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts
@@ -5,15 +5,16 @@
* 2.0.
*/
-import axios from 'axios';
+import axios, { AxiosResponse } from 'axios';
import { createExternalService } from './service';
import * as utils from '../lib/axios_utils';
-import { ExternalService } from './types';
+import { ExternalService, ServiceNowITSMIncident } from './types';
import { Logger } from '../../../../../../src/core/server';
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { actionsConfigMock } from '../../actions_config.mock';
import { serviceNowCommonFields, serviceNowChoices } from './mocks';
+import { snExternalServiceConfig } from './config';
const logger = loggingSystemMock.create().get() as jest.Mocked;
jest.mock('axios');
@@ -28,24 +29,134 @@ jest.mock('../lib/axios_utils', () => {
axios.create = jest.fn(() => axios);
const requestMock = utils.request as jest.Mock;
-const patchMock = utils.patch as jest.Mock;
const configurationUtilities = actionsConfigMock.create();
-const table = 'incident';
+
+const getImportSetAPIResponse = (update = false) => ({
+ import_set: 'ISET01',
+ staging_table: 'x_elas2_inc_int_elastic_incident',
+ result: [
+ {
+ transform_map: 'Elastic Incident',
+ table: 'incident',
+ display_name: 'number',
+ display_value: 'INC01',
+ record_link: 'https://example.com/api/now/table/incident/1',
+ status: update ? 'updated' : 'inserted',
+ sys_id: '1',
+ },
+ ],
+});
+
+const getImportSetAPIError = () => ({
+ import_set: 'ISET01',
+ staging_table: 'x_elas2_inc_int_elastic_incident',
+ result: [
+ {
+ transform_map: 'Elastic Incident',
+ status: 'error',
+ error_message: 'An error has occurred while importing the incident',
+ status_message: 'failure',
+ },
+ ],
+});
+
+const mockApplicationVersion = () =>
+ requestMock.mockImplementationOnce(() => ({
+ data: {
+ result: { name: 'Elastic', scope: 'x_elas2_inc_int', version: '1.0.0' },
+ },
+ }));
+
+const mockImportIncident = (update: boolean) =>
+ requestMock.mockImplementationOnce(() => ({
+ data: getImportSetAPIResponse(update),
+ }));
+
+const mockIncidentResponse = (update: boolean) =>
+ requestMock.mockImplementation(() => ({
+ data: {
+ result: {
+ sys_id: '1',
+ number: 'INC01',
+ ...(update
+ ? { sys_updated_on: '2020-03-10 12:24:20' }
+ : { sys_created_on: '2020-03-10 12:24:20' }),
+ },
+ },
+ }));
+
+const createIncident = async (service: ExternalService) => {
+ // Get application version
+ mockApplicationVersion();
+ // Import set api response
+ mockImportIncident(false);
+ // Get incident response
+ mockIncidentResponse(false);
+
+ return await service.createIncident({
+ incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident,
+ });
+};
+
+const updateIncident = async (service: ExternalService) => {
+ // Get application version
+ mockApplicationVersion();
+ // Import set api response
+ mockImportIncident(true);
+ // Get incident response
+ mockIncidentResponse(true);
+
+ return await service.updateIncident({
+ incidentId: '1',
+ incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident,
+ });
+};
+
+const expectImportedIncident = (update: boolean) => {
+ expect(requestMock).toHaveBeenNthCalledWith(1, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/x_elas2_inc_int/elastic_api/health',
+ method: 'get',
+ });
+
+ expect(requestMock).toHaveBeenNthCalledWith(2, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/now/import/x_elas2_inc_int_elastic_incident',
+ method: 'post',
+ data: {
+ u_short_description: 'title',
+ u_description: 'desc',
+ ...(update ? { elastic_incident_id: '1' } : {}),
+ },
+ });
+
+ expect(requestMock).toHaveBeenNthCalledWith(3, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/now/v2/table/incident/1',
+ method: 'get',
+ });
+};
describe('ServiceNow service', () => {
let service: ExternalService;
beforeEach(() => {
service = createExternalService(
- table,
{
// The trailing slash at the end of the url is intended.
// All API calls need to have the trailing slash removed.
- config: { apiUrl: 'https://dev102283.service-now.com/' },
+ config: { apiUrl: 'https://example.com/' },
secrets: { username: 'admin', password: 'admin' },
},
logger,
- configurationUtilities
+ configurationUtilities,
+ snExternalServiceConfig['.servicenow']
);
});
@@ -57,13 +168,13 @@ describe('ServiceNow service', () => {
test('throws without url', () => {
expect(() =>
createExternalService(
- table,
{
config: { apiUrl: null },
secrets: { username: 'admin', password: 'admin' },
},
logger,
- configurationUtilities
+ configurationUtilities,
+ snExternalServiceConfig['.servicenow']
)
).toThrow();
});
@@ -71,13 +182,13 @@ describe('ServiceNow service', () => {
test('throws without username', () => {
expect(() =>
createExternalService(
- table,
{
config: { apiUrl: 'test.com' },
secrets: { username: '', password: 'admin' },
},
logger,
- configurationUtilities
+ configurationUtilities,
+ snExternalServiceConfig['.servicenow']
)
).toThrow();
});
@@ -85,13 +196,13 @@ describe('ServiceNow service', () => {
test('throws without password', () => {
expect(() =>
createExternalService(
- table,
{
config: { apiUrl: 'test.com' },
secrets: { username: '', password: undefined },
},
logger,
- configurationUtilities
+ configurationUtilities,
+ snExternalServiceConfig['.servicenow']
)
).toThrow();
});
@@ -116,19 +227,20 @@ describe('ServiceNow service', () => {
axios,
logger,
configurationUtilities,
- url: 'https://dev102283.service-now.com/api/now/v2/table/incident/1',
+ url: 'https://example.com/api/now/v2/table/incident/1',
+ method: 'get',
});
});
test('it should call request with correct arguments when table changes', async () => {
service = createExternalService(
- 'sn_si_incident',
{
- config: { apiUrl: 'https://dev102283.service-now.com/' },
+ config: { apiUrl: 'https://example.com/' },
secrets: { username: 'admin', password: 'admin' },
},
logger,
- configurationUtilities
+ configurationUtilities,
+ { ...snExternalServiceConfig['.servicenow'], table: 'sn_si_incident' }
);
requestMock.mockImplementation(() => ({
@@ -140,7 +252,8 @@ describe('ServiceNow service', () => {
axios,
logger,
configurationUtilities,
- url: 'https://dev102283.service-now.com/api/now/v2/table/sn_si_incident/1',
+ url: 'https://example.com/api/now/v2/table/sn_si_incident/1',
+ method: 'get',
});
});
@@ -166,214 +279,346 @@ describe('ServiceNow service', () => {
});
describe('createIncident', () => {
- test('it creates the incident correctly', async () => {
- requestMock.mockImplementation(() => ({
- data: { result: { sys_id: '1', number: 'INC01', sys_created_on: '2020-03-10 12:24:20' } },
- }));
-
- const res = await service.createIncident({
- incident: { short_description: 'title', description: 'desc' },
+ // new connectors
+ describe('import set table', () => {
+ test('it creates the incident correctly', async () => {
+ const res = await createIncident(service);
+ expect(res).toEqual({
+ title: 'INC01',
+ id: '1',
+ pushedDate: '2020-03-10T12:24:20.000Z',
+ url: 'https://example.com/nav_to.do?uri=incident.do?sys_id=1',
+ });
});
- expect(res).toEqual({
- title: 'INC01',
- id: '1',
- pushedDate: '2020-03-10T12:24:20.000Z',
- url: 'https://dev102283.service-now.com/nav_to.do?uri=incident.do?sys_id=1',
+ test('it should call request with correct arguments', async () => {
+ await createIncident(service);
+ expect(requestMock).toHaveBeenCalledTimes(3);
+ expectImportedIncident(false);
});
- });
- test('it should call request with correct arguments', async () => {
- requestMock.mockImplementation(() => ({
- data: { result: { sys_id: '1', number: 'INC01', sys_created_on: '2020-03-10 12:24:20' } },
- }));
+ test('it should call request with correct arguments when table changes', async () => {
+ service = createExternalService(
+ {
+ config: { apiUrl: 'https://example.com/' },
+ secrets: { username: 'admin', password: 'admin' },
+ },
+ logger,
+ configurationUtilities,
+ snExternalServiceConfig['.servicenow-sir']
+ );
- await service.createIncident({
- incident: { short_description: 'title', description: 'desc' },
- });
+ const res = await createIncident(service);
- expect(requestMock).toHaveBeenCalledWith({
- axios,
- logger,
- configurationUtilities,
- url: 'https://dev102283.service-now.com/api/now/v2/table/incident',
- method: 'post',
- data: { short_description: 'title', description: 'desc' },
- });
- });
+ expect(requestMock).toHaveBeenNthCalledWith(1, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/x_elas2_sir_int/elastic_api/health',
+ method: 'get',
+ });
- test('it should call request with correct arguments when table changes', async () => {
- service = createExternalService(
- 'sn_si_incident',
- {
- config: { apiUrl: 'https://dev102283.service-now.com/' },
- secrets: { username: 'admin', password: 'admin' },
- },
- logger,
- configurationUtilities
- );
+ expect(requestMock).toHaveBeenNthCalledWith(2, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/now/import/x_elas2_sir_int_elastic_si_incident',
+ method: 'post',
+ data: { u_short_description: 'title', u_description: 'desc' },
+ });
+
+ expect(requestMock).toHaveBeenNthCalledWith(3, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/now/v2/table/sn_si_incident/1',
+ method: 'get',
+ });
- requestMock.mockImplementation(() => ({
- data: { result: { sys_id: '1', number: 'INC01', sys_created_on: '2020-03-10 12:24:20' } },
- }));
+ expect(res.url).toEqual('https://example.com/nav_to.do?uri=sn_si_incident.do?sys_id=1');
+ });
- const res = await service.createIncident({
- incident: { short_description: 'title', description: 'desc' },
+ test('it should throw an error when the application is not installed', async () => {
+ requestMock.mockImplementation(() => {
+ throw new Error('An error has occurred');
+ });
+
+ await expect(
+ service.createIncident({
+ incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident,
+ })
+ ).rejects.toThrow(
+ '[Action][ServiceNow]: Unable to create incident. Error: [Action][ServiceNow]: Unable to get application version. Error: An error has occurred Reason: unknown: errorResponse was null Reason: unknown: errorResponse was null'
+ );
});
- expect(requestMock).toHaveBeenCalledWith({
- axios,
- logger,
- configurationUtilities,
- url: 'https://dev102283.service-now.com/api/now/v2/table/sn_si_incident',
- method: 'post',
- data: { short_description: 'title', description: 'desc' },
+ test('it should throw an error when instance is not alive', async () => {
+ requestMock.mockImplementation(() => ({
+ status: 200,
+ data: {},
+ request: { connection: { servername: 'Developer instance' } },
+ }));
+ await expect(
+ service.createIncident({
+ incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident,
+ })
+ ).rejects.toThrow(
+ 'There is an issue with your Service Now Instance. Please check Developer instance.'
+ );
});
- expect(res.url).toEqual(
- 'https://dev102283.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=1'
- );
+ test('it should throw an error when there is an import set api error', async () => {
+ requestMock.mockImplementation(() => ({ data: getImportSetAPIError() }));
+ await expect(
+ service.createIncident({
+ incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident,
+ })
+ ).rejects.toThrow(
+ '[Action][ServiceNow]: Unable to create incident. Error: An error has occurred while importing the incident Reason: unknown'
+ );
+ });
});
- test('it should throw an error', async () => {
- requestMock.mockImplementation(() => {
- throw new Error('An error has occurred');
+ // old connectors
+ describe('table API', () => {
+ beforeEach(() => {
+ service = createExternalService(
+ {
+ config: { apiUrl: 'https://example.com/' },
+ secrets: { username: 'admin', password: 'admin' },
+ },
+ logger,
+ configurationUtilities,
+ { ...snExternalServiceConfig['.servicenow'], useImportAPI: false }
+ );
});
- await expect(
- service.createIncident({
- incident: { short_description: 'title', description: 'desc' },
- })
- ).rejects.toThrow(
- '[Action][ServiceNow]: Unable to create incident. Error: An error has occurred'
- );
- });
+ test('it creates the incident correctly', async () => {
+ mockIncidentResponse(false);
+ const res = await service.createIncident({
+ incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident,
+ });
+
+ expect(res).toEqual({
+ title: 'INC01',
+ id: '1',
+ pushedDate: '2020-03-10T12:24:20.000Z',
+ url: 'https://example.com/nav_to.do?uri=incident.do?sys_id=1',
+ });
+
+ expect(requestMock).toHaveBeenCalledTimes(2);
+ expect(requestMock).toHaveBeenNthCalledWith(1, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/now/v2/table/incident',
+ method: 'post',
+ data: { short_description: 'title', description: 'desc' },
+ });
+ });
- test('it should throw an error when instance is not alive', async () => {
- requestMock.mockImplementation(() => ({
- status: 200,
- data: {},
- request: { connection: { servername: 'Developer instance' } },
- }));
- await expect(service.getIncident('1')).rejects.toThrow(
- 'There is an issue with your Service Now Instance. Please check Developer instance.'
- );
- });
- });
+ test('it should call request with correct arguments when table changes', async () => {
+ service = createExternalService(
+ {
+ config: { apiUrl: 'https://example.com/' },
+ secrets: { username: 'admin', password: 'admin' },
+ },
+ logger,
+ configurationUtilities,
+ { ...snExternalServiceConfig['.servicenow-sir'], useImportAPI: false }
+ );
- describe('updateIncident', () => {
- test('it updates the incident correctly', async () => {
- patchMock.mockImplementation(() => ({
- data: { result: { sys_id: '1', number: 'INC01', sys_updated_on: '2020-03-10 12:24:20' } },
- }));
+ mockIncidentResponse(false);
- const res = await service.updateIncident({
- incidentId: '1',
- incident: { short_description: 'title', description: 'desc' },
- });
+ const res = await service.createIncident({
+ incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident,
+ });
+
+ expect(requestMock).toHaveBeenNthCalledWith(1, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/now/v2/table/sn_si_incident',
+ method: 'post',
+ data: { short_description: 'title', description: 'desc' },
+ });
- expect(res).toEqual({
- title: 'INC01',
- id: '1',
- pushedDate: '2020-03-10T12:24:20.000Z',
- url: 'https://dev102283.service-now.com/nav_to.do?uri=incident.do?sys_id=1',
+ expect(res.url).toEqual('https://example.com/nav_to.do?uri=sn_si_incident.do?sys_id=1');
});
});
+ });
- test('it should call request with correct arguments', async () => {
- patchMock.mockImplementation(() => ({
- data: { result: { sys_id: '1', number: 'INC01', sys_updated_on: '2020-03-10 12:24:20' } },
- }));
-
- await service.updateIncident({
- incidentId: '1',
- incident: { short_description: 'title', description: 'desc' },
+ describe('updateIncident', () => {
+ // new connectors
+ describe('import set table', () => {
+ test('it updates the incident correctly', async () => {
+ const res = await updateIncident(service);
+
+ expect(res).toEqual({
+ title: 'INC01',
+ id: '1',
+ pushedDate: '2020-03-10T12:24:20.000Z',
+ url: 'https://example.com/nav_to.do?uri=incident.do?sys_id=1',
+ });
});
- expect(patchMock).toHaveBeenCalledWith({
- axios,
- logger,
- configurationUtilities,
- url: 'https://dev102283.service-now.com/api/now/v2/table/incident/1',
- data: { short_description: 'title', description: 'desc' },
+ test('it should call request with correct arguments', async () => {
+ await updateIncident(service);
+ expectImportedIncident(true);
});
- });
- test('it should call request with correct arguments when table changes', async () => {
- service = createExternalService(
- 'sn_si_incident',
- {
- config: { apiUrl: 'https://dev102283.service-now.com/' },
- secrets: { username: 'admin', password: 'admin' },
- },
- logger,
- configurationUtilities
- );
+ test('it should call request with correct arguments when table changes', async () => {
+ service = createExternalService(
+ {
+ config: { apiUrl: 'https://example.com/' },
+ secrets: { username: 'admin', password: 'admin' },
+ },
+ logger,
+ configurationUtilities,
+ snExternalServiceConfig['.servicenow-sir']
+ );
- patchMock.mockImplementation(() => ({
- data: { result: { sys_id: '1', number: 'INC01', sys_updated_on: '2020-03-10 12:24:20' } },
- }));
+ const res = await updateIncident(service);
+ expect(requestMock).toHaveBeenNthCalledWith(1, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/x_elas2_sir_int/elastic_api/health',
+ method: 'get',
+ });
- const res = await service.updateIncident({
- incidentId: '1',
- incident: { short_description: 'title', description: 'desc' },
+ expect(requestMock).toHaveBeenNthCalledWith(2, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/now/import/x_elas2_sir_int_elastic_si_incident',
+ method: 'post',
+ data: { u_short_description: 'title', u_description: 'desc', elastic_incident_id: '1' },
+ });
+
+ expect(requestMock).toHaveBeenNthCalledWith(3, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/now/v2/table/sn_si_incident/1',
+ method: 'get',
+ });
+
+ expect(res.url).toEqual('https://example.com/nav_to.do?uri=sn_si_incident.do?sys_id=1');
});
- expect(patchMock).toHaveBeenCalledWith({
- axios,
- logger,
- configurationUtilities,
- url: 'https://dev102283.service-now.com/api/now/v2/table/sn_si_incident/1',
- data: { short_description: 'title', description: 'desc' },
+ test('it should throw an error when the application is not installed', async () => {
+ requestMock.mockImplementation(() => {
+ throw new Error('An error has occurred');
+ });
+
+ await expect(
+ service.updateIncident({
+ incidentId: '1',
+ incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident,
+ })
+ ).rejects.toThrow(
+ '[Action][ServiceNow]: Unable to update incident with id 1. Error: [Action][ServiceNow]: Unable to get application version. Error: An error has occurred Reason: unknown: errorResponse was null Reason: unknown: errorResponse was null'
+ );
});
- expect(res.url).toEqual(
- 'https://dev102283.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=1'
- );
+ test('it should throw an error when instance is not alive', async () => {
+ requestMock.mockImplementation(() => ({
+ status: 200,
+ data: {},
+ request: { connection: { servername: 'Developer instance' } },
+ }));
+ await expect(
+ service.updateIncident({
+ incidentId: '1',
+ incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident,
+ })
+ ).rejects.toThrow(
+ 'There is an issue with your Service Now Instance. Please check Developer instance.'
+ );
+ });
+
+ test('it should throw an error when there is an import set api error', async () => {
+ requestMock.mockImplementation(() => ({ data: getImportSetAPIError() }));
+ await expect(
+ service.updateIncident({
+ incidentId: '1',
+ incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident,
+ })
+ ).rejects.toThrow(
+ '[Action][ServiceNow]: Unable to update incident with id 1. Error: An error has occurred while importing the incident Reason: unknown'
+ );
+ });
});
- test('it should throw an error', async () => {
- patchMock.mockImplementation(() => {
- throw new Error('An error has occurred');
+ // old connectors
+ describe('table API', () => {
+ beforeEach(() => {
+ service = createExternalService(
+ {
+ config: { apiUrl: 'https://example.com/' },
+ secrets: { username: 'admin', password: 'admin' },
+ },
+ logger,
+ configurationUtilities,
+ { ...snExternalServiceConfig['.servicenow'], useImportAPI: false }
+ );
});
- await expect(
- service.updateIncident({
+ test('it updates the incident correctly', async () => {
+ mockIncidentResponse(true);
+ const res = await service.updateIncident({
incidentId: '1',
- incident: { short_description: 'title', description: 'desc' },
- })
- ).rejects.toThrow(
- '[Action][ServiceNow]: Unable to update incident with id 1. Error: An error has occurred'
- );
- });
+ incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident,
+ });
+
+ expect(res).toEqual({
+ title: 'INC01',
+ id: '1',
+ pushedDate: '2020-03-10T12:24:20.000Z',
+ url: 'https://example.com/nav_to.do?uri=incident.do?sys_id=1',
+ });
+
+ expect(requestMock).toHaveBeenCalledTimes(2);
+ expect(requestMock).toHaveBeenNthCalledWith(1, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/now/v2/table/incident/1',
+ method: 'patch',
+ data: { short_description: 'title', description: 'desc' },
+ });
+ });
- test('it creates the comment correctly', async () => {
- patchMock.mockImplementation(() => ({
- data: { result: { sys_id: '11', number: 'INC011', sys_updated_on: '2020-03-10 12:24:20' } },
- }));
+ test('it should call request with correct arguments when table changes', async () => {
+ service = createExternalService(
+ {
+ config: { apiUrl: 'https://example.com/' },
+ secrets: { username: 'admin', password: 'admin' },
+ },
+ logger,
+ configurationUtilities,
+ { ...snExternalServiceConfig['.servicenow-sir'], useImportAPI: false }
+ );
- const res = await service.updateIncident({
- incidentId: '1',
- comment: 'comment-1',
- });
+ mockIncidentResponse(false);
- expect(res).toEqual({
- title: 'INC011',
- id: '11',
- pushedDate: '2020-03-10T12:24:20.000Z',
- url: 'https://dev102283.service-now.com/nav_to.do?uri=incident.do?sys_id=11',
- });
- });
+ const res = await service.updateIncident({
+ incidentId: '1',
+ incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident,
+ });
- test('it should throw an error when instance is not alive', async () => {
- requestMock.mockImplementation(() => ({
- status: 200,
- data: {},
- request: { connection: { servername: 'Developer instance' } },
- }));
- await expect(service.getIncident('1')).rejects.toThrow(
- 'There is an issue with your Service Now Instance. Please check Developer instance.'
- );
+ expect(requestMock).toHaveBeenNthCalledWith(1, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/now/v2/table/sn_si_incident/1',
+ method: 'patch',
+ data: { short_description: 'title', description: 'desc' },
+ });
+
+ expect(res.url).toEqual('https://example.com/nav_to.do?uri=sn_si_incident.do?sys_id=1');
+ });
});
});
@@ -388,7 +633,7 @@ describe('ServiceNow service', () => {
axios,
logger,
configurationUtilities,
- url: 'https://dev102283.service-now.com/api/now/v2/table/sys_dictionary?sysparm_query=name=task^ORname=incident^internal_type=string&active=true&array=false&read_only=false&sysparm_fields=max_length,element,column_label,mandatory',
+ url: 'https://example.com/api/now/table/sys_dictionary?sysparm_query=name=task^ORname=incident^internal_type=string&active=true&array=false&read_only=false&sysparm_fields=max_length,element,column_label,mandatory',
});
});
@@ -402,13 +647,13 @@ describe('ServiceNow service', () => {
test('it should call request with correct arguments when table changes', async () => {
service = createExternalService(
- 'sn_si_incident',
{
- config: { apiUrl: 'https://dev102283.service-now.com/' },
+ config: { apiUrl: 'https://example.com/' },
secrets: { username: 'admin', password: 'admin' },
},
logger,
- configurationUtilities
+ configurationUtilities,
+ { ...snExternalServiceConfig['.servicenow'], table: 'sn_si_incident' }
);
requestMock.mockImplementation(() => ({
@@ -420,7 +665,7 @@ describe('ServiceNow service', () => {
axios,
logger,
configurationUtilities,
- url: 'https://dev102283.service-now.com/api/now/v2/table/sys_dictionary?sysparm_query=name=task^ORname=sn_si_incident^internal_type=string&active=true&array=false&read_only=false&sysparm_fields=max_length,element,column_label,mandatory',
+ url: 'https://example.com/api/now/table/sys_dictionary?sysparm_query=name=task^ORname=sn_si_incident^internal_type=string&active=true&array=false&read_only=false&sysparm_fields=max_length,element,column_label,mandatory',
});
});
@@ -456,7 +701,7 @@ describe('ServiceNow service', () => {
axios,
logger,
configurationUtilities,
- url: 'https://dev102283.service-now.com/api/now/v2/table/sys_choice?sysparm_query=name=task^ORname=incident^element=priority^ORelement=category&sysparm_fields=label,value,dependent_value,element',
+ url: 'https://example.com/api/now/table/sys_choice?sysparm_query=name=task^ORname=incident^element=priority^ORelement=category&sysparm_fields=label,value,dependent_value,element',
});
});
@@ -470,13 +715,13 @@ describe('ServiceNow service', () => {
test('it should call request with correct arguments when table changes', async () => {
service = createExternalService(
- 'sn_si_incident',
{
- config: { apiUrl: 'https://dev102283.service-now.com/' },
+ config: { apiUrl: 'https://example.com/' },
secrets: { username: 'admin', password: 'admin' },
},
logger,
- configurationUtilities
+ configurationUtilities,
+ { ...snExternalServiceConfig['.servicenow'], table: 'sn_si_incident' }
);
requestMock.mockImplementation(() => ({
@@ -489,7 +734,7 @@ describe('ServiceNow service', () => {
axios,
logger,
configurationUtilities,
- url: 'https://dev102283.service-now.com/api/now/v2/table/sys_choice?sysparm_query=name=task^ORname=sn_si_incident^element=priority^ORelement=category&sysparm_fields=label,value,dependent_value,element',
+ url: 'https://example.com/api/now/table/sys_choice?sysparm_query=name=task^ORname=sn_si_incident^element=priority^ORelement=category&sysparm_fields=label,value,dependent_value,element',
});
});
@@ -513,4 +758,79 @@ describe('ServiceNow service', () => {
);
});
});
+
+ describe('getUrl', () => {
+ test('it returns the instance url', async () => {
+ expect(service.getUrl()).toBe('https://example.com');
+ });
+ });
+
+ describe('checkInstance', () => {
+ test('it throws an error if there is no result on data', () => {
+ const res = { status: 200, data: {} } as AxiosResponse;
+ expect(() => service.checkInstance(res)).toThrow();
+ });
+
+ test('it does NOT throws an error if the status > 400', () => {
+ const res = { status: 500, data: {} } as AxiosResponse;
+ expect(() => service.checkInstance(res)).not.toThrow();
+ });
+
+ test('it shows the servername', () => {
+ const res = {
+ status: 200,
+ data: {},
+ request: { connection: { servername: 'https://example.com' } },
+ } as AxiosResponse;
+ expect(() => service.checkInstance(res)).toThrow(
+ 'There is an issue with your Service Now Instance. Please check https://example.com.'
+ );
+ });
+
+ describe('getApplicationInformation', () => {
+ test('it returns the application information', async () => {
+ mockApplicationVersion();
+ const res = await service.getApplicationInformation();
+ expect(res).toEqual({
+ name: 'Elastic',
+ scope: 'x_elas2_inc_int',
+ version: '1.0.0',
+ });
+ });
+
+ test('it should throw an error', async () => {
+ requestMock.mockImplementation(() => {
+ throw new Error('An error has occurred');
+ });
+ await expect(service.getApplicationInformation()).rejects.toThrow(
+ '[Action][ServiceNow]: Unable to get application version. Error: An error has occurred Reason: unknown'
+ );
+ });
+ });
+
+ describe('checkIfApplicationIsInstalled', () => {
+ test('it logs the application information', async () => {
+ mockApplicationVersion();
+ await service.checkIfApplicationIsInstalled();
+ expect(logger.debug).toHaveBeenCalledWith(
+ 'Create incident: Application scope: x_elas2_inc_int: Application version1.0.0'
+ );
+ });
+
+ test('it does not log if useOldApi = true', async () => {
+ service = createExternalService(
+ {
+ config: { apiUrl: 'https://example.com/' },
+ secrets: { username: 'admin', password: 'admin' },
+ },
+ logger,
+ configurationUtilities,
+ { ...snExternalServiceConfig['.servicenow'], useImportAPI: false }
+ );
+ await service.checkIfApplicationIsInstalled();
+ expect(requestMock).not.toHaveBeenCalled();
+ expect(logger.debug).not.toHaveBeenCalled();
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts
index 07ed9edc94d39..cb030c7bb6933 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts
@@ -7,28 +7,35 @@
import axios, { AxiosResponse } from 'axios';
-import { ExternalServiceCredentials, ExternalService, ExternalServiceParams } from './types';
+import {
+ ExternalServiceCredentials,
+ ExternalService,
+ ExternalServiceParamsCreate,
+ ExternalServiceParamsUpdate,
+ ImportSetApiResponse,
+ ImportSetApiResponseError,
+ ServiceNowIncident,
+ GetApplicationInfoResponse,
+ SNProductsConfigValue,
+ ServiceFactory,
+} from './types';
import * as i18n from './translations';
import { Logger } from '../../../../../../src/core/server';
-import {
- ServiceNowPublicConfigurationType,
- ServiceNowSecretConfigurationType,
- ResponseError,
-} from './types';
-import { request, getErrorMessage, addTimeZoneToDate, patch } from '../lib/axios_utils';
+import { ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType } from './types';
+import { request } from '../lib/axios_utils';
import { ActionsConfigurationUtilities } from '../../actions_config';
+import { createServiceError, getPushedDate, prepareIncident } from './utils';
-const API_VERSION = 'v2';
-const SYS_DICTIONARY = `api/now/${API_VERSION}/table/sys_dictionary`;
+export const SYS_DICTIONARY_ENDPOINT = `api/now/table/sys_dictionary`;
-export const createExternalService = (
- table: string,
+export const createExternalService: ServiceFactory = (
{ config, secrets }: ExternalServiceCredentials,
logger: Logger,
- configurationUtilities: ActionsConfigurationUtilities
+ configurationUtilities: ActionsConfigurationUtilities,
+ { table, importSetTable, useImportAPI, appScope }: SNProductsConfigValue
): ExternalService => {
- const { apiUrl: url } = config as ServiceNowPublicConfigurationType;
+ const { apiUrl: url, isLegacy } = config as ServiceNowPublicConfigurationType;
const { username, password } = secrets as ServiceNowSecretConfigurationType;
if (!url || !username || !password) {
@@ -36,13 +43,26 @@ export const createExternalService = (
}
const urlWithoutTrailingSlash = url.endsWith('/') ? url.slice(0, -1) : url;
- const incidentUrl = `${urlWithoutTrailingSlash}/api/now/${API_VERSION}/table/${table}`;
- const fieldsUrl = `${urlWithoutTrailingSlash}/${SYS_DICTIONARY}?sysparm_query=name=task^ORname=${table}^internal_type=string&active=true&array=false&read_only=false&sysparm_fields=max_length,element,column_label,mandatory`;
- const choicesUrl = `${urlWithoutTrailingSlash}/api/now/${API_VERSION}/table/sys_choice`;
+ const importSetTableUrl = `${urlWithoutTrailingSlash}/api/now/import/${importSetTable}`;
+ const tableApiIncidentUrl = `${urlWithoutTrailingSlash}/api/now/v2/table/${table}`;
+ const fieldsUrl = `${urlWithoutTrailingSlash}/${SYS_DICTIONARY_ENDPOINT}?sysparm_query=name=task^ORname=${table}^internal_type=string&active=true&array=false&read_only=false&sysparm_fields=max_length,element,column_label,mandatory`;
+ const choicesUrl = `${urlWithoutTrailingSlash}/api/now/table/sys_choice`;
+ /**
+ * Need to be set the same at:
+ * x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts
+ */
+ const getVersionUrl = () => `${urlWithoutTrailingSlash}/api/${appScope}/elastic_api/health`;
+
const axiosInstance = axios.create({
auth: { username, password },
});
+ const useOldApi = !useImportAPI || isLegacy;
+
+ const getCreateIncidentUrl = () => (useOldApi ? tableApiIncidentUrl : importSetTableUrl);
+ const getUpdateIncidentUrl = (incidentId: string) =>
+ useOldApi ? `${tableApiIncidentUrl}/${incidentId}` : importSetTableUrl;
+
const getIncidentViewURL = (id: string) => {
// Based on: https://docs.servicenow.com/bundle/orlando-platform-user-interface/page/use/navigation/reference/r_NavigatingByURLExamples.html
return `${urlWithoutTrailingSlash}/nav_to.do?uri=${table}.do?sys_id=${id}`;
@@ -57,7 +77,7 @@ export const createExternalService = (
};
const checkInstance = (res: AxiosResponse) => {
- if (res.status === 200 && res.data.result == null) {
+ if (res.status >= 200 && res.status < 400 && res.data.result == null) {
throw new Error(
`There is an issue with your Service Now Instance. Please check ${
res.request?.connection?.servername ?? ''
@@ -66,34 +86,70 @@ export const createExternalService = (
}
};
- const createErrorMessage = (errorResponse: ResponseError): string => {
- if (errorResponse == null) {
- return '';
+ const isImportSetApiResponseAnError = (
+ data: ImportSetApiResponse['result'][0]
+ ): data is ImportSetApiResponseError['result'][0] => data.status === 'error';
+
+ const throwIfImportSetApiResponseIsAnError = (res: ImportSetApiResponse) => {
+ if (res.result.length === 0) {
+ throw new Error('Unexpected result');
}
- const { error } = errorResponse;
- return error != null ? `${error?.message}: ${error?.detail}` : '';
+ const data = res.result[0];
+
+ // Create ResponseError message?
+ if (isImportSetApiResponseAnError(data)) {
+ throw new Error(data.error_message);
+ }
};
- const getIncident = async (id: string) => {
+ /**
+ * Gets the Elastic SN Application information including the current version.
+ * It should not be used on legacy connectors.
+ */
+ const getApplicationInformation = async (): Promise => {
try {
const res = await request({
axios: axiosInstance,
- url: `${incidentUrl}/${id}`,
+ url: getVersionUrl(),
logger,
configurationUtilities,
+ method: 'get',
});
+
checkInstance(res);
+
return { ...res.data.result };
} catch (error) {
- throw new Error(
- getErrorMessage(
- i18n.SERVICENOW,
- `Unable to get incident with id ${id}. Error: ${
- error.message
- } Reason: ${createErrorMessage(error.response?.data)}`
- )
- );
+ throw createServiceError(error, 'Unable to get application version');
+ }
+ };
+
+ const logApplicationInfo = (scope: string, version: string) =>
+ logger.debug(`Create incident: Application scope: ${scope}: Application version${version}`);
+
+ const checkIfApplicationIsInstalled = async () => {
+ if (!useOldApi) {
+ const { version, scope } = await getApplicationInformation();
+ logApplicationInfo(scope, version);
+ }
+ };
+
+ const getIncident = async (id: string): Promise => {
+ try {
+ const res = await request({
+ axios: axiosInstance,
+ url: `${tableApiIncidentUrl}/${id}`,
+ logger,
+ configurationUtilities,
+ method: 'get',
+ });
+
+ checkInstance(res);
+
+ return { ...res.data.result };
+ } catch (error) {
+ throw createServiceError(error, `Unable to get incident with id ${id}`);
}
};
@@ -101,7 +157,7 @@ export const createExternalService = (
try {
const res = await request({
axios: axiosInstance,
- url: incidentUrl,
+ url: tableApiIncidentUrl,
logger,
params,
configurationUtilities,
@@ -109,71 +165,80 @@ export const createExternalService = (
checkInstance(res);
return res.data.result.length > 0 ? { ...res.data.result } : undefined;
} catch (error) {
- throw new Error(
- getErrorMessage(
- i18n.SERVICENOW,
- `Unable to find incidents by query. Error: ${error.message} Reason: ${createErrorMessage(
- error.response?.data
- )}`
- )
- );
+ throw createServiceError(error, 'Unable to find incidents by query');
}
};
- const createIncident = async ({ incident }: ExternalServiceParams) => {
+ const getUrl = () => urlWithoutTrailingSlash;
+
+ const createIncident = async ({ incident }: ExternalServiceParamsCreate) => {
try {
+ await checkIfApplicationIsInstalled();
+
const res = await request({
axios: axiosInstance,
- url: `${incidentUrl}`,
+ url: getCreateIncidentUrl(),
logger,
method: 'post',
- data: { ...(incident as Record) },
+ data: prepareIncident(useOldApi, incident),
configurationUtilities,
});
+
checkInstance(res);
+
+ if (!useOldApi) {
+ throwIfImportSetApiResponseIsAnError(res.data);
+ }
+
+ const incidentId = useOldApi ? res.data.result.sys_id : res.data.result[0].sys_id;
+ const insertedIncident = await getIncident(incidentId);
+
return {
- title: res.data.result.number,
- id: res.data.result.sys_id,
- pushedDate: new Date(addTimeZoneToDate(res.data.result.sys_created_on)).toISOString(),
- url: getIncidentViewURL(res.data.result.sys_id),
+ title: insertedIncident.number,
+ id: insertedIncident.sys_id,
+ pushedDate: getPushedDate(insertedIncident.sys_created_on),
+ url: getIncidentViewURL(insertedIncident.sys_id),
};
} catch (error) {
- throw new Error(
- getErrorMessage(
- i18n.SERVICENOW,
- `Unable to create incident. Error: ${error.message} Reason: ${createErrorMessage(
- error.response?.data
- )}`
- )
- );
+ throw createServiceError(error, 'Unable to create incident');
}
};
- const updateIncident = async ({ incidentId, incident }: ExternalServiceParams) => {
+ const updateIncident = async ({ incidentId, incident }: ExternalServiceParamsUpdate) => {
try {
- const res = await patch({
+ await checkIfApplicationIsInstalled();
+
+ const res = await request({
axios: axiosInstance,
- url: `${incidentUrl}/${incidentId}`,
+ url: getUpdateIncidentUrl(incidentId),
+ // Import Set API supports only POST.
+ method: useOldApi ? 'patch' : 'post',
logger,
- data: { ...(incident as Record) },
+ data: {
+ ...prepareIncident(useOldApi, incident),
+ // elastic_incident_id is used to update the incident when using the Import Set API.
+ ...(useOldApi ? {} : { elastic_incident_id: incidentId }),
+ },
configurationUtilities,
});
+
checkInstance(res);
+
+ if (!useOldApi) {
+ throwIfImportSetApiResponseIsAnError(res.data);
+ }
+
+ const id = useOldApi ? res.data.result.sys_id : res.data.result[0].sys_id;
+ const updatedIncident = await getIncident(id);
+
return {
- title: res.data.result.number,
- id: res.data.result.sys_id,
- pushedDate: new Date(addTimeZoneToDate(res.data.result.sys_updated_on)).toISOString(),
- url: getIncidentViewURL(res.data.result.sys_id),
+ title: updatedIncident.number,
+ id: updatedIncident.sys_id,
+ pushedDate: getPushedDate(updatedIncident.sys_updated_on),
+ url: getIncidentViewURL(updatedIncident.sys_id),
};
} catch (error) {
- throw new Error(
- getErrorMessage(
- i18n.SERVICENOW,
- `Unable to update incident with id ${incidentId}. Error: ${
- error.message
- } Reason: ${createErrorMessage(error.response?.data)}`
- )
- );
+ throw createServiceError(error, `Unable to update incident with id ${incidentId}`);
}
};
@@ -185,17 +250,12 @@ export const createExternalService = (
logger,
configurationUtilities,
});
+
checkInstance(res);
+
return res.data.result.length > 0 ? res.data.result : [];
} catch (error) {
- throw new Error(
- getErrorMessage(
- i18n.SERVICENOW,
- `Unable to get fields. Error: ${error.message} Reason: ${createErrorMessage(
- error.response?.data
- )}`
- )
- );
+ throw createServiceError(error, 'Unable to get fields');
}
};
@@ -210,14 +270,7 @@ export const createExternalService = (
checkInstance(res);
return res.data.result;
} catch (error) {
- throw new Error(
- getErrorMessage(
- i18n.SERVICENOW,
- `Unable to get choices. Error: ${error.message} Reason: ${createErrorMessage(
- error.response?.data
- )}`
- )
- );
+ throw createServiceError(error, 'Unable to get choices');
}
};
@@ -228,5 +281,9 @@ export const createExternalService = (
getIncident,
updateIncident,
getChoices,
+ getUrl,
+ checkInstance,
+ getApplicationInformation,
+ checkIfApplicationIsInstalled,
};
};
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_sir.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_sir.test.ts
new file mode 100644
index 0000000000000..0fc94b6287abd
--- /dev/null
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_sir.test.ts
@@ -0,0 +1,129 @@
+/*
+ * 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 axios from 'axios';
+
+import { createExternalServiceSIR } from './service_sir';
+import * as utils from '../lib/axios_utils';
+import { ExternalServiceSIR } from './types';
+import { Logger } from '../../../../../../src/core/server';
+import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
+import { actionsConfigMock } from '../../actions_config.mock';
+import { observables } from './mocks';
+import { snExternalServiceConfig } from './config';
+
+const logger = loggingSystemMock.create().get() as jest.Mocked;
+
+jest.mock('axios');
+jest.mock('../lib/axios_utils', () => {
+ const originalUtils = jest.requireActual('../lib/axios_utils');
+ return {
+ ...originalUtils,
+ request: jest.fn(),
+ patch: jest.fn(),
+ };
+});
+
+axios.create = jest.fn(() => axios);
+const requestMock = utils.request as jest.Mock;
+const configurationUtilities = actionsConfigMock.create();
+
+const mockApplicationVersion = () =>
+ requestMock.mockImplementationOnce(() => ({
+ data: {
+ result: { name: 'Elastic', scope: 'x_elas2_sir_int', version: '1.0.0' },
+ },
+ }));
+
+const getAddObservablesResponse = () => [
+ {
+ value: '5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9',
+ observable_sys_id: '1',
+ },
+ {
+ value: '127.0.0.1',
+ observable_sys_id: '2',
+ },
+ {
+ value: 'https://example.com',
+ observable_sys_id: '3',
+ },
+];
+
+const mockAddObservablesResponse = (single: boolean) => {
+ const res = getAddObservablesResponse();
+ requestMock.mockImplementation(() => ({
+ data: {
+ result: single ? res[0] : res,
+ },
+ }));
+};
+
+const expectAddObservables = (single: boolean) => {
+ expect(requestMock).toHaveBeenNthCalledWith(1, {
+ axios,
+ logger,
+ configurationUtilities,
+ url: 'https://example.com/api/x_elas2_sir_int/elastic_api/health',
+ method: 'get',
+ });
+
+ const url = single
+ ? 'https://example.com/api/x_elas2_sir_int/elastic_api/incident/incident-1/observables'
+ : 'https://example.com/api/x_elas2_sir_int/elastic_api/incident/incident-1/observables/bulk';
+
+ const data = single ? observables[0] : observables;
+
+ expect(requestMock).toHaveBeenNthCalledWith(2, {
+ axios,
+ logger,
+ configurationUtilities,
+ url,
+ method: 'post',
+ data,
+ });
+};
+
+describe('ServiceNow SIR service', () => {
+ let service: ExternalServiceSIR;
+
+ beforeEach(() => {
+ service = createExternalServiceSIR(
+ {
+ config: { apiUrl: 'https://example.com/' },
+ secrets: { username: 'admin', password: 'admin' },
+ },
+ logger,
+ configurationUtilities,
+ snExternalServiceConfig['.servicenow-sir']
+ ) as ExternalServiceSIR;
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('bulkAddObservableToIncident', () => {
+ test('it adds multiple observables correctly', async () => {
+ mockApplicationVersion();
+ mockAddObservablesResponse(false);
+
+ const res = await service.bulkAddObservableToIncident(observables, 'incident-1');
+ expect(res).toEqual(getAddObservablesResponse());
+ expectAddObservables(false);
+ });
+
+ test('it adds a single observable correctly', async () => {
+ mockApplicationVersion();
+ mockAddObservablesResponse(true);
+
+ const res = await service.addObservableToIncident(observables[0], 'incident-1');
+ expect(res).toEqual(getAddObservablesResponse()[0]);
+ expectAddObservables(true);
+ });
+ });
+});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_sir.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_sir.ts
new file mode 100644
index 0000000000000..fc8d8cc555bc8
--- /dev/null
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_sir.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import axios from 'axios';
+
+import {
+ ExternalServiceCredentials,
+ SNProductsConfigValue,
+ Observable,
+ ExternalServiceSIR,
+ ObservableResponse,
+ ServiceFactory,
+} from './types';
+
+import { Logger } from '../../../../../../src/core/server';
+import { ServiceNowSecretConfigurationType } from './types';
+import { request } from '../lib/axios_utils';
+import { ActionsConfigurationUtilities } from '../../actions_config';
+import { createExternalService } from './service';
+import { createServiceError } from './utils';
+
+const getAddObservableToIncidentURL = (url: string, incidentID: string) =>
+ `${url}/api/x_elas2_sir_int/elastic_api/incident/${incidentID}/observables`;
+
+const getBulkAddObservableToIncidentURL = (url: string, incidentID: string) =>
+ `${url}/api/x_elas2_sir_int/elastic_api/incident/${incidentID}/observables/bulk`;
+
+export const createExternalServiceSIR: ServiceFactory = (
+ credentials: ExternalServiceCredentials,
+ logger: Logger,
+ configurationUtilities: ActionsConfigurationUtilities,
+ serviceConfig: SNProductsConfigValue
+): ExternalServiceSIR => {
+ const snService = createExternalService(
+ credentials,
+ logger,
+ configurationUtilities,
+ serviceConfig
+ );
+
+ const { username, password } = credentials.secrets as ServiceNowSecretConfigurationType;
+ const axiosInstance = axios.create({
+ auth: { username, password },
+ });
+
+ const _addObservable = async (data: Observable | Observable[], url: string) => {
+ snService.checkIfApplicationIsInstalled();
+
+ const res = await request({
+ axios: axiosInstance,
+ url,
+ logger,
+ method: 'post',
+ data,
+ configurationUtilities,
+ });
+
+ snService.checkInstance(res);
+ return res.data.result;
+ };
+
+ const addObservableToIncident = async (
+ observable: Observable,
+ incidentID: string
+ ): Promise => {
+ try {
+ return await _addObservable(
+ observable,
+ getAddObservableToIncidentURL(snService.getUrl(), incidentID)
+ );
+ } catch (error) {
+ throw createServiceError(
+ error,
+ `Unable to add observable to security incident with id ${incidentID}`
+ );
+ }
+ };
+
+ const bulkAddObservableToIncident = async (
+ observables: Observable[],
+ incidentID: string
+ ): Promise => {
+ try {
+ return await _addObservable(
+ observables,
+ getBulkAddObservableToIncidentURL(snService.getUrl(), incidentID)
+ );
+ } catch (error) {
+ throw createServiceError(
+ error,
+ `Unable to add observables to security incident with id ${incidentID}`
+ );
+ }
+ };
+ return {
+ ...snService,
+ addObservableToIncident,
+ bulkAddObservableToIncident,
+ };
+};
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts
index 50631cf289a73..ecca1e55e0fec 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts
@@ -7,6 +7,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
+import { AxiosError, AxiosResponse } from 'axios';
import { TypeOf } from '@kbn/config-schema';
import {
ExecutorParamsSchemaITSM,
@@ -78,15 +79,29 @@ export interface PushToServiceResponse extends ExternalServiceIncidentResponse {
comments?: ExternalServiceCommentResponse[];
}
-export type ExternalServiceParams = Record;
+export type Incident = ServiceNowITSMIncident | ServiceNowSIRIncident;
+export type PartialIncident = Partial;
+
+export interface ExternalServiceParamsCreate {
+ incident: Incident & Record;
+}
+
+export interface ExternalServiceParamsUpdate {
+ incidentId: string;
+ incident: PartialIncident & Record;
+}
export interface ExternalService {
getChoices: (fields: string[]) => Promise;
- getIncident: (id: string) => Promise;
+ getIncident: (id: string) => Promise;
getFields: () => Promise;
- createIncident: (params: ExternalServiceParams) => Promise;
- updateIncident: (params: ExternalServiceParams) => Promise;
- findIncidents: (params?: Record) => Promise;
+ createIncident: (params: ExternalServiceParamsCreate) => Promise;
+ updateIncident: (params: ExternalServiceParamsUpdate) => Promise;
+ findIncidents: (params?: Record) => Promise;
+ getUrl: () => string;
+ checkInstance: (res: AxiosResponse) => void;
+ getApplicationInformation: () => Promise;
+ checkIfApplicationIsInstalled: () => Promise;
}
export type PushToServiceApiParams = ExecutorSubActionPushParams;
@@ -115,10 +130,9 @@ export type ServiceNowSIRIncident = Omit<
'externalId'
>;
-export type Incident = ServiceNowITSMIncident | ServiceNowSIRIncident;
-
export interface PushToServiceApiHandlerArgs extends ExternalServiceApiHandlerArgs {
params: PushToServiceApiParams;
+ config: Record;
secrets: Record;
logger: Logger;
commentFieldKey: string;
@@ -158,12 +172,20 @@ export interface GetChoicesHandlerArgs {
params: ExecutorSubActionGetChoicesParams;
}
-export interface ExternalServiceApi {
+export interface ServiceNowIncident {
+ sys_id: string;
+ number: string;
+ sys_created_on: string;
+ sys_updated_on: string;
+ [x: string]: unknown;
+}
+
+export interface ExternalServiceAPI {
getChoices: (args: GetChoicesHandlerArgs) => Promise;
getFields: (args: GetCommonFieldsHandlerArgs) => Promise;
handshake: (args: HandshakeApiHandlerArgs) => Promise;
pushToService: (args: PushToServiceApiHandlerArgs) => Promise;
- getIncident: (args: GetIncidentApiHandlerArgs) => Promise;
+ getIncident: (args: GetIncidentApiHandlerArgs) => Promise;
}
export interface ExternalServiceCommentResponse {
@@ -173,10 +195,90 @@ export interface ExternalServiceCommentResponse {
}
type TypeNullOrUndefined = T | null | undefined;
-export interface ResponseError {
+
+export interface ServiceNowError {
error: TypeNullOrUndefined<{
message: TypeNullOrUndefined;
detail: TypeNullOrUndefined;
}>;
status: TypeNullOrUndefined;
}
+
+export type ResponseError = AxiosError;
+
+export interface ImportSetApiResponseSuccess {
+ import_set: string;
+ staging_table: string;
+ result: Array<{
+ display_name: string;
+ display_value: string;
+ record_link: string;
+ status: string;
+ sys_id: string;
+ table: string;
+ transform_map: string;
+ }>;
+}
+
+export interface ImportSetApiResponseError {
+ import_set: string;
+ staging_table: string;
+ result: Array<{
+ error_message: string;
+ status_message: string;
+ status: string;
+ transform_map: string;
+ }>;
+}
+
+export type ImportSetApiResponse = ImportSetApiResponseSuccess | ImportSetApiResponseError;
+export interface GetApplicationInfoResponse {
+ id: string;
+ name: string;
+ scope: string;
+ version: string;
+}
+
+export interface SNProductsConfigValue {
+ table: string;
+ appScope: string;
+ useImportAPI: boolean;
+ importSetTable: string;
+ commentFieldKey: string;
+}
+
+export type SNProductsConfig = Record;
+
+export enum ObservableTypes {
+ ip4 = 'ipv4-addr',
+ url = 'URL',
+ sha256 = 'SHA256',
+}
+
+export interface Observable {
+ value: string;
+ type: ObservableTypes;
+}
+
+export interface ObservableResponse {
+ value: string;
+ observable_sys_id: ObservableTypes;
+}
+
+export interface ExternalServiceSIR extends ExternalService {
+ addObservableToIncident: (
+ observable: Observable,
+ incidentID: string
+ ) => Promise;
+ bulkAddObservableToIncident: (
+ observables: Observable[],
+ incidentID: string
+ ) => Promise;
+}
+
+export type ServiceFactory = (
+ credentials: ExternalServiceCredentials,
+ logger: Logger,
+ configurationUtilities: ActionsConfigurationUtilities,
+ serviceConfig: SNProductsConfigValue
+) => ExternalServiceSIR | ExternalService;
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/utils.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/utils.test.ts
new file mode 100644
index 0000000000000..87f27da6d213f
--- /dev/null
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/utils.test.ts
@@ -0,0 +1,84 @@
+/*
+ * 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 { AxiosError } from 'axios';
+import { prepareIncident, createServiceError, getPushedDate } from './utils';
+
+/**
+ * The purpose of this test is to
+ * prevent developers from accidentally
+ * change important configuration values
+ * such as the scope or the import set table
+ * of our ServiceNow application
+ */
+
+describe('utils', () => {
+ describe('prepareIncident', () => {
+ test('it prepares the incident correctly when useOldApi=false', async () => {
+ const incident = { short_description: 'title', description: 'desc' };
+ const newIncident = prepareIncident(false, incident);
+ expect(newIncident).toEqual({ u_short_description: 'title', u_description: 'desc' });
+ });
+
+ test('it prepares the incident correctly when useOldApi=true', async () => {
+ const incident = { short_description: 'title', description: 'desc' };
+ const newIncident = prepareIncident(true, incident);
+ expect(newIncident).toEqual(incident);
+ });
+ });
+
+ describe('createServiceError', () => {
+ test('it creates an error when the response is null', async () => {
+ const error = new Error('An error occurred');
+ // @ts-expect-error
+ expect(createServiceError(error, 'Unable to do action').message).toBe(
+ '[Action][ServiceNow]: Unable to do action. Error: An error occurred Reason: unknown: errorResponse was null'
+ );
+ });
+
+ test('it creates an error with response correctly', async () => {
+ const axiosError = {
+ message: 'An error occurred',
+ response: { data: { error: { message: 'Denied', detail: 'no access' } } },
+ } as AxiosError;
+
+ expect(createServiceError(axiosError, 'Unable to do action').message).toBe(
+ '[Action][ServiceNow]: Unable to do action. Error: An error occurred Reason: Denied: no access'
+ );
+ });
+
+ test('it creates an error correctly when the ServiceNow error is null', async () => {
+ const axiosError = {
+ message: 'An error occurred',
+ response: { data: { error: null } },
+ } as AxiosError;
+
+ expect(createServiceError(axiosError, 'Unable to do action').message).toBe(
+ '[Action][ServiceNow]: Unable to do action. Error: An error occurred Reason: unknown: no error in error response'
+ );
+ });
+ });
+
+ describe('getPushedDate', () => {
+ beforeAll(() => {
+ jest.useFakeTimers('modern');
+ jest.setSystemTime(new Date('2021-10-04 11:15:06 GMT'));
+ });
+
+ afterAll(() => {
+ jest.useRealTimers();
+ });
+
+ test('it formats the date correctly if timestamp is provided', async () => {
+ expect(getPushedDate('2021-10-04 11:15:06')).toBe('2021-10-04T11:15:06.000Z');
+ });
+
+ test('it formats the date correctly if timestamp is not provided', async () => {
+ expect(getPushedDate()).toBe('2021-10-04T11:15:06.000Z');
+ });
+ });
+});
diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/utils.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/utils.ts
new file mode 100644
index 0000000000000..5b7ca99ffc709
--- /dev/null
+++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/utils.ts
@@ -0,0 +1,46 @@
+/*
+ * 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 { Incident, PartialIncident, ResponseError, ServiceNowError } from './types';
+import { FIELD_PREFIX } from './config';
+import { addTimeZoneToDate, getErrorMessage } from '../lib/axios_utils';
+import * as i18n from './translations';
+
+export const prepareIncident = (useOldApi: boolean, incident: PartialIncident): PartialIncident =>
+ useOldApi
+ ? incident
+ : Object.entries(incident).reduce(
+ (acc, [key, value]) => ({ ...acc, [`${FIELD_PREFIX}${key}`]: value }),
+ {} as Incident
+ );
+
+const createErrorMessage = (errorResponse?: ServiceNowError): string => {
+ if (errorResponse == null) {
+ return 'unknown: errorResponse was null';
+ }
+
+ const { error } = errorResponse;
+ return error != null
+ ? `${error?.message}: ${error?.detail}`
+ : 'unknown: no error in error response';
+};
+
+export const createServiceError = (error: ResponseError, message: string) =>
+ new Error(
+ getErrorMessage(
+ i18n.SERVICENOW,
+ `${message}. Error: ${error.message} Reason: ${createErrorMessage(error.response?.data)}`
+ )
+ );
+
+export const getPushedDate = (timestamp?: string) => {
+ if (timestamp != null) {
+ return new Date(addTimeZoneToDate(timestamp)).toISOString();
+ }
+
+ return new Date().toISOString();
+};
diff --git a/x-pack/plugins/actions/server/constants/connectors.ts b/x-pack/plugins/actions/server/constants/connectors.ts
new file mode 100644
index 0000000000000..f20d499716cf0
--- /dev/null
+++ b/x-pack/plugins/actions/server/constants/connectors.ts
@@ -0,0 +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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+// TODO: Remove when Elastic for ITSM is published.
+export const ENABLE_NEW_SN_ITSM_CONNECTOR = true;
+
+// TODO: Remove when Elastic for Security Operations is published.
+export const ENABLE_NEW_SN_SIR_CONNECTOR = true;
diff --git a/x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts b/x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts
index c094109a43d97..9f8e62c77e3a7 100644
--- a/x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts
+++ b/x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts
@@ -165,6 +165,47 @@ describe('successful migrations', () => {
});
expect(migratedAction).toEqual(action);
});
+
+ test('set isLegacy config property for .servicenow', () => {
+ const migration716 = getActionsMigrations(encryptedSavedObjectsSetup)['7.16.0'];
+ const action = getMockDataForServiceNow();
+ const migratedAction = migration716(action, context);
+
+ expect(migratedAction).toEqual({
+ ...action,
+ attributes: {
+ ...action.attributes,
+ config: {
+ apiUrl: 'https://example.com',
+ isLegacy: true,
+ },
+ },
+ });
+ });
+
+ test('set isLegacy config property for .servicenow-sir', () => {
+ const migration716 = getActionsMigrations(encryptedSavedObjectsSetup)['7.16.0'];
+ const action = getMockDataForServiceNow({ actionTypeId: '.servicenow-sir' });
+ const migratedAction = migration716(action, context);
+
+ expect(migratedAction).toEqual({
+ ...action,
+ attributes: {
+ ...action.attributes,
+ config: {
+ apiUrl: 'https://example.com',
+ isLegacy: true,
+ },
+ },
+ });
+ });
+
+ test('it does not set isLegacy config for other connectors', () => {
+ const migration716 = getActionsMigrations(encryptedSavedObjectsSetup)['7.16.0'];
+ const action = getMockData();
+ const migratedAction = migration716(action, context);
+ expect(migratedAction).toEqual(action);
+ });
});
describe('8.0.0', () => {
@@ -306,3 +347,19 @@ function getMockData(
type: 'action',
};
}
+
+function getMockDataForServiceNow(
+ overwrites: Record = {}
+): SavedObjectUnsanitizedDoc> {
+ return {
+ attributes: {
+ name: 'abc',
+ actionTypeId: '.servicenow',
+ config: { apiUrl: 'https://example.com' },
+ secrets: { user: 'test', password: '123' },
+ ...overwrites,
+ },
+ id: uuid.v4(),
+ type: 'action',
+ };
+}
diff --git a/x-pack/plugins/actions/server/saved_objects/actions_migrations.ts b/x-pack/plugins/actions/server/saved_objects/actions_migrations.ts
index e75f3eb41f2df..688839eb89858 100644
--- a/x-pack/plugins/actions/server/saved_objects/actions_migrations.ts
+++ b/x-pack/plugins/actions/server/saved_objects/actions_migrations.ts
@@ -59,13 +59,16 @@ export function getActionsMigrations(
const migrationActionsFourteen = createEsoMigration(
encryptedSavedObjects,
(doc): doc is SavedObjectUnsanitizedDoc => true,
- pipeMigrations(addisMissingSecretsField)
+ pipeMigrations(addIsMissingSecretsField)
);
- const migrationEmailActionsSixteen = createEsoMigration(
+ const migrationActionsSixteen = createEsoMigration(
encryptedSavedObjects,
- (doc): doc is SavedObjectUnsanitizedDoc => doc.attributes.actionTypeId === '.email',
- pipeMigrations(setServiceConfigIfNotSet)
+ (doc): doc is SavedObjectUnsanitizedDoc =>
+ doc.attributes.actionTypeId === '.servicenow' ||
+ doc.attributes.actionTypeId === '.servicenow-sir' ||
+ doc.attributes.actionTypeId === '.email',
+ pipeMigrations(markOldServiceNowITSMConnectorAsLegacy, setServiceConfigIfNotSet)
);
const migrationActions800 = createEsoMigration(
@@ -79,7 +82,7 @@ export function getActionsMigrations(
'7.10.0': executeMigrationWithErrorHandling(migrationActionsTen, '7.10.0'),
'7.11.0': executeMigrationWithErrorHandling(migrationActionsEleven, '7.11.0'),
'7.14.0': executeMigrationWithErrorHandling(migrationActionsFourteen, '7.14.0'),
- '7.16.0': executeMigrationWithErrorHandling(migrationEmailActionsSixteen, '7.16.0'),
+ '7.16.0': executeMigrationWithErrorHandling(migrationActionsSixteen, '7.16.0'),
'8.0.0': executeMigrationWithErrorHandling(migrationActions800, '8.0.0'),
};
}
@@ -182,7 +185,7 @@ const setServiceConfigIfNotSet = (
};
};
-const addisMissingSecretsField = (
+const addIsMissingSecretsField = (
doc: SavedObjectUnsanitizedDoc
): SavedObjectUnsanitizedDoc => {
return {
@@ -194,6 +197,28 @@ const addisMissingSecretsField = (
};
};
+const markOldServiceNowITSMConnectorAsLegacy = (
+ doc: SavedObjectUnsanitizedDoc
+): SavedObjectUnsanitizedDoc => {
+ if (
+ doc.attributes.actionTypeId !== '.servicenow' &&
+ doc.attributes.actionTypeId !== '.servicenow-sir'
+ ) {
+ return doc;
+ }
+
+ return {
+ ...doc,
+ attributes: {
+ ...doc.attributes,
+ config: {
+ ...doc.attributes.config,
+ isLegacy: true,
+ },
+ },
+ };
+};
+
function pipeMigrations(...migrations: ActionMigration[]): ActionMigration {
return (doc: SavedObjectUnsanitizedDoc) =>
migrations.reduce((migratedDoc, nextMigration) => nextMigration(migratedDoc), doc);
diff --git a/x-pack/plugins/apm/dev_docs/linting.md b/x-pack/plugins/apm/dev_docs/linting.md
index a4fd3094f121c..7db7053e59061 100644
--- a/x-pack/plugins/apm/dev_docs/linting.md
+++ b/x-pack/plugins/apm/dev_docs/linting.md
@@ -1,6 +1,6 @@
-## Linting
+# Linting
-_Note: Run the following commands from the root of Kibana._
+_Note: Run the commands from the root of Kibana._
### Typescript
@@ -19,4 +19,3 @@ yarn prettier "./x-pack/plugins/apm/**/*.{tsx,ts,js}" --write
```
node scripts/eslint.js x-pack/legacy/plugins/apm
```
-diff --git a/x-pack/plugins/apm/dev_docs/feature_flags.md b/x-pack/plugins/apm/dev_docs/feature_flags.md
diff --git a/x-pack/plugins/apm/dev_docs/testing.md b/x-pack/plugins/apm/dev_docs/testing.md
index 4d0edc27fe644..ba48e7e229e27 100644
--- a/x-pack/plugins/apm/dev_docs/testing.md
+++ b/x-pack/plugins/apm/dev_docs/testing.md
@@ -64,3 +64,13 @@ node scripts/functional_test_runner --config x-pack/test/functional/config.js --
APM tests are located in `x-pack/test/functional/apps/apm`.
For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
diff --git a/x-pack/plugins/apm/scripts/test/README.md b/x-pack/plugins/apm/scripts/test/README.md
+
+
+## Storybook
+
+### Start
+```
+yarn storybook apm
+```
+
+All files with a .stories.tsx extension will be loaded. You can access the development environment at http://localhost:9001.
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/no_data_screen.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/no_data_screen.ts
index 1e954d9982295..47eba11e6f6fb 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/no_data_screen.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/no_data_screen.ts
@@ -4,7 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-/* eslint-disable @typescript-eslint/naming-convention */
const apmIndicesSaveURL = '/internal/apm/settings/apm-indices/save';
@@ -20,12 +19,12 @@ describe('No data screen', () => {
url: apmIndicesSaveURL,
method: 'POST',
body: {
- 'apm_oss.sourcemapIndices': 'foo-*',
- 'apm_oss.errorIndices': 'foo-*',
- 'apm_oss.onboardingIndices': 'foo-*',
- 'apm_oss.spanIndices': 'foo-*',
- 'apm_oss.transactionIndices': 'foo-*',
- 'apm_oss.metricsIndices': 'foo-*',
+ sourcemaps: 'foo-*',
+ errors: 'foo-*',
+ onboarding: 'foo-*',
+ spans: 'foo-*',
+ transactions: 'foo-*',
+ metrics: 'foo-*',
},
headers: {
'kbn-xsrf': true,
@@ -50,12 +49,12 @@ describe('No data screen', () => {
url: apmIndicesSaveURL,
method: 'POST',
body: {
- 'apm_oss.sourcemapIndices': '',
- 'apm_oss.errorIndices': '',
- 'apm_oss.onboardingIndices': '',
- 'apm_oss.spanIndices': '',
- 'apm_oss.transactionIndices': '',
- 'apm_oss.metricsIndices': '',
+ sourcemaps: '',
+ errors: '',
+ onboarding: '',
+ spans: '',
+ transactions: '',
+ metrics: '',
},
headers: { 'kbn-xsrf': true },
auth: { user: 'apm_power_user', pass: 'changeme' },
diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json
index 4e82d82d655b4..865358959ea72 100644
--- a/x-pack/plugins/apm/kibana.json
+++ b/x-pack/plugins/apm/kibana.json
@@ -8,7 +8,6 @@
"version": "8.0.0",
"kibanaVersion": "kibana",
"requiredPlugins": [
- "apmOss",
"data",
"embeddable",
"features",
diff --git a/x-pack/plugins/apm/public/components/alerting/chart_preview/index.tsx b/x-pack/plugins/apm/public/components/alerting/chart_preview/index.tsx
index 8a54c76df0f69..ee6a58b0dbb76 100644
--- a/x-pack/plugins/apm/public/components/alerting/chart_preview/index.tsx
+++ b/x-pack/plugins/apm/public/components/alerting/chart_preview/index.tsx
@@ -96,7 +96,7 @@ export function ChartPreview({
position={Position.Left}
tickFormat={yTickFormat}
ticks={5}
- domain={{ max: yMax }}
+ domain={{ max: yMax, min: NaN }}
/>
{
+ const onBrushEnd = ({ x }: XYBrushEvent) => {
if (!x) {
return;
}
@@ -99,7 +100,7 @@ export function PageLoadDistChart({
diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx
index 6685dddd87d7f..2e526eff04346 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx
@@ -30,40 +30,40 @@ import {
const APM_INDEX_LABELS = [
{
- configurationName: 'apm_oss.sourcemapIndices',
+ configurationName: 'sourcemap',
label: i18n.translate(
'xpack.apm.settings.apmIndices.sourcemapIndicesLabel',
{ defaultMessage: 'Sourcemap Indices' }
),
},
{
- configurationName: 'apm_oss.errorIndices',
+ configurationName: 'error',
label: i18n.translate('xpack.apm.settings.apmIndices.errorIndicesLabel', {
defaultMessage: 'Error Indices',
}),
},
{
- configurationName: 'apm_oss.onboardingIndices',
+ configurationName: 'onboarding',
label: i18n.translate(
'xpack.apm.settings.apmIndices.onboardingIndicesLabel',
{ defaultMessage: 'Onboarding Indices' }
),
},
{
- configurationName: 'apm_oss.spanIndices',
+ configurationName: 'span',
label: i18n.translate('xpack.apm.settings.apmIndices.spanIndicesLabel', {
defaultMessage: 'Span Indices',
}),
},
{
- configurationName: 'apm_oss.transactionIndices',
+ configurationName: 'transaction',
label: i18n.translate(
'xpack.apm.settings.apmIndices.transactionIndicesLabel',
{ defaultMessage: 'Transaction Indices' }
),
},
{
- configurationName: 'apm_oss.metricsIndices',
+ configurationName: 'metric',
label: i18n.translate('xpack.apm.settings.apmIndices.metricsIndicesLabel', {
defaultMessage: 'Metrics Indices',
}),
@@ -145,7 +145,7 @@ export function ApmIndices() {
}
),
});
- } catch (error) {
+ } catch (error: any) {
notifications.toasts.addDanger({
title: i18n.translate(
'xpack.apm.settings.apmIndices.applyChanges.failed.title',
@@ -215,7 +215,10 @@ export function ApmIndices() {
{
defaultMessage:
'Overrides {configurationName}: {defaultValue}',
- values: { configurationName, defaultValue },
+ values: {
+ configurationName: `xpack.apm.indices.${configurationName}`,
+ defaultValue,
+ },
}
)}
fullWidth
diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/List/List.test.tsx b/x-pack/plugins/apm/public/components/app/error_group_overview/List/List.test.tsx
index a2a92b7e16f8e..12fa1c955ccc8 100644
--- a/x-pack/plugins/apm/public/components/app/error_group_overview/List/List.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/error_group_overview/List/List.test.tsx
@@ -15,12 +15,6 @@ import props from './__fixtures__/props.json';
import { MemoryRouter } from 'react-router-dom';
import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common';
-jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
- return {
- htmlIdGenerator: () => () => `generated-id`,
- };
-});
-
describe('ErrorGroupOverview -> List', () => {
beforeAll(() => {
mockMoment();
diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap
index 890c692096a66..c8c7bf82dff04 100644
--- a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap
+++ b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap
@@ -56,7 +56,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = `
List should render with data 1`] = `
List should render with data 1`] = `
className="euiPagination__item"
>
void;
onClearSelection: () => void;
selection?: Selection;
traceSamples: TabContentProps['traceSamples'];
@@ -126,10 +126,8 @@ export function TransactionDistribution({
const trackApmEvent = useUiTracker({ app: 'apm' });
- const onTrackedChartSelection: BrushEndListener = (
- brushArea: XYBrushArea
- ) => {
- onChartSelection(brushArea);
+ const onTrackedChartSelection = (brushEvent: XYBrushEvent) => {
+ onChartSelection(brushEvent);
trackApmEvent({ metric: 'transaction_distribution_chart_selection' });
};
@@ -216,7 +214,7 @@ export function TransactionDistribution({
markerCurrentTransaction={markerCurrentTransaction}
markerPercentile={DEFAULT_PERCENTILE_THRESHOLD}
markerValue={response.percentileThresholdValue ?? 0}
- onChartSelection={onTrackedChartSelection}
+ onChartSelection={onTrackedChartSelection as BrushEndListener}
hasData={hasData}
selection={selection}
status={status}
diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx
index b249161980586..9ccca9886e679 100644
--- a/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx
+++ b/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx
@@ -10,7 +10,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import { omit } from 'lodash';
import { useHistory } from 'react-router-dom';
-import { XYBrushArea } from '@elastic/charts';
+import { XYBrushEvent } from '@elastic/charts';
import { EuiPanel, EuiSpacer, EuiTabs, EuiTab } from '@elastic/eui';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
@@ -48,7 +48,7 @@ export function TransactionDetailsTabs() {
environment,
});
- const selectSampleFromChartSelection = (selection: XYBrushArea) => {
+ const selectSampleFromChartSelection = (selection: XYBrushEvent) => {
if (selection !== undefined) {
const { x } = selection;
if (Array.isArray(x)) {
diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/types.ts b/x-pack/plugins/apm/public/components/app/transaction_details/types.ts
index 1ccb3d01a9b28..c3d2b9648e82a 100644
--- a/x-pack/plugins/apm/public/components/app/transaction_details/types.ts
+++ b/x-pack/plugins/apm/public/components/app/transaction_details/types.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { XYBrushArea } from '@elastic/charts';
+import { XYBrushEvent } from '@elastic/charts';
import type { TraceSample } from '../../../hooks/use_transaction_trace_samples_fetcher';
@@ -14,6 +14,6 @@ export interface TabContentProps {
onFilter: () => void;
sampleRangeFrom?: number;
sampleRangeTo?: number;
- selectSampleFromChartSelection: (selection: XYBrushArea) => void;
+ selectSampleFromChartSelection: (selection: XYBrushEvent) => void;
traceSamples: TraceSample[];
}
diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_accordion.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_accordion.tsx
index de9c7f651019e..8f66658785b97 100644
--- a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_accordion.tsx
+++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_accordion.tsx
@@ -24,7 +24,6 @@ import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent';
import { AgentIcon } from '../../shared/agent_icon';
import { NewPackagePolicy } from '../apm_policy_form/typings';
import { getCommands } from '../../../tutorial/config_agent/commands/get_commands';
-import { CopyCommands } from '../../../tutorial/config_agent/copy_commands';
import { replaceTemplateStrings } from './replace_template_strings';
function AccordionButtonContent({
@@ -91,14 +90,9 @@ function TutorialConfigAgent({
policyDetails: { apmServerUrl, secretToken },
});
return (
-
-
-
-
-
- {commandBlock}
-
-
+
+ {commandBlock}
+
);
}
@@ -153,23 +147,16 @@ export function AgentInstructionsAccordion({
{textPre && (
-
-
-
-
- {commandBlock && (
-
-
-
- )}
-
+
)}
{commandBlock && (
<>
- {commandBlock}
+
+ {commandBlock}
+
>
)}
{customComponentName === 'TutorialConfigAgent' && (
diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.test.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.test.tsx
index c73d312e0cf18..6bab77dbe4970 100644
--- a/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/Stackframe.test.tsx
@@ -12,12 +12,6 @@ import { mountWithTheme } from '../../../utils/testHelpers';
import { Stackframe as StackframeComponent } from './Stackframe';
import stacktracesMock from './__fixtures__/stacktraces.json';
-jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => {
- return {
- htmlIdGenerator: () => () => `generated-id`,
- };
-});
-
describe('Stackframe', () => {
describe('when stackframe has source lines', () => {
let wrapper: ReactWrapper;
diff --git a/x-pack/plugins/apm/public/components/shared/charts/breakdown_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/breakdown_chart/index.tsx
index 213bac40c2248..16157071affcd 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/breakdown_chart/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/breakdown_chart/index.tsx
@@ -17,6 +17,7 @@ import {
ScaleType,
Settings,
TickFormatter,
+ XYBrushEvent,
} from '@elastic/charts';
import { EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -96,7 +97,9 @@ export function BreakdownChart({
onBrushEnd({ x, history })}
+ onBrushEnd={(event) =>
+ onBrushEnd({ x: (event as XYBrushEvent).x, history })
+ }
showLegend
showLegendExtra
legendPosition={Position.Bottom}
diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts
index d94f2ce8f5c5d..9dccddd509387 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts
+++ b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { XYBrushArea } from '@elastic/charts';
+import { XYBrushEvent } from '@elastic/charts';
import { History } from 'history';
import { Coordinate, TimeSeries } from '../../../../../typings/timeseries';
import { fromQuery, toQuery } from '../../Links/url_helpers';
@@ -14,7 +14,7 @@ export const onBrushEnd = ({
x,
history,
}: {
- x: XYBrushArea['x'];
+ x: XYBrushEvent['x'];
history: History;
}) => {
if (x) {
diff --git a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx
index 65ecdec0f36a5..08e8908d50e7a 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx
@@ -20,6 +20,7 @@ import {
ScaleType,
Settings,
YDomainRange,
+ XYBrushEvent,
} from '@elastic/charts';
import { EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -115,7 +116,9 @@ export function TimeseriesChart({
onBrushEnd({ x, history })}
+ onBrushEnd={(event) =>
+ onBrushEnd({ x: (event as XYBrushEvent).x, history })
+ }
theme={{
...chartTheme,
areaSeriesStyle: {
diff --git a/x-pack/plugins/apm/public/components/shared/managed_table/__snapshots__/managed_table.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/managed_table/__snapshots__/managed_table.test.tsx.snap
index e4674b3add880..15613af4daf98 100644
--- a/x-pack/plugins/apm/public/components/shared/managed_table/__snapshots__/managed_table.test.tsx.snap
+++ b/x-pack/plugins/apm/public/components/shared/managed_table/__snapshots__/managed_table.test.tsx.snap
@@ -35,7 +35,12 @@ exports[`ManagedTable should render a page-full of items, with defaults 1`] = `
]
}
loading={false}
- noItemsMessage="No items found"
+ noItemsMessage={
+
+ }
onChange={[Function]}
pagination={
Object {
@@ -85,7 +90,12 @@ exports[`ManagedTable should render when specifying initial values 1`] = `
]
}
loading={false}
- noItemsMessage="No items found"
+ noItemsMessage={
+
+ }
onChange={[Function]}
pagination={
Object {
diff --git a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
index 617af6dae484d..abdab939f4a0a 100644
--- a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
+++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
@@ -100,7 +100,7 @@ const urlService = new UrlService({
getUrl: async ({ app, path }, { absolute }) => {
return `${absolute ? 'http://localhost:8888' : ''}/app/${app}${path}`;
},
- shortUrls: {} as any,
+ shortUrls: () => ({ get: () => {} } as any),
});
const locator = urlService.locators.create(new MlLocatorDefinition());
diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/copy_commands.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/copy_commands.tsx
deleted file mode 100644
index c5261cfc1dc04..0000000000000
--- a/x-pack/plugins/apm/public/tutorial/config_agent/copy_commands.tsx
+++ /dev/null
@@ -1,26 +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 { EuiButton, EuiCopy } from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import React from 'react';
-
-interface Props {
- commands: string;
-}
-export function CopyCommands({ commands }: Props) {
- return (
-
- {(copy) => (
-
- {i18n.translate('xpack.apm.tutorial.copySnippet', {
- defaultMessage: 'Copy snippet',
- })}
-
- )}
-
- );
-}
diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx
index 5ff1fd7f42119..bce16ae6ef1f9 100644
--- a/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx
+++ b/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx
@@ -4,20 +4,13 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import {
- EuiCodeBlock,
- EuiFlexGroup,
- EuiFlexItem,
- EuiLoadingSpinner,
- EuiSpacer,
-} from '@elastic/eui';
+import { EuiCodeBlock, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { HttpStart } from 'kibana/public';
import React, { useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import { APIReturnType } from '../..//services/rest/createCallApmApi';
import { getCommands } from './commands/get_commands';
-import { CopyCommands } from './copy_commands';
import { getPolicyOptions, PolicyOption } from './get_policy_options';
import { PolicySelector } from './policy_selector';
@@ -136,27 +129,19 @@ function TutorialConfigAgent({
return (
<>
-
-
-
- setSelectedOption(newSelectedOption)
- }
- fleetLink={getFleetLink({
- isFleetEnabled: data.isFleetEnabled,
- hasFleetAgents,
- basePath,
- })}
- />
-
-
-
-
-
+ setSelectedOption(newSelectedOption)}
+ fleetLink={getFleetLink({
+ isFleetEnabled: data.isFleetEnabled,
+ hasFleetAgents,
+ basePath,
+ })}
+ />
+
-
+
{commands}
>
diff --git a/x-pack/plugins/apm/public/utils/testHelpers.tsx b/x-pack/plugins/apm/public/utils/testHelpers.tsx
index 8764ac48c5440..2203bc63f68cd 100644
--- a/x-pack/plugins/apm/public/utils/testHelpers.tsx
+++ b/x-pack/plugins/apm/public/utils/testHelpers.tsx
@@ -119,14 +119,12 @@ interface MockSetup {
config: APMConfig;
uiFilters: UxUIFilters;
indices: {
- /* eslint-disable @typescript-eslint/naming-convention */
- 'apm_oss.sourcemapIndices': string;
- 'apm_oss.errorIndices': string;
- 'apm_oss.onboardingIndices': string;
- 'apm_oss.spanIndices': string;
- 'apm_oss.transactionIndices': string;
- 'apm_oss.metricsIndices': string;
- /* eslint-enable @typescript-eslint/naming-convention */
+ sourcemaps: string;
+ errors: string;
+ onboarding: string;
+ spans: string;
+ transactions: string;
+ metrics: string;
apmAgentConfigurationIndex: string;
apmCustomLinkIndex: string;
};
@@ -178,14 +176,12 @@ export async function inspectSearchParams(
) as APMConfig,
uiFilters: {},
indices: {
- /* eslint-disable @typescript-eslint/naming-convention */
- 'apm_oss.sourcemapIndices': 'myIndex',
- 'apm_oss.errorIndices': 'myIndex',
- 'apm_oss.onboardingIndices': 'myIndex',
- 'apm_oss.spanIndices': 'myIndex',
- 'apm_oss.transactionIndices': 'myIndex',
- 'apm_oss.metricsIndices': 'myIndex',
- /* eslint-enable @typescript-eslint/naming-convention */
+ sourcemaps: 'myIndex',
+ errors: 'myIndex',
+ onboarding: 'myIndex',
+ spans: 'myIndex',
+ transactions: 'myIndex',
+ metrics: 'myIndex',
apmAgentConfigurationIndex: 'myIndex',
apmCustomLinkIndex: 'myIndex',
},
diff --git a/x-pack/plugins/apm/readme.md b/x-pack/plugins/apm/readme.md
index 6ebe07d44683c..040e620ae91ca 100644
--- a/x-pack/plugins/apm/readme.md
+++ b/x-pack/plugins/apm/readme.md
@@ -1,31 +1,18 @@
# Documentation for APM UI developers
-## Local environment setup
+## Getting started
+- [Local setup](./dev_docs/local_setup.md)
+- [Testing (unit, api, e2e, storybook)](./dev_docs/testing.md)
+- [Linting (typescript, eslint, prettier)](./dev_docs/linting.md)
-[Local setup documentation](./dev_docs/local_setup.md)
-
-## Testing
-
-[Testing documentation](./dev_docs/testing.md)
-
-## Linting
-
-[Linting documentation](./dev_docs/linting.md)
-
-## Storybook
-
-**Start**
-```
-yarn storybook apm
-```
-
-All files with a .stories.tsx extension will be loaded. You can access the development environment at http://localhost:9001.
-
-## Further resources
+## APM concepts
- [Queries and data model](./dev_docs/apm_queries.md)
+- [Telemetry](./dev_docs/telemetry.md)
+- [Routing and Linking](./dev_docs/routing_and_linking.md)
+
+## Tooling
- [VSCode setup instructions](./dev_docs/vscode_setup.md)
- [Github PR commands](./dev_docs/github_commands.md)
-- [Routing and Linking](./dev_docs/routing_and_linking.md)
-- [Telemetry](./dev_docs/telemetry.md)
-- [Features flags](./dev_docs/feature_flags.md)
+
+## Other resources
- [Official APM UI settings docs](https://www.elastic.co/guide/en/kibana/current/apm-settings-in-kibana.html)
diff --git a/x-pack/plugins/apm/scripts/shared/read-kibana-config.ts b/x-pack/plugins/apm/scripts/shared/read-kibana-config.ts
index a85bd007bc4f3..f3e2b48390468 100644
--- a/x-pack/plugins/apm/scripts/shared/read-kibana-config.ts
+++ b/x-pack/plugins/apm/scripts/shared/read-kibana-config.ts
@@ -38,14 +38,12 @@ export const readKibanaConfig = () => {
};
return {
- /* eslint-disable @typescript-eslint/naming-convention */
- 'apm_oss.transactionIndices': 'apm-*',
- 'apm_oss.metricsIndices': 'apm-*',
- 'apm_oss.errorIndices': 'apm-*',
- 'apm_oss.spanIndices': 'apm-*',
- 'apm_oss.onboardingIndices': 'apm-*',
- 'apm_oss.sourcemapIndices': 'apm-*',
- /* eslint-enable @typescript-eslint/naming-convention */
+ 'xpack.apm.indices.transaction': 'traces-apm*,apm-*',
+ 'xpack.apm.indices.metric': 'metrics-apm*,apm-*',
+ 'xpack.apm.indices.error': 'logs-apm*,apm-*',
+ 'xpack.apm.indices.span': 'traces-apm*,apm-*',
+ 'xpack.apm.indices.onboarding': 'apm-*',
+ 'xpack.apm.indices.sourcemap': 'apm-*',
'elasticsearch.hosts': 'http://localhost:9200',
...loadedKibanaConfig,
...cliEsCredentials,
diff --git a/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts b/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts
index 0dab75cfba9c7..c900123c6cee9 100644
--- a/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts
+++ b/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts
@@ -78,7 +78,12 @@ async function uploadData() {
collectTelemetryParams: {
logger: console as unknown as Logger,
indices: {
- ...config,
+ transaction: config['xpack.apm.indices.transaction'],
+ metric: config['xpack.apm.indices.metric'],
+ error: config['xpack.apm.indices.error'],
+ span: config['xpack.apm.indices.span'],
+ onboarding: config['xpack.apm.indices.onboarding'],
+ sourcemap: config['xpack.apm.indices.sourcemap'],
apmCustomLinkIndex: '.apm-custom-links',
apmAgentConfigurationIndex: '.apm-agent-configuration',
},
diff --git a/x-pack/plugins/apm/server/index.test.ts b/x-pack/plugins/apm/server/index.test.ts
deleted file mode 100644
index be93557fea6fc..0000000000000
--- a/x-pack/plugins/apm/server/index.test.ts
+++ /dev/null
@@ -1,40 +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.
- */
-
-/* eslint-disable @typescript-eslint/naming-convention */
-
-import { APMOSSConfig } from 'src/plugins/apm_oss/server';
-import { APMXPackConfig } from '.';
-import { mergeConfigs } from './index';
-
-describe('mergeConfigs', () => {
- it('merges the configs', () => {
- const apmOssConfig = {
- transactionIndices: 'apm-*-transaction-*',
- spanIndices: 'apm-*-span-*',
- errorIndices: 'apm-*-error-*',
- metricsIndices: 'apm-*-metric-*',
- } as APMOSSConfig;
-
- const apmConfig = {
- ui: { enabled: false },
- enabled: true,
- metricsInterval: 2000,
- agent: { migrations: { enabled: true } },
- } as APMXPackConfig;
-
- expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({
- 'apm_oss.errorIndices': 'logs-apm*,apm-*-error-*',
- 'apm_oss.metricsIndices': 'metrics-apm*,apm-*-metric-*',
- 'apm_oss.spanIndices': 'traces-apm*,apm-*-span-*',
- 'apm_oss.transactionIndices': 'traces-apm*,apm-*-transaction-*',
- 'xpack.apm.metricsInterval': 2000,
- 'xpack.apm.ui.enabled': false,
- 'xpack.apm.agent.migrations.enabled': true,
- });
- });
-});
diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts
index 22787b0301ce0..c0dffc50e4e4f 100644
--- a/x-pack/plugins/apm/server/index.ts
+++ b/x-pack/plugins/apm/server/index.ts
@@ -10,11 +10,12 @@ import {
PluginConfigDescriptor,
PluginInitializerContext,
} from 'src/core/server';
-import { APMOSSConfig } from 'src/plugins/apm_oss/server';
import { maxSuggestions } from '../../observability/common';
import { SearchAggregatedTransactionSetting } from '../common/aggregated_transactions';
import { APMPlugin } from './plugin';
+// All options should be documented in the APM configuration settings: https://github.com/elastic/kibana/blob/master/docs/settings/apm-settings.asciidoc
+// and be included on cloud allow list unless there are specific reasons not to
const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: true }),
serviceMapEnabled: schema.boolean({ defaultValue: true }),
@@ -47,12 +48,37 @@ const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: false }),
}),
}),
+ indices: schema.object({
+ transaction: schema.string({ defaultValue: 'traces-apm*,apm-*' }),
+ span: schema.string({ defaultValue: 'traces-apm*,apm-*' }),
+ error: schema.string({ defaultValue: 'logs-apm*,apm-*' }),
+ metric: schema.string({ defaultValue: 'metrics-apm*,apm-*' }),
+ sourcemap: schema.string({ defaultValue: 'apm-*' }),
+ onboarding: schema.string({ defaultValue: 'apm-*' }),
+ }),
});
// plugin config
-export const config: PluginConfigDescriptor = {
- deprecations: ({ deprecate, renameFromRoot }) => [
+export const config: PluginConfigDescriptor = {
+ deprecations: ({
+ deprecate,
+ renameFromRoot,
+ deprecateFromRoot,
+ unusedFromRoot,
+ }) => [
deprecate('enabled', '8.0.0'),
+ renameFromRoot(
+ 'apm_oss.transactionIndices',
+ 'xpack.apm.indices.transaction'
+ ),
+ renameFromRoot('apm_oss.spanIndices', 'xpack.apm.indices.span'),
+ renameFromRoot('apm_oss.errorIndices', 'xpack.apm.indices.error'),
+ renameFromRoot('apm_oss.metricsIndices', 'xpack.apm.indices.metric'),
+ renameFromRoot('apm_oss.sourcemapIndices', 'xpack.apm.indices.sourcemap'),
+ renameFromRoot('apm_oss.onboardingIndices', 'xpack.apm.indices.onboarding'),
+ deprecateFromRoot('apm_oss.enabled', '8.0.0'),
+ unusedFromRoot('apm_oss.fleetMode'),
+ unusedFromRoot('apm_oss.indexPattern'),
renameFromRoot(
'xpack.apm.maxServiceEnvironments',
`uiSettings.overrides[${maxSuggestions}]`
@@ -70,69 +96,8 @@ export const config: PluginConfigDescriptor = {
schema: configSchema,
};
-export type APMXPackConfig = TypeOf;
-export type APMConfig = ReturnType;
-
-// plugin config and ui indices settings
-// All options should be documented in the APM configuration settings: https://github.com/elastic/kibana/blob/master/docs/settings/apm-settings.asciidoc
-// and be included on cloud allow list unless there are specific reasons not to
-export function mergeConfigs(
- apmOssConfig: APMOSSConfig,
- apmConfig: APMXPackConfig
-) {
- const mergedConfig = {
- /* eslint-disable @typescript-eslint/naming-convention */
- // TODO: Remove all apm_oss options by 8.0
- 'apm_oss.transactionIndices': apmOssConfig.transactionIndices,
- 'apm_oss.spanIndices': apmOssConfig.spanIndices,
- 'apm_oss.errorIndices': apmOssConfig.errorIndices,
- 'apm_oss.metricsIndices': apmOssConfig.metricsIndices,
- 'apm_oss.sourcemapIndices': apmOssConfig.sourcemapIndices,
- 'apm_oss.onboardingIndices': apmOssConfig.onboardingIndices,
- /* eslint-enable @typescript-eslint/naming-convention */
- 'xpack.apm.serviceMapEnabled': apmConfig.serviceMapEnabled,
- 'xpack.apm.serviceMapFingerprintBucketSize':
- apmConfig.serviceMapFingerprintBucketSize,
- 'xpack.apm.serviceMapTraceIdBucketSize':
- apmConfig.serviceMapTraceIdBucketSize,
- 'xpack.apm.serviceMapFingerprintGlobalBucketSize':
- apmConfig.serviceMapFingerprintGlobalBucketSize,
- 'xpack.apm.serviceMapTraceIdGlobalBucketSize':
- apmConfig.serviceMapTraceIdGlobalBucketSize,
- 'xpack.apm.serviceMapMaxTracesPerRequest':
- apmConfig.serviceMapMaxTracesPerRequest,
- 'xpack.apm.ui.enabled': apmConfig.ui.enabled,
- 'xpack.apm.ui.maxTraceItems': apmConfig.ui.maxTraceItems,
- 'xpack.apm.ui.transactionGroupBucketSize':
- apmConfig.ui.transactionGroupBucketSize,
- 'xpack.apm.autocreateApmIndexPattern': apmConfig.autocreateApmIndexPattern,
- 'xpack.apm.telemetryCollectionEnabled':
- apmConfig.telemetryCollectionEnabled,
- 'xpack.apm.searchAggregatedTransactions':
- apmConfig.searchAggregatedTransactions,
- 'xpack.apm.metricsInterval': apmConfig.metricsInterval,
- 'xpack.apm.agent.migrations.enabled': apmConfig.agent.migrations.enabled,
- };
-
- // Add data stream indices to list of configured values
- mergedConfig[
- 'apm_oss.transactionIndices'
- ] = `traces-apm*,${mergedConfig['apm_oss.transactionIndices']}`;
-
- mergedConfig[
- 'apm_oss.spanIndices'
- ] = `traces-apm*,${mergedConfig['apm_oss.spanIndices']}`;
-
- mergedConfig[
- 'apm_oss.errorIndices'
- ] = `logs-apm*,${mergedConfig['apm_oss.errorIndices']}`;
-
- mergedConfig[
- 'apm_oss.metricsIndices'
- ] = `metrics-apm*,${mergedConfig['apm_oss.metricsIndices']}`;
-
- return mergedConfig;
-}
+export type APMConfig = TypeOf;
+export type ApmIndicesConfigName = keyof APMConfig['indices'];
export const plugin = (initContext: PluginInitializerContext) =>
new APMPlugin(initContext);
diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts
index d1026b0b6ca8b..7fe2adcfe24d7 100644
--- a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts
+++ b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts
@@ -99,7 +99,7 @@ export function registerErrorCountAlertType({
});
const searchParams = {
- index: indices['apm_oss.errorIndices'],
+ index: indices.error,
size: 0,
body: {
query: {
diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts
index b383b4777eca4..df4de254346c9 100644
--- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts
+++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts
@@ -115,12 +115,12 @@ export function registerTransactionDurationAlertType({
// to prevent (likely) unnecessary blocking request
// in rule execution
const searchAggregatedTransactions =
- config['xpack.apm.searchAggregatedTransactions'] !==
+ config.searchAggregatedTransactions !==
SearchAggregatedTransactionSetting.never;
const index = searchAggregatedTransactions
- ? indices['apm_oss.metricsIndices']
- : indices['apm_oss.transactionIndices'];
+ ? indices.metric
+ : indices.transaction;
const field = getTransactionDurationFieldForAggregatedTransactions(
searchAggregatedTransactions
diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts
index 6c59bcc4107b0..598487d02625a 100644
--- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts
+++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts
@@ -110,12 +110,12 @@ export function registerTransactionErrorRateAlertType({
// to prevent (likely) unnecessary blocking request
// in rule execution
const searchAggregatedTransactions =
- config['xpack.apm.searchAggregatedTransactions'] !==
+ config.searchAggregatedTransactions !==
SearchAggregatedTransactionSetting.never;
const index = searchAggregatedTransactions
- ? indices['apm_oss.metricsIndices']
- : indices['apm_oss.transactionIndices'];
+ ? indices.metric
+ : indices.transaction;
const searchParams = {
index,
diff --git a/x-pack/plugins/apm/server/lib/alerts/test_utils/index.ts b/x-pack/plugins/apm/server/lib/alerts/test_utils/index.ts
index 5d5865bdd2289..22649a7010461 100644
--- a/x-pack/plugins/apm/server/lib/alerts/test_utils/index.ts
+++ b/x-pack/plugins/apm/server/lib/alerts/test_utils/index.ts
@@ -17,10 +17,10 @@ export const createRuleTypeMocks = () => {
let alertExecutor: (...args: any[]) => Promise;
const mockedConfig$ = of({
- /* eslint-disable @typescript-eslint/naming-convention */
- 'apm_oss.errorIndices': 'apm-*',
- 'apm_oss.transactionIndices': 'apm-*',
- /* eslint-enable @typescript-eslint/naming-convention */
+ indices: {
+ error: 'apm-*',
+ transaction: 'apm-*',
+ },
} as APMConfig);
const loggerMock = {
diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts
index 324202b207237..10758b6d90cdc 100644
--- a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts
+++ b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts
@@ -50,7 +50,7 @@ export async function createAnomalyDetectionJobs(
`Creating ML anomaly detection jobs for environments: [${uniqueMlJobEnvs}].`
);
- const indexPatternName = indices['apm_oss.metricsIndices'];
+ const indexPatternName = indices.metric;
const responses = await Promise.all(
uniqueMlJobEnvs.map((environment) =>
createAnomalyDetectionJob({ ml, environment, indexPatternName })
diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts
index 4bfac442b4a3c..1e697ebdcae06 100644
--- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts
+++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts
@@ -14,12 +14,10 @@ import {
describe('data telemetry collection tasks', () => {
const indices = {
- /* eslint-disable @typescript-eslint/naming-convention */
- 'apm_oss.errorIndices': 'apm-8.0.0-error',
- 'apm_oss.metricsIndices': 'apm-8.0.0-metric',
- 'apm_oss.spanIndices': 'apm-8.0.0-span',
- 'apm_oss.transactionIndices': 'apm-8.0.0-transaction',
- /* eslint-enable @typescript-eslint/naming-convention */
+ error: 'apm-8.0.0-error',
+ metric: 'apm-8.0.0-metric',
+ span: 'apm-8.0.0-span',
+ transaction: 'apm-8.0.0-transaction',
} as ApmIndicesConfig;
describe('environments', () => {
diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts
index d624c8527df86..8764223ad1ebb 100644
--- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts
+++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts
@@ -78,7 +78,7 @@ export const tasks: TelemetryTask[] = [
};
const params = {
- index: [indices['apm_oss.transactionIndices']],
+ index: [indices.transaction],
body: {
size: 0,
timeout,
@@ -138,7 +138,7 @@ export const tasks: TelemetryTask[] = [
// fixed date range for reliable results
const lastTransaction = (
await search({
- index: indices['apm_oss.transactionIndices'],
+ index: indices.transaction,
body: {
query: {
bool: {
@@ -253,10 +253,10 @@ export const tasks: TelemetryTask[] = [
const response = await search({
index: [
- indices['apm_oss.errorIndices'],
- indices['apm_oss.metricsIndices'],
- indices['apm_oss.spanIndices'],
- indices['apm_oss.transactionIndices'],
+ indices.error,
+ indices.metric,
+ indices.span,
+ indices.transaction,
],
body: {
size: 0,
@@ -310,10 +310,10 @@ export const tasks: TelemetryTask[] = [
const response = await search({
index: [
- indices['apm_oss.errorIndices'],
- indices['apm_oss.metricsIndices'],
- indices['apm_oss.spanIndices'],
- indices['apm_oss.transactionIndices'],
+ indices.error,
+ indices.metric,
+ indices.span,
+ indices.transaction,
],
body: {
size: 0,
@@ -345,7 +345,7 @@ export const tasks: TelemetryTask[] = [
name: 'environments',
executor: async ({ indices, search }) => {
const response = await search({
- index: [indices['apm_oss.transactionIndices']],
+ index: [indices.transaction],
body: {
query: {
bool: {
@@ -426,12 +426,12 @@ export const tasks: TelemetryTask[] = [
name: 'processor_events',
executor: async ({ indices, search }) => {
const indicesByProcessorEvent = {
- error: indices['apm_oss.errorIndices'],
- metric: indices['apm_oss.metricsIndices'],
- span: indices['apm_oss.spanIndices'],
- transaction: indices['apm_oss.transactionIndices'],
- onboarding: indices['apm_oss.onboardingIndices'],
- sourcemap: indices['apm_oss.sourcemapIndices'],
+ error: indices.error,
+ metric: indices.metric,
+ span: indices.span,
+ transaction: indices.transaction,
+ onboarding: indices.onboarding,
+ sourcemap: indices.sourcemap,
};
type ProcessorEvent = keyof typeof indicesByProcessorEvent;
@@ -549,10 +549,10 @@ export const tasks: TelemetryTask[] = [
return prevJob.then(async (data) => {
const response = await search({
index: [
- indices['apm_oss.errorIndices'],
- indices['apm_oss.spanIndices'],
- indices['apm_oss.metricsIndices'],
- indices['apm_oss.transactionIndices'],
+ indices.error,
+ indices.span,
+ indices.metric,
+ indices.transaction,
],
body: {
size: 0,
@@ -598,11 +598,7 @@ export const tasks: TelemetryTask[] = [
name: 'versions',
executor: async ({ search, indices }) => {
const response = await search({
- index: [
- indices['apm_oss.transactionIndices'],
- indices['apm_oss.spanIndices'],
- indices['apm_oss.errorIndices'],
- ],
+ index: [indices.transaction, indices.span, indices.error],
terminateAfter: 1,
body: {
query: {
@@ -647,7 +643,7 @@ export const tasks: TelemetryTask[] = [
executor: async ({ search, indices }) => {
const errorGroupsCount = (
await search({
- index: indices['apm_oss.errorIndices'],
+ index: indices.error,
body: {
size: 0,
timeout,
@@ -683,7 +679,7 @@ export const tasks: TelemetryTask[] = [
const transactionGroupsCount = (
await search({
- index: indices['apm_oss.transactionIndices'],
+ index: indices.transaction,
body: {
size: 0,
timeout,
@@ -719,7 +715,7 @@ export const tasks: TelemetryTask[] = [
const tracesPerDayCount = (
await search({
- index: indices['apm_oss.transactionIndices'],
+ index: indices.transaction,
body: {
query: {
bool: {
@@ -741,11 +737,7 @@ export const tasks: TelemetryTask[] = [
const servicesCount = (
await search({
- index: [
- indices['apm_oss.transactionIndices'],
- indices['apm_oss.errorIndices'],
- indices['apm_oss.metricsIndices'],
- ],
+ index: [indices.transaction, indices.error, indices.metric],
body: {
size: 0,
timeout,
@@ -811,11 +803,7 @@ export const tasks: TelemetryTask[] = [
const data = await prevJob;
const response = await search({
- index: [
- indices['apm_oss.errorIndices'],
- indices['apm_oss.metricsIndices'],
- indices['apm_oss.transactionIndices'],
- ],
+ index: [indices.error, indices.metric, indices.transaction],
body: {
size: 0,
timeout,
@@ -1006,12 +994,12 @@ export const tasks: TelemetryTask[] = [
const response = await indicesStats({
index: [
indices.apmAgentConfigurationIndex,
- indices['apm_oss.errorIndices'],
- indices['apm_oss.metricsIndices'],
- indices['apm_oss.onboardingIndices'],
- indices['apm_oss.sourcemapIndices'],
- indices['apm_oss.spanIndices'],
- indices['apm_oss.transactionIndices'],
+ indices.error,
+ indices.metric,
+ indices.onboarding,
+ indices.sourcemap,
+ indices.span,
+ indices.transaction,
],
});
diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts
index 809869e13de7f..871df10d9bafa 100644
--- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts
+++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.test.ts
@@ -43,14 +43,12 @@ describe('get buckets', () => {
}
) as APMConfig,
indices: {
- /* eslint-disable @typescript-eslint/naming-convention */
- 'apm_oss.sourcemapIndices': 'apm-*',
- 'apm_oss.errorIndices': 'apm-*',
- 'apm_oss.onboardingIndices': 'apm-*',
- 'apm_oss.spanIndices': 'apm-*',
- 'apm_oss.transactionIndices': 'apm-*',
- 'apm_oss.metricsIndices': 'apm-*',
- /* eslint-enable @typescript-eslint/naming-convention */
+ sourcemap: 'apm-*',
+ error: 'apm-*',
+ onboarding: 'apm-*',
+ span: 'apm-*',
+ transaction: 'apm-*',
+ metric: 'apm-*',
apmAgentConfigurationIndex: '.apm-agent-configuration',
apmCustomLinkIndex: '.apm-custom-link',
},
diff --git a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/get_is_using_transaction_events.test.ts b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/get_is_using_transaction_events.test.ts
index f17224384842d..1fac873ced7be 100644
--- a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/get_is_using_transaction_events.test.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/get_is_using_transaction_events.test.ts
@@ -58,8 +58,7 @@ describe('getIsUsingTransactionEvents', () => {
describe('with config xpack.apm.searchAggregatedTransactions: never', () => {
const config = {
- 'xpack.apm.searchAggregatedTransactions':
- SearchAggregatedTransactionSetting.never,
+ searchAggregatedTransactions: SearchAggregatedTransactionSetting.never,
};
it('should be false', async () => {
@@ -81,8 +80,7 @@ describe('getIsUsingTransactionEvents', () => {
describe('with config xpack.apm.searchAggregatedTransactions: always', () => {
const config = {
- 'xpack.apm.searchAggregatedTransactions':
- SearchAggregatedTransactionSetting.always,
+ searchAggregatedTransactions: SearchAggregatedTransactionSetting.always,
};
it('should be false when kuery is empty', async () => {
mock = await inspectSearchParams(
@@ -164,8 +162,7 @@ describe('getIsUsingTransactionEvents', () => {
describe('with config xpack.apm.searchAggregatedTransactions: auto', () => {
const config = {
- 'xpack.apm.searchAggregatedTransactions':
- SearchAggregatedTransactionSetting.auto,
+ searchAggregatedTransactions: SearchAggregatedTransactionSetting.auto,
};
it('should query for data once if metrics data found', async () => {
diff --git a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/get_is_using_transaction_events.ts b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/get_is_using_transaction_events.ts
index 70df0959a63b6..66e9697ab7c91 100644
--- a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/get_is_using_transaction_events.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/get_is_using_transaction_events.ts
@@ -23,8 +23,7 @@ export async function getIsUsingTransactionEvents({
start?: number;
end?: number;
}): Promise {
- const searchAggregatedTransactions =
- config['xpack.apm.searchAggregatedTransactions'];
+ const searchAggregatedTransactions = config.searchAggregatedTransactions;
if (
searchAggregatedTransactions === SearchAggregatedTransactionSetting.never
diff --git a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts
index 478f3218ef38c..a58a95dd43fcc 100644
--- a/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/aggregated_transactions/index.ts
@@ -63,8 +63,7 @@ export async function getSearchAggregatedTransactions({
apmEventClient: APMEventClient;
kuery: string;
}): Promise {
- const searchAggregatedTransactions =
- config['xpack.apm.searchAggregatedTransactions'];
+ const searchAggregatedTransactions = config.searchAggregatedTransactions;
if (
kuery ||
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.test.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.test.ts
index 4983d6d515944..5ef3786e9bde4 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.test.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.test.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-/* eslint-disable @typescript-eslint/naming-convention */
import { APMEventESSearchRequest } from '.';
import { ApmIndicesConfig } from '../../../settings/apm_indices/get_apm_indices';
import { unpackProcessorEvents } from './unpack_processor_events';
@@ -19,12 +18,12 @@ describe('unpackProcessorEvents', () => {
} as APMEventESSearchRequest;
const indices = {
- 'apm_oss.transactionIndices': 'my-apm-*-transaction-*',
- 'apm_oss.metricsIndices': 'my-apm-*-metric-*',
- 'apm_oss.errorIndices': 'my-apm-*-error-*',
- 'apm_oss.spanIndices': 'my-apm-*-span-*',
- 'apm_oss.onboardingIndices': 'my-apm-*-onboarding-',
- 'apm_oss.sourcemapIndices': 'my-apm-*-sourcemap-*',
+ transaction: 'my-apm-*-transaction-*',
+ metric: 'my-apm-*-metric-*',
+ error: 'my-apm-*-error-*',
+ span: 'my-apm-*-span-*',
+ onboarding: 'my-apm-*-onboarding-*',
+ sourcemap: 'my-apm-*-sourcemap-*',
} as ApmIndicesConfig;
res = unpackProcessorEvents(request, indices);
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.ts
index 47a2b3fe7e5c8..582fe0374c5ca 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/unpack_processor_events.ts
@@ -13,19 +13,16 @@ import {
ESFilter,
} from '../../../../../../../../src/core/types/elasticsearch';
import { APMEventESSearchRequest, APMEventESTermsEnumRequest } from '.';
-import {
- ApmIndicesConfig,
- ApmIndicesName,
-} from '../../../settings/apm_indices/get_apm_indices';
+import { ApmIndicesConfig } from '../../../settings/apm_indices/get_apm_indices';
-const processorEventIndexMap: Record = {
- [ProcessorEvent.transaction]: 'apm_oss.transactionIndices',
- [ProcessorEvent.span]: 'apm_oss.spanIndices',
- [ProcessorEvent.metric]: 'apm_oss.metricsIndices',
- [ProcessorEvent.error]: 'apm_oss.errorIndices',
+const processorEventIndexMap = {
+ [ProcessorEvent.transaction]: 'transaction',
+ [ProcessorEvent.span]: 'span',
+ [ProcessorEvent.metric]: 'metric',
+ [ProcessorEvent.error]: 'error',
// TODO: should have its own config setting
- [ProcessorEvent.profile]: 'apm_oss.transactionIndices',
-};
+ [ProcessorEvent.profile]: 'transaction',
+} as const;
export function unpackProcessorEvents(
request: APMEventESSearchRequest | APMEventESTermsEnumRequest,
diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
index 94e88a09ea35c..d5ff97c050d9d 100644
--- a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
@@ -10,19 +10,20 @@ import { APMConfig } from '../..';
import { APMRouteHandlerResources } from '../../routes/typings';
import { ProcessorEvent } from '../../../common/processor_event';
import { PROCESSOR_EVENT } from '../../../common/elasticsearch_fieldnames';
+import { getApmIndices } from '../settings/apm_indices/get_apm_indices';
+import { PromiseReturnType } from '../../../../observability/typings/common';
jest.mock('../settings/apm_indices/get_apm_indices', () => ({
- getApmIndices: async () => ({
- /* eslint-disable @typescript-eslint/naming-convention */
- 'apm_oss.sourcemapIndices': 'apm-*',
- 'apm_oss.errorIndices': 'apm-*',
- 'apm_oss.onboardingIndices': 'apm-*',
- 'apm_oss.spanIndices': 'apm-*',
- 'apm_oss.transactionIndices': 'apm-*',
- 'apm_oss.metricsIndices': 'apm-*',
- /* eslint-enable @typescript-eslint/naming-convention */
- apmAgentConfigurationIndex: 'apm-*',
- }),
+ getApmIndices: async () =>
+ ({
+ sourcemap: 'apm-*',
+ error: 'apm-*',
+ onboarding: 'apm-*',
+ span: 'apm-*',
+ transaction: 'apm-*',
+ metric: 'apm-*',
+ apmAgentConfigurationIndex: 'apm-*',
+ } as PromiseReturnType),
}));
jest.mock('../index_pattern/get_dynamic_index_pattern', () => ({
diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts
index aae707c6e4689..83adab6ae6cbc 100644
--- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts
+++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-/* eslint-disable @typescript-eslint/naming-convention */
import { createStaticIndexPattern } from './create_static_index_pattern';
import { Setup } from '../helpers/setup_request';
import * as HistoricalAgentData from '../../routes/historical_data/has_historical_agent_data';
@@ -25,11 +24,11 @@ function getMockSavedObjectsClient(existingIndexPatternTitle: string) {
const setup = {
indices: {
- 'apm_oss.transactionIndices': 'apm-*-transaction-*',
- 'apm_oss.spanIndices': 'apm-*-span-*',
- 'apm_oss.errorIndices': 'apm-*-error-*',
- 'apm_oss.metricsIndices': 'apm-*-metrics-*',
- },
+ transaction: 'apm-*-transaction-*',
+ span: 'apm-*-span-*',
+ error: 'apm-*-error-*',
+ metric: 'apm-*-metrics-*',
+ } as APMConfig['indices'],
} as unknown as Setup;
describe('createStaticIndexPattern', () => {
@@ -37,7 +36,7 @@ describe('createStaticIndexPattern', () => {
const savedObjectsClient = getMockSavedObjectsClient('apm-*');
await createStaticIndexPattern({
setup,
- config: { 'xpack.apm.autocreateApmIndexPattern': false } as APMConfig,
+ config: { autocreateApmIndexPattern: false } as APMConfig,
savedObjectsClient,
spaceId: 'default',
});
@@ -54,7 +53,7 @@ describe('createStaticIndexPattern', () => {
await createStaticIndexPattern({
setup,
- config: { 'xpack.apm.autocreateApmIndexPattern': true } as APMConfig,
+ config: { autocreateApmIndexPattern: true } as APMConfig,
savedObjectsClient,
spaceId: 'default',
});
@@ -71,7 +70,7 @@ describe('createStaticIndexPattern', () => {
await createStaticIndexPattern({
setup,
- config: { 'xpack.apm.autocreateApmIndexPattern': true } as APMConfig,
+ config: { autocreateApmIndexPattern: true } as APMConfig,
savedObjectsClient,
spaceId: 'default',
});
@@ -91,9 +90,7 @@ describe('createStaticIndexPattern', () => {
await createStaticIndexPattern({
setup,
- config: {
- 'xpack.apm.autocreateApmIndexPattern': true,
- } as APMConfig,
+ config: { autocreateApmIndexPattern: true } as APMConfig,
savedObjectsClient,
spaceId: 'default',
});
@@ -120,9 +117,7 @@ describe('createStaticIndexPattern', () => {
await createStaticIndexPattern({
setup,
- config: {
- 'xpack.apm.autocreateApmIndexPattern': true,
- } as APMConfig,
+ config: { autocreateApmIndexPattern: true } as APMConfig,
savedObjectsClient,
spaceId: 'default',
});
diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts
index 4f35e7e639151..26ae2ac337e88 100644
--- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts
+++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts
@@ -34,7 +34,7 @@ export async function createStaticIndexPattern({
}): Promise {
return withApmSpan('create_static_index_pattern', async () => {
// don't autocreate APM index pattern if it's been disabled via the config
- if (!config['xpack.apm.autocreateApmIndexPattern']) {
+ if (!config.autocreateApmIndexPattern) {
return false;
}
diff --git a/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.test.ts b/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.test.ts
index 8103630157584..8b7444ffdf6fa 100644
--- a/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.test.ts
+++ b/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.test.ts
@@ -5,18 +5,16 @@
* 2.0.
*/
-/* eslint-disable @typescript-eslint/naming-convention */
-
import { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices';
import { getApmIndexPatternTitle } from './get_apm_index_pattern_title';
describe('getApmIndexPatternTitle', () => {
it('returns an index pattern title by combining existing indicies', () => {
const title = getApmIndexPatternTitle({
- 'apm_oss.transactionIndices': 'apm-*-transaction-*',
- 'apm_oss.spanIndices': 'apm-*-span-*',
- 'apm_oss.errorIndices': 'apm-*-error-*',
- 'apm_oss.metricsIndices': 'apm-*-metrics-*',
+ transaction: 'apm-*-transaction-*',
+ span: 'apm-*-span-*',
+ error: 'apm-*-error-*',
+ metric: 'apm-*-metrics-*',
} as ApmIndicesConfig);
expect(title).toBe(
'apm-*-transaction-*,apm-*-span-*,apm-*-error-*,apm-*-metrics-*'
@@ -25,10 +23,10 @@ describe('getApmIndexPatternTitle', () => {
it('removes duplicates', () => {
const title = getApmIndexPatternTitle({
- 'apm_oss.transactionIndices': 'apm-*',
- 'apm_oss.spanIndices': 'apm-*',
- 'apm_oss.errorIndices': 'apm-*',
- 'apm_oss.metricsIndices': 'apm-*',
+ transaction: 'apm-*',
+ span: 'apm-*',
+ error: 'apm-*',
+ metric: 'apm-*',
} as ApmIndicesConfig);
expect(title).toBe('apm-*');
});
diff --git a/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.ts b/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.ts
index e65f200130e9a..5e055ff1c2fdc 100644
--- a/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.ts
+++ b/x-pack/plugins/apm/server/lib/index_pattern/get_apm_index_pattern_title.ts
@@ -10,9 +10,9 @@ import { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices';
export function getApmIndexPatternTitle(apmIndicesConfig: ApmIndicesConfig) {
return uniq([
- apmIndicesConfig['apm_oss.transactionIndices'],
- apmIndicesConfig['apm_oss.spanIndices'],
- apmIndicesConfig['apm_oss.errorIndices'],
- apmIndicesConfig['apm_oss.metricsIndices'],
+ apmIndicesConfig.transaction,
+ apmIndicesConfig.span,
+ apmIndicesConfig.error,
+ apmIndicesConfig.metric,
]).join(',');
}
diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts
index ba35836452122..06138931c004e 100644
--- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts
+++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts
@@ -85,7 +85,7 @@ export async function fetchAndTransformGcMetrics({
date_histogram: getMetricsDateHistogramParams({
start,
end,
- metricsInterval: config['xpack.apm.metricsInterval'],
+ metricsInterval: config.metricsInterval,
}),
aggs: {
// get the max value
diff --git a/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts
index a3fce0368f4a5..581a0782e4d72 100644
--- a/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts
+++ b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts
@@ -99,7 +99,7 @@ export async function fetchAndTransformMetrics({
date_histogram: getMetricsDateHistogramParams({
start,
end,
- metricsInterval: config['xpack.apm.metricsInterval'],
+ metricsInterval: config.metricsInterval,
}),
aggs,
},
diff --git a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts
index 9409e94fa9ba9..ba35ac5c5c89c 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/has_rum_data.ts
@@ -56,7 +56,7 @@ export async function hasRumData({
const response = await apmEventClient.search('has_rum_data', params);
return {
- indices: setup.indices['apm_oss.transactionIndices']!,
+ indices: setup.indices.transaction,
hasData: response.hits.total.value > 0,
serviceName:
response.aggregations?.services?.mostTraffic?.buckets?.[0]?.key,
@@ -65,7 +65,7 @@ export async function hasRumData({
return {
hasData: false,
serviceName: undefined,
- indices: setup.indices['apm_oss.transactionIndices']!,
+ indices: setup.indices.transaction,
};
}
}
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service.ts b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service.ts
index 02ba0a8514b62..239cf39f15ffe 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service.ts
@@ -65,7 +65,7 @@ export const failedTransactionsCorrelationsSearchServiceProvider: FailedTransact
const params: FailedTransactionsCorrelationsRequestParams &
SearchStrategyServerParams = {
...searchServiceParams,
- index: indices['apm_oss.transactionIndices'],
+ index: indices.transaction,
includeFrozen,
};
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts
index 7e420c821a746..91f4a0d3349a4 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts
@@ -67,7 +67,7 @@ export const latencyCorrelationsSearchServiceProvider: LatencyCorrelationsSearch
const indices = await getApmIndices();
params = {
...searchServiceParams,
- index: indices['apm_oss.transactionIndices'],
+ index: indices.transaction,
includeFrozen,
};
diff --git a/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.test.ts
index 6e03c879f9b97..8a9d04df32036 100644
--- a/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.test.ts
+++ b/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.test.ts
@@ -90,10 +90,7 @@ const clientSearchMock = (
};
const getApmIndicesMock = async () =>
- ({
- // eslint-disable-next-line @typescript-eslint/naming-convention
- 'apm_oss.transactionIndices': 'apm-*',
- } as ApmIndicesConfig);
+ ({ transaction: 'apm-*' } as ApmIndicesConfig);
describe('APM Correlations search strategy', () => {
describe('strategy interface', () => {
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts
index 2497a85c0c774..ae511d0fed8f8 100644
--- a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts
@@ -53,10 +53,7 @@ async function getConnectionData({
end,
});
- const chunks = chunk(
- traceIds,
- setup.config['xpack.apm.serviceMapMaxTracesPerRequest']
- );
+ const chunks = chunk(traceIds, setup.config.serviceMapMaxTracesPerRequest);
const init = {
connections: [],
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts
index 2129606e69fc3..afb88189a5fd2 100644
--- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts
@@ -70,9 +70,7 @@ describe('getServiceMapServiceNodeInfo', () => {
indices: {},
start: 1593460053026000,
end: 1593497863217000,
- config: {
- 'xpack.apm.metricsInterval': 30,
- },
+ config: { metricsInterval: 30 },
uiFilters: { environment: 'test environment' },
} as unknown as Setup;
const serviceName = 'test service name';
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts
index c1c11f7bf639a..7e16e69498e7c 100644
--- a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts
@@ -60,13 +60,11 @@ export async function getTraceSampleIds({
query.bool.filter.push(...environmentQuery(environment));
const fingerprintBucketSize = serviceName
- ? config['xpack.apm.serviceMapFingerprintBucketSize']
- : config['xpack.apm.serviceMapFingerprintGlobalBucketSize'];
-
+ ? config.serviceMapFingerprintBucketSize
+ : config.serviceMapFingerprintGlobalBucketSize;
const traceIdBucketSize = serviceName
- ? config['xpack.apm.serviceMapTraceIdBucketSize']
- : config['xpack.apm.serviceMapTraceIdGlobalBucketSize'];
-
+ ? config.serviceMapTraceIdBucketSize
+ : config.serviceMapTraceIdGlobalBucketSize;
const samplerShardSize = traceIdBucketSize * 10;
const params = {
@@ -137,8 +135,7 @@ export async function getTraceSampleIds({
'get_trace_sample_ids',
params
);
- // make sure at least one trace per composite/connection bucket
- // is queried
+ // make sure at least one trace per composite/connection bucket is queried
const traceIdsWithPriority =
tracesSampleResponse.aggregations?.connections.buckets.flatMap((bucket) =>
bucket.sample.trace_ids.buckets.map((sampleDocBucket, index) => ({
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts
index a51f4c4e0fb7d..089282d6f1c34 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts
@@ -17,6 +17,7 @@ import { Coordinate } from '../../../../typings/timeseries';
import { kqlQuery, rangeQuery } from '../../../../../observability/server';
import { environmentQuery } from '../../../../common/utils/environment_query';
import {
+ getDocumentTypeFilterForAggregatedTransactions,
getProcessorEventForAggregatedTransactions,
getTransactionDurationFieldForAggregatedTransactions,
} from '../../helpers/aggregated_transactions';
@@ -111,6 +112,9 @@ export async function getServiceInstancesTransactionStatistics<
...rangeQuery(start, end),
...environmentQuery(environment),
...kqlQuery(kuery),
+ ...getDocumentTypeFilterForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
...(isComparisonSearch && serviceNodeIds
? [{ terms: { [SERVICE_NODE_NAME]: serviceNodeIds } }]
: []),
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts
index d5a2006060395..fbc1e2880495b 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups.ts
@@ -57,7 +57,7 @@ export async function getServiceTransactionGroups({
end: number;
}) {
const { apmEventClient, config } = setup;
- const bucketSize = config['xpack.apm.ui.transactionGroupBucketSize'];
+ const bucketSize = config.ui.transactionGroupBucketSize;
const field = getTransactionDurationFieldForAggregatedTransactions(
searchAggregatedTransactions
diff --git a/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts b/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts
index 0ade96682b362..107493af1a0c0 100644
--- a/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts
+++ b/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts
@@ -5,8 +5,6 @@
* 2.0.
*/
-import { merge } from 'lodash';
-
import { SavedObjectsClient } from 'src/core/server';
import { PromiseReturnType } from '../../../../../observability/typings/common';
import {
@@ -22,8 +20,6 @@ export { ApmIndicesConfig };
type ISavedObjectsClient = Pick;
-export type ApmIndicesName = keyof ApmIndicesConfig;
-
async function getApmIndicesSavedObject(
savedObjectsClient: ISavedObjectsClient
) {
@@ -38,14 +34,12 @@ async function getApmIndicesSavedObject(
export function getApmIndicesConfig(config: APMConfig): ApmIndicesConfig {
return {
- /* eslint-disable @typescript-eslint/naming-convention */
- 'apm_oss.sourcemapIndices': config['apm_oss.sourcemapIndices'],
- 'apm_oss.errorIndices': config['apm_oss.errorIndices'],
- 'apm_oss.onboardingIndices': config['apm_oss.onboardingIndices'],
- 'apm_oss.spanIndices': config['apm_oss.spanIndices'],
- 'apm_oss.transactionIndices': config['apm_oss.transactionIndices'],
- 'apm_oss.metricsIndices': config['apm_oss.metricsIndices'],
- /* eslint-enable @typescript-eslint/naming-convention */
+ sourcemap: config.indices.sourcemap,
+ error: config.indices.error,
+ onboarding: config.indices.onboarding,
+ span: config.indices.span,
+ transaction: config.indices.transaction,
+ metric: config.indices.metric,
// system indices, not configurable
apmAgentConfigurationIndex: '.apm-agent-configuration',
apmCustomLinkIndex: '.apm-custom-link',
@@ -64,21 +58,12 @@ export async function getApmIndices({
savedObjectsClient
);
const apmIndicesConfig = getApmIndicesConfig(config);
- return merge({}, apmIndicesConfig, apmIndicesSavedObject);
+ return { ...apmIndicesConfig, ...apmIndicesSavedObject };
} catch (error) {
return getApmIndicesConfig(config);
}
}
-const APM_UI_INDICES: ApmIndicesName[] = [
- 'apm_oss.sourcemapIndices',
- 'apm_oss.errorIndices',
- 'apm_oss.onboardingIndices',
- 'apm_oss.spanIndices',
- 'apm_oss.transactionIndices',
- 'apm_oss.metricsIndices',
-];
-
export async function getApmIndexSettings({
context,
config,
@@ -88,7 +73,7 @@ export async function getApmIndexSettings({
apmIndicesSavedObject = await getApmIndicesSavedObject(
context.core.savedObjects.client
);
- } catch (error) {
+ } catch (error: any) {
if (error.output && error.output.statusCode === 404) {
apmIndicesSavedObject = {};
} else {
@@ -97,7 +82,11 @@ export async function getApmIndexSettings({
}
const apmIndicesConfig = getApmIndicesConfig(config);
- return APM_UI_INDICES.map((configurationName) => ({
+ const apmIndices = Object.keys(config.indices) as Array<
+ keyof typeof config.indices
+ >;
+
+ return apmIndices.map((configurationName) => ({
configurationName,
defaultValue: apmIndicesConfig[configurationName], // value defined in kibana[.dev].yml
savedValue: apmIndicesSavedObject[configurationName], // value saved via Saved Objects service
diff --git a/x-pack/plugins/apm/server/lib/traces/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/traces/__snapshots__/queries.test.ts.snap
index 53318fe5fe594..ea8f39f9d9b6d 100644
--- a/x-pack/plugins/apm/server/lib/traces/__snapshots__/queries.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/traces/__snapshots__/queries.test.ts.snap
@@ -37,7 +37,7 @@ Object {
},
},
},
- "size": "myIndex",
+ "size": 1000,
},
}
`;
diff --git a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts
index e940100edcf52..60a28fd9abdbd 100644
--- a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts
+++ b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts
@@ -24,7 +24,7 @@ export async function getTraceItems(
end: number
) {
const { apmEventClient, config } = setup;
- const maxTraceItems = config['xpack.apm.ui.maxTraceItems'];
+ const maxTraceItems = config.ui.maxTraceItems;
const excludedLogLevels = ['debug', 'info', 'warning'];
const errorResponsePromise = apmEventClient.search('get_errors_docs', {
@@ -80,9 +80,5 @@ export async function getTraceItems(
const traceDocs = traceResponse.hits.hits.map((hit) => hit._source);
const errorDocs = errorResponse.hits.hits.map((hit) => hit._source);
- return {
- exceedsMax,
- traceDocs,
- errorDocs,
- };
+ return { exceedsMax, traceDocs, errorDocs };
}
diff --git a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.test.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.test.ts
index 6e9d0aad96b71..76cabd3e3af93 100644
--- a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.test.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.test.ts
@@ -11,16 +11,15 @@ import noDataResponse from './mock_responses/no_data.json';
import dataResponse from './mock_responses/data.json';
import { APMConfig } from '../../..';
import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
-
-const mockIndices = {
- /* eslint-disable @typescript-eslint/naming-convention */
- 'apm_oss.sourcemapIndices': 'myIndex',
- 'apm_oss.errorIndices': 'myIndex',
- 'apm_oss.onboardingIndices': 'myIndex',
- 'apm_oss.spanIndices': 'myIndex',
- 'apm_oss.transactionIndices': 'myIndex',
- 'apm_oss.metricsIndices': 'myIndex',
- /* eslint-enable @typescript-eslint/naming-convention */
+import { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices';
+
+const mockIndices: ApmIndicesConfig = {
+ sourcemap: 'myIndex',
+ error: 'myIndex',
+ onboarding: 'myIndex',
+ span: 'myIndex',
+ transaction: 'myIndex',
+ metric: 'myIndex',
apmAgentConfigurationIndex: 'myIndex',
apmCustomLinkIndex: 'myIndex',
};
diff --git a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts
index 62277dba8ac29..a5c11776c70b0 100644
--- a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts
@@ -124,7 +124,7 @@ export async function getTransactionBreakdown({
date_histogram: getMetricsDateHistogramParams({
start,
end,
- metricsInterval: config['xpack.apm.metricsInterval'],
+ metricsInterval: config.metricsInterval,
}),
aggs: subAggs,
},
diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts
index 2296227de2a33..d2d8dbf602364 100644
--- a/x-pack/plugins/apm/server/plugin.ts
+++ b/x-pack/plugins/apm/server/plugin.ts
@@ -5,8 +5,7 @@
* 2.0.
*/
-import { combineLatest } from 'rxjs';
-import { map, take } from 'rxjs/operators';
+import { take } from 'rxjs/operators';
import {
CoreSetup,
CoreStart,
@@ -19,8 +18,7 @@ import { isEmpty, mapValues } from 'lodash';
import { SavedObjectsClient } from '../../../../src/core/server';
import { mappingFromFieldMap } from '../../rule_registry/common/mapping_from_field_map';
import { Dataset } from '../../rule_registry/server';
-import { APMConfig, APMXPackConfig, APM_SERVER_FEATURE_ID } from '.';
-import { mergeConfigs } from './index';
+import { APMConfig, APM_SERVER_FEATURE_ID } from '.';
import { UI_SETTINGS } from '../../../../src/plugins/data/common';
import { APM_FEATURE, registerFeaturesUsage } from './feature';
import { registerApmAlerts } from './lib/alerts/register_apm_alerts';
@@ -73,29 +71,23 @@ export class APMPlugin
plugins: Omit
) {
this.logger = this.initContext.logger.get();
- const config$ = this.initContext.config.create();
- const mergedConfig$ = combineLatest(plugins.apmOss.config$, config$).pipe(
- map(([apmOssConfig, apmConfig]) => mergeConfigs(apmOssConfig, apmConfig))
- );
+ const config$ = this.initContext.config.create();
core.savedObjects.registerType(apmIndices);
core.savedObjects.registerType(apmTelemetry);
core.savedObjects.registerType(apmServerSettings);
- const currentConfig = mergeConfigs(
- plugins.apmOss.config,
- this.initContext.config.get()
- );
+ const currentConfig = this.initContext.config.get();
this.currentConfig = currentConfig;
if (
plugins.taskManager &&
plugins.usageCollection &&
- currentConfig['xpack.apm.telemetryCollectionEnabled']
+ currentConfig.telemetryCollectionEnabled
) {
createApmTelemetry({
core,
- config$: mergedConfig$,
+ config$,
usageCollector: plugins.usageCollection,
taskManager: plugins.taskManager,
logger: this.logger,
@@ -156,7 +148,7 @@ export class APMPlugin
const boundGetApmIndices = async () =>
getApmIndices({
savedObjectsClient: await getInternalSavedObjectsClient(core),
- config: await mergedConfig$.pipe(take(1)).toPromise(),
+ config: await config$.pipe(take(1)).toPromise(),
});
boundGetApmIndices().then((indices) => {
@@ -193,7 +185,7 @@ export class APMPlugin
ruleDataClient,
alerting: plugins.alerting,
ml: plugins.ml,
- config$: mergedConfig$,
+ config$,
logger: this.logger!.get('rule'),
});
}
@@ -231,7 +223,7 @@ export class APMPlugin
});
return {
- config$: mergedConfig$,
+ config$,
getApmIndices: boundGetApmIndices,
createApmEventClient: async ({
request,
diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts
index d8097228df0dc..2884c08ceb9a1 100644
--- a/x-pack/plugins/apm/server/routes/fleet.ts
+++ b/x-pack/plugins/apm/server/routes/fleet.ts
@@ -129,8 +129,7 @@ const getMigrationCheckRoute = createApmServerRoute({
options: { tags: ['access:apm'] },
handler: async (resources) => {
const { plugins, context, config, request } = resources;
- const cloudApmMigrationEnabled =
- config['xpack.apm.agent.migrations.enabled'];
+ const cloudApmMigrationEnabled = config.agent.migrations.enabled;
if (!plugins.fleet || !plugins.security) {
throw Boom.internal(FLEET_SECURITY_REQUIRED_MESSAGE);
}
@@ -158,8 +157,7 @@ const createCloudApmPackagePolicyRoute = createApmServerRoute({
options: { tags: ['access:apm', 'access:apm_write'] },
handler: async (resources) => {
const { plugins, context, config, request, logger } = resources;
- const cloudApmMigrationEnabled =
- config['xpack.apm.agent.migrations.enabled'];
+ const cloudApmMigrationEnabled = config.agent.migrations.enabled;
if (!plugins.fleet || !plugins.security) {
throw Boom.internal(FLEET_SECURITY_REQUIRED_MESSAGE);
}
diff --git a/x-pack/plugins/apm/server/routes/service_map.ts b/x-pack/plugins/apm/server/routes/service_map.ts
index f9062ac13e049..17fb9d7c98c5f 100644
--- a/x-pack/plugins/apm/server/routes/service_map.ts
+++ b/x-pack/plugins/apm/server/routes/service_map.ts
@@ -33,7 +33,7 @@ const serviceMapRoute = createApmServerRoute({
options: { tags: ['access:apm'] },
handler: async (resources) => {
const { config, context, params, logger } = resources;
- if (!config['xpack.apm.serviceMapEnabled']) {
+ if (!config.serviceMapEnabled) {
throw Boom.notFound();
}
if (!isActivePlatinumLicense(context.licensing.license)) {
@@ -81,7 +81,7 @@ const serviceMapServiceNodeRoute = createApmServerRoute({
handler: async (resources) => {
const { config, context, params } = resources;
- if (!config['xpack.apm.serviceMapEnabled']) {
+ if (!config.serviceMapEnabled) {
throw Boom.notFound();
}
if (!isActivePlatinumLicense(context.licensing.license)) {
@@ -125,7 +125,7 @@ const serviceMapBackendNodeRoute = createApmServerRoute({
handler: async (resources) => {
const { config, context, params } = resources;
- if (!config['xpack.apm.serviceMapEnabled']) {
+ if (!config.serviceMapEnabled) {
throw Boom.notFound();
}
if (!isActivePlatinumLicense(context.licensing.license)) {
diff --git a/x-pack/plugins/apm/server/routes/settings/apm_indices.ts b/x-pack/plugins/apm/server/routes/settings/apm_indices.ts
index 1cba5f972c27e..156f4d1af0bb2 100644
--- a/x-pack/plugins/apm/server/routes/settings/apm_indices.ts
+++ b/x-pack/plugins/apm/server/routes/settings/apm_indices.ts
@@ -13,6 +13,7 @@ import {
getApmIndexSettings,
} from '../../lib/settings/apm_indices/get_apm_indices';
import { saveApmIndices } from '../../lib/settings/apm_indices/save_apm_indices';
+import { APMConfig } from '../..';
// get list of apm indices and values
const apmIndexSettingsRoute = createApmServerRoute({
@@ -37,6 +38,10 @@ const apmIndicesRoute = createApmServerRoute({
},
});
+type SaveApmIndicesBodySchema = {
+ [Property in keyof APMConfig['indices']]: t.StringC;
+};
+
// save ui indices
const saveApmIndicesRoute = createApmServerRoute({
endpoint: 'POST /internal/apm/settings/apm-indices/save',
@@ -45,15 +50,13 @@ const saveApmIndicesRoute = createApmServerRoute({
},
params: t.type({
body: t.partial({
- /* eslint-disable @typescript-eslint/naming-convention */
- 'apm_oss.sourcemapIndices': t.string,
- 'apm_oss.errorIndices': t.string,
- 'apm_oss.onboardingIndices': t.string,
- 'apm_oss.spanIndices': t.string,
- 'apm_oss.transactionIndices': t.string,
- 'apm_oss.metricsIndices': t.string,
- /* eslint-enable @typescript-eslint/naming-convention */
- }),
+ sourcemap: t.string,
+ error: t.string,
+ onboarding: t.string,
+ span: t.string,
+ transaction: t.string,
+ metric: t.string,
+ } as SaveApmIndicesBodySchema),
}),
handler: async (resources) => {
const { params, context } = resources;
diff --git a/x-pack/plugins/apm/server/saved_objects/apm_indices.ts b/x-pack/plugins/apm/server/saved_objects/apm_indices.ts
index df5267023ae89..4aa6c4953056a 100644
--- a/x-pack/plugins/apm/server/saved_objects/apm_indices.ts
+++ b/x-pack/plugins/apm/server/saved_objects/apm_indices.ts
@@ -7,34 +7,24 @@
import { SavedObjectsType } from 'src/core/server';
import { i18n } from '@kbn/i18n';
+import { updateApmOssIndexPaths } from './migrations/update_apm_oss_index_paths';
+import { ApmIndicesConfigName } from '..';
+
+const properties: { [Property in ApmIndicesConfigName]: { type: 'keyword' } } =
+ {
+ sourcemap: { type: 'keyword' },
+ error: { type: 'keyword' },
+ onboarding: { type: 'keyword' },
+ span: { type: 'keyword' },
+ transaction: { type: 'keyword' },
+ metric: { type: 'keyword' },
+ };
export const apmIndices: SavedObjectsType = {
name: 'apm-indices',
hidden: false,
namespaceType: 'agnostic',
- mappings: {
- properties: {
- /* eslint-disable @typescript-eslint/naming-convention */
- 'apm_oss.sourcemapIndices': {
- type: 'keyword',
- },
- 'apm_oss.errorIndices': {
- type: 'keyword',
- },
- 'apm_oss.onboardingIndices': {
- type: 'keyword',
- },
- 'apm_oss.spanIndices': {
- type: 'keyword',
- },
- 'apm_oss.transactionIndices': {
- type: 'keyword',
- },
- 'apm_oss.metricsIndices': {
- type: 'keyword',
- },
- },
- },
+ mappings: { properties },
management: {
importableAndExportable: true,
icon: 'apmApp',
@@ -43,4 +33,10 @@ export const apmIndices: SavedObjectsType = {
defaultMessage: 'APM Settings - Index',
}),
},
+ migrations: {
+ '7.16.0': (doc) => {
+ const attributes = updateApmOssIndexPaths(doc.attributes);
+ return { ...doc, attributes };
+ },
+ },
};
diff --git a/x-pack/plugins/apm/server/saved_objects/migrations/update_apm_oss_index_paths.ts b/x-pack/plugins/apm/server/saved_objects/migrations/update_apm_oss_index_paths.ts
new file mode 100644
index 0000000000000..72ba40db0ce05
--- /dev/null
+++ b/x-pack/plugins/apm/server/saved_objects/migrations/update_apm_oss_index_paths.ts
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+const apmIndexConfigs = [
+ ['sourcemap', 'apm_oss.sourcemapIndices'],
+ ['error', 'apm_oss.errorIndices'],
+ ['onboarding', 'apm_oss.onboardingIndices'],
+ ['span', 'apm_oss.spanIndices'],
+ ['transaction', 'apm_oss.transactionIndices'],
+ ['metric', 'apm_oss.metricsIndices'],
+] as const;
+
+type ApmIndexConfigs = typeof apmIndexConfigs[number][0];
+type ApmIndicesSavedObjectAttributes = Partial<{
+ [Property in ApmIndexConfigs]: string;
+}>;
+type DeprecatedApmIndexConfigPaths = typeof apmIndexConfigs[number][1];
+type DeprecatedApmIndicesSavedObjectAttributes = Partial<{
+ [Property in DeprecatedApmIndexConfigPaths]: string;
+}>;
+
+export function updateApmOssIndexPaths(
+ attributes: DeprecatedApmIndicesSavedObjectAttributes
+) {
+ return apmIndexConfigs.reduce((attrs, [configPath, deprecatedConfigPath]) => {
+ const indexConfig: string | undefined = attributes[deprecatedConfigPath];
+ if (indexConfig) {
+ attrs[configPath] = indexConfig;
+ }
+ return attrs;
+ }, {} as ApmIndicesSavedObjectAttributes);
+}
diff --git a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts
index fb9fbae33ac82..ba99b0624c441 100644
--- a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts
+++ b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts
@@ -37,13 +37,7 @@ export function onPremInstructions({
apmConfig,
isFleetPluginEnabled,
}: {
- apmConfig: Pick<
- APMConfig,
- | 'apm_oss.errorIndices'
- | 'apm_oss.transactionIndices'
- | 'apm_oss.metricsIndices'
- | 'apm_oss.onboardingIndices'
- >;
+ apmConfig: APMConfig;
isFleetPluginEnabled: boolean;
}): InstructionsSchema {
const EDIT_CONFIG = createEditConfig();
@@ -76,7 +70,12 @@ export function onPremInstructions({
{
id: INSTRUCTION_VARIANT.FLEET,
instructions: [
- { customComponentName: 'TutorialFleetInstructions' },
+ {
+ title: i18n.translate('xpack.apm.tutorial.fleet.title', {
+ defaultMessage: 'Fleet',
+ }),
+ customComponentName: 'TutorialFleetInstructions',
+ },
],
},
]
@@ -144,7 +143,7 @@ export function onPremInstructions({
}
),
esHitsCheck: {
- index: apmConfig['apm_oss.onboardingIndices'],
+ index: apmConfig.indices.onboarding,
query: {
bool: {
filter: [
@@ -237,9 +236,9 @@ export function onPremInstructions({
),
esHitsCheck: {
index: [
- apmConfig['apm_oss.errorIndices'],
- apmConfig['apm_oss.transactionIndices'],
- apmConfig['apm_oss.metricsIndices'],
+ apmConfig.indices.error,
+ apmConfig.indices.transaction,
+ apmConfig.indices.metric,
],
query: {
bool: {
diff --git a/x-pack/plugins/apm/server/tutorial/index.ts b/x-pack/plugins/apm/server/tutorial/index.ts
index 66e6ffaed95a8..5caf2b4372483 100644
--- a/x-pack/plugins/apm/server/tutorial/index.ts
+++ b/x-pack/plugins/apm/server/tutorial/index.ts
@@ -67,7 +67,7 @@ export const tutorialProvider =
],
};
- if (apmConfig['xpack.apm.ui.enabled']) {
+ if (apmConfig.ui.enabled) {
// @ts-expect-error artifacts.application is readonly
artifacts.application = {
path: '/app/apm',
diff --git a/x-pack/plugins/apm/server/types.ts b/x-pack/plugins/apm/server/types.ts
index 325891d8c1d33..c686c42beb6ef 100644
--- a/x-pack/plugins/apm/server/types.ts
+++ b/x-pack/plugins/apm/server/types.ts
@@ -16,7 +16,6 @@ import {
PluginStart as DataPluginStart,
} from '../../../../src/plugins/data/server';
import { SpacesPluginSetup, SpacesPluginStart } from '../../spaces/server';
-import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server';
import {
HomeServerPluginSetup,
HomeServerPluginStart,
@@ -71,10 +70,6 @@ interface DependencyMap {
setup: SpacesPluginSetup;
start: SpacesPluginStart;
};
- apmOss: {
- setup: APMOSSPluginSetup;
- start: undefined;
- };
home: {
setup: HomeServerPluginSetup;
start: HomeServerPluginStart;
@@ -135,7 +130,6 @@ interface DependencyMap {
const requiredDependencies = [
'features',
- 'apmOss',
'data',
'licensing',
'triggersActionsUi',
diff --git a/x-pack/plugins/apm/server/utils/test_helpers.tsx b/x-pack/plugins/apm/server/utils/test_helpers.tsx
index 7b6b549e07c8d..5cf5016aec2e9 100644
--- a/x-pack/plugins/apm/server/utils/test_helpers.tsx
+++ b/x-pack/plugins/apm/server/utils/test_helpers.tsx
@@ -12,6 +12,7 @@ import {
ESSearchResponse,
} from '../../../../../src/core/types/elasticsearch';
import { UxUIFilters } from '../../typings/ui_filters';
+import { ApmIndicesConfig } from '../lib/settings/apm_indices/get_apm_indices';
interface Options {
mockResponse?: (
@@ -26,18 +27,7 @@ interface MockSetup {
internalClient: any;
config: APMConfig;
uiFilters: UxUIFilters;
- indices: {
- /* eslint-disable @typescript-eslint/naming-convention */
- 'apm_oss.sourcemapIndices': string;
- 'apm_oss.errorIndices': string;
- 'apm_oss.onboardingIndices': string;
- 'apm_oss.spanIndices': string;
- 'apm_oss.transactionIndices': string;
- 'apm_oss.metricsIndices': string;
- /* eslint-enable @typescript-eslint/naming-convention */
- apmAgentConfigurationIndex: string;
- apmCustomLinkIndex: string;
- };
+ indices: ApmIndicesConfig;
}
export async function inspectSearchParams(
@@ -61,6 +51,16 @@ export async function inspectSearchParams(
let response;
let error;
+ const mockApmIndices: {
+ [Property in keyof APMConfig['indices']]: string;
+ } = {
+ sourcemap: 'myIndex',
+ error: 'myIndex',
+ onboarding: 'myIndex',
+ span: 'myIndex',
+ transaction: 'myIndex',
+ metric: 'myIndex',
+ };
const mockSetup = {
apmEventClient: { search: spy } as any,
internalClient: { search: spy } as any,
@@ -76,8 +76,15 @@ export async function inspectSearchParams(
switch (key) {
default:
return 'myIndex';
-
- case 'xpack.apm.metricsInterval':
+ case 'indices':
+ return mockApmIndices;
+ case 'ui':
+ return {
+ enabled: true,
+ transactionGroupBucketSize: 1000,
+ maxTraceItems: 1000,
+ };
+ case 'metricsInterval':
return 30;
}
},
@@ -85,14 +92,7 @@ export async function inspectSearchParams(
) as APMConfig,
uiFilters: options?.uiFilters ?? {},
indices: {
- /* eslint-disable @typescript-eslint/naming-convention */
- 'apm_oss.sourcemapIndices': 'myIndex',
- 'apm_oss.errorIndices': 'myIndex',
- 'apm_oss.onboardingIndices': 'myIndex',
- 'apm_oss.spanIndices': 'myIndex',
- 'apm_oss.transactionIndices': 'myIndex',
- 'apm_oss.metricsIndices': 'myIndex',
- /* eslint-enable @typescript-eslint/naming-convention */
+ ...mockApmIndices,
apmAgentConfigurationIndex: 'myIndex',
apmCustomLinkIndex: 'myIndex',
},
diff --git a/x-pack/plugins/apm/tsconfig.json b/x-pack/plugins/apm/tsconfig.json
index c1030d2a4be1d..5db20725dd785 100644
--- a/x-pack/plugins/apm/tsconfig.json
+++ b/x-pack/plugins/apm/tsconfig.json
@@ -19,7 +19,6 @@
],
"references": [
{ "path": "../../../src/core/tsconfig.json" },
- { "path": "../../../src/plugins/apm_oss/tsconfig.json" },
{ "path": "../../../src/plugins/data/tsconfig.json" },
{ "path": "../../../src/plugins/embeddable/tsconfig.json" },
{ "path": "../../../src/plugins/home/tsconfig.json" },
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot
index 6ef6d19e446db..45b9d896db5f2 100644
--- a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot
@@ -54,7 +54,7 @@ exports[`Storyshots components/Assets/AssetManager no assets 1`] = `
>