Skip to content

Commit

Permalink
Put APM links into header action menu (#82292) (#83137)
Browse files Browse the repository at this point in the history
  • Loading branch information
smith committed Nov 11, 2020
1 parent 4e3db8e commit bd01a46
Show file tree
Hide file tree
Showing 34 changed files with 384 additions and 553 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
*/

import {
EuiButtonEmpty,
EuiContextMenu,
EuiContextMenuPanelDescriptor,
EuiHeaderLink,
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';
import { IBasePath } from '../../../../../../src/core/public';
import { AlertType } from '../../../common/alert_types';
import { AlertingFlyout } from '../../components/alerting/AlertingFlyout';

const alertLabel = i18n.translate('xpack.apm.home.alertsMenu.alerts', {
defaultMessage: 'Alerts',
Expand Down Expand Up @@ -46,28 +46,32 @@ const CREATE_TRANSACTION_ERROR_RATE_ALERT_PANEL_ID =
const CREATE_ERROR_COUNT_ALERT_PANEL_ID = 'create_error_count_panel';

interface Props {
basePath: IBasePath;
canReadAlerts: boolean;
canSaveAlerts: boolean;
canReadAnomalies: boolean;
includeTransactionDuration: boolean;
}

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

const plugin = useApmPluginContext();

export function AlertingPopoverAndFlyout({
basePath,
canSaveAlerts,
canReadAlerts,
canReadAnomalies,
includeTransactionDuration,
}: Props) {
const [popoverOpen, setPopoverOpen] = useState(false);

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

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

const panels: EuiContextMenuPanelDescriptor[] = [
Expand Down Expand Up @@ -98,7 +102,7 @@ export function AlertingPopoverAndFlyout(props: Props) {
'xpack.apm.home.alertsMenu.viewActiveAlerts',
{ defaultMessage: 'View active alerts' }
),
href: plugin.core.http.basePath.prepend(
href: basePath.prepend(
'/app/management/insightsAndAlerting/triggersActions/alerts'
),
icon: 'tableOfContents',
Expand All @@ -113,6 +117,19 @@ export function AlertingPopoverAndFlyout(props: Props) {
id: CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID,
title: transactionDurationLabel,
items: [
// threshold alerts
...(includeTransactionDuration
? [
{
name: createThresholdAlertLabel,
onClick: () => {
setAlertType(AlertType.TransactionDuration);
setPopoverOpen(false);
},
},
]
: []),

// anomaly alerts
...(canReadAnomalies
? [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { MissingJobsAlert } from './AnomalyDetectionSetupLink';
import * as hooks from '../../../../hooks/useFetcher';
import { MissingJobsAlert } from './anomaly_detection_setup_link';
import * as hooks from '../../hooks/useFetcher';

async function renderTooltipAnchor({
jobs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@
* 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 { EuiButtonEmpty, EuiToolTip, EuiIcon } from '@elastic/eui';
import {
EuiHeaderLink,
EuiIcon,
EuiLoadingSpinner,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useApmPluginContext } from '../../../../hooks/useApmPluginContext';
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
import { APMLink } from './APMLink';
import React from 'react';
import {
ENVIRONMENT_ALL,
getEnvironmentLabel,
} from '../../../../../common/environment_filter_values';
import { useUrlParams } from '../../../../hooks/useUrlParams';
import { useFetcher, FETCH_STATUS } from '../../../../hooks/useFetcher';
import { useLicense } from '../../../../hooks/useLicense';
} from '../../../common/environment_filter_values';
import { getAPMHref } from '../../components/shared/Links/apm/APMLink';
import { useApmPluginContext } from '../../hooks/useApmPluginContext';
import { FETCH_STATUS, useFetcher } from '../../hooks/useFetcher';
import { useLicense } from '../../hooks/useLicense';
import { useUrlParams } from '../../hooks/useUrlParams';
import { APIReturnType } from '../../services/rest/createCallApmApi';
import { units } from '../../style/variables';

export type AnomalyDetectionApiResponse = APIReturnType<
'/api/apm/settings/anomaly-detection',
Expand All @@ -27,24 +33,27 @@ const DEFAULT_DATA = { jobs: [], hasLegacyJobs: false };
export function AnomalyDetectionSetupLink() {
const { uiFilters } = useUrlParams();
const environment = uiFilters.environment;
const plugin = useApmPluginContext();
const canGetJobs = !!plugin.core.application.capabilities.ml?.canGetJobs;
const { core } = useApmPluginContext();
const canGetJobs = !!core.application.capabilities.ml?.canGetJobs;
const license = useLicense();
const hasValidLicense = license?.isActive && license?.hasAtLeast('platinum');
const { basePath } = core.http;

return (
<APMLink
path="/settings/anomaly-detection"
<EuiHeaderLink
color="primary"
href={getAPMHref({ basePath, path: '/settings/anomaly-detection' })}
style={{ whiteSpace: 'nowrap' }}
>
<EuiButtonEmpty size="s" color="primary" iconType="inspect">
{ANOMALY_DETECTION_LINK_LABEL}
</EuiButtonEmpty>

{canGetJobs && hasValidLicense ? (
<MissingJobsAlert environment={environment} />
) : null}
</APMLink>
) : (
<EuiIcon type="inspect" color="primary" />
)}
<span style={{ marginInlineStart: units.half }}>
{ANOMALY_DETECTION_LINK_LABEL}
</span>
</EuiHeaderLink>
);
}

Expand All @@ -56,24 +65,30 @@ export function MissingJobsAlert({ environment }: { environment?: string }) {
{ preservePreviousData: false, showToastOnError: false }
);

const defaultIcon = <EuiIcon type="inspect" color="primary" />;

if (status === FETCH_STATUS.LOADING) {
return <EuiLoadingSpinner />;
}

if (status !== FETCH_STATUS.SUCCESS) {
return null;
return defaultIcon;
}

const isEnvironmentSelected =
environment && environment !== ENVIRONMENT_ALL.value;

// there are jobs for at least one environment
if (!isEnvironmentSelected && data.jobs.length > 0) {
return null;
return defaultIcon;
}

// there are jobs for the selected environment
if (
isEnvironmentSelected &&
data.jobs.some((job) => environment === job.environment)
) {
return null;
return defaultIcon;
}

return (
Expand Down
72 changes: 72 additions & 0 deletions x-pack/plugins/apm/public/application/action_menu/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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 { EuiHeaderLink, EuiHeaderLinks } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { useParams } from 'react-router-dom';
import { getAlertingCapabilities } from '../../components/alerting/get_alert_capabilities';
import { getAPMHref } from '../../components/shared/Links/apm/APMLink';
import { useApmPluginContext } from '../../hooks/useApmPluginContext';
import { AlertingPopoverAndFlyout } from './alerting_popover_flyout';
import { AnomalyDetectionSetupLink } from './anomaly_detection_setup_link';

export function ActionMenu() {
const { core, plugins } = useApmPluginContext();
const { serviceName } = useParams<{ serviceName?: string }>();
const { search } = window.location;
const { application, http } = core;
const { basePath } = http;
const { capabilities } = application;
const canAccessML = !!capabilities.ml?.canAccessML;
const {
isAlertingAvailable,
canReadAlerts,
canSaveAlerts,
canReadAnomalies,
} = getAlertingCapabilities(plugins, capabilities);

function apmHref(path: string) {
return getAPMHref({ basePath, path, search });
}

function kibanaHref(path: string) {
return basePath.prepend(path);
}

return (
<EuiHeaderLinks>
<EuiHeaderLink
color="primary"
href={apmHref('/settings')}
iconType="gear"
>
{i18n.translate('xpack.apm.settingsLinkLabel', {
defaultMessage: 'Settings',
})}
</EuiHeaderLink>
{isAlertingAvailable && (
<AlertingPopoverAndFlyout
basePath={basePath}
canReadAlerts={canReadAlerts}
canSaveAlerts={canSaveAlerts}
canReadAnomalies={canReadAnomalies}
includeTransactionDuration={serviceName !== undefined}
/>
)}
{canAccessML && <AnomalyDetectionSetupLink />}
<EuiHeaderLink
color="primary"
href={kibanaHref('/app/home#/tutorial/apm')}
iconType="indexOpen"
>
{i18n.translate('xpack.apm.addDataButtonLabel', {
defaultMessage: 'Add data',
})}
</EuiHeaderLink>
</EuiHeaderLinks>
);
}
1 change: 1 addition & 0 deletions x-pack/plugins/apm/public/application/application.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ describe('renderApp', () => {
const params = {
element: document.createElement('div'),
history: createMemoryHistory(),
setHeaderActionMenu: () => {},
};
jest.spyOn(window, 'scrollTo').mockReturnValueOnce(undefined);
createCallApmApi((core.http as unknown) as HttpSetup);
Expand Down
12 changes: 8 additions & 4 deletions x-pack/plugins/apm/public/application/csmApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,23 @@ function CsmApp() {
}

export function CsmAppRoot({
appMountParameters,
core,
deps,
history,
config,
corePlugins: { embeddable },
}: {
appMountParameters: AppMountParameters;
core: CoreStart;
deps: ApmPluginSetupDeps;
history: AppMountParameters['history'];
config: ConfigSchema;
corePlugins: ApmPluginStartDeps;
}) {
const { history } = appMountParameters;
const i18nCore = core.i18n;
const plugins = deps;
const apmPluginContextValue = {
appMountParameters,
config,
core,
plugins,
Expand Down Expand Up @@ -109,10 +111,12 @@ export function CsmAppRoot({
export const renderApp = (
core: CoreStart,
deps: ApmPluginSetupDeps,
{ element, history }: AppMountParameters,
appMountParameters: AppMountParameters,
config: ConfigSchema,
corePlugins: ApmPluginStartDeps
) => {
const { element } = appMountParameters;

createCallApmApi(core.http);

// Automatically creates static index pattern and stores as saved object
Expand All @@ -123,9 +127,9 @@ export const renderApp = (

ReactDOM.render(
<CsmAppRoot
appMountParameters={appMountParameters}
core={core}
deps={deps}
history={history}
config={config}
corePlugins={corePlugins}
/>,
Expand Down
Loading

0 comments on commit bd01a46

Please sign in to comment.