Skip to content

Commit

Permalink
[ResponseOps][Cases] Miscount of total numbers of alerts in telemetry (
Browse files Browse the repository at this point in the history
…#196112)

Closes #177208

## Summary

Problem: 
- the metrics collected in telemetry for alerts don't count the total
number of alerts on a case correctly.

Solution: 
- added new aggregation function: getUniqueAlertCommentsCountQuery,
which is now responsible for defining the cardinality aggregation for
counting unique alert comments by alertId.
- in the aggs section of the savedObjectsClient.find, the new
cardinality aggregation query was added
- the total number of alerts is updated to be the result extracted from
the new aggregation

Example: 

![Screenshot 2024-10-22 at 15 20
40](https://github.com/user-attachments/assets/c418c82e-2e35-4c7f-969d-7f4f25bdbc9d)


- in the telemetry object, we have the following info: 
<img width="331" alt="Screenshot 2024-10-22 at 15 21 40"
src="https://github.com/user-attachments/assets/6419e72d-84b4-4068-a741-6e32c6e966f7">

---------

Co-authored-by: Antonio <[email protected]>
  • Loading branch information
georgianaonoleata1904 and adcoelho authored Oct 28, 2024
1 parent 97f227e commit 73c22a5
Show file tree
Hide file tree
Showing 6 changed files with 438 additions and 14 deletions.
40 changes: 33 additions & 7 deletions x-pack/plugins/cases/server/telemetry/queries/alerts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,20 @@ describe('alerts', () => {
const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient);

savedObjectsClient.find.mockResolvedValue({
total: 5,
total: 3,
saved_objects: [],
per_page: 1,
page: 1,
aggregations: {
counts: {
buckets: [
{ doc_count: 1, key: 1 },
{ doc_count: 2, key: 2 },
{ doc_count: 3, key: 3 },
{ topAlertsPerBucket: { value: 12 } },
{ topAlertsPerBucket: { value: 5 } },
{ topAlertsPerBucket: { value: 3 } },
],
},
references: { cases: { max: { value: 1 } } },
uniqueAlertCommentsCount: { value: 5 },
},
});

Expand All @@ -42,12 +43,13 @@ describe('alerts', () => {
savedObjectsClient: telemetrySavedObjectsClient,
logger,
});

expect(res).toEqual({
all: {
total: 5,
daily: 3,
weekly: 2,
monthly: 1,
weekly: 5,
monthly: 12,
maxOnACase: 1,
},
});
Expand Down Expand Up @@ -76,6 +78,13 @@ describe('alerts', () => {
},
],
},
aggregations: {
topAlertsPerBucket: {
cardinality: {
field: 'cases-comments.attributes.alertId',
},
},
},
},
references: {
aggregations: {
Expand All @@ -85,10 +94,22 @@ describe('alerts', () => {
terms: {
field: 'cases-comments.references.id',
},
aggregations: {
reverse: {
reverse_nested: {},
aggregations: {
topAlerts: {
cardinality: {
field: 'cases-comments.attributes.alertId',
},
},
},
},
},
},
max: {
max_bucket: {
buckets_path: 'ids._count',
buckets_path: 'ids>reverse.topAlerts',
},
},
},
Expand All @@ -103,6 +124,11 @@ describe('alerts', () => {
path: 'cases-comments.references',
},
},
uniqueAlertCommentsCount: {
cardinality: {
field: 'cases-comments.attributes.alertId',
},
},
},
filter: {
arguments: [
Expand Down
7 changes: 2 additions & 5 deletions x-pack/plugins/cases/server/telemetry/queries/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@
* 2.0.
*/

import { CASE_COMMENT_SAVED_OBJECT } from '../../../common/constants';
import type { CasesTelemetry, CollectTelemetryDataParams } from '../types';
import { getCountsAndMaxData, getOnlyAlertsCommentsFilter } from './utils';
import { getCountsAndMaxAlertsData } from './utils';

export const getAlertsTelemetryData = async ({
savedObjectsClient,
}: CollectTelemetryDataParams): Promise<CasesTelemetry['comments']> => {
const res = await getCountsAndMaxData({
const res = await getCountsAndMaxAlertsData({
savedObjectsClient,
savedObjectType: CASE_COMMENT_SAVED_OBJECT,
filter: getOnlyAlertsCommentsFilter(),
});

return res;
Expand Down
228 changes: 228 additions & 0 deletions x-pack/plugins/cases/server/telemetry/queries/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import type {
import {
findValueInBuckets,
getAggregationsBuckets,
getAlertsCountsFromBuckets,
getAttachmentsFrameworkStats,
getBucketFromAggregation,
getConnectorsCardinalityAggregationQuery,
getCountsAggregationQuery,
getCountsAndMaxAlertsData,
getCountsAndMaxData,
getCountsFromBuckets,
getCustomFieldsTelemetry,
Expand All @@ -28,6 +30,7 @@ import {
getOnlyConnectorsFilter,
getReferencesAggregationQuery,
getSolutionValues,
getUniqueAlertCommentsCountQuery,
} from './utils';
import { TelemetrySavedObjectsClient } from '../telemetry_saved_objects_client';

Expand Down Expand Up @@ -994,6 +997,63 @@ describe('utils', () => {
});
});

describe('getAlertsCountsFromBuckets', () => {
it('returns the correct counts', () => {
const buckets = [
{ topAlertsPerBucket: { value: 12 } },
{ topAlertsPerBucket: { value: 5 } },
{ topAlertsPerBucket: { value: 3 } },
];

expect(getAlertsCountsFromBuckets(buckets)).toEqual({
daily: 3,
weekly: 5,
monthly: 12,
});
});

it('returns zero counts when the bucket does not have the topAlertsPerBucket field', () => {
const buckets = [{}];
// @ts-expect-error
expect(getAlertsCountsFromBuckets(buckets)).toEqual({
daily: 0,
weekly: 0,
monthly: 0,
});
});

it('returns zero counts when the bucket is undefined', () => {
// @ts-expect-error
expect(getAlertsCountsFromBuckets(undefined)).toEqual({
daily: 0,
weekly: 0,
monthly: 0,
});
});

it('returns zero counts when the topAlertsPerBucket field is missing in some buckets', () => {
const buckets = [{ doc_count: 1, key: 1, topAlertsPerBucket: { value: 5 } }, {}, {}];
// @ts-expect-error
expect(getAlertsCountsFromBuckets(buckets)).toEqual({
daily: 0,
weekly: 0,
monthly: 5,
});
});
});

describe('getUniqueAlertCommentsCountQuery', () => {
it('returns the correct query', () => {
expect(getUniqueAlertCommentsCountQuery()).toEqual({
uniqueAlertCommentsCount: {
cardinality: {
field: 'cases-comments.attributes.alertId',
},
},
});
});
});

describe('getCountsAndMaxData', () => {
const savedObjectsClient = savedObjectsRepositoryMock.create();
savedObjectsClient.find.mockResolvedValue({
Expand Down Expand Up @@ -1125,6 +1185,174 @@ describe('utils', () => {
});
});

describe('getCountsAndMaxAlertsData', () => {
const savedObjectsClient = savedObjectsRepositoryMock.create();
savedObjectsClient.find.mockResolvedValue({
total: 3,
saved_objects: [],
per_page: 1,
page: 1,
aggregations: {
counts: {
buckets: [
{ doc_count: 1, key: 1, topAlertsPerBucket: { value: 5 } },
{ doc_count: 2, key: 2, topAlertsPerBucket: { value: 3 } },
{ doc_count: 3, key: 3, topAlertsPerBucket: { value: 1 } },
],
},
references: { cases: { max: { value: 1 } } },
uniqueAlertCommentsCount: { value: 5 },
},
});

beforeEach(() => {
jest.clearAllMocks();
});

it('returns the correct counts and max data', async () => {
const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient);

const res = await getCountsAndMaxAlertsData({
savedObjectsClient: telemetrySavedObjectsClient,
});
expect(res).toEqual({
all: {
total: 5,
daily: 1,
weekly: 3,
monthly: 5,
maxOnACase: 1,
},
});
});

it('returns zero data if the response aggregation is not as expected', async () => {
const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient);
savedObjectsClient.find.mockResolvedValue({
total: 5,
saved_objects: [],
per_page: 1,
page: 1,
});

const res = await getCountsAndMaxAlertsData({
savedObjectsClient: telemetrySavedObjectsClient,
});
expect(res).toEqual({
all: {
total: 0,
daily: 0,
weekly: 0,
monthly: 0,
maxOnACase: 0,
},
});
});

it('should call find with correct arguments', async () => {
const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient);

await getCountsAndMaxAlertsData({
savedObjectsClient: telemetrySavedObjectsClient,
});

expect(savedObjectsClient.find).toBeCalledWith({
aggs: {
counts: {
date_range: {
field: 'cases-comments.attributes.created_at',
format: 'dd/MM/YYYY',
ranges: [
{
from: 'now-1d',
to: 'now',
},
{
from: 'now-1w',
to: 'now',
},
{
from: 'now-1M',
to: 'now',
},
],
},
aggregations: {
topAlertsPerBucket: {
cardinality: {
field: 'cases-comments.attributes.alertId',
},
},
},
},
references: {
aggregations: {
cases: {
aggregations: {
ids: {
terms: {
field: 'cases-comments.references.id',
},
aggregations: {
reverse: {
reverse_nested: {},
aggregations: {
topAlerts: {
cardinality: {
field: 'cases-comments.attributes.alertId',
},
},
},
},
},
},
max: {
max_bucket: {
buckets_path: 'ids>reverse.topAlerts',
},
},
},
filter: {
term: {
'cases-comments.references.type': 'cases',
},
},
},
},
nested: {
path: 'cases-comments.references',
},
},
uniqueAlertCommentsCount: {
cardinality: {
field: 'cases-comments.attributes.alertId',
},
},
},
filter: {
arguments: [
{
isQuoted: false,
type: 'literal',
value: 'cases-comments.attributes.type',
},
{
isQuoted: false,
type: 'literal',
value: 'alert',
},
],
function: 'is',
type: 'function',
},
page: 0,
perPage: 0,
type: 'cases-comments',
namespaces: ['*'],
});
});
});

describe('getBucketFromAggregation', () => {
it('returns the buckets', () => {
expect(
Expand Down
Loading

0 comments on commit 73c22a5

Please sign in to comment.