Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
31cf82d
[APM] Alert counts in Service groups (#143613)
ogupte Nov 2, 2022
4afd7dd
cleanup
ogupte Nov 2, 2022
63a2e88
- moves dynamic: true mapping to specific labels field rather than to…
ogupte Nov 3, 2022
a5c5799
combined service group services and alerts count into 1 api call
ogupte Nov 3, 2022
96fd200
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Nov 4, 2022
d539d87
fixes linting errors
ogupte Nov 4, 2022
d9bb604
PR feedback
ogupte Nov 4, 2022
a88c08b
addresses feedback:
ogupte Nov 9, 2022
d190927
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Nov 9, 2022
c849a3d
querying with the alertsClient.find rather than esClient directly
ogupte Nov 10, 2022
31b728b
PR feedback
ogupte Nov 10, 2022
a1f559e
joins multiple alerts indices
ogupte Nov 10, 2022
ea5742b
adds stubs to for API tests
ogupte Nov 10, 2022
2dbee54
PR feedback and adds API tests
ogupte Nov 10, 2022
87a810e
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Nov 10, 2022
1c59715
- improves API tests for service group counts
ogupte Nov 11, 2022
f77850b
Merge branch 'main' into apm-143613-service-groups-alert-count
MiriamAparicio Nov 16, 2022
c3ed19d
fix apmEventClient user
MiriamAparicio Nov 17, 2022
59301eb
fix conflicts
MiriamAparicio Nov 23, 2022
7405cfc
fix conflicts
MiriamAparicio Nov 30, 2022
d5ce90e
abort if alert indices are not found
MiriamAparicio Nov 30, 2022
6e774c4
Merge remote-tracking branch 'upstream/main' into apm-143613-service-…
MiriamAparicio Dec 1, 2022
c7f64ff
PR review comments
MiriamAparicio Dec 2, 2022
011d8c6
Merge remote-tracking branch 'upstream/main' into apm-143613-service-…
MiriamAparicio Dec 2, 2022
71a92da
fix promise return
MiriamAparicio Dec 2, 2022
17ec246
Merge remote-tracking branch 'upstream/main' into apm-143613-service-…
MiriamAparicio Dec 12, 2022
0085041
small fix
MiriamAparicio Dec 12, 2022
ac36646
Merge branch 'main' into apm-143613-service-groups-alert-count
MiriamAparicio Dec 12, 2022
9a525f5
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Dec 12, 2022
2de8ac1
Merge branch 'main' into apm-143613-service-groups-alert-count
MiriamAparicio Dec 12, 2022
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 @@ -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';
Comment thread
ogupte marked this conversation as resolved.
Outdated
// 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;</>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really needed? Can we just render the translation if serviceCountis defined?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was in place for 8.5 in order to avoid a jump in height after service counts were loaded. But we may want to re-think the loading state for these cards of service groups.

) : (
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
Comment thread
ogupte marked this conversation as resolved.
Outdated
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: {
Comment thread
yngrdyn marked this conversation as resolved.
Outdated
type: 'object',
dynamic: true,
Comment thread
ogupte marked this conversation as resolved.
Outdated
},
},
'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)) {
Comment thread
sorenlouv marked this conversation as resolved.
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