Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cases] Implement alert details metrics #120982

Merged
merged 22 commits into from
Jan 4, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4cf7681
Implementing the alert count metrics
jonathan-buttner Dec 2, 2021
a0da20a
Fixing type errors
jonathan-buttner Dec 2, 2021
877cd05
Fixing tests
jonathan-buttner Dec 2, 2021
bed24f4
Building new aggregation function in alert service
jonathan-buttner Dec 3, 2021
fbbad7b
Merge branch 'main' of github.com:elastic/kibana into metrics-alert-d…
jonathan-buttner Dec 6, 2021
ad1f0e0
Fleshing out compute for alert details
jonathan-buttner Dec 6, 2021
cf69d04
Merge branch 'main' of github.com:elastic/kibana into metrics-alert-d…
jonathan-buttner Dec 8, 2021
4ae7d1b
Adding tests for handler
jonathan-buttner Dec 8, 2021
c20abda
Refactoring the types and adding another test
jonathan-buttner Dec 8, 2021
0d7325b
Merge branch 'main' of github.com:elastic/kibana into metrics-alert-d…
jonathan-buttner Dec 9, 2021
e3e8aac
Removing dead code
jonathan-buttner Dec 9, 2021
2a9691c
Removing unused snapshots
jonathan-buttner Dec 9, 2021
6118920
Refactoring to only call handlers once
jonathan-buttner Dec 9, 2021
3206b53
Merge branch 'main' into metrics-alert-details
kibanamachine Dec 13, 2021
a9d7522
Merge branch 'main' into metrics-alert-details
kibanamachine Dec 14, 2021
58324f2
Fixing merge conflicts
jonathan-buttner Dec 16, 2021
6fb495c
Refactoring aggregations
jonathan-buttner Dec 17, 2021
7476444
Merge branch 'main' of github.com:elastic/kibana into metrics-alert-d…
jonathan-buttner Dec 17, 2021
46379f7
some code cleanup
jonathan-buttner Dec 17, 2021
f5932dd
Merge branch 'main' into metrics-alert-details
kibanamachine Dec 20, 2021
160edde
Merge branch 'main' into metrics-alert-details
kibanamachine Jan 4, 2022
8283bea
Addressing review feedback
jonathan-buttner Jan 4, 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
6 changes: 6 additions & 0 deletions x-pack/plugins/cases/common/api/cases/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ const AlertRt = rt.type({

export const AlertResponseRt = rt.array(AlertRt);
export type AlertResponse = rt.TypeOf<typeof AlertResponseRt>;

export const AlertsCountRt = rt.type({
count: rt.number,
});

export type AlertsCount = rt.TypeOf<typeof AlertsCountRt>;
82 changes: 46 additions & 36 deletions x-pack/plugins/cases/common/api/metrics/case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,50 @@
import * as rt from 'io-ts';

export type CaseMetricsResponse = rt.TypeOf<typeof CaseMetricsResponseRt>;
export type AlertHostsMetrics = rt.TypeOf<typeof AlertHostsMetricsRt>;
export type AlertUsersMetrics = rt.TypeOf<typeof AlertUsersMetricsRt>;

const AlertHostsMetricsRt = rt.type({
/**
* Total unique hosts represented in the alerts
*/
total: rt.number,
values: rt.array(
rt.type({
/**
* Host name
*/
name: rt.union([rt.string, rt.undefined]),
/**
* Unique identifier for the host
*/
id: rt.string,
/**
* Number of alerts that have this particular host name
*/
count: rt.number,
})
),
});

const AlertUsersMetricsRt = rt.type({
/**
* Total unique users represented in the alerts
*/
total: rt.number,
values: rt.array(
rt.type({
/**
* Username
*/
name: rt.string,
/**
* Number of alerts that have this particular username
*/
count: rt.number,
})
),
});

export const CaseMetricsResponseRt = rt.partial(
rt.type({
Expand All @@ -20,45 +64,11 @@ export const CaseMetricsResponseRt = rt.partial(
/**
* Host information represented from the alerts attached to this case
*/
hosts: rt.type({
/**
* Total unique hosts represented in the alerts
*/
total: rt.number,
values: rt.array(
rt.type({
/**
* Host name
*/
name: rt.string,
/**
* Number of alerts that have this particular host name
*/
count: rt.number,
})
),
}),
hosts: AlertHostsMetricsRt,
/**
* User information represented from the alerts attached to this case
*/
users: rt.type({
/**
* Total unique users represented in the alerts
*/
total: rt.number,
values: rt.array(
rt.type({
/**
* Username
*/
name: rt.string,
/**
* Number of alerts that have this particular username
*/
count: rt.number,
})
),
}),
users: AlertUsersMetricsRt,
}).props
),
/**
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

188 changes: 188 additions & 0 deletions x-pack/plugins/cases/server/client/metrics/alert_details.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* 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 { createCasesClientMock } from '../mocks';
import { CasesClientArgs } from '../types';
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { createAlertServiceMock } from '../../services/mocks';

import { AlertDetails } from './alert_details';
import { AggregationFields } from '../../services/alerts/types';

describe('AlertDetails', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('returns empty alert details metrics when there are no alerts', async () => {
const client = createCasesClientMock();
client.attachments.getAllAlertsAttachToCase.mockImplementation(async () => {
return [];
});

const handler = new AlertDetails('', client, {} as CasesClientArgs);
expect(await handler.compute()).toEqual({});
});

it('returns empty alert details metrics when no features were setup', async () => {
const client = createCasesClientMock();
client.attachments.getAllAlertsAttachToCase.mockImplementation(async () => {
return [{ id: '1', index: '2', attached_at: '3' }];
});

const handler = new AlertDetails('', client, {} as CasesClientArgs);
expect(await handler.compute()).toEqual({});
});

it('returns empty alert details metrics when no features were setup when called twice', async () => {
const client = createCasesClientMock();
client.attachments.getAllAlertsAttachToCase.mockImplementation(async () => {
return [{ id: '1', index: '2', attached_at: '3' }];
});

const handler = new AlertDetails('', client, {} as CasesClientArgs);
expect(await handler.compute()).toEqual({});
expect(await handler.compute()).toEqual({});
});

it('returns host details when the host feature is setup', async () => {
const client = createMockClient();
const clientArgs = createMockClientArgs();
const handler = new AlertDetails('', client, clientArgs);

handler.setupFeature('alertHosts');

expect(await handler.compute()).toEqual({
alerts: {
hosts: {
total: 2,
values: [{ id: '1', name: 'host1', count: 1 }],
},
},
});
});

it('only performs a single query to retrieve the details when compute is called twice', async () => {
expect.assertions(2);

const client = createMockClient();
const alertsService = mockAlertsService();

const clientArgs = {
alertsService,
} as unknown as CasesClientArgs;

const handler = new AlertDetails('', client, clientArgs);

handler.setupFeature('alertHosts');

await handler.compute();
await handler.compute();
expect(alertsService.getMostFrequentValuesForFields).toHaveBeenCalledTimes(1);
expect(alertsService.countUniqueValuesForFields).toHaveBeenCalledTimes(1);
});

it('returns user details when the user feature is setup', async () => {
const client = createMockClient();
const clientArgs = createMockClientArgs();
const handler = new AlertDetails('', client, clientArgs);

handler.setupFeature('alertUsers');

expect(await handler.compute()).toEqual({
alerts: {
users: {
total: 2,
values: [{ name: 'user1', count: 1 }],
},
},
});
});

it('returns user and host details when the user and host features are setup', async () => {
const client = createMockClient();
const clientArgs = createMockClientArgs();
const handler = new AlertDetails('', client, clientArgs);

handler.setupFeature('alertUsers');
handler.setupFeature('alertHosts');

expect(await handler.compute()).toEqual({
alerts: {
hosts: {
total: 2,
values: [{ id: '1', name: 'host1', count: 1 }],
},
users: {
total: 2,
values: [{ name: 'user1', count: 1 }],
},
},
});
});
});

function createMockClient() {
const client = createCasesClientMock();
client.attachments.getAllAlertsAttachToCase.mockImplementation(async () => {
return [{ id: '1', index: '2', attached_at: '3' }];
});

return client;
}

function createMockClientArgs() {
const alertsService = mockAlertsService();

const logger = loggingSystemMock.createLogger();

const clientArgs = {
logger,
alertsService,
} as unknown as CasesClientArgs;

return clientArgs;
}

function mockAlertsService() {
const alertsService = createAlertServiceMock();
alertsService.getMostFrequentValuesForFields.mockImplementation(
async ({ fields }: { fields: AggregationFields[] }) => {
let result = {};
for (const field of fields) {
switch (field) {
case AggregationFields.Hosts:
result = { ...result, hosts: [{ name: 'host1', id: '1', count: 1 }] };
break;
case AggregationFields.Users:
result = { ...result, users: [{ name: 'user1', count: 1 }] };
break;
}
}
return result;
}
);

alertsService.countUniqueValuesForFields.mockImplementation(
async ({ fields }: { fields: AggregationFields[] }) => {
let result = {};
for (const field of fields) {
switch (field) {
case AggregationFields.Hosts:
result = { ...result, totalHosts: 2 };
break;
case AggregationFields.Users:
result = { ...result, totalUsers: 2 };
break;
}
}
return result;
}
);

return alertsService;
}
Loading