Skip to content

Commit

Permalink
[APM] Alert counts in Service groups (#144484)
Browse files Browse the repository at this point in the history
  • Loading branch information
4 people authored Dec 12, 2022
1 parent 80a6e6c commit 5ae469d
Show file tree
Hide file tree
Showing 18 changed files with 630 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function ServerlessSummary({ serverlessId }: Props) {
<EuiFlexItem grow={false}>
<EuiLink href="https://ela.st/feedback-aws-lambda" target="_blank">
{i18n.translate('xpack.apm.serverlessMetrics.summary.feedback', {
defaultMessage: 'Send feedback',
defaultMessage: 'Give feedback',
})}
</EuiLink>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,21 @@
import { EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
// import { ServiceGroupsTour } from '../service_groups_tour';
// import { useServiceGroupsTour } from '../use_service_groups_tour';

interface Props {
onClick: () => void;
}

export function CreateButton({ onClick }: Props) {
// const { tourEnabled, dismissTour } = useServiceGroupsTour('createGroup');
return (
// <ServiceGroupsTour
// tourEnabled={tourEnabled}
// dismissTour={dismissTour}
// title={i18n.translate('xpack.apm.serviceGroups.tour.createGroups.title', {
// defaultMessage: 'Introducing service groups',
// })}
// content={i18n.translate(
// 'xpack.apm.serviceGroups.tour.createGroups.content',
// {
// defaultMessage:
// 'Group services together to build curated inventory views that remove noise and simplify investigations across services. Groups are Kibana space-specific and available for any users with appropriate access.',
// }
// )}
// >
<EuiButton
iconType="plusInCircle"
data-test-subj="apmCreateServiceGroupButton"
onClick={() => {
// dismissTour();
onClick();
}}
onClick={onClick}
>
{i18n.translate('xpack.apm.serviceGroups.createGroupLabel', {
defaultMessage: 'Create group',
})}
</EuiButton>
// </ServiceGroupsTour>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isEmpty, sortBy } from 'lodash';
import React, { useState, useCallback, useMemo } from 'react';
import React, { useState, useCallback } from 'react';
import { isPending, useFetcher } from '../../../../hooks/use_fetcher';
import { ServiceGroupsListItems } from './service_groups_list';
import { Sort } from './sort';
import { RefreshServiceGroupsSubscriber } from '../refresh_service_groups_subscriber';
import { getDateRange } from '../../../../context/url_params_context/helpers';
import { ServiceGroupSaveButton } from '../service_group_save';
import { BetaBadge } from '../../../shared/beta_badge';

Expand All @@ -44,20 +43,13 @@ export function ServiceGroupsList() {

const { serviceGroups } = data;

const { start, end } = useMemo(
() => getDateRange({ rangeFrom: 'now-24h', rangeTo: 'now' }),
[]
);

const { data: servicesCountData = { servicesCounts: {} } } = useFetcher(
const { data: servicesGroupCounts = {} } = useFetcher(
(callApmApi) => {
if (start && end && serviceGroups.length) {
return callApmApi('GET /internal/apm/service_groups/services_count', {
params: { query: { start, end } },
});
if (serviceGroups.length) {
return callApmApi('GET /internal/apm/service-group/counts');
}
},
[start, end, serviceGroups.length]
[serviceGroups.length]
);

const isLoading = isPending(status);
Expand Down Expand Up @@ -188,7 +180,7 @@ export function ServiceGroupsList() {
>
{i18n.translate(
'xpack.apm.serviceGroups.beta.feedback.link',
{ defaultMessage: 'Send feedback' }
{ defaultMessage: 'Give feedback' }
)}
</EuiLink>
</EuiFlexItem>
Expand All @@ -199,7 +191,7 @@ export function ServiceGroupsList() {
items.length ? (
<ServiceGroupsListItems
items={items}
servicesCounts={servicesCountData.servicesCounts}
serviceGroupCounts={servicesGroupCounts}
isLoading={isLoading}
/>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,67 @@
*/
import {
EuiAvatar,
EuiBadge,
EuiCard,
EuiCardProps,
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiSpacer,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import {
ServiceGroup,
SERVICE_GROUP_COLOR_DEFAULT,
} from '../../../../../common/service_groups';
import { useObservabilityActiveAlertsHref } from '../../../shared/links/kibana';

interface Props {
serviceGroup: ServiceGroup;
hideServiceCount?: boolean;
onClick?: () => void;
href?: string;
servicesCount?: number;
serviceGroupCounts?: { services: number; alerts: number };
}

export function ServiceGroupsCard({
serviceGroup,
hideServiceCount = false,
onClick,
href,
servicesCount,
serviceGroupCounts,
}: Props) {
const activeAlertsHref = useObservabilityActiveAlertsHref(serviceGroup.kuery);
const cardProps: EuiCardProps = {
style: { width: 286 },
icon: (
<EuiAvatar
name={serviceGroup.groupName}
color={serviceGroup.color || SERVICE_GROUP_COLOR_DEFAULT}
size="l"
/>
<>
{serviceGroupCounts?.alerts && (
<div>
<EuiBadge
iconType="alert"
color="danger"
href={activeAlertsHref}
{...({
onClick(e: React.SyntheticEvent) {
e.stopPropagation(); // prevents extra click thru to EuiCard's href destination
},
} as object)} // workaround for type check that prevents href + onclick
>
{i18n.translate('xpack.apm.serviceGroups.cardsList.alertCount', {
defaultMessage:
'{alertsCount} {alertsCount, plural, one {alert} other {alerts}}',
values: { alertsCount: serviceGroupCounts.alerts },
})}
</EuiBadge>
<EuiSpacer size="s" />
</div>
)}
<EuiAvatar
name={serviceGroup.groupName}
color={serviceGroup.color || SERVICE_GROUP_COLOR_DEFAULT}
size="l"
/>
</>
),
title: serviceGroup.groupName,
description: (
Expand All @@ -58,15 +83,15 @@ export function ServiceGroupsCard({
{!hideServiceCount && (
<EuiFlexItem>
<EuiText size="s">
{servicesCount === undefined ? (
{serviceGroupCounts === undefined ? (
<>&nbsp;</>
) : (
i18n.translate(
'xpack.apm.serviceGroups.cardsList.serviceCount',
{
defaultMessage:
'{servicesCount} {servicesCount, plural, one {service} other {services}}',
values: { servicesCount },
values: { servicesCount: serviceGroupCounts.services },
}
)
)}
Expand All @@ -75,7 +100,6 @@ export function ServiceGroupsCard({
)}
</EuiFlexGroup>
),
onClick,
href,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import { ServiceGroupsCard } from './service_group_card';
import { useApmRouter } from '../../../../hooks/use_apm_router';
import { useApmParams } from '../../../../hooks/use_apm_params';
import { useDefaultEnvironment } from '../../../../hooks/use_default_environment';
import { APIReturnType } from '../../../../services/rest/create_call_apm_api';

interface Props {
items: SavedServiceGroup[];
servicesCounts: Record<string, number>;
serviceGroupCounts: APIReturnType<'GET /internal/apm/service-group/counts'>;
isLoading: boolean;
}

export function ServiceGroupsListItems({ items, servicesCounts }: Props) {
export function ServiceGroupsListItems({ items, serviceGroupCounts }: Props) {
const router = useApmRouter();
const { query } = useApmParams('/service-groups');

Expand All @@ -28,8 +29,9 @@ export function ServiceGroupsListItems({ items, servicesCounts }: Props) {
<EuiFlexGrid gutterSize="m">
{items.map((item) => (
<ServiceGroupsCard
key={item.id}
serviceGroup={item}
servicesCount={servicesCounts[item.id]}
serviceGroupCounts={serviceGroupCounts[item.id]}
href={router.link('/services', {
query: {
...query,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export function TipsAndResources() {
label: i18n.translate(
'xpack.apm.storageExplorer.resources.sendFeedback',
{
defaultMessage: 'Send feedback',
defaultMessage: 'Give feedback',
}
),
href: getStorageExplorerFeedbackHref(),
Expand Down
12 changes: 12 additions & 0 deletions x-pack/plugins/apm/public/components/shared/links/kibana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import rison from '@kbn/rison';
import { IBasePath } from '@kbn/core/public';
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';

Expand All @@ -31,3 +32,14 @@ export function useUpgradeApmPackagePolicyHref(packagePolicyId = '') {
`/app/fleet/policies/policy-elastic-agent-on-cloud/upgrade-package-policy/${packagePolicyId}?from=integrations-policy-list`
);
}

export function useObservabilityActiveAlertsHref(kuery: string) {
const {
core: {
http: { basePath },
},
} = useApmPluginContext();
return basePath.prepend(
`/app/observability/alerts?_a=${rison.encode({ kuery, status: 'active' })}`
);
}
12 changes: 12 additions & 0 deletions x-pack/plugins/apm/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ import {
SERVICE_ENVIRONMENT,
SERVICE_NAME,
TRANSACTION_TYPE,
AGENT_NAME,
SERVICE_LANGUAGE_NAME,
} from '../common/es_fields/apm';
import { tutorialProvider } from './tutorial';
import { migrateLegacyAPMIndicesToSpaceAware } from './saved_objects/migrations/migrate_legacy_apm_indices_to_space_aware';
Expand Down Expand Up @@ -132,6 +134,16 @@ export class APMPlugin
[PROCESSOR_EVENT]: {
type: 'keyword',
},
[AGENT_NAME]: {
type: 'keyword',
},
[SERVICE_LANGUAGE_NAME]: {
type: 'keyword',
},
labels: {
type: 'object',
dynamic: true,
},
},
'strict'
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 } from 'lodash';
import { APMRouteHandlerResources } from '../typings';

export type ApmAlertsClient = Awaited<ReturnType<typeof getApmAlertsClient>>;

export async function getApmAlertsClient({
plugins,
request,
}: APMRouteHandlerResources) {
const ruleRegistryPluginStart = await plugins.ruleRegistry.start();
const alertsClient = await ruleRegistryPluginStart.getRacClientWithRequest(
request
);
const apmAlertsIndices = await alertsClient.getAuthorizedAlertsIndices([
'apm',
]);

if (!apmAlertsIndices || isEmpty(apmAlertsIndices)) {
throw Error('No alert indices exist for "apm"');
}

type ApmAlertsClientSearchParams = Omit<
Parameters<typeof alertsClient.find>[0],
'index'
>;

return {
search(searchParams: ApmAlertsClientSearchParams) {
return alertsClient.find({
...searchParams,
index: apmAlertsIndices.join(','),
});
},
};
}
Loading

0 comments on commit 5ae469d

Please sign in to comment.