mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
feat(slo): group by instance id (#186131)
This commit is contained in:
parent
328187b4f8
commit
c0b65d605c
8 changed files with 225 additions and 27 deletions
|
@ -12,6 +12,7 @@ const groupBySchema = t.union([
|
|||
t.literal('slo.tags'),
|
||||
t.literal('status'),
|
||||
t.literal('slo.indicator.type'),
|
||||
t.literal('slo.instanceId'),
|
||||
t.literal('_index'),
|
||||
]);
|
||||
|
||||
|
|
|
@ -81,6 +81,7 @@ const groupSummarySchema = t.type({
|
|||
id: t.string,
|
||||
instanceId: t.string,
|
||||
name: t.string,
|
||||
groupings: t.record(t.string, t.unknown),
|
||||
}),
|
||||
}),
|
||||
violated: t.number,
|
||||
|
|
|
@ -27,12 +27,12 @@ import React, { memo, useState } from 'react';
|
|||
import { paths } from '../../../../../common/locators/paths';
|
||||
import { useFetchSloList } from '../../../../hooks/use_fetch_slo_list';
|
||||
import { useKibana } from '../../../../utils/kibana_react';
|
||||
import { SLI_OPTIONS } from '../../../slo_edit/constants';
|
||||
import { useSloFormattedSLIValue } from '../../hooks/use_slo_summary';
|
||||
import type { SortDirection, SortField } from '../../hooks/use_url_search_state';
|
||||
import { SlosView } from '../slos_view';
|
||||
import { GroupByField } from '../slo_list_group_by';
|
||||
import { SLOView } from '../toggle_slo_view';
|
||||
import { useGroupName } from './hooks/use_group_name';
|
||||
|
||||
interface Props {
|
||||
group: string;
|
||||
|
@ -57,26 +57,7 @@ export function GroupListView({
|
|||
}: Props) {
|
||||
const groupQuery = `"${groupBy}": "${group}"`;
|
||||
const query = kqlQuery ? `${groupQuery} and ${kqlQuery}` : groupQuery;
|
||||
let groupName = group.toLowerCase();
|
||||
if (groupBy === 'slo.indicator.type') {
|
||||
groupName = SLI_OPTIONS.find((option) => option.value === group)?.text ?? group;
|
||||
}
|
||||
if (groupBy === '_index') {
|
||||
// get remote cluster name from index name
|
||||
if (groupName.includes(':.')) {
|
||||
const [remoteClusterName] = groupName.split(':.');
|
||||
groupName = i18n.translate('xpack.slo.group.remoteCluster', {
|
||||
defaultMessage: 'Remote Cluster: {remoteClusterName}',
|
||||
values: {
|
||||
remoteClusterName,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
groupName = i18n.translate('xpack.slo.group.remoteCluster.localKibana', {
|
||||
defaultMessage: 'Local Kibana',
|
||||
});
|
||||
}
|
||||
}
|
||||
const groupName = useGroupName(groupBy, group, summary);
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const [accordionState, setAccordionState] = useState<'open' | 'closed'>('closed');
|
||||
|
|
|
@ -105,17 +105,65 @@ describe('Group View', () => {
|
|||
{
|
||||
group: 'production',
|
||||
groupBy: 'slo.tags',
|
||||
summary: { total: 3, worst: 0.95, healthy: 2, violated: 1, degrading: 0, noData: 0 },
|
||||
summary: {
|
||||
total: 3,
|
||||
worst: {
|
||||
sliValue: 1,
|
||||
status: 'healthy',
|
||||
slo: {
|
||||
id: 'irrelevant',
|
||||
instanceId: 'irrelevant',
|
||||
name: 'irrelevant',
|
||||
groupings: {},
|
||||
},
|
||||
},
|
||||
healthy: 2,
|
||||
violated: 1,
|
||||
degrading: 0,
|
||||
noData: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'something',
|
||||
groupBy: 'slo.tags',
|
||||
summary: { total: 1, worst: 0.9, healthy: 0, violated: 1, degrading: 0, noData: 0 },
|
||||
summary: {
|
||||
total: 1,
|
||||
worst: {
|
||||
sliValue: 1,
|
||||
status: 'healthy',
|
||||
slo: {
|
||||
id: 'irrelevant',
|
||||
instanceId: 'irrelevant',
|
||||
name: 'irrelevant',
|
||||
groupings: {},
|
||||
},
|
||||
},
|
||||
healthy: 0,
|
||||
violated: 1,
|
||||
degrading: 0,
|
||||
noData: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'anything',
|
||||
groupBy: 'slo.tags',
|
||||
summary: { total: 2, worst: 0.85, healthy: 1, violated: 0, degrading: 0, noData: 1 },
|
||||
summary: {
|
||||
total: 2,
|
||||
worst: {
|
||||
sliValue: 1,
|
||||
status: 'healthy',
|
||||
slo: {
|
||||
id: 'irrelevant',
|
||||
instanceId: 'irrelevant',
|
||||
name: 'irrelevant',
|
||||
groupings: {},
|
||||
},
|
||||
},
|
||||
healthy: 1,
|
||||
violated: 0,
|
||||
degrading: 0,
|
||||
noData: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -163,7 +211,7 @@ describe('Group View', () => {
|
|||
results: [
|
||||
{
|
||||
group: 'production',
|
||||
groupBy: 'tags',
|
||||
groupBy: 'slo.tags',
|
||||
summary: { total: 3, worst: 0.95, healthy: 2, violated: 1, degrading: 0, noData: 0 },
|
||||
},
|
||||
],
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 { SLO_SUMMARY_DESTINATION_INDEX_PATTERN } from '../../../../../../common/constants';
|
||||
import { GroupSummary } from '@kbn/slo-schema';
|
||||
import { useGroupName } from './use_group_name';
|
||||
|
||||
describe('useGroupName', () => {
|
||||
it('returns the group name for ungrouped', () => {
|
||||
const groupName = useGroupName('ungrouped', 'irrelevant', {} as GroupSummary);
|
||||
expect(groupName).toBe('irrelevant');
|
||||
});
|
||||
|
||||
it('returns the group name for slo tags', () => {
|
||||
const groupName = useGroupName('slo.tags', 'some-tag', {} as GroupSummary);
|
||||
expect(groupName).toBe('some-tag');
|
||||
});
|
||||
|
||||
it('returns the group name for status', () => {
|
||||
const groupName = useGroupName('status', 'HEALTHY', {} as GroupSummary);
|
||||
expect(groupName).toBe('healthy');
|
||||
});
|
||||
|
||||
it('returns the group name for slo instanceId', () => {
|
||||
const summary = {
|
||||
total: 2,
|
||||
violated: 0,
|
||||
healthy: 2,
|
||||
degrading: 0,
|
||||
noData: 0,
|
||||
worst: {
|
||||
sliValue: 1,
|
||||
status: 'healthy',
|
||||
slo: {
|
||||
id: 'slo-id',
|
||||
name: 'slo-name',
|
||||
instanceId: 'domain.com',
|
||||
groupings: {
|
||||
host: {
|
||||
name: 'domain.com',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as GroupSummary;
|
||||
const groupName = useGroupName('slo.instanceId', 'domain.com', summary);
|
||||
expect(groupName).toBe('host.name: domain.com');
|
||||
});
|
||||
|
||||
it('returns the group name for slo indicator type', () => {
|
||||
const groupName = useGroupName('slo.indicator.type', 'sli.kql.custom', {} as GroupSummary);
|
||||
expect(groupName).toBe('Custom Query');
|
||||
});
|
||||
|
||||
it('returns the group name for local index', () => {
|
||||
const groupName = useGroupName(
|
||||
'_index',
|
||||
SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
|
||||
{} as GroupSummary
|
||||
);
|
||||
expect(groupName).toBe('Local Kibana');
|
||||
});
|
||||
|
||||
it('returns the group name for remote index', () => {
|
||||
const groupName = useGroupName(
|
||||
'_index',
|
||||
`my-remote-cluster:${SLO_SUMMARY_DESTINATION_INDEX_PATTERN}`,
|
||||
{} as GroupSummary
|
||||
);
|
||||
expect(groupName).toBe('Remote Cluster: my-remote-cluster');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { ALL_VALUE, GroupSummary } from '@kbn/slo-schema';
|
||||
import { assertNever } from '@kbn/std';
|
||||
import { SLI_OPTIONS } from '../../../../slo_edit/constants';
|
||||
import { GroupByField } from '../../slo_list_group_by';
|
||||
|
||||
export function useGroupName(groupBy: GroupByField, group: string, summary?: GroupSummary) {
|
||||
const groupName = group.toLowerCase();
|
||||
|
||||
switch (groupBy) {
|
||||
case 'ungrouped':
|
||||
case 'slo.tags':
|
||||
case 'status':
|
||||
return groupName;
|
||||
case 'slo.instanceId':
|
||||
if (groupName === ALL_VALUE || !summary) {
|
||||
return i18n.translate('xpack.slo.group.ungroupedInstanceId', {
|
||||
defaultMessage: 'Ungrouped',
|
||||
});
|
||||
}
|
||||
|
||||
const groupNames = flattenObject(summary.worst.slo.groupings);
|
||||
return Object.entries(groupNames)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join(', ');
|
||||
case 'slo.indicator.type':
|
||||
return SLI_OPTIONS.find((option) => option.value === group)?.text ?? groupName;
|
||||
case '_index':
|
||||
if (groupName.includes(':.')) {
|
||||
const [remoteClusterName] = groupName.split(':.');
|
||||
return i18n.translate('xpack.slo.group.remoteCluster', {
|
||||
defaultMessage: 'Remote Cluster: {remoteClusterName}',
|
||||
values: {
|
||||
remoteClusterName,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return i18n.translate('xpack.slo.group.remoteCluster.localKibana', {
|
||||
defaultMessage: 'Local Kibana',
|
||||
});
|
||||
|
||||
default:
|
||||
assertNever(groupBy);
|
||||
}
|
||||
}
|
||||
|
||||
function flattenObject(obj: Record<string, any>, parentKey = '', result: Record<string, any> = {}) {
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
const newKey = parentKey ? `${parentKey}.${key}` : key;
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
flattenObject(obj[key], newKey, result);
|
||||
} else {
|
||||
result[newKey] = obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -13,7 +13,13 @@ import type { SearchState } from '../hooks/use_url_search_state';
|
|||
import type { Option } from './slo_context_menu';
|
||||
import { ContextMenuItem, SLOContextMenu } from './slo_context_menu';
|
||||
|
||||
export type GroupByField = 'ungrouped' | 'slo.tags' | 'status' | 'slo.indicator.type' | '_index';
|
||||
export type GroupByField =
|
||||
| 'ungrouped'
|
||||
| 'slo.tags'
|
||||
| 'status'
|
||||
| 'slo.indicator.type'
|
||||
| 'slo.instanceId'
|
||||
| '_index';
|
||||
export interface Props {
|
||||
onStateChange: (newState: Partial<SearchState>) => void;
|
||||
state: SearchState;
|
||||
|
@ -80,6 +86,16 @@ export function SloGroupBy({ onStateChange, state, loading }: Props) {
|
|||
handleChangeGroupBy('slo.indicator.type');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.slo.list.groupBy.sloInstanceId', {
|
||||
defaultMessage: 'SLO instance id',
|
||||
}),
|
||||
checked: groupBy === 'slo.instanceId',
|
||||
value: 'slo.instanceId',
|
||||
onClick: () => {
|
||||
handleChangeGroupBy('slo.instanceId');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (hasRemoteEnabled) {
|
||||
|
|
|
@ -87,7 +87,14 @@ export class FindSLOGroups {
|
|||
},
|
||||
},
|
||||
_source: {
|
||||
includes: ['sliValue', 'status', 'slo.id', 'slo.instanceId', 'slo.name'],
|
||||
includes: [
|
||||
'sliValue',
|
||||
'status',
|
||||
'slo.id',
|
||||
'slo.instanceId',
|
||||
'slo.name',
|
||||
'slo.groupings',
|
||||
],
|
||||
},
|
||||
size: 1,
|
||||
},
|
||||
|
@ -165,6 +172,7 @@ export class FindSLOGroups {
|
|||
id: sourceSummaryDoc.slo.id,
|
||||
instanceId: sourceSummaryDoc.slo.instanceId,
|
||||
name: sourceSummaryDoc.slo.name,
|
||||
groupings: sourceSummaryDoc.slo.groupings,
|
||||
},
|
||||
},
|
||||
violated: bucket.violated?.doc_count,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue