Skip to content

Commit

Permalink
[Inventory][ECO] Show alerts for entities (#195250)
Browse files Browse the repository at this point in the history
## Summary

Show alerts related to entities

close #194381 

### Checklist

- change default sorting from last seen to alertsCount
- when alertsCount is not available server side sorting fallbacks to
last seen
- [Change app route from /app/observability/inventory to
/app/inventory](57598d0)
(causing issue when importing observability plugin
- refactoring: move columns into seperate file 




https://github.com/user-attachments/assets/ea3abc5a-0581-41e7-a174-6655a39c1133



### How to test
- run any synthtrace scenario ex`node scripts/synthtrace
infra_hosts_with_apm_hosts.ts`
- create a rule (SLO or apm) 
- click on the alert count

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Cauê Marcondes <[email protected]>
  • Loading branch information
3 people authored Oct 15, 2024
1 parent 562bf21 commit c0bd82b
Show file tree
Hide file tree
Showing 25 changed files with 1,056 additions and 105 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/*
* 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 { joinByKey } from './join_by_key';

describe('joinByKey', () => {
it('joins by a string key', () => {
const joined = joinByKey(
[
{
serviceName: 'opbeans-node',
avg: 10,
},
{
serviceName: 'opbeans-node',
count: 12,
},
{
serviceName: 'opbeans-java',
avg: 11,
},
{
serviceName: 'opbeans-java',
p95: 18,
},
],
'serviceName'
);

expect(joined.length).toBe(2);

expect(joined).toEqual([
{
serviceName: 'opbeans-node',
avg: 10,
count: 12,
},
{
serviceName: 'opbeans-java',
avg: 11,
p95: 18,
},
]);
});

it('joins by a record key', () => {
const joined = joinByKey(
[
{
key: {
serviceName: 'opbeans-node',
transactionName: '/api/opbeans-node',
},
avg: 10,
},
{
key: {
serviceName: 'opbeans-node',
transactionName: '/api/opbeans-node',
},
count: 12,
},
{
key: {
serviceName: 'opbeans-java',
transactionName: '/api/opbeans-java',
},
avg: 11,
},
{
key: {
serviceName: 'opbeans-java',
transactionName: '/api/opbeans-java',
},
p95: 18,
},
],
'key'
);

expect(joined.length).toBe(2);

expect(joined).toEqual([
{
key: {
serviceName: 'opbeans-node',
transactionName: '/api/opbeans-node',
},
avg: 10,
count: 12,
},
{
key: {
serviceName: 'opbeans-java',
transactionName: '/api/opbeans-java',
},
avg: 11,
p95: 18,
},
]);
});

it('joins by multiple keys', () => {
const data = [
{
serviceName: 'opbeans-node',
environment: 'production',
type: 'service',
},
{
serviceName: 'opbeans-node',
environment: 'stage',
type: 'service',
},
{
serviceName: 'opbeans-node',
hostName: 'host-1',
},
{
containerId: 'containerId',
},
];

const alerts = [
{
serviceName: 'opbeans-node',
environment: 'production',
type: 'service',
alertCount: 10,
},
{
containerId: 'containerId',
alertCount: 1,
},
{
hostName: 'host-1',
environment: 'production',
alertCount: 5,
},
];

const joined = joinByKey(
[...data, ...alerts],
['serviceName', 'environment', 'hostName', 'containerId']
);

expect(joined.length).toBe(5);

expect(joined).toEqual([
{ environment: 'stage', serviceName: 'opbeans-node', type: 'service' },
{ hostName: 'host-1', serviceName: 'opbeans-node' },
{ alertCount: 10, environment: 'production', serviceName: 'opbeans-node', type: 'service' },
{ alertCount: 1, containerId: 'containerId' },
{ alertCount: 5, environment: 'production', hostName: 'host-1' },
]);
});

it('uses the custom merge fn to replace items', () => {
const joined = joinByKey(
[
{
serviceName: 'opbeans-java',
values: ['a'],
},
{
serviceName: 'opbeans-node',
values: ['a'],
},
{
serviceName: 'opbeans-node',
values: ['b'],
},
{
serviceName: 'opbeans-node',
values: ['c'],
},
],
'serviceName',
(a, b) => ({
...a,
...b,
values: a.values.concat(b.values),
})
);

expect(joined.find((item) => item.serviceName === 'opbeans-node')?.values).toEqual([
'a',
'b',
'c',
]);
});

it('deeply merges objects', () => {
const joined = joinByKey(
[
{
serviceName: 'opbeans-node',
properties: {
foo: '',
},
},
{
serviceName: 'opbeans-node',
properties: {
bar: '',
},
},
],
'serviceName'
);

expect(joined[0]).toEqual({
serviceName: 'opbeans-node',
properties: {
foo: '',
bar: '',
},
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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 { UnionToIntersection, ValuesType } from 'utility-types';
import { merge, castArray } from 'lodash';
import stableStringify from 'json-stable-stringify';

export type JoinedReturnType<
T extends Record<string, any>,
U extends UnionToIntersection<T>
> = Array<
Partial<U> & {
[k in keyof T]: T[k];
}
>;

type ArrayOrSingle<T> = T | T[];

export function joinByKey<
T extends Record<string, any>,
U extends UnionToIntersection<T>,
V extends ArrayOrSingle<keyof T & keyof U>
>(items: T[], key: V): JoinedReturnType<T, U>;

export function joinByKey<
T extends Record<string, any>,
U extends UnionToIntersection<T>,
V extends ArrayOrSingle<keyof T & keyof U>,
W extends JoinedReturnType<T, U>,
X extends (a: T, b: T) => ValuesType<W>
>(items: T[], key: V, mergeFn: X): W;

export function joinByKey(
items: Array<Record<string, any>>,
key: string | string[],
mergeFn: Function = (a: Record<string, any>, b: Record<string, any>) => merge({}, a, b)
) {
const keys = castArray(key);
// Create a map to quickly query the key of group.
const map = new Map();
items.forEach((current) => {
// The key of the map is a stable JSON string of the values from given keys.
// We need stable JSON string to support plain object values.
const stableKey = stableStringify(keys.map((k) => current[k]));

if (map.has(stableKey)) {
const item = map.get(stableKey);
// delete and set the key to put it last
map.delete(stableKey);
map.set(stableKey, mergeFn(item, current));
} else {
map.set(stableKey, { ...current });
}
});
return [...map.values()];
}
18 changes: 15 additions & 3 deletions x-pack/plugins/observability_solution/inventory/common/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
*/
import { ENTITY_LATEST, entitiesAliasPattern } from '@kbn/entities-schema';
import {
HOST_NAME,
SERVICE_ENVIRONMENT,
SERVICE_NAME,
AGENT_NAME,
CLOUD_PROVIDER,
CONTAINER_ID,
Expand All @@ -15,9 +18,6 @@ import {
ENTITY_IDENTITY_FIELDS,
ENTITY_LAST_SEEN,
ENTITY_TYPE,
HOST_NAME,
SERVICE_ENVIRONMENT,
SERVICE_NAME,
} from '@kbn/observability-shared-plugin/common';
import { isRight } from 'fp-ts/lib/Either';
import * as t from 'io-ts';
Expand All @@ -28,8 +28,19 @@ export const entityTypeRt = t.union([
t.literal('container'),
]);

export const entityColumnIdsRt = t.union([
t.literal(ENTITY_DISPLAY_NAME),
t.literal(ENTITY_LAST_SEEN),
t.literal(ENTITY_TYPE),
t.literal('alertsCount'),
]);

export type EntityColumnIds = t.TypeOf<typeof entityColumnIdsRt>;

export type EntityType = t.TypeOf<typeof entityTypeRt>;

export const defaultEntitySortField: EntityColumnIds = 'alertsCount';

export const MAX_NUMBER_OF_ENTITIES = 500;

export const ENTITIES_LATEST_ALIAS = entitiesAliasPattern({
Expand Down Expand Up @@ -79,6 +90,7 @@ interface BaseEntity {
[ENTITY_DISPLAY_NAME]: string;
[ENTITY_DEFINITION_ID]: string;
[ENTITY_IDENTITY_FIELDS]: string | string[];
alertsCount?: number;
[key: string]: any;
}

Expand Down
Loading

0 comments on commit c0bd82b

Please sign in to comment.