Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { AlertType } from '../../../../../../common/alert_types';
import { AlertAdd } from '../../../../../../../triggers_actions_ui/public';
import { AlertType } from '../../../../common/alert_types';
import { AlertAdd } from '../../../../../triggers_actions_ui/public';

type AlertAddProps = React.ComponentProps<typeof AlertAdd>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ export function ServiceAlertTrigger(props: Props) {

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(),
]);

const alertName = params.serviceName
? `${alertTypeName} | ${params.serviceName}`
: alertTypeName;
setAlertProperty('name', alertName);

const tags = ['apm'];
if (params.serviceName) {
tags.push(`service.name:${params.serviceName}`.toLowerCase());
}
setAlertProperty('tags', tags);
Object.keys(params).forEach((key) => {
setAlertParams(key, params[key]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import {
interface Params {
windowSize: number;
windowUnit: string;
serviceName: string;
transactionType: string;
serviceName?: string;
transactionType?: string;
environment: string;
anomalySeverityType:
| ANOMALY_SEVERITY.CRITICAL
Expand All @@ -59,7 +59,7 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) {
[TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST].includes(transactionType)
);

if (!supportedTransactionTypes.length || !serviceName) {
if (serviceName && !supportedTransactionTypes.length) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function TransactionErrorRateAlertTrigger(props: Props) {
const { start, end, transactionType } = urlParams;
const { environmentOptions } = useEnvironments({ serviceName, start, end });

if (!transactionTypes.length || !serviceName) {
if (serviceName && !transactionTypes.length) {
return null;
}

Expand Down
50 changes: 50 additions & 0 deletions x-pack/plugins/apm/public/components/alerting/fields.test.tsx
Original file line number Diff line number Diff line change
@@ -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 React from 'react';
import { ServiceField, TransactionTypeField } from './fields';
import { render } from '@testing-library/react';
import { expectTextsInDocument } from '../../utils/testHelpers';

describe('alerting fields', () => {
describe('Service Fiels', () => {
it('renders with value', () => {
const component = render(<ServiceField value="foo" />);
expectTextsInDocument(component, ['foo']);
});
it('renders with All when value is not defined', () => {
const component = render(<ServiceField />);
expectTextsInDocument(component, ['All']);
});
});
describe('Transaction Type Field', () => {
it('renders select field when multiple options available', () => {
const options = [
{ text: 'Foo', value: 'foo' },
{ text: 'Bar', value: 'bar' },
];
const component = render(
<TransactionTypeField currentValue="Foo" options={options} />
);
expectTextsInDocument(component, ['Foo']);
});
it('renders read-only field when single option available', () => {
const options = [{ text: 'Bar', value: 'bar' }];
const component = render(
<TransactionTypeField currentValue="Bar" options={options} />
);
expectTextsInDocument(component, ['Bar']);
});
it('renders read-only All option when no option available', () => {
const component = render(<TransactionTypeField currentValue="" />);
expectTextsInDocument(component, ['All']);
});

it('renders current value when available', () => {
const component = render(<TransactionTypeField currentValue="foo" />);
expectTextsInDocument(component, ['foo']);
});
});
});
12 changes: 9 additions & 3 deletions x-pack/plugins/apm/public/components/alerting/fields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ import { EuiSelectOption } from '@elastic/eui';
import { getEnvironmentLabel } from '../../../common/environment_filter_values';
import { PopoverExpression } from './ServiceAlertTrigger/PopoverExpression';

const ALL_OPTION = i18n.translate('xpack.apm.alerting.fields.all_option', {
defaultMessage: 'All',
});

export function ServiceField({ value }: { value?: string }) {
return (
<EuiExpression
description={i18n.translate('xpack.apm.alerting.fields.service', {
defaultMessage: 'Service',
})}
value={value}
value={value || ALL_OPTION}
/>
);
}
Expand Down Expand Up @@ -61,8 +65,10 @@ export function TransactionTypeField({
defaultMessage: 'Type',
});

if (!options || options.length === 1) {
return <EuiExpression description={label} value={currentValue} />;
if (!options || options.length <= 1) {
return (
<EuiExpression description={label} value={currentValue || ALL_OPTION} />
);
}

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/*
* 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 {
EuiButtonEmpty,
EuiContextMenu,
EuiContextMenuPanelDescriptor,
EuiPopover,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useState } from 'react';
import { AlertType } from '../../../../../common/alert_types';
import { useApmPluginContext } from '../../../../hooks/useApmPluginContext';
import { AlertingFlyout } from '../../../alerting/AlertingFlyout';

const alertLabel = i18n.translate('xpack.apm.home.alertsMenu.alerts', {
defaultMessage: 'Alerts',
});
const transactionDurationLabel = i18n.translate(
'xpack.apm.home.alertsMenu.transactionDuration',
{ defaultMessage: 'Transaction duration' }
);
const transactionErrorRateLabel = i18n.translate(
'xpack.apm.home.alertsMenu.transactionErrorRate',
{ defaultMessage: 'Transaction error rate' }
);
const errorCountLabel = i18n.translate('xpack.apm.home.alertsMenu.errorCount', {
defaultMessage: 'Error count',
});
const createThresholdAlertLabel = i18n.translate(
'xpack.apm.home.alertsMenu.createThresholdAlert',
{ defaultMessage: 'Create threshold alert' }
);
const createAnomalyAlertAlertLabel = i18n.translate(
'xpack.apm.home.alertsMenu.createAnomalyAlert',
{ defaultMessage: 'Create anomaly alert' }
);

const CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID =
'create_transaction_duration_panel';
const CREATE_TRANSACTION_ERROR_RATE_ALERT_PANEL_ID =
'create_transaction_error_rate_panel';
const CREATE_ERROR_COUNT_ALERT_PANEL_ID = 'create_error_count_panel';

interface Props {
canReadAlerts: boolean;
canSaveAlerts: boolean;
canReadAnomalies: boolean;
}

export function AlertIntegrations(props: Props) {
const { canSaveAlerts, canReadAlerts, canReadAnomalies } = props;

const plugin = useApmPluginContext();

const [popoverOpen, setPopoverOpen] = useState(false);

const [alertType, setAlertType] = useState<AlertType | null>(null);

const button = (
<EuiButtonEmpty
iconType="arrowDown"
iconSide="right"
onClick={() => setPopoverOpen(true)}
>
{alertLabel}
</EuiButtonEmpty>
);

const panels: EuiContextMenuPanelDescriptor[] = [
{
id: 0,
title: alertLabel,
items: [
...(canSaveAlerts
? [
{
name: transactionDurationLabel,
panel: CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID,
},
{
name: transactionErrorRateLabel,
panel: CREATE_TRANSACTION_ERROR_RATE_ALERT_PANEL_ID,
},
{
name: errorCountLabel,
panel: CREATE_ERROR_COUNT_ALERT_PANEL_ID,
},
]
: []),
...(canReadAlerts
? [
{
name: i18n.translate(
'xpack.apm.home.alertsMenu.viewActiveAlerts',
{ defaultMessage: 'View active alerts' }
),
href: plugin.core.http.basePath.prepend(
'/app/management/insightsAndAlerting/triggersActions/alerts'
),
icon: 'tableOfContents',
},
]
: []),
],
},

// transaction duration panel
{
id: CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID,
title: transactionDurationLabel,
items: [
// anomaly alerts
...(canReadAnomalies
? [
{
name: createAnomalyAlertAlertLabel,
onClick: () => {
setAlertType(AlertType.TransactionDurationAnomaly);
setPopoverOpen(false);
},
},
]
: []),
],
},

// transaction error rate panel
{
id: CREATE_TRANSACTION_ERROR_RATE_ALERT_PANEL_ID,
title: transactionErrorRateLabel,
items: [
// threshold alerts
{
name: createThresholdAlertLabel,
onClick: () => {
setAlertType(AlertType.TransactionErrorRate);
setPopoverOpen(false);
},
},
],
},

// error alerts panel
{
id: CREATE_ERROR_COUNT_ALERT_PANEL_ID,
title: errorCountLabel,
items: [
{
name: createThresholdAlertLabel,
onClick: () => {
setAlertType(AlertType.ErrorCount);
setPopoverOpen(false);
},
},
],
},
];

return (
<>
<EuiPopover
id="integrations-menu"
button={button}
isOpen={popoverOpen}
closePopover={() => setPopoverOpen(false)}
panelPaddingSize="none"
anchorPosition="downRight"
>
<EuiContextMenu initialPanelId={0} panels={panels} />
</EuiPopover>
<AlertingFlyout
alertType={alertType}
addFlyoutVisible={!!alertType}
setAddFlyoutVisibility={(visible) => {
if (!visible) {
setAlertType(null);
}
}}
/>
</>
);
}
21 changes: 20 additions & 1 deletion x-pack/plugins/apm/public/components/app/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { SetupInstructionsLink } from '../../shared/Links/SetupInstructionsLink'
import { ServiceMap } from '../ServiceMap';
import { ServiceOverview } from '../ServiceOverview';
import { TraceOverview } from '../TraceOverview';
import { AlertIntegrations } from './AlertIntegrations';
import { useAlertingIntegrations } from '../../../hooks/use_alerting_integrations';

function getHomeTabs({
serviceMapEnabled = true,
Expand Down Expand Up @@ -84,12 +86,20 @@ interface Props {

export function Home({ tab }: Props) {
const { config, core } = useApmPluginContext();
const canAccessML = !!core.application.capabilities.ml?.canAccessML;
const capabilities = core.application.capabilities;
const canAccessML = !!capabilities.ml?.canAccessML;
const homeTabs = getHomeTabs(config);
const selectedTab = homeTabs.find(
(homeTab) => homeTab.name === tab
) as $ElementType<typeof homeTabs, number>;

const {
isAlertingAvailable,
canReadAlerts,
canSaveAlerts,
canReadAnomalies,
} = useAlertingIntegrations();

return (
<div>
<ApmHeader>
Expand All @@ -106,6 +116,15 @@ export function Home({ tab }: Props) {
</EuiButtonEmpty>
</SettingsLink>
</EuiFlexItem>
{isAlertingAvailable && (
<EuiFlexItem grow={false}>
<AlertIntegrations
canReadAlerts={canReadAlerts}
canSaveAlerts={canSaveAlerts}
canReadAnomalies={canReadAnomalies}
/>
</EuiFlexItem>
)}
{canAccessML && (
<EuiFlexItem grow={false}>
<AnomalyDetectionSetupLink />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
import {
EuiButtonEmpty,
EuiContextMenu,
EuiPopover,
EuiContextMenuPanelDescriptor,
EuiPopover,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useState } from 'react';
import { AlertType } from '../../../../../common/alert_types';
import { AlertingFlyout } from './AlertingFlyout';
import { useApmPluginContext } from '../../../../hooks/useApmPluginContext';
import { AlertingFlyout } from '../../../alerting/AlertingFlyout';

const alertLabel = i18n.translate(
'xpack.apm.serviceDetails.alertsMenu.alerts',
Expand Down
Loading