@@ -31,6 +43,14 @@ export function ServiceDetails({ tab }: Props) {
+ {isAlertingAvailable && (
+
+
+
+ )}
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx
index 7a066b520cc3b..46754c8c7cb6b 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx
@@ -194,6 +194,7 @@ storiesOf('app/ServiceMap/Cytoscape', module)
const height = 640;
const width = 1340;
const serviceName = undefined; // global service map
+
return (
{
describe('render', () => {
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx
index 241f272b54a1d..b286d33ca74e9 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx
@@ -11,11 +11,11 @@ import * as urlParamsHooks from '../../../../hooks/useUrlParams';
import * as useLocalUIFilters from '../../../../hooks/useLocalUIFilters';
import { FETCH_STATUS } from '../../../../hooks/useFetcher';
import { SessionStorageMock } from '../../../../services/__test__/SessionStorageMock';
+import { ApmPluginContextValue } from '../../../../context/ApmPluginContext';
import {
MockApmPluginContextWrapper,
mockApmPluginContextValue
-} from '../../../../utils/testHelpers';
-import { ApmPluginContextValue } from '../../../../context/ApmPluginContext';
+} from '../../../../context/ApmPluginContext/MockApmPluginContext';
jest.mock('ui/new_platform');
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx
index fd71bf9709ce9..272c4b3add415 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx
@@ -7,8 +7,8 @@
import { render, wait } from '@testing-library/react';
import React from 'react';
import { ApmIndices } from '.';
-import { MockApmPluginContextWrapper } from '../../../../utils/testHelpers';
import * as hooks from '../../../../hooks/useFetcher';
+import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext';
describe('ApmIndices', () => {
it('should not get stuck in infinite loop', async () => {
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx
index 7c39356189891..b5bee5a5a1ebb 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx
@@ -13,11 +13,11 @@ import * as hooks from '../../../../../hooks/useFetcher';
import { LicenseContext } from '../../../../../context/LicenseContext';
import { CustomLinkOverview } from '.';
import {
- MockApmPluginContextWrapper,
expectTextsInDocument,
expectTextsNotInDocument
} from '../../../../../utils/testHelpers';
import * as saveCustomLink from './CustomLinkFlyout/saveCustomLink';
+import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext';
const data = [
{
diff --git a/x-pack/legacy/plugins/apm/public/components/app/TraceLink/__test__/TraceLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/TraceLink/__test__/TraceLink.test.tsx
index fe58fc39c6cfa..b8d6d9818eb2c 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/TraceLink/__test__/TraceLink.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/TraceLink/__test__/TraceLink.test.tsx
@@ -9,7 +9,7 @@ import React from 'react';
import { TraceLink } from '../';
import * as hooks from '../../../../hooks/useFetcher';
import * as urlParamsHooks from '../../../../hooks/useUrlParams';
-import { MockApmPluginContextWrapper } from '../../../../utils/testHelpers';
+import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext';
const renderOptions = { wrapper: MockApmPluginContextWrapper };
diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx
index 882682f1f6760..22cbeee5c6b7c 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx
@@ -22,7 +22,7 @@ import * as useFetcherHook from '../../../../hooks/useFetcher';
import { fromQuery } from '../../../shared/Links/url_helpers';
import { Router } from 'react-router-dom';
import { UrlParamsProvider } from '../../../../context/UrlParamsContext';
-import { MockApmPluginContextWrapper } from '../../../../utils/testHelpers';
+import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext';
jest.spyOn(history, 'push');
jest.spyOn(history, 'replace');
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.stories.tsx b/x-pack/legacy/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.stories.tsx
new file mode 100644
index 0000000000000..4ef8de7c2b208
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.stories.tsx
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { storiesOf } from '@storybook/react';
+import React from 'react';
+import { ErrorRateAlertTrigger } from '.';
+
+storiesOf('app/ErrorRateAlertTrigger', module).add('example', props => {
+ const params = {
+ threshold: 2,
+ window: '5m'
+ };
+
+ return (
+
+ undefined}
+ setAlertProperty={() => undefined}
+ />
+
+ );
+});
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx
new file mode 100644
index 0000000000000..6d0a2b96092a1
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import { EuiFieldNumber } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { ForLastExpression } from '../../../../../../../plugins/triggers_actions_ui/public';
+import { ALERT_TYPES_CONFIG } from '../../../../../../../plugins/apm/common/alert_types';
+import { ServiceAlertTrigger } from '../ServiceAlertTrigger';
+import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression';
+
+export interface ErrorRateAlertTriggerParams {
+ windowSize: number;
+ windowUnit: string;
+ threshold: number;
+}
+
+interface Props {
+ alertParams: ErrorRateAlertTriggerParams;
+ setAlertParams: (key: string, value: any) => void;
+ setAlertProperty: (key: string, value: any) => void;
+}
+
+export function ErrorRateAlertTrigger(props: Props) {
+ const { setAlertParams, setAlertProperty, alertParams } = props;
+
+ const defaults = {
+ threshold: 25,
+ windowSize: 1,
+ windowUnit: 'm'
+ };
+
+ const params = {
+ ...defaults,
+ ...alertParams
+ };
+
+ const fields = [
+
+
+ setAlertParams('threshold', parseInt(e.target.value, 10))
+ }
+ compressed
+ append={i18n.translate('xpack.apm.errorRateAlertTrigger.errors', {
+ defaultMessage: 'errors'
+ })}
+ />
+ ,
+
+ setAlertParams('windowSize', windowSize)
+ }
+ onChangeWindowUnit={windowUnit =>
+ setAlertParams('windowUnit', windowUnit)
+ }
+ timeWindowSize={params.windowSize}
+ timeWindowUnit={params.windowUnit}
+ errors={{
+ timeWindowSize: [],
+ timeWindowUnit: []
+ }}
+ />
+ ];
+
+ return (
+
+ );
+}
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx
index 0c60d523b8f3f..258788252379a 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx
@@ -10,9 +10,9 @@ import { render } from '@testing-library/react';
import { APMError } from '../../../../../../../../../plugins/apm/typings/es_schemas/ui/apm_error';
import {
expectTextsInDocument,
- expectTextsNotInDocument,
- MockApmPluginContextWrapper
+ expectTextsNotInDocument
} from '../../../../../utils/testHelpers';
+import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext';
const renderOptions = {
wrapper: MockApmPluginContextWrapper
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx
index ee66636d88ba9..0059b7b8fb4b3 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx
@@ -10,9 +10,9 @@ import { SpanMetadata } from '..';
import { Span } from '../../../../../../../../../plugins/apm/typings/es_schemas/ui/span';
import {
expectTextsInDocument,
- expectTextsNotInDocument,
- MockApmPluginContextWrapper
+ expectTextsNotInDocument
} from '../../../../../utils/testHelpers';
+import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext';
const renderOptions = {
wrapper: MockApmPluginContextWrapper
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx
index f426074fbef80..3d78f36db9786 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx
@@ -10,9 +10,9 @@ import { render } from '@testing-library/react';
import { Transaction } from '../../../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
import {
expectTextsInDocument,
- expectTextsNotInDocument,
- MockApmPluginContextWrapper
+ expectTextsNotInDocument
} from '../../../../../utils/testHelpers';
+import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext';
const renderOptions = {
wrapper: MockApmPluginContextWrapper
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx
index 979b9118a7534..96202525c8661 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx
@@ -7,11 +7,9 @@
import React from 'react';
import { render } from '@testing-library/react';
import { MetadataTable } from '..';
-import {
- expectTextsInDocument,
- MockApmPluginContextWrapper
-} from '../../../../utils/testHelpers';
+import { expectTextsInDocument } from '../../../../utils/testHelpers';
import { SectionsWithRows } from '../helper';
+import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext';
const renderOptions = {
wrapper: MockApmPluginContextWrapper
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/ServiceAlertTrigger/PopoverExpression/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/ServiceAlertTrigger/PopoverExpression/index.tsx
new file mode 100644
index 0000000000000..1abdb94c8313e
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/ServiceAlertTrigger/PopoverExpression/index.tsx
@@ -0,0 +1,39 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useState } from 'react';
+import { EuiExpression, EuiPopover } from '@elastic/eui';
+
+interface Props {
+ title: string;
+ value: string;
+ children?: React.ReactNode;
+}
+
+export const PopoverExpression = (props: Props) => {
+ const { title, value, children } = props;
+
+ const [popoverOpen, setPopoverOpen] = useState(false);
+
+ return (
+ setPopoverOpen(false)}
+ button={
+ setPopoverOpen(true)}
+ />
+ }
+ >
+ {children}
+
+ );
+};
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/ServiceAlertTrigger/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/ServiceAlertTrigger/index.tsx
new file mode 100644
index 0000000000000..98391b277caf6
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/ServiceAlertTrigger/index.tsx
@@ -0,0 +1,61 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useEffect } from 'react';
+import { EuiSpacer, EuiFlexGrid, EuiFlexItem } from '@elastic/eui';
+import { useUrlParams } from '../../../hooks/useUrlParams';
+
+interface Props {
+ alertTypeName: string;
+ setAlertParams: (key: string, value: any) => void;
+ setAlertProperty: (key: string, value: any) => void;
+ defaults: Record;
+ fields: React.ReactNode[];
+}
+
+export function ServiceAlertTrigger(props: Props) {
+ const { urlParams } = useUrlParams();
+
+ const {
+ fields,
+ setAlertParams,
+ setAlertProperty,
+ alertTypeName,
+ defaults
+ } = props;
+
+ const params: Record = {
+ ...defaults,
+ serviceName: urlParams.serviceName!
+ };
+
+ useEffect(() => {
+ // we only want to run this on mount to set default values
+ setAlertProperty('name', `${alertTypeName} | ${params.serviceName}`);
+ setAlertProperty('tags', [
+ 'apm',
+ `service.name:${params.serviceName}`.toLowerCase()
+ ]);
+ Object.keys(params).forEach(key => {
+ setAlertParams(key, params[key]);
+ });
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+ return (
+ <>
+
+
+ {fields.map((field, index) => (
+
+ {field}
+
+ ))}
+
+
+ >
+ );
+}
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx
index 9094662e34914..560884aec554a 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx
@@ -10,13 +10,13 @@ import { TransactionActionMenu } from '../TransactionActionMenu';
import { Transaction } from '../../../../../../../../plugins/apm/typings/es_schemas/ui/transaction';
import * as Transactions from './mockData';
import {
- MockApmPluginContextWrapper,
expectTextsNotInDocument,
expectTextsInDocument
} from '../../../../utils/testHelpers';
import * as hooks from '../../../../hooks/useFetcher';
import { LicenseContext } from '../../../../context/LicenseContext';
import { License } from '../../../../../../../../plugins/licensing/common/license';
+import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext';
const renderTransaction = async (transaction: Record) => {
const rendered = render(
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.stories.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.stories.tsx
new file mode 100644
index 0000000000000..a8f834103e6c1
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.stories.tsx
@@ -0,0 +1,50 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { cloneDeep, merge } from 'lodash';
+import { storiesOf } from '@storybook/react';
+import React from 'react';
+import { TransactionDurationAlertTrigger } from '.';
+import {
+ MockApmPluginContextWrapper,
+ mockApmPluginContextValue
+} from '../../../context/ApmPluginContext/MockApmPluginContext';
+import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/MockUrlParamsContextProvider';
+import { ApmPluginContextValue } from '../../../context/ApmPluginContext';
+
+storiesOf('app/TransactionDurationAlertTrigger', module).add(
+ 'example',
+ context => {
+ const params = {
+ threshold: 1500,
+ aggregationType: 'avg' as const,
+ window: '5m'
+ };
+
+ const contextMock = (merge(cloneDeep(mockApmPluginContextValue), {
+ core: {
+ http: {
+ get: () => {
+ return Promise.resolve({ transactionTypes: ['request'] });
+ }
+ }
+ }
+ }) as unknown) as ApmPluginContextValue;
+
+ return (
+
+
+
+ undefined}
+ setAlertProperty={() => undefined}
+ />
+
+
+
+ );
+ }
+);
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx
new file mode 100644
index 0000000000000..cdc7c30089b4f
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx
@@ -0,0 +1,149 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import { map } from 'lodash';
+import { EuiFieldNumber, EuiSelect } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { ForLastExpression } from '../../../../../../../plugins/triggers_actions_ui/public';
+import {
+ TRANSACTION_ALERT_AGGREGATION_TYPES,
+ ALERT_TYPES_CONFIG
+} from '../../../../../../../plugins/apm/common/alert_types';
+import { ServiceAlertTrigger } from '../ServiceAlertTrigger';
+import { useUrlParams } from '../../../hooks/useUrlParams';
+import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes';
+import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression';
+
+interface Params {
+ windowSize: number;
+ windowUnit: string;
+ threshold: number;
+ aggregationType: 'avg' | '95th' | '99th';
+ serviceName: string;
+ transactionType: string;
+}
+
+interface Props {
+ alertParams: Params;
+ setAlertParams: (key: string, value: any) => void;
+ setAlertProperty: (key: string, value: any) => void;
+}
+
+export function TransactionDurationAlertTrigger(props: Props) {
+ const { setAlertParams, alertParams, setAlertProperty } = props;
+
+ const { urlParams } = useUrlParams();
+
+ const transactionTypes = useServiceTransactionTypes(urlParams);
+
+ if (!transactionTypes.length) {
+ return null;
+ }
+
+ const defaults = {
+ threshold: 1500,
+ aggregationType: 'avg',
+ windowSize: 5,
+ windowUnit: 'm',
+ transactionType: transactionTypes[0]
+ };
+
+ const params = {
+ ...defaults,
+ ...alertParams
+ };
+
+ const fields = [
+
+ {
+ return {
+ text: key,
+ value: key
+ };
+ })}
+ onChange={e =>
+ setAlertParams(
+ 'transactionType',
+ e.target.value as Params['transactionType']
+ )
+ }
+ compressed
+ />
+ ,
+
+ {
+ return {
+ text: label,
+ value: key
+ };
+ })}
+ onChange={e =>
+ setAlertParams(
+ 'aggregationType',
+ e.target.value as Params['aggregationType']
+ )
+ }
+ compressed
+ />
+ ,
+
+ setAlertParams('threshold', e.target.value)}
+ append={i18n.translate('xpack.apm.transactionDurationAlertTrigger.ms', {
+ defaultMessage: 'ms'
+ })}
+ compressed
+ />
+ ,
+
+ setAlertParams('windowSize', timeWindowSize)
+ }
+ onChangeWindowUnit={timeWindowUnit =>
+ setAlertParams('windowUnit', timeWindowUnit)
+ }
+ timeWindowSize={params.windowSize}
+ timeWindowUnit={params.windowUnit}
+ errors={{
+ timeWindowSize: [],
+ timeWindowUnit: []
+ }}
+ />
+ ];
+
+ return (
+
+ );
+}
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.test.tsx
index 6d3e29ec09985..9f112475a4a78 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.test.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import { BrowserLineChart } from './BrowserLineChart';
-import { MockApmPluginContextWrapper } from '../../../../utils/testHelpers';
+import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext';
describe('BrowserLineChart', () => {
describe('render', () => {
diff --git a/x-pack/legacy/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx b/x-pack/legacy/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx
new file mode 100644
index 0000000000000..8775dc98c3e1a
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import { ApmPluginContext, ApmPluginContextValue } from '.';
+import { createCallApmApi } from '../../services/rest/createCallApmApi';
+import { ConfigSchema } from '../../new-platform/plugin';
+
+const mockCore = {
+ chrome: {
+ setBreadcrumbs: () => {}
+ },
+ http: {
+ basePath: {
+ prepend: (path: string) => `/basepath${path}`
+ }
+ },
+ notifications: {
+ toasts: {
+ addWarning: () => {},
+ addDanger: () => {}
+ }
+ }
+};
+
+const mockConfig: ConfigSchema = {
+ indexPatternTitle: 'apm-*',
+ serviceMapEnabled: true,
+ ui: {
+ enabled: false
+ }
+};
+
+export const mockApmPluginContextValue = {
+ config: mockConfig,
+ core: mockCore,
+ packageInfo: { version: '0' },
+ plugins: {}
+};
+
+export function MockApmPluginContextWrapper({
+ children,
+ value = {} as ApmPluginContextValue
+}: {
+ children?: React.ReactNode;
+ value?: ApmPluginContextValue;
+}) {
+ if (value.core?.http) {
+ createCallApmApi(value.core?.http);
+ }
+ return (
+
+ {children}
+
+ );
+}
diff --git a/x-pack/legacy/plugins/apm/public/context/ApmPluginContext.tsx b/x-pack/legacy/plugins/apm/public/context/ApmPluginContext/index.tsx
similarity index 89%
rename from x-pack/legacy/plugins/apm/public/context/ApmPluginContext.tsx
rename to x-pack/legacy/plugins/apm/public/context/ApmPluginContext/index.tsx
index 7a9aaa6dfb920..d8934ba4b0151 100644
--- a/x-pack/legacy/plugins/apm/public/context/ApmPluginContext.tsx
+++ b/x-pack/legacy/plugins/apm/public/context/ApmPluginContext/index.tsx
@@ -6,7 +6,7 @@
import { createContext } from 'react';
import { AppMountContext, PackageInfo } from 'kibana/public';
-import { ApmPluginSetupDeps, ConfigSchema } from '../new-platform/plugin';
+import { ApmPluginSetupDeps, ConfigSchema } from '../../new-platform/plugin';
export type AppMountContextBasePath = AppMountContext['core']['http']['basePath'];
diff --git a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/MockUrlParamsContextProvider.tsx b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/MockUrlParamsContextProvider.tsx
new file mode 100644
index 0000000000000..46f51da49692a
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/MockUrlParamsContextProvider.tsx
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import { IUrlParams } from './types';
+import { UrlParamsContext, useUiFilters } from '.';
+
+const defaultUrlParams = {
+ page: 0,
+ serviceName: 'opbeans-python',
+ transactionType: 'request',
+ start: '2018-01-10T09:51:41.050Z',
+ end: '2018-01-10T10:06:41.050Z'
+};
+
+interface Props {
+ params?: IUrlParams;
+ children: React.ReactNode;
+ refreshTimeRange?: (time: any) => void;
+}
+
+export const MockUrlParamsContextProvider = ({
+ params,
+ children,
+ refreshTimeRange = () => undefined
+}: Props) => {
+ const urlParams = { ...defaultUrlParams, ...params };
+ return (
+
+ );
+};
diff --git a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx
index 389737a815ab2..8918d992b4f53 100644
--- a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx
@@ -4,10 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
import { render, wait } from '@testing-library/react';
-import { delay, MockApmPluginContextWrapper } from '../utils/testHelpers';
+import React from 'react';
+import { delay } from '../utils/testHelpers';
import { useFetcher } from './useFetcher';
+import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext';
const wrapper = MockApmPluginContextWrapper;
diff --git a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.test.tsx b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.test.tsx
index e3ef1d44c8b03..deb805c542b1e 100644
--- a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.test.tsx
@@ -5,8 +5,9 @@
*/
import { renderHook } from '@testing-library/react-hooks';
-import { delay, MockApmPluginContextWrapper } from '../utils/testHelpers';
+import { delay } from '../utils/testHelpers';
import { useFetcher } from './useFetcher';
+import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext';
// Wrap the hook with a provider so it can useApmPluginContext
const wrapper = MockApmPluginContextWrapper;
diff --git a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx
index e34e2c904defb..b85f88040e513 100644
--- a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx
+++ b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx
@@ -9,6 +9,8 @@ import ReactDOM from 'react-dom';
import { Route, Router, Switch } from 'react-router-dom';
import styled from 'styled-components';
import { metadata } from 'ui/metadata';
+import { i18n } from '@kbn/i18n';
+import { AlertType } from '../../../../../plugins/apm/common/alert_types';
import {
CoreSetup,
CoreStart,
@@ -38,6 +40,12 @@ import { toggleAppLinkInNav } from './toggleAppLinkInNav';
import { setReadonlyBadge } from './updateBadge';
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
import { APMIndicesPermission } from '../components/app/APMIndicesPermission';
+import {
+ TriggersAndActionsUIPublicPluginSetup,
+ AlertsContextProvider
+} from '../../../../../plugins/triggers_actions_ui/public';
+import { ErrorRateAlertTrigger } from '../components/shared/ErrorRateAlertTrigger';
+import { TransactionDurationAlertTrigger } from '../components/shared/TransactionDurationAlertTrigger';
import { createCallApmApi } from '../services/rest/createCallApmApi';
export const REACT_APP_ROOT_ID = 'react-apm-root';
@@ -71,6 +79,7 @@ export interface ApmPluginSetupDeps {
data: DataPublicPluginSetup;
home: HomePublicPluginSetup;
licensing: LicensingPluginSetup;
+ triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup;
}
export interface ConfigSchema {
@@ -134,25 +143,58 @@ export class ApmPlugin
plugins
};
+ plugins.triggers_actions_ui.alertTypeRegistry.register({
+ id: AlertType.ErrorRate,
+ name: i18n.translate('xpack.apm.alertTypes.errorRate', {
+ defaultMessage: 'Error rate'
+ }),
+ iconClass: 'bell',
+ alertParamsExpression: ErrorRateAlertTrigger,
+ validate: () => ({
+ errors: []
+ })
+ });
+
+ plugins.triggers_actions_ui.alertTypeRegistry.register({
+ id: AlertType.TransactionDuration,
+ name: i18n.translate('xpack.apm.alertTypes.transactionDuration', {
+ defaultMessage: 'Transaction duration'
+ }),
+ iconClass: 'bell',
+ alertParamsExpression: TransactionDurationAlertTrigger,
+ validate: () => ({
+ errors: []
+ })
+ });
+
ReactDOM.render(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
,
document.getElementById(REACT_APP_ROOT_ID)
);
diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx
index 6bcfbc4541b64..36c0e18777bfd 100644
--- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx
+++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx
@@ -11,7 +11,7 @@ import enzymeToJson from 'enzyme-to-json';
import { Location } from 'history';
import moment from 'moment';
import { Moment } from 'moment-timezone';
-import React, { ReactNode } from 'react';
+import React from 'react';
import { render, waitForElement } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { MemoryRouter } from 'react-router-dom';
@@ -24,12 +24,7 @@ import {
ESSearchResponse,
ESSearchRequest
} from '../../../../../plugins/apm/typings/elasticsearch';
-import {
- ApmPluginContext,
- ApmPluginContextValue
-} from '../context/ApmPluginContext';
-import { ConfigSchema } from '../new-platform/plugin';
-import { createCallApmApi } from '../services/rest/createCallApmApi';
+import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext';
export function toJson(wrapper: ReactWrapper) {
return enzymeToJson(wrapper, {
@@ -186,57 +181,3 @@ export async function inspectSearchParams(
}
export type SearchParamsMock = PromiseReturnType;
-
-const mockCore = {
- chrome: {
- setBreadcrumbs: () => {}
- },
- http: {
- basePath: {
- prepend: (path: string) => `/basepath${path}`
- }
- },
- notifications: {
- toasts: {
- addWarning: () => {},
- addDanger: () => {}
- }
- }
-};
-
-const mockConfig: ConfigSchema = {
- indexPatternTitle: 'apm-*',
- serviceMapEnabled: true,
- ui: {
- enabled: false
- }
-};
-
-export const mockApmPluginContextValue = {
- config: mockConfig,
- core: mockCore,
- packageInfo: { version: '0' },
- plugins: {}
-};
-
-export function MockApmPluginContextWrapper({
- children,
- value = {} as ApmPluginContextValue
-}: {
- children?: ReactNode;
- value?: ApmPluginContextValue;
-}) {
- if (value.core?.http) {
- createCallApmApi(value.core?.http);
- }
- return (
-
- {children}
-
- );
-}
diff --git a/x-pack/plugins/apm/common/alert_types.ts b/x-pack/plugins/apm/common/alert_types.ts
new file mode 100644
index 0000000000000..51e1f88512965
--- /dev/null
+++ b/x-pack/plugins/apm/common/alert_types.ts
@@ -0,0 +1,67 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export enum AlertType {
+ ErrorRate = 'apm.error_rate',
+ TransactionDuration = 'apm.transaction_duration'
+}
+
+export const ALERT_TYPES_CONFIG = {
+ [AlertType.ErrorRate]: {
+ name: i18n.translate('xpack.apm.errorRateAlert.name', {
+ defaultMessage: 'Error rate threshold'
+ }),
+ actionGroups: [
+ {
+ id: 'threshold_met',
+ name: i18n.translate('xpack.apm.errorRateAlert.thresholdMet', {
+ defaultMessage: 'Threshold met'
+ })
+ }
+ ],
+ defaultActionGroupId: 'threshold_met'
+ },
+ [AlertType.TransactionDuration]: {
+ name: i18n.translate('xpack.apm.transactionDurationAlert.name', {
+ defaultMessage: 'Transaction duration threshold'
+ }),
+ actionGroups: [
+ {
+ id: 'threshold_met',
+ name: i18n.translate(
+ 'xpack.apm.transactionDurationAlert.thresholdMet',
+ {
+ defaultMessage: 'Threshold met'
+ }
+ )
+ }
+ ],
+ defaultActionGroupId: 'threshold_met'
+ }
+};
+
+export const TRANSACTION_ALERT_AGGREGATION_TYPES = {
+ avg: i18n.translate(
+ 'xpack.apm.transactionDurationAlert.aggregationType.avg',
+ {
+ defaultMessage: 'Average'
+ }
+ ),
+ '95th': i18n.translate(
+ 'xpack.apm.transactionDurationAlert.aggregationType.95th',
+ {
+ defaultMessage: '95th percentile'
+ }
+ ),
+ '99th': i18n.translate(
+ 'xpack.apm.transactionDurationAlert.aggregationType.99th',
+ {
+ defaultMessage: '99th percentile'
+ }
+ )
+};
diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json
index 96579377c95e8..931fd92e1ecc3 100644
--- a/x-pack/plugins/apm/kibana.json
+++ b/x-pack/plugins/apm/kibana.json
@@ -6,5 +6,5 @@
"configPath": ["xpack", "apm"],
"ui": false,
"requiredPlugins": ["apm_oss", "data", "home", "licensing"],
- "optionalPlugins": ["cloud", "usageCollection"]
+ "optionalPlugins": ["cloud", "usageCollection", "taskManager","actions", "alerting"]
}
diff --git a/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts b/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts
new file mode 100644
index 0000000000000..cb3dd761040da
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Observable } from 'rxjs';
+import { AlertingPlugin } from '../../../../alerting/server';
+import { ActionsPlugin } from '../../../../actions/server';
+import { registerTransactionDurationAlertType } from './register_transaction_duration_alert_type';
+import { registerErrorRateAlertType } from './register_error_rate_alert_type';
+import { APMConfig } from '../..';
+
+interface Params {
+ alerting: AlertingPlugin['setup'];
+ actions: ActionsPlugin['setup'];
+ config$: Observable;
+}
+
+export function registerApmAlerts(params: Params) {
+ registerTransactionDurationAlertType({
+ alerting: params.alerting,
+ config$: params.config$
+ });
+ registerErrorRateAlertType({
+ alerting: params.alerting,
+ config$: params.config$
+ });
+}
diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts
new file mode 100644
index 0000000000000..187a75d0b61f2
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts
@@ -0,0 +1,108 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { schema, TypeOf } from '@kbn/config-schema';
+import { Observable } from 'rxjs';
+import { take } from 'rxjs/operators';
+import { AlertType, ALERT_TYPES_CONFIG } from '../../../common/alert_types';
+import {
+ ESSearchResponse,
+ ESSearchRequest
+} from '../../../typings/elasticsearch';
+import {
+ PROCESSOR_EVENT,
+ SERVICE_NAME
+} from '../../../common/elasticsearch_fieldnames';
+import { AlertingPlugin } from '../../../../alerting/server';
+import { getApmIndices } from '../settings/apm_indices/get_apm_indices';
+import { APMConfig } from '../..';
+
+interface RegisterAlertParams {
+ alerting: AlertingPlugin['setup'];
+ config$: Observable;
+}
+
+const paramsSchema = schema.object({
+ serviceName: schema.string(),
+ windowSize: schema.number(),
+ windowUnit: schema.string(),
+ threshold: schema.number()
+});
+
+const alertTypeConfig = ALERT_TYPES_CONFIG[AlertType.ErrorRate];
+
+export function registerErrorRateAlertType({
+ alerting,
+ config$
+}: RegisterAlertParams) {
+ alerting.registerType({
+ id: AlertType.ErrorRate,
+ name: alertTypeConfig.name,
+ actionGroups: alertTypeConfig.actionGroups,
+ defaultActionGroupId: alertTypeConfig.defaultActionGroupId,
+ validate: {
+ params: paramsSchema
+ },
+
+ executor: async ({ services, params }) => {
+ const config = await config$.pipe(take(1)).toPromise();
+
+ const alertParams = params as TypeOf;
+
+ const indices = await getApmIndices({
+ config,
+ savedObjectsClient: services.savedObjectsClient
+ });
+
+ const searchParams = {
+ index: indices['apm_oss.errorIndices'],
+ size: 0,
+ body: {
+ query: {
+ bool: {
+ filter: [
+ {
+ range: {
+ '@timestamp': {
+ gte: `now-${alertParams.windowSize}${alertParams.windowUnit}`
+ }
+ }
+ },
+ {
+ term: {
+ [PROCESSOR_EVENT]: 'error'
+ }
+ },
+ {
+ term: {
+ [SERVICE_NAME]: alertParams.serviceName
+ }
+ }
+ ]
+ }
+ },
+ track_total_hits: true
+ }
+ };
+
+ const response: ESSearchResponse<
+ unknown,
+ ESSearchRequest
+ > = await services.callCluster('search', searchParams);
+
+ const value = response.hits.total.value;
+
+ if (value && value > alertParams.threshold) {
+ const alertInstance = services.alertInstanceFactory(
+ AlertType.ErrorRate
+ );
+ alertInstance.scheduleActions(alertTypeConfig.defaultActionGroupId);
+ }
+
+ return {};
+ }
+ });
+}
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
new file mode 100644
index 0000000000000..7575a8268bc26
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts
@@ -0,0 +1,140 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { schema, TypeOf } from '@kbn/config-schema';
+import { Observable } from 'rxjs';
+import { take } from 'rxjs/operators';
+import { AlertType, ALERT_TYPES_CONFIG } from '../../../common/alert_types';
+import { ESSearchResponse } from '../../../typings/elasticsearch';
+import {
+ PROCESSOR_EVENT,
+ SERVICE_NAME,
+ TRANSACTION_TYPE,
+ TRANSACTION_DURATION
+} from '../../../common/elasticsearch_fieldnames';
+import { AlertingPlugin } from '../../../../alerting/server';
+import { getApmIndices } from '../settings/apm_indices/get_apm_indices';
+import { APMConfig } from '../..';
+
+interface RegisterAlertParams {
+ alerting: AlertingPlugin['setup'];
+ config$: Observable;
+}
+
+const paramsSchema = schema.object({
+ serviceName: schema.string(),
+ transactionType: schema.string(),
+ windowSize: schema.number(),
+ windowUnit: schema.string(),
+ threshold: schema.number(),
+ aggregationType: schema.oneOf([
+ schema.literal('avg'),
+ schema.literal('95th'),
+ schema.literal('99th')
+ ])
+});
+
+const alertTypeConfig = ALERT_TYPES_CONFIG[AlertType.TransactionDuration];
+
+export function registerTransactionDurationAlertType({
+ alerting,
+ config$
+}: RegisterAlertParams) {
+ alerting.registerType({
+ id: AlertType.TransactionDuration,
+ name: alertTypeConfig.name,
+ actionGroups: alertTypeConfig.actionGroups,
+ defaultActionGroupId: alertTypeConfig.defaultActionGroupId,
+ validate: {
+ params: paramsSchema
+ },
+
+ executor: async ({ services, params }) => {
+ const config = await config$.pipe(take(1)).toPromise();
+
+ const alertParams = params as TypeOf;
+
+ const indices = await getApmIndices({
+ config,
+ savedObjectsClient: services.savedObjectsClient
+ });
+
+ const searchParams = {
+ index: indices['apm_oss.transactionIndices'],
+ size: 0,
+ body: {
+ query: {
+ bool: {
+ filter: [
+ {
+ range: {
+ '@timestamp': {
+ gte: `now-${alertParams.windowSize}${alertParams.windowUnit}`
+ }
+ }
+ },
+ {
+ term: {
+ [PROCESSOR_EVENT]: 'transaction'
+ }
+ },
+ {
+ term: {
+ [SERVICE_NAME]: alertParams.serviceName
+ }
+ },
+ {
+ term: {
+ [TRANSACTION_TYPE]: alertParams.transactionType
+ }
+ }
+ ]
+ }
+ },
+ aggs: {
+ agg:
+ alertParams.aggregationType === 'avg'
+ ? {
+ avg: {
+ field: TRANSACTION_DURATION
+ }
+ }
+ : {
+ percentiles: {
+ field: TRANSACTION_DURATION,
+ percents: [
+ alertParams.aggregationType === '95th' ? 95 : 99
+ ]
+ }
+ }
+ }
+ }
+ };
+
+ const response: ESSearchResponse<
+ unknown,
+ typeof searchParams
+ > = await services.callCluster('search', searchParams);
+
+ if (!response.aggregations) {
+ return;
+ }
+
+ const { agg } = response.aggregations;
+
+ const value = 'values' in agg ? agg.values[0] : agg.value;
+
+ if (value && value > alertParams.threshold * 1000) {
+ const alertInstance = services.alertInstanceFactory(
+ AlertType.TransactionDuration
+ );
+ alertInstance.scheduleActions(alertTypeConfig.defaultActionGroupId);
+ }
+
+ return {};
+ }
+ });
+}
diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts
index db14730f802a9..e140340786e8a 100644
--- a/x-pack/plugins/apm/server/plugin.ts
+++ b/x-pack/plugins/apm/server/plugin.ts
@@ -8,7 +8,10 @@ import { Observable, combineLatest, AsyncSubject } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { Server } from 'hapi';
import { once } from 'lodash';
-import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server';
+import { TaskManagerSetupContract } from '../../task_manager/server';
+import { AlertingPlugin } from '../../alerting/server';
+import { ActionsPlugin } from '../../actions/server';
import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server';
import { makeApmUsageCollector } from './lib/apm_telemetry';
import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index';
@@ -21,6 +24,7 @@ import { tutorialProvider } from './tutorial';
import { CloudSetup } from '../../cloud/server';
import { getInternalSavedObjectsClient } from './lib/helpers/get_internal_saved_objects_client';
import { LicensingPluginSetup } from '../../licensing/public';
+import { registerApmAlerts } from './lib/alerts/register_apm_alerts';
export interface LegacySetup {
server: Server;
@@ -47,6 +51,9 @@ export class APMPlugin implements Plugin {
licensing: LicensingPluginSetup;
cloud?: CloudSetup;
usageCollection?: UsageCollectionSetup;
+ taskManager?: TaskManagerSetupContract;
+ alerting?: AlertingPlugin['setup'];
+ actions?: ActionsPlugin['setup'];
}
) {
const logger = this.initContext.logger.get('apm');
@@ -55,6 +62,14 @@ export class APMPlugin implements Plugin {
map(([apmOssConfig, apmConfig]) => mergeConfigs(apmOssConfig, apmConfig))
);
+ if (plugins.actions && plugins.alerting) {
+ registerApmAlerts({
+ alerting: plugins.alerting,
+ actions: plugins.actions,
+ config$: mergedConfig$
+ });
+ }
+
this.legacySetup$.subscribe(__LEGACY => {
createApmApi().init(core, { config$: mergedConfig$, logger, __LEGACY });
});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts
index e5693e31c2d66..f8102189c425c 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts
@@ -10,44 +10,21 @@
* will possibly go away with https://github.com/elastic/kibana/issues/52300.
*/
-export function hasShowAlertsCapability(capabilities: any): boolean {
- if (capabilities.siem && capabilities.siem['alerting:show']) {
- return true;
- }
- return false;
-}
+type Capabilities = Record;
-export function hasShowActionsCapability(capabilities: any): boolean {
- if (capabilities.siem && capabilities.siem['actions:show']) {
- return true;
- }
- return false;
-}
+const apps = ['apm', 'siem'];
-export function hasSaveAlertsCapability(capabilities: any): boolean {
- if (capabilities.siem && capabilities.siem['alerting:save']) {
- return true;
- }
- return false;
+function hasCapability(capabilities: Capabilities, capability: string) {
+ return apps.some(app => capabilities[app]?.[capability]);
}
-export function hasSaveActionsCapability(capabilities: any): boolean {
- if (capabilities.siem && capabilities.siem['actions:save']) {
- return true;
- }
- return false;
+function createCapabilityCheck(capability: string) {
+ return (capabilities: Capabilities) => hasCapability(capabilities, capability);
}
-export function hasDeleteAlertsCapability(capabilities: any): boolean {
- if (capabilities.siem && capabilities.siem['alerting:delete']) {
- return true;
- }
- return false;
-}
-
-export function hasDeleteActionsCapability(capabilities: any): boolean {
- if (capabilities.siem && capabilities.siem['actions:delete']) {
- return true;
- }
- return false;
-}
+export const hasShowAlertsCapability = createCapabilityCheck('alerting:show');
+export const hasShowActionsCapability = createCapabilityCheck('actions:show');
+export const hasSaveAlertsCapability = createCapabilityCheck('alerting:save');
+export const hasSaveActionsCapability = createCapabilityCheck('actions:save');
+export const hasDeleteAlertsCapability = createCapabilityCheck('alerting:delete');
+export const hasDeleteActionsCapability = createCapabilityCheck('actions:delete');
diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts
index 342401c4778d8..96645e856e418 100644
--- a/x-pack/plugins/triggers_actions_ui/public/index.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/index.ts
@@ -23,3 +23,7 @@ export function plugin(ctx: PluginInitializerContext) {
export { Plugin };
export * from './plugin';
+
+export { TIME_UNITS } from './application/constants';
+export { getTimeUnitLabel } from './common/lib/get_time_unit_label';
+export { ForLastExpression } from './common/expression_items/for_the_last';