mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[APM] Fixes service count API and modal scroll (#141725)
* [APM] Fixes multiple parallel search calls for service count to one call with a filter agg (#141242) * updates the description text when saving a group * fixes scrolling issue in save group modal * adds descripion for service count time range in service group list * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Add comment about only aggregating over metrics documents. * Update x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts PR feedback Co-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com>
This commit is contained in:
parent
e0b486051a
commit
c71ab04eb5
8 changed files with 120 additions and 124 deletions
|
@ -36,7 +36,7 @@ const CentralizedContainer = styled.div`
|
|||
`;
|
||||
|
||||
const MAX_CONTAINER_HEIGHT = 600;
|
||||
const MODAL_HEADER_HEIGHT = 122;
|
||||
const MODAL_HEADER_HEIGHT = 180;
|
||||
const MODAL_FOOTER_HEIGHT = 80;
|
||||
|
||||
const suggestedFieldsWhitelist = [
|
||||
|
@ -118,10 +118,73 @@ export function SelectServices({
|
|||
'xpack.apm.serviceGroups.selectServicesForm.subtitle',
|
||||
{
|
||||
defaultMessage:
|
||||
'Use a query to select services for this group. Services that match this query within the last 24 hours will be assigned to the group.',
|
||||
'Use a query to select services for this group. The preview shows services that match this query within the last 24 hours.',
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<KueryBar
|
||||
placeholder={i18n.translate(
|
||||
'xpack.apm.serviceGroups.selectServicesForm.kql',
|
||||
{ defaultMessage: 'E.g. labels.team: "web"' }
|
||||
)}
|
||||
onSubmit={(value) => {
|
||||
setKuery(value);
|
||||
}}
|
||||
onChange={(value) => {
|
||||
setStagedKuery(value);
|
||||
}}
|
||||
value={kuery}
|
||||
suggestionFilter={(querySuggestion) => {
|
||||
if ('field' in querySuggestion) {
|
||||
const {
|
||||
field: {
|
||||
spec: { name: fieldName },
|
||||
},
|
||||
} = querySuggestion;
|
||||
|
||||
return (
|
||||
fieldName.startsWith('label') ||
|
||||
suggestedFieldsWhitelist.includes(fieldName)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={() => {
|
||||
setKuery(stagedKuery);
|
||||
}}
|
||||
iconType={!kuery ? 'search' : 'refresh'}
|
||||
isDisabled={isServiceListPreviewLoading || !stagedKuery}
|
||||
>
|
||||
{!kuery
|
||||
? i18n.translate(
|
||||
'xpack.apm.serviceGroups.selectServicesForm.preview',
|
||||
{ defaultMessage: 'Preview' }
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.apm.serviceGroups.selectServicesForm.refresh',
|
||||
{ defaultMessage: 'Refresh' }
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{kuery && data?.items && (
|
||||
<EuiText color="success" size="s">
|
||||
{i18n.translate(
|
||||
'xpack.apm.serviceGroups.selectServicesForm.matchingServiceCount',
|
||||
{
|
||||
defaultMessage:
|
||||
'{servicesCount} {servicesCount, plural, =0 {services} one {service} other {services}} match the query',
|
||||
values: { servicesCount: data?.items.length },
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
)}
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody
|
||||
|
@ -136,73 +199,6 @@ export function SelectServices({
|
|||
gutterSize="s"
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<KueryBar
|
||||
placeholder={i18n.translate(
|
||||
'xpack.apm.serviceGroups.selectServicesForm.kql',
|
||||
{ defaultMessage: 'E.g. labels.team: "web"' }
|
||||
)}
|
||||
onSubmit={(value) => {
|
||||
setKuery(value);
|
||||
}}
|
||||
onChange={(value) => {
|
||||
setStagedKuery(value);
|
||||
}}
|
||||
value={kuery}
|
||||
suggestionFilter={(querySuggestion) => {
|
||||
if ('field' in querySuggestion) {
|
||||
const {
|
||||
field: {
|
||||
spec: { name: fieldName },
|
||||
},
|
||||
} = querySuggestion;
|
||||
|
||||
return (
|
||||
fieldName.startsWith('label') ||
|
||||
suggestedFieldsWhitelist.includes(fieldName)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={() => {
|
||||
setKuery(stagedKuery);
|
||||
}}
|
||||
iconType={!kuery ? 'search' : 'refresh'}
|
||||
isDisabled={isServiceListPreviewLoading || !stagedKuery}
|
||||
>
|
||||
{!kuery
|
||||
? i18n.translate(
|
||||
'xpack.apm.serviceGroups.selectServicesForm.preview',
|
||||
{ defaultMessage: 'Preview' }
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.apm.serviceGroups.selectServicesForm.refresh',
|
||||
{ defaultMessage: 'Refresh' }
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{kuery && data?.items && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="success" size="s">
|
||||
{i18n.translate(
|
||||
'xpack.apm.serviceGroups.selectServicesForm.matchingServiceCount',
|
||||
{
|
||||
defaultMessage:
|
||||
'{servicesCount} {servicesCount, plural, =0 {services} one {service} other {services}} match the query',
|
||||
values: { servicesCount: data?.items.length },
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="s">
|
||||
{!kuery && (
|
||||
|
|
|
@ -37,7 +37,7 @@ type SORT_FIELD = 'serviceName' | 'environments' | 'agentName';
|
|||
|
||||
export function ServiceListPreview({ items, isLoading }: Props) {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(5);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [sortField, setSortField] = useState<SORT_FIELD>(DEFAULT_SORT_FIELD);
|
||||
const [sortDirection, setSortDirection] = useState<DIRECTION>(
|
||||
DEFAULT_SORT_DIRECTION
|
||||
|
|
|
@ -156,6 +156,12 @@ export function ServiceGroupsList() {
|
|||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiText color="subdued" size="s">
|
||||
{i18n.translate('xpack.apm.serviceGroups.listDescription', {
|
||||
defaultMessage:
|
||||
'Displayed service counts reflect the last 24 hours.',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{items.length ? (
|
||||
|
|
|
@ -7,50 +7,72 @@
|
|||
|
||||
import { ProcessorEvent } from '@kbn/observability-plugin/common';
|
||||
import { rangeQuery, kqlQuery } from '@kbn/observability-plugin/server';
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { Setup } from '../../lib/helpers/setup_request';
|
||||
import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames';
|
||||
import { SavedServiceGroup } from '../../../common/service_groups';
|
||||
|
||||
export async function getServicesCounts({
|
||||
setup,
|
||||
kuery,
|
||||
maxNumberOfServices,
|
||||
start,
|
||||
end,
|
||||
serviceGroups,
|
||||
}: {
|
||||
setup: Setup;
|
||||
kuery: string;
|
||||
maxNumberOfServices: number;
|
||||
start: number;
|
||||
end: number;
|
||||
serviceGroups: SavedServiceGroup[];
|
||||
}) {
|
||||
const { apmEventClient } = setup;
|
||||
|
||||
const response = await apmEventClient.search('get_services_count', {
|
||||
const serviceGroupsKueryMap: Record<string, QueryDslQueryContainer> =
|
||||
serviceGroups.reduce((acc, sg) => {
|
||||
return {
|
||||
...acc,
|
||||
[sg.id]: kqlQuery(sg.kuery)[0],
|
||||
};
|
||||
}, {});
|
||||
|
||||
const params = {
|
||||
apm: {
|
||||
events: [
|
||||
ProcessorEvent.metric,
|
||||
ProcessorEvent.transaction,
|
||||
ProcessorEvent.span,
|
||||
ProcessorEvent.error,
|
||||
],
|
||||
// We're limiting the service count to only metrics documents. If a user
|
||||
// actively disables system/app metrics and a service only ingests error
|
||||
// events, that service will not be included in the service groups count.
|
||||
// This is an edge case that only effects the count preview label.
|
||||
events: [ProcessorEvent.metric],
|
||||
},
|
||||
body: {
|
||||
track_total_hits: 0,
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [...rangeQuery(start, end), ...kqlQuery(kuery)],
|
||||
filter: rangeQuery(start, end),
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
services_count: {
|
||||
cardinality: {
|
||||
field: SERVICE_NAME,
|
||||
service_groups: {
|
||||
filters: {
|
||||
filters: serviceGroupsKueryMap,
|
||||
},
|
||||
aggs: {
|
||||
services_count: {
|
||||
cardinality: {
|
||||
field: SERVICE_NAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
const response = await apmEventClient.search('get_services_count', params);
|
||||
|
||||
return response?.aggregations?.services_count.value ?? 0;
|
||||
const buckets: Record<string, { services_count: { value: number } }> =
|
||||
response?.aggregations?.service_groups.buckets ?? {};
|
||||
return Object.keys(buckets).reduce((acc, key) => {
|
||||
return {
|
||||
...acc,
|
||||
[key]: buckets[key].services_count.value,
|
||||
};
|
||||
}, {});
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import * as t from 'io-ts';
|
||||
import { apmServiceGroupMaxNumberOfServices } from '@kbn/observability-plugin/common';
|
||||
import { keyBy, mapValues } from 'lodash';
|
||||
import { setupRequest } from '../../lib/helpers/setup_request';
|
||||
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
|
||||
import { kueryRt, rangeRt } from '../default_api_types';
|
||||
|
@ -52,50 +51,26 @@ const serviceGroupsWithServiceCountRoute = createApmServerRoute({
|
|||
const { context, params } = resources;
|
||||
const {
|
||||
savedObjects: { client: savedObjectsClient },
|
||||
uiSettings: { client: uiSettingsClient },
|
||||
} = await context.core;
|
||||
|
||||
const {
|
||||
query: { start, end },
|
||||
} = params;
|
||||
|
||||
const [setup, maxNumberOfServices] = await Promise.all([
|
||||
setupRequest(resources),
|
||||
uiSettingsClient.get<number>(apmServiceGroupMaxNumberOfServices),
|
||||
]);
|
||||
const setup = await setupRequest(resources);
|
||||
|
||||
const serviceGroups = await getServiceGroups({
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
const serviceGroupsWithServiceCount = await Promise.all(
|
||||
serviceGroups.map(
|
||||
async ({
|
||||
id,
|
||||
kuery,
|
||||
}): Promise<{ id: string; servicesCount: number }> => {
|
||||
const servicesCount = await getServicesCounts({
|
||||
setup,
|
||||
kuery,
|
||||
maxNumberOfServices,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
|
||||
return {
|
||||
id,
|
||||
servicesCount,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const servicesCounts = mapValues(
|
||||
keyBy(serviceGroupsWithServiceCount, 'id'),
|
||||
'servicesCount'
|
||||
);
|
||||
|
||||
return { servicesCounts };
|
||||
return {
|
||||
servicesCounts: await getServicesCounts({
|
||||
setup,
|
||||
serviceGroups,
|
||||
start,
|
||||
end,
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -7542,7 +7542,6 @@
|
|||
"xpack.apm.serviceGroups.selectServicesForm.preview": "Aperçu",
|
||||
"xpack.apm.serviceGroups.selectServicesForm.refresh": "Actualiser",
|
||||
"xpack.apm.serviceGroups.selectServicesForm.saveGroup": "Enregistrer le groupe",
|
||||
"xpack.apm.serviceGroups.selectServicesForm.subtitle": "Utilisez une requête pour sélectionner les services pour ce groupe. Les services qui correspondent à cette requête dans les dernières 24 heures seront affectés au groupe.",
|
||||
"xpack.apm.serviceGroups.selectServicesForm.title": "Sélectionner des services",
|
||||
"xpack.apm.serviceGroups.selectServicesList.environmentColumnLabel": "Environnements",
|
||||
"xpack.apm.serviceGroups.selectServicesList.nameColumnLabel": "Nom",
|
||||
|
|
|
@ -7529,7 +7529,6 @@
|
|||
"xpack.apm.serviceGroups.selectServicesForm.preview": "プレビュー",
|
||||
"xpack.apm.serviceGroups.selectServicesForm.refresh": "更新",
|
||||
"xpack.apm.serviceGroups.selectServicesForm.saveGroup": "グループを保存",
|
||||
"xpack.apm.serviceGroups.selectServicesForm.subtitle": "クエリを使用してこのグループのサービスを選択します。過去24時間以内にこのクエリと一致したサービスはグループに割り当てられます。",
|
||||
"xpack.apm.serviceGroups.selectServicesForm.title": "サービスを選択",
|
||||
"xpack.apm.serviceGroups.selectServicesList.environmentColumnLabel": "環境",
|
||||
"xpack.apm.serviceGroups.selectServicesList.nameColumnLabel": "名前",
|
||||
|
|
|
@ -7546,7 +7546,6 @@
|
|||
"xpack.apm.serviceGroups.selectServicesForm.preview": "预览",
|
||||
"xpack.apm.serviceGroups.selectServicesForm.refresh": "刷新",
|
||||
"xpack.apm.serviceGroups.selectServicesForm.saveGroup": "保存组",
|
||||
"xpack.apm.serviceGroups.selectServicesForm.subtitle": "使用查询为该组选择服务。过去 24 小时内与此查询匹配的服务将分配给该组。",
|
||||
"xpack.apm.serviceGroups.selectServicesForm.title": "选择服务",
|
||||
"xpack.apm.serviceGroups.selectServicesList.environmentColumnLabel": "环境",
|
||||
"xpack.apm.serviceGroups.selectServicesList.nameColumnLabel": "名称",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue