mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ResponseOps][Cases] Adding assignees telemetry (#141168)
* Adding assignees telemetry * Addressing feedback and errors * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
5fe44708a6
commit
ec91f5e184
9 changed files with 647 additions and 175 deletions
|
@ -65,9 +65,14 @@ const existsSchema = s.object({
|
|||
// For more details see how the types are defined in the elasticsearch javascript client:
|
||||
// https://github.com/elastic/elasticsearch-js/blob/4ad5daeaf401ce8ebb28b940075e0a67e56ff9ce/src/api/typesWithBodyKey.ts#L5295
|
||||
const boolSchema = s.object({
|
||||
bool: s.object({
|
||||
must_not: s.oneOf([termSchema]),
|
||||
}),
|
||||
bool: s.oneOf([
|
||||
s.object({
|
||||
must_not: s.oneOf([termSchema, existsSchema]),
|
||||
}),
|
||||
s.object({
|
||||
filter: s.oneOf([termSchema, existsSchema]),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
const orderSchema = s.oneOf([
|
||||
|
|
13
x-pack/plugins/cases/server/telemetry/constants.ts
Normal file
13
x-pack/plugins/cases/server/telemetry/constants.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 { GENERAL_CASES_OWNER, OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER } from '../../common';
|
||||
|
||||
/**
|
||||
* This should only be used within telemetry
|
||||
*/
|
||||
export const OWNERS = [OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER, GENERAL_CASES_OWNER] as const;
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { SavedObjectsFindResponse } from '@kbn/core/server';
|
||||
import { savedObjectsRepositoryMock, loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { CaseAggregationResult } from '../types';
|
||||
import { getCasesTelemetryData } from './cases';
|
||||
|
||||
describe('getCasesTelemetryData', () => {
|
||||
|
@ -50,13 +51,33 @@ describe('getCasesTelemetryData', () => {
|
|||
],
|
||||
};
|
||||
|
||||
mockFind({
|
||||
const assignees = {
|
||||
assigneeFilters: {
|
||||
buckets: {
|
||||
atLeastOne: {
|
||||
doc_count: 0,
|
||||
},
|
||||
zero: {
|
||||
doc_count: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
totalAssignees: { value: 5 },
|
||||
};
|
||||
|
||||
const solutionValues = {
|
||||
counts,
|
||||
...assignees,
|
||||
};
|
||||
|
||||
const caseAggsResult: CaseAggregationResult = {
|
||||
users: { value: 1 },
|
||||
tags: { value: 2 },
|
||||
...assignees,
|
||||
counts,
|
||||
securitySolution: { counts },
|
||||
observability: { counts },
|
||||
cases: { counts },
|
||||
securitySolution: { ...solutionValues },
|
||||
observability: { ...solutionValues },
|
||||
cases: { ...solutionValues },
|
||||
syncAlerts: {
|
||||
buckets: [
|
||||
{
|
||||
|
@ -93,7 +114,9 @@ describe('getCasesTelemetryData', () => {
|
|||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
mockFind(caseAggsResult);
|
||||
mockFind({ participants: { value: 2 } });
|
||||
mockFind({ references: { referenceType: { referenceAgg: { value: 3 } } } });
|
||||
mockFind({ references: { referenceType: { referenceAgg: { value: 4 } } } });
|
||||
|
@ -139,20 +162,40 @@ describe('getCasesTelemetryData', () => {
|
|||
totalUsers: 1,
|
||||
totalWithAlerts: 3,
|
||||
totalWithConnectors: 4,
|
||||
assignees: {
|
||||
total: 5,
|
||||
totalWithZero: 100,
|
||||
totalWithAtLeastOne: 0,
|
||||
},
|
||||
},
|
||||
main: {
|
||||
assignees: {
|
||||
total: 5,
|
||||
totalWithZero: 100,
|
||||
totalWithAtLeastOne: 0,
|
||||
},
|
||||
total: 1,
|
||||
daily: 3,
|
||||
weekly: 2,
|
||||
monthly: 1,
|
||||
},
|
||||
obs: {
|
||||
assignees: {
|
||||
total: 5,
|
||||
totalWithZero: 100,
|
||||
totalWithAtLeastOne: 0,
|
||||
},
|
||||
total: 1,
|
||||
daily: 3,
|
||||
weekly: 2,
|
||||
monthly: 1,
|
||||
},
|
||||
sec: {
|
||||
assignees: {
|
||||
total: 5,
|
||||
totalWithZero: 100,
|
||||
totalWithAtLeastOne: 0,
|
||||
},
|
||||
total: 1,
|
||||
daily: 3,
|
||||
weekly: 2,
|
||||
|
@ -166,145 +209,263 @@ describe('getCasesTelemetryData', () => {
|
|||
|
||||
await getCasesTelemetryData({ savedObjectsClient, logger });
|
||||
|
||||
expect(savedObjectsClient.find.mock.calls[0][0]).toEqual({
|
||||
aggs: {
|
||||
cases: {
|
||||
aggs: {
|
||||
counts: {
|
||||
date_range: {
|
||||
field: 'cases.attributes.created_at',
|
||||
format: 'dd/MM/YYYY',
|
||||
ranges: [
|
||||
{
|
||||
from: 'now-1d',
|
||||
to: 'now',
|
||||
expect(savedObjectsClient.find.mock.calls[0][0]).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"aggs": Object {
|
||||
"assigneeFilters": Object {
|
||||
"filters": Object {
|
||||
"filters": Object {
|
||||
"atLeastOne": Object {
|
||||
"bool": Object {
|
||||
"filter": Object {
|
||||
"exists": Object {
|
||||
"field": "cases.attributes.assignees.uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
from: 'now-1w',
|
||||
to: 'now',
|
||||
},
|
||||
"zero": Object {
|
||||
"bool": Object {
|
||||
"must_not": Object {
|
||||
"exists": Object {
|
||||
"field": "cases.attributes.assignees.uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
from: 'now-1M',
|
||||
to: 'now',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
term: {
|
||||
'cases.attributes.owner': 'cases',
|
||||
"cases": Object {
|
||||
"aggs": Object {
|
||||
"assigneeFilters": Object {
|
||||
"filters": Object {
|
||||
"filters": Object {
|
||||
"atLeastOne": Object {
|
||||
"bool": Object {
|
||||
"filter": Object {
|
||||
"exists": Object {
|
||||
"field": "cases.attributes.assignees.uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"zero": Object {
|
||||
"bool": Object {
|
||||
"must_not": Object {
|
||||
"exists": Object {
|
||||
"field": "cases.attributes.assignees.uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"counts": Object {
|
||||
"date_range": Object {
|
||||
"field": "cases.attributes.created_at",
|
||||
"format": "dd/MM/YYYY",
|
||||
"ranges": Array [
|
||||
Object {
|
||||
"from": "now-1d",
|
||||
"to": "now",
|
||||
},
|
||||
Object {
|
||||
"from": "now-1w",
|
||||
"to": "now",
|
||||
},
|
||||
Object {
|
||||
"from": "now-1M",
|
||||
"to": "now",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"totalAssignees": Object {
|
||||
"value_count": Object {
|
||||
"field": "cases.attributes.assignees.uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
counts: {
|
||||
date_range: {
|
||||
field: 'cases.attributes.created_at',
|
||||
format: 'dd/MM/YYYY',
|
||||
ranges: [
|
||||
{
|
||||
from: 'now-1d',
|
||||
to: 'now',
|
||||
},
|
||||
{
|
||||
from: 'now-1w',
|
||||
to: 'now',
|
||||
},
|
||||
{
|
||||
from: 'now-1M',
|
||||
to: 'now',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
observability: {
|
||||
aggs: {
|
||||
counts: {
|
||||
date_range: {
|
||||
field: 'cases.attributes.created_at',
|
||||
format: 'dd/MM/YYYY',
|
||||
ranges: [
|
||||
{
|
||||
from: 'now-1d',
|
||||
to: 'now',
|
||||
},
|
||||
{
|
||||
from: 'now-1w',
|
||||
to: 'now',
|
||||
},
|
||||
{
|
||||
from: 'now-1M',
|
||||
to: 'now',
|
||||
},
|
||||
],
|
||||
"filter": Object {
|
||||
"term": Object {
|
||||
"cases.attributes.owner": "cases",
|
||||
},
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
term: {
|
||||
'cases.attributes.owner': 'observability',
|
||||
"counts": Object {
|
||||
"date_range": Object {
|
||||
"field": "cases.attributes.created_at",
|
||||
"format": "dd/MM/YYYY",
|
||||
"ranges": Array [
|
||||
Object {
|
||||
"from": "now-1d",
|
||||
"to": "now",
|
||||
},
|
||||
Object {
|
||||
"from": "now-1w",
|
||||
"to": "now",
|
||||
},
|
||||
Object {
|
||||
"from": "now-1M",
|
||||
"to": "now",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
securitySolution: {
|
||||
aggs: {
|
||||
counts: {
|
||||
date_range: {
|
||||
field: 'cases.attributes.created_at',
|
||||
format: 'dd/MM/YYYY',
|
||||
ranges: [
|
||||
{
|
||||
from: 'now-1d',
|
||||
to: 'now',
|
||||
"observability": Object {
|
||||
"aggs": Object {
|
||||
"assigneeFilters": Object {
|
||||
"filters": Object {
|
||||
"filters": Object {
|
||||
"atLeastOne": Object {
|
||||
"bool": Object {
|
||||
"filter": Object {
|
||||
"exists": Object {
|
||||
"field": "cases.attributes.assignees.uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"zero": Object {
|
||||
"bool": Object {
|
||||
"must_not": Object {
|
||||
"exists": Object {
|
||||
"field": "cases.attributes.assignees.uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
from: 'now-1w',
|
||||
to: 'now',
|
||||
},
|
||||
{
|
||||
from: 'now-1M',
|
||||
to: 'now',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"counts": Object {
|
||||
"date_range": Object {
|
||||
"field": "cases.attributes.created_at",
|
||||
"format": "dd/MM/YYYY",
|
||||
"ranges": Array [
|
||||
Object {
|
||||
"from": "now-1d",
|
||||
"to": "now",
|
||||
},
|
||||
Object {
|
||||
"from": "now-1w",
|
||||
"to": "now",
|
||||
},
|
||||
Object {
|
||||
"from": "now-1M",
|
||||
"to": "now",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"totalAssignees": Object {
|
||||
"value_count": Object {
|
||||
"field": "cases.attributes.assignees.uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
"filter": Object {
|
||||
"term": Object {
|
||||
"cases.attributes.owner": "observability",
|
||||
},
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
term: {
|
||||
'cases.attributes.owner': 'securitySolution',
|
||||
"securitySolution": Object {
|
||||
"aggs": Object {
|
||||
"assigneeFilters": Object {
|
||||
"filters": Object {
|
||||
"filters": Object {
|
||||
"atLeastOne": Object {
|
||||
"bool": Object {
|
||||
"filter": Object {
|
||||
"exists": Object {
|
||||
"field": "cases.attributes.assignees.uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"zero": Object {
|
||||
"bool": Object {
|
||||
"must_not": Object {
|
||||
"exists": Object {
|
||||
"field": "cases.attributes.assignees.uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"counts": Object {
|
||||
"date_range": Object {
|
||||
"field": "cases.attributes.created_at",
|
||||
"format": "dd/MM/YYYY",
|
||||
"ranges": Array [
|
||||
Object {
|
||||
"from": "now-1d",
|
||||
"to": "now",
|
||||
},
|
||||
Object {
|
||||
"from": "now-1w",
|
||||
"to": "now",
|
||||
},
|
||||
Object {
|
||||
"from": "now-1M",
|
||||
"to": "now",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"totalAssignees": Object {
|
||||
"value_count": Object {
|
||||
"field": "cases.attributes.assignees.uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
"filter": Object {
|
||||
"term": Object {
|
||||
"cases.attributes.owner": "securitySolution",
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": Object {
|
||||
"terms": Object {
|
||||
"field": "cases.attributes.status",
|
||||
},
|
||||
},
|
||||
"syncAlerts": Object {
|
||||
"terms": Object {
|
||||
"field": "cases.attributes.settings.syncAlerts",
|
||||
},
|
||||
},
|
||||
"tags": Object {
|
||||
"cardinality": Object {
|
||||
"field": "cases.attributes.tags",
|
||||
},
|
||||
},
|
||||
"totalAssignees": Object {
|
||||
"value_count": Object {
|
||||
"field": "cases.attributes.assignees.uid",
|
||||
},
|
||||
},
|
||||
"totalsByOwner": Object {
|
||||
"terms": Object {
|
||||
"field": "cases.attributes.owner",
|
||||
},
|
||||
},
|
||||
"users": Object {
|
||||
"cardinality": Object {
|
||||
"field": "cases.attributes.created_by.username",
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
terms: {
|
||||
field: 'cases.attributes.status',
|
||||
},
|
||||
},
|
||||
syncAlerts: {
|
||||
terms: {
|
||||
field: 'cases.attributes.settings.syncAlerts',
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
cardinality: {
|
||||
field: 'cases.attributes.tags',
|
||||
},
|
||||
},
|
||||
totalsByOwner: {
|
||||
terms: {
|
||||
field: 'cases.attributes.owner',
|
||||
},
|
||||
},
|
||||
users: {
|
||||
cardinality: {
|
||||
field: 'cases.attributes.created_by.username',
|
||||
},
|
||||
},
|
||||
},
|
||||
page: 0,
|
||||
perPage: 0,
|
||||
type: 'cases',
|
||||
});
|
||||
"page": 0,
|
||||
"perPage": 0,
|
||||
"type": "cases",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(savedObjectsClient.find.mock.calls[1][0]).toEqual({
|
||||
aggs: {
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
CASE_USER_ACTION_SAVED_OBJECT,
|
||||
} from '../../../common/constants';
|
||||
import { ESCaseAttributes } from '../../services/cases/types';
|
||||
import { OWNERS } from '../constants';
|
||||
import {
|
||||
CollectTelemetryDataParams,
|
||||
Buckets,
|
||||
|
@ -18,6 +19,7 @@ import {
|
|||
Cardinality,
|
||||
ReferencesAggregation,
|
||||
LatestDates,
|
||||
CaseAggregationResult,
|
||||
} from '../types';
|
||||
import {
|
||||
findValueInBuckets,
|
||||
|
@ -27,6 +29,7 @@ import {
|
|||
getOnlyAlertsCommentsFilter,
|
||||
getOnlyConnectorsFilter,
|
||||
getReferencesAggregationQuery,
|
||||
getSolutionValues,
|
||||
} from './utils';
|
||||
|
||||
export const getLatestCasesDates = async ({
|
||||
|
@ -58,8 +61,7 @@ export const getCasesTelemetryData = async ({
|
|||
savedObjectsClient,
|
||||
logger,
|
||||
}: CollectTelemetryDataParams): Promise<CasesTelemetry['cases']> => {
|
||||
const owners = ['observability', 'securitySolution', 'cases'] as const;
|
||||
const byOwnerAggregationQuery = owners.reduce(
|
||||
const byOwnerAggregationQuery = OWNERS.reduce(
|
||||
(aggQuery, owner) => ({
|
||||
...aggQuery,
|
||||
[owner]: {
|
||||
|
@ -68,28 +70,23 @@ export const getCasesTelemetryData = async ({
|
|||
[`${CASE_SAVED_OBJECT}.attributes.owner`]: owner,
|
||||
},
|
||||
},
|
||||
aggs: getCountsAggregationQuery(CASE_SAVED_OBJECT),
|
||||
aggs: {
|
||||
...getCountsAggregationQuery(CASE_SAVED_OBJECT),
|
||||
...getAssigneesAggregations(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
const casesRes = await savedObjectsClient.find<
|
||||
unknown,
|
||||
Record<typeof owners[number], { counts: Buckets }> & {
|
||||
counts: Buckets;
|
||||
syncAlerts: Buckets;
|
||||
status: Buckets;
|
||||
users: Cardinality;
|
||||
tags: Cardinality;
|
||||
}
|
||||
>({
|
||||
const casesRes = await savedObjectsClient.find<unknown, CaseAggregationResult>({
|
||||
page: 0,
|
||||
perPage: 0,
|
||||
type: CASE_SAVED_OBJECT,
|
||||
aggs: {
|
||||
...byOwnerAggregationQuery,
|
||||
...getCountsAggregationQuery(CASE_SAVED_OBJECT),
|
||||
...getAssigneesAggregations(),
|
||||
totalsByOwner: {
|
||||
terms: { field: `${CASE_SAVED_OBJECT}.attributes.owner` },
|
||||
},
|
||||
|
@ -116,7 +113,7 @@ export const getCasesTelemetryData = async ({
|
|||
|
||||
const commentsRes = await savedObjectsClient.find<
|
||||
unknown,
|
||||
Record<typeof owners[number], { counts: Buckets }> & {
|
||||
Record<typeof OWNERS[number], { counts: Buckets }> & {
|
||||
participants: Cardinality;
|
||||
} & ReferencesAggregation
|
||||
>({
|
||||
|
@ -164,16 +161,7 @@ export const getCasesTelemetryData = async ({
|
|||
|
||||
const aggregationsBuckets = getAggregationsBuckets({
|
||||
aggs: casesRes.aggregations,
|
||||
keys: [
|
||||
'counts',
|
||||
'observability.counts',
|
||||
'securitySolution.counts',
|
||||
'cases.counts',
|
||||
'syncAlerts',
|
||||
'status',
|
||||
'totalsByOwner',
|
||||
'users',
|
||||
],
|
||||
keys: ['counts', 'syncAlerts', 'status', 'users', 'totalAssignees'],
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -195,18 +183,47 @@ export const getCasesTelemetryData = async ({
|
|||
totalWithConnectors:
|
||||
totalConnectorsRes.aggregations?.references?.referenceType?.referenceAgg?.value ?? 0,
|
||||
latestDates,
|
||||
assignees: {
|
||||
total: casesRes.aggregations?.totalAssignees.value ?? 0,
|
||||
totalWithZero: casesRes.aggregations?.assigneeFilters.buckets.zero.doc_count ?? 0,
|
||||
totalWithAtLeastOne:
|
||||
casesRes.aggregations?.assigneeFilters.buckets.atLeastOne.doc_count ?? 0,
|
||||
},
|
||||
},
|
||||
sec: {
|
||||
total: findValueInBuckets(aggregationsBuckets.totalsByOwner, 'securitySolution'),
|
||||
...getCountsFromBuckets(aggregationsBuckets['securitySolution.counts']),
|
||||
},
|
||||
obs: {
|
||||
total: findValueInBuckets(aggregationsBuckets.totalsByOwner, 'observability'),
|
||||
...getCountsFromBuckets(aggregationsBuckets['observability.counts']),
|
||||
},
|
||||
main: {
|
||||
total: findValueInBuckets(aggregationsBuckets.totalsByOwner, 'cases'),
|
||||
...getCountsFromBuckets(aggregationsBuckets['cases.counts']),
|
||||
},
|
||||
sec: getSolutionValues(casesRes.aggregations, 'securitySolution'),
|
||||
obs: getSolutionValues(casesRes.aggregations, 'observability'),
|
||||
main: getSolutionValues(casesRes.aggregations, 'cases'),
|
||||
};
|
||||
};
|
||||
|
||||
const getAssigneesAggregations = () => ({
|
||||
totalAssignees: {
|
||||
value_count: {
|
||||
field: `${CASE_SAVED_OBJECT}.attributes.assignees.uid`,
|
||||
},
|
||||
},
|
||||
assigneeFilters: {
|
||||
filters: {
|
||||
filters: {
|
||||
zero: {
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: `${CASE_SAVED_OBJECT}.attributes.assignees.uid`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
atLeastOne: {
|
||||
bool: {
|
||||
filter: {
|
||||
exists: {
|
||||
field: `${CASE_SAVED_OBJECT}.attributes.assignees.uid`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks';
|
||||
import { CaseAggregationResult } from '../types';
|
||||
import {
|
||||
findValueInBuckets,
|
||||
getAggregationsBuckets,
|
||||
|
@ -18,9 +19,127 @@ import {
|
|||
getOnlyAlertsCommentsFilter,
|
||||
getOnlyConnectorsFilter,
|
||||
getReferencesAggregationQuery,
|
||||
getSolutionValues,
|
||||
} from './utils';
|
||||
|
||||
describe('utils', () => {
|
||||
describe('getSolutionValues', () => {
|
||||
const counts = {
|
||||
buckets: [
|
||||
{ doc_count: 1, key: 1 },
|
||||
{ doc_count: 2, key: 2 },
|
||||
{ doc_count: 3, key: 3 },
|
||||
],
|
||||
};
|
||||
|
||||
const assignees = {
|
||||
assigneeFilters: {
|
||||
buckets: {
|
||||
atLeastOne: {
|
||||
doc_count: 0,
|
||||
},
|
||||
zero: {
|
||||
doc_count: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
totalAssignees: { value: 5 },
|
||||
};
|
||||
|
||||
const solutionValues = {
|
||||
counts,
|
||||
...assignees,
|
||||
};
|
||||
|
||||
const aggsResult: CaseAggregationResult = {
|
||||
users: { value: 1 },
|
||||
tags: { value: 2 },
|
||||
...assignees,
|
||||
counts,
|
||||
securitySolution: { ...solutionValues },
|
||||
observability: { ...solutionValues },
|
||||
cases: { ...solutionValues },
|
||||
syncAlerts: {
|
||||
buckets: [
|
||||
{
|
||||
key: 0,
|
||||
doc_count: 1,
|
||||
},
|
||||
{
|
||||
key: 1,
|
||||
doc_count: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
status: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'open',
|
||||
doc_count: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
totalsByOwner: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'observability',
|
||||
doc_count: 1,
|
||||
},
|
||||
{
|
||||
key: 'securitySolution',
|
||||
doc_count: 1,
|
||||
},
|
||||
{
|
||||
key: 'cases',
|
||||
doc_count: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
it('constructs the solution values correctly', () => {
|
||||
expect(getSolutionValues(aggsResult, 'securitySolution')).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"assignees": Object {
|
||||
"total": 5,
|
||||
"totalWithAtLeastOne": 0,
|
||||
"totalWithZero": 100,
|
||||
},
|
||||
"daily": 3,
|
||||
"monthly": 1,
|
||||
"total": 1,
|
||||
"weekly": 2,
|
||||
}
|
||||
`);
|
||||
expect(getSolutionValues(aggsResult, 'cases')).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"assignees": Object {
|
||||
"total": 5,
|
||||
"totalWithAtLeastOne": 0,
|
||||
"totalWithZero": 100,
|
||||
},
|
||||
"daily": 3,
|
||||
"monthly": 1,
|
||||
"total": 1,
|
||||
"weekly": 2,
|
||||
}
|
||||
`);
|
||||
expect(getSolutionValues(aggsResult, 'observability')).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"assignees": Object {
|
||||
"total": 5,
|
||||
"totalWithAtLeastOne": 0,
|
||||
"totalWithZero": 100,
|
||||
},
|
||||
"daily": 3,
|
||||
"monthly": 1,
|
||||
"total": 1,
|
||||
"weekly": 2,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCountsAggregationQuery', () => {
|
||||
it('returns the correct query', () => {
|
||||
expect(getCountsAggregationQuery('test')).toEqual({
|
||||
|
|
|
@ -13,8 +13,15 @@ import {
|
|||
CASE_SAVED_OBJECT,
|
||||
CASE_USER_ACTION_SAVED_OBJECT,
|
||||
} from '../../../common/constants';
|
||||
import { Buckets, CasesTelemetry, MaxBucketOnCaseAggregation } from '../types';
|
||||
import {
|
||||
CaseAggregationResult,
|
||||
Buckets,
|
||||
CasesTelemetry,
|
||||
MaxBucketOnCaseAggregation,
|
||||
SolutionTelemetry,
|
||||
} from '../types';
|
||||
import { buildFilter } from '../../client/utils';
|
||||
import { OWNERS } from '../constants';
|
||||
|
||||
export const getCountsAggregationQuery = (savedObjectType: string) => ({
|
||||
counts: {
|
||||
|
@ -147,6 +154,26 @@ export const getBucketFromAggregation = ({
|
|||
aggs?: Record<string, unknown>;
|
||||
}): Buckets['buckets'] => (get(aggs, `${key}.buckets`) ?? []) as Buckets['buckets'];
|
||||
|
||||
export const getSolutionValues = (
|
||||
aggregations: CaseAggregationResult | undefined,
|
||||
owner: typeof OWNERS[number]
|
||||
): SolutionTelemetry => {
|
||||
const aggregationsBuckets = getAggregationsBuckets({
|
||||
aggs: aggregations,
|
||||
keys: ['totalsByOwner', 'securitySolution.counts', 'observability.counts', 'cases.counts'],
|
||||
});
|
||||
|
||||
return {
|
||||
total: findValueInBuckets(aggregationsBuckets.totalsByOwner, owner),
|
||||
...getCountsFromBuckets(aggregationsBuckets[`${owner}.counts`]),
|
||||
assignees: {
|
||||
total: aggregations?.[owner].totalAssignees.value ?? 0,
|
||||
totalWithZero: aggregations?.[owner].assigneeFilters.buckets.zero.doc_count ?? 0,
|
||||
totalWithAtLeastOne: aggregations?.[owner].assigneeFilters.buckets.atLeastOne.doc_count ?? 0,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const findValueInBuckets = (buckets: Buckets['buckets'], value: string | number): number =>
|
||||
buckets.find(({ key }) => key === value)?.doc_count ?? 0;
|
||||
|
||||
|
@ -184,6 +211,11 @@ export const getOnlyConnectorsFilter = () =>
|
|||
export const getTelemetryDataEmptyState = (): CasesTelemetry => ({
|
||||
cases: {
|
||||
all: {
|
||||
assignees: {
|
||||
total: 0,
|
||||
totalWithZero: 0,
|
||||
totalWithAtLeastOne: 0,
|
||||
},
|
||||
total: 0,
|
||||
monthly: 0,
|
||||
weekly: 0,
|
||||
|
@ -206,9 +238,27 @@ export const getTelemetryDataEmptyState = (): CasesTelemetry => ({
|
|||
closedAt: null,
|
||||
},
|
||||
},
|
||||
sec: { total: 0, monthly: 0, weekly: 0, daily: 0 },
|
||||
obs: { total: 0, monthly: 0, weekly: 0, daily: 0 },
|
||||
main: { total: 0, monthly: 0, weekly: 0, daily: 0 },
|
||||
sec: {
|
||||
total: 0,
|
||||
monthly: 0,
|
||||
weekly: 0,
|
||||
daily: 0,
|
||||
assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 },
|
||||
},
|
||||
obs: {
|
||||
total: 0,
|
||||
monthly: 0,
|
||||
weekly: 0,
|
||||
daily: 0,
|
||||
assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 },
|
||||
},
|
||||
main: {
|
||||
total: 0,
|
||||
monthly: 0,
|
||||
weekly: 0,
|
||||
daily: 0,
|
||||
assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 },
|
||||
},
|
||||
},
|
||||
userActions: { all: { total: 0, monthly: 0, weekly: 0, daily: 0, maxOnACase: 0 } },
|
||||
comments: { all: { total: 0, monthly: 0, weekly: 0, daily: 0, maxOnACase: 0 } },
|
||||
|
|
|
@ -12,6 +12,8 @@ import {
|
|||
StatusSchema,
|
||||
LatestDatesSchema,
|
||||
TypeString,
|
||||
SolutionTelemetrySchema,
|
||||
AssigneesSchema,
|
||||
} from './types';
|
||||
|
||||
const long: TypeLong = { type: 'long' };
|
||||
|
@ -24,6 +26,17 @@ const countSchema: CountSchema = {
|
|||
daily: long,
|
||||
};
|
||||
|
||||
const assigneesSchema: AssigneesSchema = {
|
||||
total: long,
|
||||
totalWithZero: long,
|
||||
totalWithAtLeastOne: long,
|
||||
};
|
||||
|
||||
const solutionTelemetry: SolutionTelemetrySchema = {
|
||||
...countSchema,
|
||||
assignees: assigneesSchema,
|
||||
};
|
||||
|
||||
const statusSchema: StatusSchema = {
|
||||
open: long,
|
||||
inProgress: long,
|
||||
|
@ -40,6 +53,7 @@ export const casesSchema: CasesTelemetrySchema = {
|
|||
cases: {
|
||||
all: {
|
||||
...countSchema,
|
||||
assignees: assigneesSchema,
|
||||
status: statusSchema,
|
||||
syncAlertsOn: long,
|
||||
syncAlertsOff: long,
|
||||
|
@ -50,9 +64,9 @@ export const casesSchema: CasesTelemetrySchema = {
|
|||
totalWithConnectors: long,
|
||||
latestDates: latestDatesSchema,
|
||||
},
|
||||
sec: countSchema,
|
||||
obs: countSchema,
|
||||
main: countSchema,
|
||||
sec: solutionTelemetry,
|
||||
obs: solutionTelemetry,
|
||||
main: solutionTelemetry,
|
||||
},
|
||||
userActions: { all: { ...countSchema, maxOnACase: long } },
|
||||
comments: { all: { ...countSchema, maxOnACase: long } },
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { ISavedObjectsRepository, Logger } from '@kbn/core/server';
|
||||
import { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server';
|
||||
import { OWNERS } from './constants';
|
||||
|
||||
export interface Buckets {
|
||||
buckets: Array<{
|
||||
|
@ -19,6 +20,8 @@ export interface Cardinality {
|
|||
value: number;
|
||||
}
|
||||
|
||||
export type ValueCount = Cardinality;
|
||||
|
||||
export interface MaxBucketOnCaseAggregation {
|
||||
references: { cases: { max: { value: number } } };
|
||||
}
|
||||
|
@ -47,6 +50,41 @@ export interface Count {
|
|||
daily: number;
|
||||
}
|
||||
|
||||
export interface AssigneesFilters {
|
||||
buckets: {
|
||||
zero: { doc_count: number };
|
||||
atLeastOne: { doc_count: number };
|
||||
};
|
||||
}
|
||||
|
||||
export type CaseAggregationResult = Record<
|
||||
typeof OWNERS[number],
|
||||
{
|
||||
counts: Buckets;
|
||||
totalAssignees: ValueCount;
|
||||
assigneeFilters: AssigneesFilters;
|
||||
}
|
||||
> & {
|
||||
assigneeFilters: AssigneesFilters;
|
||||
counts: Buckets;
|
||||
syncAlerts: Buckets;
|
||||
status: Buckets;
|
||||
users: Cardinality;
|
||||
tags: Cardinality;
|
||||
totalAssignees: ValueCount;
|
||||
totalsByOwner: Buckets;
|
||||
};
|
||||
|
||||
export interface Assignees {
|
||||
total: number;
|
||||
totalWithZero: number;
|
||||
totalWithAtLeastOne: number;
|
||||
}
|
||||
|
||||
export interface SolutionTelemetry extends Count {
|
||||
assignees: Assignees;
|
||||
}
|
||||
|
||||
export interface Status {
|
||||
open: number;
|
||||
inProgress: number;
|
||||
|
@ -62,6 +100,7 @@ export interface LatestDates {
|
|||
export interface CasesTelemetry {
|
||||
cases: {
|
||||
all: Count & {
|
||||
assignees: Assignees;
|
||||
status: Status;
|
||||
syncAlertsOn: number;
|
||||
syncAlertsOff: number;
|
||||
|
@ -72,9 +111,9 @@ export interface CasesTelemetry {
|
|||
totalWithConnectors: number;
|
||||
latestDates: LatestDates;
|
||||
};
|
||||
sec: Count;
|
||||
obs: Count;
|
||||
main: Count;
|
||||
sec: SolutionTelemetry;
|
||||
obs: SolutionTelemetry;
|
||||
main: SolutionTelemetry;
|
||||
};
|
||||
userActions: { all: Count & { maxOnACase: number } };
|
||||
comments: { all: Count & { maxOnACase: number } };
|
||||
|
@ -107,3 +146,5 @@ export type CountSchema = MakeSchemaFrom<Count>;
|
|||
export type StatusSchema = MakeSchemaFrom<Status>;
|
||||
export type LatestDatesSchema = MakeSchemaFrom<LatestDates>;
|
||||
export type CasesTelemetrySchema = MakeSchemaFrom<CasesTelemetry>;
|
||||
export type AssigneesSchema = MakeSchemaFrom<Assignees>;
|
||||
export type SolutionTelemetrySchema = MakeSchemaFrom<SolutionTelemetry>;
|
||||
|
|
|
@ -4644,6 +4644,19 @@
|
|||
"properties": {
|
||||
"all": {
|
||||
"properties": {
|
||||
"assignees": {
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
"totalWithZero": {
|
||||
"type": "long"
|
||||
},
|
||||
"totalWithAtLeastOne": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
|
@ -4707,6 +4720,19 @@
|
|||
},
|
||||
"sec": {
|
||||
"properties": {
|
||||
"assignees": {
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
"totalWithZero": {
|
||||
"type": "long"
|
||||
},
|
||||
"totalWithAtLeastOne": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
|
@ -4723,6 +4749,19 @@
|
|||
},
|
||||
"obs": {
|
||||
"properties": {
|
||||
"assignees": {
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
"totalWithZero": {
|
||||
"type": "long"
|
||||
},
|
||||
"totalWithAtLeastOne": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
|
@ -4739,6 +4778,19 @@
|
|||
},
|
||||
"main": {
|
||||
"properties": {
|
||||
"assignees": {
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
"totalWithZero": {
|
||||
"type": "long"
|
||||
},
|
||||
"totalWithAtLeastOne": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue