From 8268027e40b92c5947f7bd31ca071bba009093c5 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner Date: Wed, 8 Mar 2023 17:20:27 -0500 Subject: [PATCH 1/6] Adding telemetry changes --- .../cases/server/telemetry/constants.ts | 13 - .../plugins/cases/server/telemetry/index.ts | 3 +- .../cases/server/telemetry/queries/cases.ts | 287 +++++++--- .../server/telemetry/queries/utils.test.ts | 509 +++++++++++++++++- .../cases/server/telemetry/queries/utils.ts | 150 +++++- .../plugins/cases/server/telemetry/schema.ts | 23 + .../plugins/cases/server/telemetry/types.ts | 53 +- 7 files changed, 916 insertions(+), 122 deletions(-) delete mode 100644 x-pack/plugins/cases/server/telemetry/constants.ts diff --git a/x-pack/plugins/cases/server/telemetry/constants.ts b/x-pack/plugins/cases/server/telemetry/constants.ts deleted file mode 100644 index 705321e3f1fa0..0000000000000 --- a/x-pack/plugins/cases/server/telemetry/constants.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * 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; diff --git a/x-pack/plugins/cases/server/telemetry/index.ts b/x-pack/plugins/cases/server/telemetry/index.ts index 6cd796eca2c18..7ef14541ced09 100644 --- a/x-pack/plugins/cases/server/telemetry/index.ts +++ b/x-pack/plugins/cases/server/telemetry/index.ts @@ -14,6 +14,7 @@ import type { import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; +import { FILE_SO_TYPE } from '@kbn/files-plugin/common'; import { collectTelemetryData } from './collect_telemetry_data'; import { CASE_TELEMETRY_SAVED_OBJECT, @@ -42,7 +43,7 @@ export const createCasesTelemetry = async ({ }: CreateCasesTelemetryArgs) => { const getInternalSavedObjectClient = async (): Promise => { const [coreStart] = await core.getStartServices(); - return coreStart.savedObjects.createInternalRepository(SAVED_OBJECT_TYPES); + return coreStart.savedObjects.createInternalRepository([...SAVED_OBJECT_TYPES, FILE_SO_TYPE]); }; taskManager.registerTaskDefinitions({ diff --git a/x-pack/plugins/cases/server/telemetry/queries/cases.ts b/x-pack/plugins/cases/server/telemetry/queries/cases.ts index 128c0a09b5554..29868c5129cdb 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/cases.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/cases.ts @@ -5,28 +5,32 @@ * 2.0. */ +import type { ISavedObjectsRepository, SavedObjectsFindResponse } from '@kbn/core/server'; +import { FILE_SO_TYPE } from '@kbn/files-plugin/common'; +import { fromKueryExpression } from '@kbn/es-query'; import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT, CASE_USER_ACTION_SAVED_OBJECT, + OWNERS, } from '../../../common/constants'; import { ESCaseStatus } from '../../services/cases/types'; import type { ESCaseAttributes } from '../../services/cases/types'; -import { OWNERS } from '../constants'; import type { CollectTelemetryDataParams, - Buckets, CasesTelemetry, - Cardinality, ReferencesAggregation, LatestDates, CaseAggregationResult, + AttachmentAggregationResult, + FileAttachmentAggregationResult, } from '../types'; import { findValueInBuckets, getAggregationsBuckets, getCountsAggregationQuery, getCountsFromBuckets, + getMaxBucketOnCaseAggregationQuery, getOnlyAlertsCommentsFilter, getOnlyConnectorsFilter, getReferencesAggregationQuery, @@ -62,7 +66,83 @@ export const getCasesTelemetryData = async ({ savedObjectsClient, logger, }: CollectTelemetryDataParams): Promise => { - const byOwnerAggregationQuery = OWNERS.reduce( + console.log('*****collecting telemetry'); + + try { + const [casesRes, commentsRes, totalAlertsRes, totalConnectorsRes, latestDates, filesRes] = + await Promise.all([ + getCasesSavedObjectTelemetry(savedObjectsClient), + getCommentsSavedObjectTelemetry(savedObjectsClient), + getAlertsTelemetry(savedObjectsClient), + getConnectorsTelemetry(savedObjectsClient), + getLatestCasesDates({ savedObjectsClient, logger }), + getFilesTelemetry(savedObjectsClient), + ]); + + console.log('****finished collection telemetry'); + console.log('casesRes ', JSON.stringify(casesRes, null, 2)); + console.log('filesRes ', JSON.stringify(filesRes, null, 2)); + + const aggregationsBuckets = getAggregationsBuckets({ + aggs: casesRes.aggregations, + keys: ['counts', 'syncAlerts', 'status', 'users', 'totalAssignees'], + }); + + return { + all: { + total: casesRes.total, + ...getCountsFromBuckets(aggregationsBuckets.counts), + status: { + open: findValueInBuckets(aggregationsBuckets.status, ESCaseStatus.OPEN), + inProgress: findValueInBuckets(aggregationsBuckets.status, ESCaseStatus.IN_PROGRESS), + closed: findValueInBuckets(aggregationsBuckets.status, ESCaseStatus.CLOSED), + }, + syncAlertsOn: findValueInBuckets(aggregationsBuckets.syncAlerts, 1), + syncAlertsOff: findValueInBuckets(aggregationsBuckets.syncAlerts, 0), + totalUsers: casesRes.aggregations?.users?.value ?? 0, + totalParticipants: commentsRes.aggregations?.participants?.value ?? 0, + totalTags: casesRes.aggregations?.tags?.value ?? 0, + totalWithAlerts: + totalAlertsRes.aggregations?.references?.referenceType?.referenceAgg?.value ?? 0, + 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: getSolutionValues({ + caseAggregations: casesRes.aggregations, + attachmentAggregations: commentsRes.aggregations, + filesAggregations: filesRes.aggregations, + owner: 'securitySolution', + }), + obs: getSolutionValues({ + caseAggregations: casesRes.aggregations, + attachmentAggregations: commentsRes.aggregations, + filesAggregations: filesRes.aggregations, + owner: 'observability', + }), + main: getSolutionValues({ + caseAggregations: casesRes.aggregations, + attachmentAggregations: commentsRes.aggregations, + filesAggregations: filesRes.aggregations, + owner: 'cases', + }), + }; + } catch (error) { + logger.error(`Cases telemetry failed with error: ${error}`); + throw error; + } +}; + +const getCasesSavedObjectTelemetry = async ( + savedObjectsClient: ISavedObjectsRepository +): Promise> => { + const caseByOwnerAggregationQuery = OWNERS.reduce( (aggQuery, owner) => ({ ...aggQuery, [owner]: { @@ -80,12 +160,12 @@ export const getCasesTelemetryData = async ({ {} ); - const casesRes = await savedObjectsClient.find({ + return savedObjectsClient.find({ page: 0, perPage: 0, type: CASE_SAVED_OBJECT, aggs: { - ...byOwnerAggregationQuery, + ...caseByOwnerAggregationQuery, ...getCountsAggregationQuery(CASE_SAVED_OBJECT), ...getAssigneesAggregations(), totalsByOwner: { @@ -111,17 +191,83 @@ export const getCasesTelemetryData = async ({ }, }, }); +}; + +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`, + }, + }, + }, + }, + }, + }, + }, +}); + +const getCommentsSavedObjectTelemetry = async ( + savedObjectsClient: ISavedObjectsRepository +): Promise> => { + const attachmentsByOwnerAggregationQuery = OWNERS.reduce( + (aggQuery, owner) => ({ + ...aggQuery, + [owner]: { + filter: { + term: { + [`${CASE_COMMENT_SAVED_OBJECT}.attributes.owner`]: owner, + }, + }, + aggs: { + externalReferenceTypes: { + terms: { + field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.externalReferenceAttachmentTypeId`, + }, + aggs: { + ...getMaxBucketOnCaseAggregationQuery(CASE_COMMENT_SAVED_OBJECT), + }, + }, + persistableReferenceTypes: { + terms: { + field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.persistableStateAttachmentTypeId`, + }, + aggs: { + ...getMaxBucketOnCaseAggregationQuery(CASE_COMMENT_SAVED_OBJECT), + }, + }, + }, + }, + }), + {} + ); + + console.log('****collecting attachments telemetry'); - const commentsRes = await savedObjectsClient.find< - unknown, - Record & { - participants: Cardinality; - } & ReferencesAggregation - >({ + return savedObjectsClient.find({ page: 0, perPage: 0, type: CASE_COMMENT_SAVED_OBJECT, aggs: { + ...attachmentsByOwnerAggregationQuery, participants: { cardinality: { field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.created_by.username`, @@ -129,8 +275,47 @@ export const getCasesTelemetryData = async ({ }, }, }); +}; + +const getFilesTelemetry = async ( + savedObjectsClient: ISavedObjectsRepository +): Promise> => { + const filesByOwnerAggregationQuery = OWNERS.reduce( + (aggQuery, owner) => ({ + ...aggQuery, + [owner]: { + filter: { + term: { + [`${FILE_SO_TYPE}.attributes.Meta.owner`]: owner, + }, + }, + aggs: { + averageSize: { + avg: { + field: `${FILE_SO_TYPE}.attributes.size`, + }, + }, + }, + }, + }), + {} + ); - const totalAlertsRes = await savedObjectsClient.find({ + const filterCaseIdExists = fromKueryExpression(`${FILE_SO_TYPE}.attributes.Meta.caseId: *`); + + return savedObjectsClient.find({ + page: 0, + perPage: 0, + type: FILE_SO_TYPE, + // filter: filterCaseIdExists, + aggs: filesByOwnerAggregationQuery, + }); +}; + +const getAlertsTelemetry = async ( + savedObjectsClient: ISavedObjectsRepository +): Promise> => { + return savedObjectsClient.find({ page: 0, perPage: 0, type: CASE_COMMENT_SAVED_OBJECT, @@ -143,8 +328,12 @@ export const getCasesTelemetryData = async ({ }), }, }); +}; - const totalConnectorsRes = await savedObjectsClient.find({ +const getConnectorsTelemetry = async ( + savedObjectsClient: ISavedObjectsRepository +): Promise> => { + return savedObjectsClient.find({ page: 0, perPage: 0, type: CASE_USER_ACTION_SAVED_OBJECT, @@ -157,74 +346,4 @@ export const getCasesTelemetryData = async ({ }), }, }); - - const latestDates = await getLatestCasesDates({ savedObjectsClient, logger }); - - const aggregationsBuckets = getAggregationsBuckets({ - aggs: casesRes.aggregations, - keys: ['counts', 'syncAlerts', 'status', 'users', 'totalAssignees'], - }); - - return { - all: { - total: casesRes.total, - ...getCountsFromBuckets(aggregationsBuckets.counts), - status: { - open: findValueInBuckets(aggregationsBuckets.status, ESCaseStatus.OPEN), - inProgress: findValueInBuckets(aggregationsBuckets.status, ESCaseStatus.IN_PROGRESS), - closed: findValueInBuckets(aggregationsBuckets.status, ESCaseStatus.CLOSED), - }, - syncAlertsOn: findValueInBuckets(aggregationsBuckets.syncAlerts, 1), - syncAlertsOff: findValueInBuckets(aggregationsBuckets.syncAlerts, 0), - totalUsers: casesRes.aggregations?.users?.value ?? 0, - totalParticipants: commentsRes.aggregations?.participants?.value ?? 0, - totalTags: casesRes.aggregations?.tags?.value ?? 0, - totalWithAlerts: - totalAlertsRes.aggregations?.references?.referenceType?.referenceAgg?.value ?? 0, - 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: 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`, - }, - }, - }, - }, - }, - }, - }, -}); diff --git a/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts b/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts index e466ba597108a..a9a8732bd8982 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts @@ -6,10 +6,16 @@ */ import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; -import type { CaseAggregationResult } from '../types'; +import type { + AttachmentAggregationResult, + AttachmentFrameworkAggsResult, + CaseAggregationResult, + FileAttachmentAggregationResult, +} from '../types'; import { findValueInBuckets, getAggregationsBuckets, + getAttachmentsFrameworkStats, getBucketFromAggregation, getConnectorsCardinalityAggregationQuery, getCountsAggregationQuery, @@ -46,19 +52,19 @@ describe('utils', () => { totalAssignees: { value: 5 }, }; - const solutionValues = { + const caseSolutionValues = { counts, ...assignees, }; - const aggsResult: CaseAggregationResult = { + const caseAggsResult: CaseAggregationResult = { users: { value: 1 }, tags: { value: 2 }, ...assignees, counts, - securitySolution: { ...solutionValues }, - observability: { ...solutionValues }, - cases: { ...solutionValues }, + securitySolution: { ...caseSolutionValues }, + observability: { ...caseSolutionValues }, + cases: { ...caseSolutionValues }, syncAlerts: { buckets: [ { @@ -87,7 +93,7 @@ describe('utils', () => { }, { key: 'securitySolution', - doc_count: 1, + doc_count: 5, }, { key: 'cases', @@ -97,40 +103,245 @@ describe('utils', () => { }, }; + const attachmentFramework: AttachmentFrameworkAggsResult = { + externalReferenceTypes: { + buckets: [ + { + doc_count: 5, + key: '.osquery', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + { + doc_count: 5, + key: '.files', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + ], + }, + persistableReferenceTypes: { + buckets: [ + { + doc_count: 5, + key: '.ml', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + { + doc_count: 5, + key: '.files', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + ], + }, + }; + + const attachmentAggsResult: AttachmentAggregationResult = { + securitySolution: { ...attachmentFramework }, + observability: { ...attachmentFramework }, + cases: { ...attachmentFramework }, + participants: { + value: 5, + }, + }; + + const filesRes: FileAttachmentAggregationResult = { + securitySolution: { + averageSize: 500, + }, + observability: { + averageSize: 500, + }, + cases: { + averageSize: 500, + }, + }; + it('constructs the solution values correctly', () => { - expect(getSolutionValues(aggsResult, 'securitySolution')).toMatchInlineSnapshot(` + expect( + getSolutionValues({ + caseAggregations: caseAggsResult, + attachmentAggregations: attachmentAggsResult, + filesAggregations: filesRes, + owner: 'securitySolution', + }) + ).toMatchInlineSnapshot(` Object { "assignees": Object { "total": 5, "totalWithAtLeastOne": 0, "totalWithZero": 100, }, + "attachmentFramework": Object { + "externalAttachments": Array [ + Object { + "average": 1, + "maxOnACase": 10, + "total": 5, + "type": ".osquery", + }, + Object { + "average": 1, + "maxOnACase": 10, + "total": 5, + "type": ".files", + }, + ], + "files": Object { + "average": 1, + "averageSize": 500, + "maxOnACase": 10, + "total": 5, + }, + "persistableAttachments": Array [ + Object { + "average": 1, + "maxOnACase": 10, + "total": 5, + "type": ".ml", + }, + Object { + "average": 1, + "maxOnACase": 10, + "total": 5, + "type": ".files", + }, + ], + }, "daily": 3, "monthly": 1, - "total": 1, + "total": 5, "weekly": 2, } `); - expect(getSolutionValues(aggsResult, 'cases')).toMatchInlineSnapshot(` + expect( + getSolutionValues({ + caseAggregations: caseAggsResult, + attachmentAggregations: attachmentAggsResult, + filesAggregations: filesRes, + owner: 'cases', + }) + ).toMatchInlineSnapshot(` Object { "assignees": Object { "total": 5, "totalWithAtLeastOne": 0, "totalWithZero": 100, }, + "attachmentFramework": Object { + "externalAttachments": Array [ + Object { + "average": 5, + "maxOnACase": 10, + "total": 5, + "type": ".osquery", + }, + Object { + "average": 5, + "maxOnACase": 10, + "total": 5, + "type": ".files", + }, + ], + "files": Object { + "average": 5, + "averageSize": 500, + "maxOnACase": 10, + "total": 5, + }, + "persistableAttachments": Array [ + Object { + "average": 5, + "maxOnACase": 10, + "total": 5, + "type": ".ml", + }, + Object { + "average": 5, + "maxOnACase": 10, + "total": 5, + "type": ".files", + }, + ], + }, "daily": 3, "monthly": 1, "total": 1, "weekly": 2, } `); - expect(getSolutionValues(aggsResult, 'observability')).toMatchInlineSnapshot(` + expect( + getSolutionValues({ + caseAggregations: caseAggsResult, + attachmentAggregations: attachmentAggsResult, + filesAggregations: filesRes, + owner: 'observability', + }) + ).toMatchInlineSnapshot(` Object { "assignees": Object { "total": 5, "totalWithAtLeastOne": 0, "totalWithZero": 100, }, + "attachmentFramework": Object { + "externalAttachments": Array [ + Object { + "average": 5, + "maxOnACase": 10, + "total": 5, + "type": ".osquery", + }, + Object { + "average": 5, + "maxOnACase": 10, + "total": 5, + "type": ".files", + }, + ], + "files": Object { + "average": 5, + "averageSize": 500, + "maxOnACase": 10, + "total": 5, + }, + "persistableAttachments": Array [ + Object { + "average": 5, + "maxOnACase": 10, + "total": 5, + "type": ".ml", + }, + Object { + "average": 5, + "maxOnACase": 10, + "total": 5, + "type": ".files", + }, + ], + }, "daily": 3, "monthly": 1, "total": 1, @@ -140,6 +351,282 @@ describe('utils', () => { }); }); + describe('getAttachmentsFrameworkStats', () => { + it('returns empty stats if the aggregation is undefined', () => { + expect(getAttachmentsFrameworkStats({ totalCasesForOwner: 0, owner: 'securitySolution' })) + .toMatchInlineSnapshot(` + Object { + "attachmentFramework": Object { + "externalAttachments": Array [], + "files": Object { + "average": 0, + "averageSize": 0, + "maxOnACase": 0, + "total": 0, + }, + "persistableAttachments": Array [], + }, + } + `); + }); + + describe('externalAttachments', () => { + const attachmentFramework: AttachmentFrameworkAggsResult = { + externalReferenceTypes: { + buckets: [ + { + doc_count: 5, + key: '.osquery', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + { + doc_count: 10, + key: '.files', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + ], + }, + persistableReferenceTypes: { + buckets: [], + }, + }; + + const aggs: AttachmentAggregationResult = { + securitySolution: { ...attachmentFramework }, + observability: { ...attachmentFramework }, + cases: { ...attachmentFramework }, + participants: { + value: 1, + }, + }; + + it('populates the externalAttachments array', () => { + const stats = getAttachmentsFrameworkStats({ + attachmentAggregations: aggs, + totalCasesForOwner: 5, + owner: 'securitySolution', + }); + + expect(stats.attachmentFramework.externalAttachments[0]).toEqual({ + // the average is 5 from the aggs result / 5 from the function parameter + average: 1, + maxOnACase: 10, + total: 5, + type: '.osquery', + }); + + expect(stats.attachmentFramework.externalAttachments[1]).toEqual({ + // the average is 10 from the aggs result / 5 from the function parameter + average: 2, + maxOnACase: 10, + total: 10, + type: '.files', + }); + }); + }); + + describe('persistableAttachments', () => { + const attachmentFramework: AttachmentFrameworkAggsResult = { + persistableReferenceTypes: { + buckets: [ + { + doc_count: 5, + key: '.osquery', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + { + doc_count: 10, + key: '.files', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + ], + }, + externalReferenceTypes: { + buckets: [], + }, + }; + + const aggs: AttachmentAggregationResult = { + securitySolution: { ...attachmentFramework }, + observability: { ...attachmentFramework }, + cases: { ...attachmentFramework }, + participants: { + value: 1, + }, + }; + + it('populates the externalAttachments array', () => { + const stats = getAttachmentsFrameworkStats({ + attachmentAggregations: aggs, + totalCasesForOwner: 5, + owner: 'securitySolution', + }); + + expect(stats.attachmentFramework.persistableAttachments[0]).toEqual({ + // the average is 5 from the aggs result / 5 from the function parameter + average: 1, + maxOnACase: 10, + total: 5, + type: '.osquery', + }); + + expect(stats.attachmentFramework.persistableAttachments[1]).toEqual({ + // the average is 10 from the aggs result / 5 from the function parameter + average: 2, + maxOnACase: 10, + total: 10, + type: '.files', + }); + }); + }); + + describe('files', () => { + it('sets the files stats to empty when it cannot find a files entry', () => { + const attachmentFramework: AttachmentFrameworkAggsResult = { + externalReferenceTypes: { + buckets: [ + { + doc_count: 5, + key: '.osquery', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + ], + }, + persistableReferenceTypes: { + buckets: [], + }, + }; + + const aggs: AttachmentAggregationResult = { + securitySolution: { ...attachmentFramework }, + observability: { ...attachmentFramework }, + cases: { ...attachmentFramework }, + participants: { + value: 1, + }, + }; + + const filesRes: FileAttachmentAggregationResult = { + securitySolution: { + averageSize: 500, + }, + observability: { + averageSize: 500, + }, + cases: { + averageSize: 500, + }, + }; + + expect( + getAttachmentsFrameworkStats({ + attachmentAggregations: aggs, + totalCasesForOwner: 5, + filesAggregations: filesRes, + owner: 'securitySolution', + }).attachmentFramework.files + ).toMatchInlineSnapshot(` + Object { + "average": 0, + "averageSize": 0, + "maxOnACase": 0, + "total": 0, + } + `); + }); + + it('sets the files stats when it finds a files entry', () => { + const attachmentFramework: AttachmentFrameworkAggsResult = { + externalReferenceTypes: { + buckets: [ + { + doc_count: 5, + key: '.files', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + ], + }, + persistableReferenceTypes: { + buckets: [], + }, + }; + + const aggs: AttachmentAggregationResult = { + securitySolution: { ...attachmentFramework }, + observability: { ...attachmentFramework }, + cases: { ...attachmentFramework }, + participants: { + value: 1, + }, + }; + + const filesRes: FileAttachmentAggregationResult = { + securitySolution: { + averageSize: 500, + }, + observability: { + averageSize: 500, + }, + cases: { + averageSize: 500, + }, + }; + + expect( + getAttachmentsFrameworkStats({ + attachmentAggregations: aggs, + filesAggregations: filesRes, + totalCasesForOwner: 5, + owner: 'securitySolution', + }).attachmentFramework.files + ).toMatchInlineSnapshot(` + Object { + "average": 1, + "averageSize": 500, + "maxOnACase": 10, + "total": 5, + } + `); + }); + }); + }); + describe('getCountsAggregationQuery', () => { it('returns the correct query', () => { expect(getCountsAggregationQuery('test')).toEqual({ diff --git a/x-pack/plugins/cases/server/telemetry/queries/utils.ts b/x-pack/plugins/cases/server/telemetry/queries/utils.ts index 82bdef3ebe825..de578cc27013c 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/utils.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/utils.ts @@ -19,9 +19,16 @@ import type { CasesTelemetry, MaxBucketOnCaseAggregation, SolutionTelemetry, + AttachmentFramework, + AttachmentAggregationResult, + BucketsWithMaxOnCase, + AttachmentStats, + FileAttachmentStats, + FileAttachmentAggregationResult, } from '../types'; import { buildFilter } from '../../client/utils'; -import type { OWNERS } from '../constants'; +import type { Owner } from '../../../common/constants/types'; +import { FILE_ATTACHMENT_TYPE } from '../../../common/api'; export const getCountsAggregationQuery = (savedObjectType: string) => ({ counts: { @@ -154,22 +161,38 @@ export const getBucketFromAggregation = ({ aggs?: Record; }): Buckets['buckets'] => (get(aggs, `${key}.buckets`) ?? []) as Buckets['buckets']; -export const getSolutionValues = ( - aggregations: CaseAggregationResult | undefined, - owner: typeof OWNERS[number] -): SolutionTelemetry => { +export const getSolutionValues = ({ + caseAggregations, + attachmentAggregations, + filesAggregations, + owner, +}: { + caseAggregations?: CaseAggregationResult; + attachmentAggregations?: AttachmentAggregationResult; + filesAggregations?: FileAttachmentAggregationResult; + owner: Owner; +}): SolutionTelemetry => { const aggregationsBuckets = getAggregationsBuckets({ - aggs: aggregations, + aggs: caseAggregations, keys: ['totalsByOwner', 'securitySolution.counts', 'observability.counts', 'cases.counts'], }); + const totalCasesForOwner = findValueInBuckets(aggregationsBuckets.totalsByOwner, owner); + return { - total: findValueInBuckets(aggregationsBuckets.totalsByOwner, owner), + total: totalCasesForOwner, ...getCountsFromBuckets(aggregationsBuckets[`${owner}.counts`]), + ...getAttachmentsFrameworkStats({ + attachmentAggregations, + filesAggregations, + totalCasesForOwner, + owner, + }), 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, + total: caseAggregations?.[owner].totalAssignees.value ?? 0, + totalWithZero: caseAggregations?.[owner].assigneeFilters.buckets.zero.doc_count ?? 0, + totalWithAtLeastOne: + caseAggregations?.[owner].assigneeFilters.buckets.atLeastOne.doc_count ?? 0, }, }; }; @@ -192,6 +215,95 @@ export const getAggregationsBuckets = ({ {} ); +export const getAttachmentsFrameworkStats = ({ + attachmentAggregations, + filesAggregations, + totalCasesForOwner, + owner, +}: { + attachmentAggregations?: AttachmentAggregationResult; + filesAggregations?: FileAttachmentAggregationResult; + totalCasesForOwner: number; + owner: Owner; +}): AttachmentFramework => { + if (!attachmentAggregations) { + return emptyAttachmentFramework(); + } + const attachmentOwnerStats = attachmentAggregations[owner]; + const averageFileSize = filesAggregations?.[owner]?.averageSize; + + return { + attachmentFramework: { + externalAttachments: getAttachmentRegistryStats( + attachmentOwnerStats.externalReferenceTypes, + totalCasesForOwner + ), + persistableAttachments: getAttachmentRegistryStats( + attachmentOwnerStats.persistableReferenceTypes, + totalCasesForOwner + ), + files: getFileAttachmentStats({ + registryResults: attachmentOwnerStats.externalReferenceTypes, + averageFileSize, + totalCasesForOwner, + }), + }, + }; +}; + +const getAttachmentRegistryStats = ( + registryResults: BucketsWithMaxOnCase, + totalCasesForOwner: number +): AttachmentStats[] => { + const stats: AttachmentStats[] = []; + + for (const bucket of registryResults.buckets) { + const commonFields = { + average: calculateTypePerCaseAverage(bucket.doc_count, totalCasesForOwner), + maxOnACase: bucket.references.cases.max.value, + total: bucket.doc_count, + }; + + stats.push({ + type: bucket.key, + ...commonFields, + }); + } + + return stats; +}; + +const calculateTypePerCaseAverage = (typeDocCount: number, totalCases: number) => { + if (totalCases === 0) { + return 0; + } + + return Math.round(typeDocCount / totalCases); +}; + +const getFileAttachmentStats = ({ + registryResults, + averageFileSize, + totalCasesForOwner, +}: { + registryResults: BucketsWithMaxOnCase; + averageFileSize?: number; + totalCasesForOwner: number; +}): FileAttachmentStats => { + const fileBucket = registryResults.buckets.find((bucket) => bucket.key === FILE_ATTACHMENT_TYPE); + + if (!fileBucket || averageFileSize == null) { + return emptyFileAttachment(); + } + + return { + averageSize: averageFileSize, + average: calculateTypePerCaseAverage(fileBucket.doc_count, totalCasesForOwner), + maxOnACase: fileBucket.references.cases.max.value, + total: fileBucket.doc_count, + }; +}; + export const getOnlyAlertsCommentsFilter = () => buildFilter({ filters: ['alert'], @@ -244,6 +356,7 @@ export const getTelemetryDataEmptyState = (): CasesTelemetry => ({ weekly: 0, daily: 0, assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 }, + ...emptyAttachmentFramework(), }, obs: { total: 0, @@ -251,6 +364,7 @@ export const getTelemetryDataEmptyState = (): CasesTelemetry => ({ weekly: 0, daily: 0, assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 }, + ...emptyAttachmentFramework(), }, main: { total: 0, @@ -258,6 +372,7 @@ export const getTelemetryDataEmptyState = (): CasesTelemetry => ({ weekly: 0, daily: 0, assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 }, + ...emptyAttachmentFramework(), }, }, userActions: { all: { total: 0, monthly: 0, weekly: 0, daily: 0, maxOnACase: 0 } }, @@ -286,3 +401,18 @@ export const getTelemetryDataEmptyState = (): CasesTelemetry => ({ }, }, }); + +const emptyAttachmentFramework = (): AttachmentFramework => ({ + attachmentFramework: { + persistableAttachments: [], + externalAttachments: [], + files: emptyFileAttachment(), + }, +}); + +const emptyFileAttachment = (): FileAttachmentStats => ({ + average: 0, + averageSize: 0, + maxOnACase: 0, + total: 0, +}); diff --git a/x-pack/plugins/cases/server/telemetry/schema.ts b/x-pack/plugins/cases/server/telemetry/schema.ts index 1f51ca134b577..f21599e298237 100644 --- a/x-pack/plugins/cases/server/telemetry/schema.ts +++ b/x-pack/plugins/cases/server/telemetry/schema.ts @@ -14,6 +14,7 @@ import type { TypeString, SolutionTelemetrySchema, AssigneesSchema, + AttachmentFrameworkSchema, } from './types'; const long: TypeLong = { type: 'long' }; @@ -26,6 +27,27 @@ const countSchema: CountSchema = { daily: long, }; +const attachmentSchema = { + type: 'array' as const, + items: { + average: long, + maxOnACase: long, + total: long, + type: string, + }, +}; + +const attachmentFrameworkSchema: AttachmentFrameworkSchema = { + persistableAttachments: attachmentSchema, + externalAttachments: attachmentSchema, + files: { + average: long, + averageSize: long, + maxOnACase: long, + total: long, + }, +}; + const assigneesSchema: AssigneesSchema = { total: long, totalWithZero: long, @@ -35,6 +57,7 @@ const assigneesSchema: AssigneesSchema = { const solutionTelemetry: SolutionTelemetrySchema = { ...countSchema, assignees: assigneesSchema, + attachmentFramework: attachmentFrameworkSchema, }; const statusSchema: StatusSchema = { diff --git a/x-pack/plugins/cases/server/telemetry/types.ts b/x-pack/plugins/cases/server/telemetry/types.ts index 095b967d1addf..59a57d6fd655c 100644 --- a/x-pack/plugins/cases/server/telemetry/types.ts +++ b/x-pack/plugins/cases/server/telemetry/types.ts @@ -7,7 +7,7 @@ import type { ISavedObjectsRepository, Logger } from '@kbn/core/server'; import type { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server'; -import type { OWNERS } from './constants'; +import type { Owner } from '../../common/constants/types'; export interface Buckets { buckets: Array<{ @@ -57,8 +57,32 @@ export interface AssigneesFilters { }; } +export interface FileAttachmentAverageSize { + averageSize: number; +} + +export type FileAttachmentAggregationResult = Record; + +export interface BucketsWithMaxOnCase { + buckets: Array< + { + doc_count: number; + key: string; + } & MaxBucketOnCaseAggregation + >; +} + +export interface AttachmentFrameworkAggsResult { + externalReferenceTypes: BucketsWithMaxOnCase; + persistableReferenceTypes: BucketsWithMaxOnCase; +} + +export type AttachmentAggregationResult = Record & { + participants: Cardinality; +}; + export type CaseAggregationResult = Record< - typeof OWNERS[number], + Owner, { counts: Buckets; totalAssignees: ValueCount; @@ -81,7 +105,29 @@ export interface Assignees { totalWithAtLeastOne: number; } -export interface SolutionTelemetry extends Count { +interface CommonAttachmentStats { + average: number; + maxOnACase: number; + total: number; +} + +export interface AttachmentStats extends CommonAttachmentStats { + type: string; +} + +export interface FileAttachmentStats extends CommonAttachmentStats { + averageSize: number; +} + +export interface AttachmentFramework { + attachmentFramework: { + externalAttachments: AttachmentStats[]; + persistableAttachments: AttachmentStats[]; + files: FileAttachmentStats; + }; +} + +export interface SolutionTelemetry extends Count, AttachmentFramework { assignees: Assignees; } @@ -147,4 +193,5 @@ export type StatusSchema = MakeSchemaFrom; export type LatestDatesSchema = MakeSchemaFrom; export type CasesTelemetrySchema = MakeSchemaFrom; export type AssigneesSchema = MakeSchemaFrom; +export type AttachmentFrameworkSchema = MakeSchemaFrom; export type SolutionTelemetrySchema = MakeSchemaFrom; From 89229fa80af245fd5eb29575156bac76bfb80944 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner Date: Thu, 9 Mar 2023 12:24:04 -0500 Subject: [PATCH 2/6] Adding telemetry for all cases --- .../server/telemetry/queries/cases.test.ts | 547 +++++++++++++++++- .../cases/server/telemetry/queries/cases.ts | 77 +-- .../server/telemetry/queries/utils.test.ts | 81 +-- .../cases/server/telemetry/queries/utils.ts | 25 +- .../plugins/cases/server/telemetry/schema.ts | 1 + .../plugins/cases/server/telemetry/types.ts | 30 +- .../schema/xpack_plugins.json | 120 ++++ 7 files changed, 728 insertions(+), 153 deletions(-) diff --git a/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts b/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts index 41cbfc2a12000..ca6049885b298 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts @@ -8,20 +8,25 @@ import type { SavedObjectsFindResponse } from '@kbn/core/server'; import { savedObjectsRepositoryMock, loggingSystemMock } from '@kbn/core/server/mocks'; import { ESCaseStatus } from '../../services/cases/types'; -import type { CaseAggregationResult } from '../types'; +import type { + AttachmentAggregationResult, + AttachmentFrameworkAggsResult, + CaseAggregationResult, + FileAttachmentAggregationResult, +} from '../types'; import { getCasesTelemetryData } from './cases'; +const MOCK_FIND_TOTAL = 5; +const SOLUTION_TOTAL = 1; + describe('getCasesTelemetryData', () => { describe('getCasesTelemetryData', () => { const logger = loggingSystemMock.createLogger(); const savedObjectsClient = savedObjectsRepositoryMock.create(); - const mockFind = ( - aggs: Record = {}, - so: SavedObjectsFindResponse['saved_objects'] = [] - ) => { + const mockFind = (aggs: object, so: SavedObjectsFindResponse['saved_objects'] = []) => { savedObjectsClient.find.mockResolvedValueOnce({ - total: 5, + total: MOCK_FIND_TOTAL, saved_objects: so, per_page: 1, page: 1, @@ -103,22 +108,100 @@ describe('getCasesTelemetryData', () => { buckets: [ { key: 'observability', - doc_count: 1, + doc_count: SOLUTION_TOTAL, }, { key: 'securitySolution', - doc_count: 1, + doc_count: SOLUTION_TOTAL, }, { key: 'cases', - doc_count: 1, + doc_count: SOLUTION_TOTAL, }, ], }, }; + const attachmentFramework: AttachmentFrameworkAggsResult = { + externalReferenceTypes: { + buckets: [ + { + doc_count: 5, + key: '.osquery', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + { + doc_count: 5, + key: '.files', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + ], + }, + persistableReferenceTypes: { + buckets: [ + { + doc_count: 5, + key: '.ml', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + { + doc_count: 5, + key: '.files', + references: { + cases: { + max: { + value: 10, + }, + }, + }, + }, + ], + }, + }; + + const attachmentAggsResult: AttachmentAggregationResult = { + securitySolution: { ...attachmentFramework }, + observability: { ...attachmentFramework }, + cases: { ...attachmentFramework }, + participants: { + value: 2, + }, + ...attachmentFramework, + }; + + const filesRes: FileAttachmentAggregationResult = { + securitySolution: { + averageSize: 500, + }, + observability: { + averageSize: 500, + }, + cases: { + averageSize: 500, + }, + averageSize: 500, + }; + mockFind(caseAggsResult); - mockFind({ participants: { value: 2 } }); + mockFind(attachmentAggsResult); mockFind({ references: { referenceType: { referenceAgg: { value: 3 } } } }); mockFind({ references: { referenceType: { referenceAgg: { value: 4 } } } }); mockSavedObjectResponse({ @@ -130,6 +213,7 @@ describe('getCasesTelemetryData', () => { mockSavedObjectResponse({ closed_at: '2022-03-08T12:24:11.429Z', }); + mockFind(filesRes); }; beforeEach(() => { @@ -139,10 +223,62 @@ describe('getCasesTelemetryData', () => { it('it returns the correct res', async () => { mockResponse(); + const attachmentFramework = (total: number, average: number) => { + return { + attachmentFramework: { + externalAttachments: [ + { + average, + maxOnACase: 10, + total, + type: '.osquery', + }, + { + average, + maxOnACase: 10, + total, + type: '.files', + }, + ], + persistableAttachments: [ + { + average, + maxOnACase: 10, + total, + type: '.ml', + }, + { + average, + maxOnACase: 10, + total, + type: '.files', + }, + ], + files: { + averageSize: 500, + average, + maxOnACase: 10, + total, + }, + }, + }; + }; + const res = await getCasesTelemetryData({ savedObjectsClient, logger }); + + const allAttachmentsTotal = 5; + const allAttachmentsAverage = allAttachmentsTotal / MOCK_FIND_TOTAL; + + const solutionAttachmentsTotal = 5; + const solutionAttachmentsAverage = solutionAttachmentsTotal / SOLUTION_TOTAL; + const solutionAttachmentFrameworkStats = attachmentFramework( + solutionAttachmentsTotal, + solutionAttachmentsAverage + ); + expect(res).toEqual({ all: { - total: 5, + total: MOCK_FIND_TOTAL, daily: 3, weekly: 2, monthly: 1, @@ -168,6 +304,7 @@ describe('getCasesTelemetryData', () => { totalWithZero: 100, totalWithAtLeastOne: 0, }, + ...attachmentFramework(allAttachmentsTotal, allAttachmentsAverage), }, main: { assignees: { @@ -175,6 +312,7 @@ describe('getCasesTelemetryData', () => { totalWithZero: 100, totalWithAtLeastOne: 0, }, + ...solutionAttachmentFrameworkStats, total: 1, daily: 3, weekly: 2, @@ -186,6 +324,7 @@ describe('getCasesTelemetryData', () => { totalWithZero: 100, totalWithAtLeastOne: 0, }, + ...solutionAttachmentFrameworkStats, total: 1, daily: 3, weekly: 2, @@ -197,6 +336,7 @@ describe('getCasesTelemetryData', () => { totalWithZero: 100, totalWithAtLeastOne: 0, }, + ...solutionAttachmentFrameworkStats, total: 1, daily: 3, weekly: 2, @@ -468,18 +608,311 @@ describe('getCasesTelemetryData', () => { } `); - expect(savedObjectsClient.find.mock.calls[1][0]).toEqual({ - aggs: { - participants: { - cardinality: { - field: 'cases-comments.attributes.created_by.username', + expect(savedObjectsClient.find.mock.calls[1][0]).toMatchInlineSnapshot(` + Object { + "aggs": Object { + "cases": Object { + "aggs": Object { + "externalReferenceTypes": Object { + "aggs": Object { + "references": Object { + "aggregations": Object { + "cases": Object { + "aggregations": Object { + "ids": Object { + "terms": Object { + "field": "cases-comments.references.id", + }, + }, + "max": Object { + "max_bucket": Object { + "buckets_path": "ids._count", + }, + }, + }, + "filter": Object { + "term": Object { + "cases-comments.references.type": "cases", + }, + }, + }, + }, + "nested": Object { + "path": "cases-comments.references", + }, + }, + }, + "terms": Object { + "field": "cases-comments.attributes.externalReferenceAttachmentTypeId", + }, + }, + "persistableReferenceTypes": Object { + "aggs": Object { + "references": Object { + "aggregations": Object { + "cases": Object { + "aggregations": Object { + "ids": Object { + "terms": Object { + "field": "cases-comments.references.id", + }, + }, + "max": Object { + "max_bucket": Object { + "buckets_path": "ids._count", + }, + }, + }, + "filter": Object { + "term": Object { + "cases-comments.references.type": "cases", + }, + }, + }, + }, + "nested": Object { + "path": "cases-comments.references", + }, + }, + }, + "terms": Object { + "field": "cases-comments.attributes.persistableStateAttachmentTypeId", + }, + }, + }, + "filter": Object { + "term": Object { + "cases-comments.attributes.owner": "cases", + }, + }, + }, + "externalReferenceTypes": Object { + "aggs": Object { + "references": Object { + "aggregations": Object { + "cases": Object { + "aggregations": Object { + "ids": Object { + "terms": Object { + "field": "cases-comments.references.id", + }, + }, + "max": Object { + "max_bucket": Object { + "buckets_path": "ids._count", + }, + }, + }, + "filter": Object { + "term": Object { + "cases-comments.references.type": "cases", + }, + }, + }, + }, + "nested": Object { + "path": "cases-comments.references", + }, + }, + }, + "terms": Object { + "field": "cases-comments.attributes.externalReferenceAttachmentTypeId", + }, + }, + "observability": Object { + "aggs": Object { + "externalReferenceTypes": Object { + "aggs": Object { + "references": Object { + "aggregations": Object { + "cases": Object { + "aggregations": Object { + "ids": Object { + "terms": Object { + "field": "cases-comments.references.id", + }, + }, + "max": Object { + "max_bucket": Object { + "buckets_path": "ids._count", + }, + }, + }, + "filter": Object { + "term": Object { + "cases-comments.references.type": "cases", + }, + }, + }, + }, + "nested": Object { + "path": "cases-comments.references", + }, + }, + }, + "terms": Object { + "field": "cases-comments.attributes.externalReferenceAttachmentTypeId", + }, + }, + "persistableReferenceTypes": Object { + "aggs": Object { + "references": Object { + "aggregations": Object { + "cases": Object { + "aggregations": Object { + "ids": Object { + "terms": Object { + "field": "cases-comments.references.id", + }, + }, + "max": Object { + "max_bucket": Object { + "buckets_path": "ids._count", + }, + }, + }, + "filter": Object { + "term": Object { + "cases-comments.references.type": "cases", + }, + }, + }, + }, + "nested": Object { + "path": "cases-comments.references", + }, + }, + }, + "terms": Object { + "field": "cases-comments.attributes.persistableStateAttachmentTypeId", + }, + }, + }, + "filter": Object { + "term": Object { + "cases-comments.attributes.owner": "observability", + }, + }, + }, + "participants": Object { + "cardinality": Object { + "field": "cases-comments.attributes.created_by.username", + }, + }, + "persistableReferenceTypes": Object { + "aggs": Object { + "references": Object { + "aggregations": Object { + "cases": Object { + "aggregations": Object { + "ids": Object { + "terms": Object { + "field": "cases-comments.references.id", + }, + }, + "max": Object { + "max_bucket": Object { + "buckets_path": "ids._count", + }, + }, + }, + "filter": Object { + "term": Object { + "cases-comments.references.type": "cases", + }, + }, + }, + }, + "nested": Object { + "path": "cases-comments.references", + }, + }, + }, + "terms": Object { + "field": "cases-comments.attributes.persistableStateAttachmentTypeId", + }, + }, + "securitySolution": Object { + "aggs": Object { + "externalReferenceTypes": Object { + "aggs": Object { + "references": Object { + "aggregations": Object { + "cases": Object { + "aggregations": Object { + "ids": Object { + "terms": Object { + "field": "cases-comments.references.id", + }, + }, + "max": Object { + "max_bucket": Object { + "buckets_path": "ids._count", + }, + }, + }, + "filter": Object { + "term": Object { + "cases-comments.references.type": "cases", + }, + }, + }, + }, + "nested": Object { + "path": "cases-comments.references", + }, + }, + }, + "terms": Object { + "field": "cases-comments.attributes.externalReferenceAttachmentTypeId", + }, + }, + "persistableReferenceTypes": Object { + "aggs": Object { + "references": Object { + "aggregations": Object { + "cases": Object { + "aggregations": Object { + "ids": Object { + "terms": Object { + "field": "cases-comments.references.id", + }, + }, + "max": Object { + "max_bucket": Object { + "buckets_path": "ids._count", + }, + }, + }, + "filter": Object { + "term": Object { + "cases-comments.references.type": "cases", + }, + }, + }, + }, + "nested": Object { + "path": "cases-comments.references", + }, + }, + }, + "terms": Object { + "field": "cases-comments.attributes.persistableStateAttachmentTypeId", + }, + }, + }, + "filter": Object { + "term": Object { + "cases-comments.attributes.owner": "securitySolution", + }, + }, }, }, - }, - page: 0, - perPage: 0, - type: 'cases-comments', - }); + "page": 0, + "perPage": 0, + "type": "cases-comments", + } + `); expect(savedObjectsClient.find.mock.calls[2][0]).toEqual({ aggs: { @@ -582,6 +1015,78 @@ describe('getCasesTelemetryData', () => { type: 'cases', }); } + + expect(savedObjectsClient.find.mock.calls[7][0]).toMatchInlineSnapshot(` + Object { + "aggs": Object { + "averageSize": Object { + "avg": Object { + "field": "file.attributes.size", + }, + }, + "cases": Object { + "aggs": Object { + "averageSize": Object { + "avg": Object { + "field": "file.attributes.size", + }, + }, + }, + "filter": Object { + "term": Object { + "file.attributes.Meta.owner": "cases", + }, + }, + }, + "observability": Object { + "aggs": Object { + "averageSize": Object { + "avg": Object { + "field": "file.attributes.size", + }, + }, + }, + "filter": Object { + "term": Object { + "file.attributes.Meta.owner": "observability", + }, + }, + }, + "securitySolution": Object { + "aggs": Object { + "averageSize": Object { + "avg": Object { + "field": "file.attributes.size", + }, + }, + }, + "filter": Object { + "term": Object { + "file.attributes.Meta.owner": "securitySolution", + }, + }, + }, + }, + "filter": Object { + "arguments": Array [ + Object { + "isQuoted": false, + "type": "literal", + "value": "file.attributes.Meta.caseId", + }, + Object { + "type": "wildcard", + "value": "@kuery-wildcard@", + }, + ], + "function": "is", + "type": "function", + }, + "page": 0, + "perPage": 0, + "type": "file", + } + `); }); }); }); diff --git a/x-pack/plugins/cases/server/telemetry/queries/cases.ts b/x-pack/plugins/cases/server/telemetry/queries/cases.ts index 29868c5129cdb..a918089cbe0e6 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/cases.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/cases.ts @@ -28,6 +28,7 @@ import type { import { findValueInBuckets, getAggregationsBuckets, + getAttachmentsFrameworkStats, getCountsAggregationQuery, getCountsFromBuckets, getMaxBucketOnCaseAggregationQuery, @@ -56,9 +57,9 @@ export const getLatestCasesDates = async ({ ]); return { - createdAt: savedObjects?.[0].saved_objects?.[0].attributes?.created_at ?? null, - updatedAt: savedObjects?.[1].saved_objects?.[0].attributes?.updated_at ?? null, - closedAt: savedObjects?.[2].saved_objects?.[0].attributes?.closed_at ?? null, + createdAt: savedObjects?.[0]?.saved_objects?.[0]?.attributes?.created_at ?? null, + updatedAt: savedObjects?.[1]?.saved_objects?.[0]?.attributes?.updated_at ?? null, + closedAt: savedObjects?.[2]?.saved_objects?.[0]?.attributes?.closed_at ?? null, }; }; @@ -66,8 +67,6 @@ export const getCasesTelemetryData = async ({ savedObjectsClient, logger, }: CollectTelemetryDataParams): Promise => { - console.log('*****collecting telemetry'); - try { const [casesRes, commentsRes, totalAlertsRes, totalConnectorsRes, latestDates, filesRes] = await Promise.all([ @@ -79,15 +78,17 @@ export const getCasesTelemetryData = async ({ getFilesTelemetry(savedObjectsClient), ]); - console.log('****finished collection telemetry'); - console.log('casesRes ', JSON.stringify(casesRes, null, 2)); - console.log('filesRes ', JSON.stringify(filesRes, null, 2)); - const aggregationsBuckets = getAggregationsBuckets({ aggs: casesRes.aggregations, keys: ['counts', 'syncAlerts', 'status', 'users', 'totalAssignees'], }); + const allAttachmentFrameworkStats = getAttachmentsFrameworkStats({ + attachmentAggregations: commentsRes.aggregations, + totalCasesForOwner: casesRes.total, + filesAggregations: filesRes.aggregations, + }); + return { all: { total: casesRes.total, @@ -113,6 +114,7 @@ export const getCasesTelemetryData = async ({ totalWithAtLeastOne: casesRes.aggregations?.assigneeFilters.buckets.atLeastOne.doc_count ?? 0, }, + ...allAttachmentFrameworkStats, }, sec: getSolutionValues({ caseAggregations: casesRes.aggregations, @@ -228,6 +230,25 @@ const getAssigneesAggregations = () => ({ const getCommentsSavedObjectTelemetry = async ( savedObjectsClient: ISavedObjectsRepository ): Promise> => { + const attachmentRegistries = () => ({ + externalReferenceTypes: { + terms: { + field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.externalReferenceAttachmentTypeId`, + }, + aggs: { + ...getMaxBucketOnCaseAggregationQuery(CASE_COMMENT_SAVED_OBJECT), + }, + }, + persistableReferenceTypes: { + terms: { + field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.persistableStateAttachmentTypeId`, + }, + aggs: { + ...getMaxBucketOnCaseAggregationQuery(CASE_COMMENT_SAVED_OBJECT), + }, + }, + }); + const attachmentsByOwnerAggregationQuery = OWNERS.reduce( (aggQuery, owner) => ({ ...aggQuery, @@ -238,36 +259,20 @@ const getCommentsSavedObjectTelemetry = async ( }, }, aggs: { - externalReferenceTypes: { - terms: { - field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.externalReferenceAttachmentTypeId`, - }, - aggs: { - ...getMaxBucketOnCaseAggregationQuery(CASE_COMMENT_SAVED_OBJECT), - }, - }, - persistableReferenceTypes: { - terms: { - field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.persistableStateAttachmentTypeId`, - }, - aggs: { - ...getMaxBucketOnCaseAggregationQuery(CASE_COMMENT_SAVED_OBJECT), - }, - }, + ...attachmentRegistries(), }, }, }), {} ); - console.log('****collecting attachments telemetry'); - return savedObjectsClient.find({ page: 0, perPage: 0, type: CASE_COMMENT_SAVED_OBJECT, aggs: { ...attachmentsByOwnerAggregationQuery, + ...attachmentRegistries(), participants: { cardinality: { field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.created_by.username`, @@ -280,6 +285,14 @@ const getCommentsSavedObjectTelemetry = async ( const getFilesTelemetry = async ( savedObjectsClient: ISavedObjectsRepository ): Promise> => { + const averageSize = () => ({ + averageSize: { + avg: { + field: `${FILE_SO_TYPE}.attributes.size`, + }, + }, + }); + const filesByOwnerAggregationQuery = OWNERS.reduce( (aggQuery, owner) => ({ ...aggQuery, @@ -290,11 +303,7 @@ const getFilesTelemetry = async ( }, }, aggs: { - averageSize: { - avg: { - field: `${FILE_SO_TYPE}.attributes.size`, - }, - }, + ...averageSize(), }, }, }), @@ -307,8 +316,8 @@ const getFilesTelemetry = async ( page: 0, perPage: 0, type: FILE_SO_TYPE, - // filter: filterCaseIdExists, - aggs: filesByOwnerAggregationQuery, + filter: filterCaseIdExists, + aggs: { ...filesByOwnerAggregationQuery, ...averageSize() }, }); }; diff --git a/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts b/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts index a9a8732bd8982..d7c6a0e9bf7b9 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts @@ -165,6 +165,7 @@ describe('utils', () => { participants: { value: 5, }, + ...attachmentFramework, }; const filesRes: FileAttachmentAggregationResult = { @@ -177,6 +178,7 @@ describe('utils', () => { cases: { averageSize: 500, }, + averageSize: 500, }; it('constructs the solution values correctly', () => { @@ -353,8 +355,7 @@ describe('utils', () => { describe('getAttachmentsFrameworkStats', () => { it('returns empty stats if the aggregation is undefined', () => { - expect(getAttachmentsFrameworkStats({ totalCasesForOwner: 0, owner: 'securitySolution' })) - .toMatchInlineSnapshot(` + expect(getAttachmentsFrameworkStats({ totalCasesForOwner: 0 })).toMatchInlineSnapshot(` Object { "attachmentFramework": Object { "externalAttachments": Array [], @@ -403,20 +404,10 @@ describe('utils', () => { }, }; - const aggs: AttachmentAggregationResult = { - securitySolution: { ...attachmentFramework }, - observability: { ...attachmentFramework }, - cases: { ...attachmentFramework }, - participants: { - value: 1, - }, - }; - it('populates the externalAttachments array', () => { const stats = getAttachmentsFrameworkStats({ - attachmentAggregations: aggs, + attachmentAggregations: attachmentFramework, totalCasesForOwner: 5, - owner: 'securitySolution', }); expect(stats.attachmentFramework.externalAttachments[0]).toEqual({ @@ -470,20 +461,10 @@ describe('utils', () => { }, }; - const aggs: AttachmentAggregationResult = { - securitySolution: { ...attachmentFramework }, - observability: { ...attachmentFramework }, - cases: { ...attachmentFramework }, - participants: { - value: 1, - }, - }; - it('populates the externalAttachments array', () => { const stats = getAttachmentsFrameworkStats({ - attachmentAggregations: aggs, + attachmentAggregations: attachmentFramework, totalCasesForOwner: 5, - owner: 'securitySolution', }); expect(stats.attachmentFramework.persistableAttachments[0]).toEqual({ @@ -527,33 +508,11 @@ describe('utils', () => { }, }; - const aggs: AttachmentAggregationResult = { - securitySolution: { ...attachmentFramework }, - observability: { ...attachmentFramework }, - cases: { ...attachmentFramework }, - participants: { - value: 1, - }, - }; - - const filesRes: FileAttachmentAggregationResult = { - securitySolution: { - averageSize: 500, - }, - observability: { - averageSize: 500, - }, - cases: { - averageSize: 500, - }, - }; - expect( getAttachmentsFrameworkStats({ - attachmentAggregations: aggs, + attachmentAggregations: attachmentFramework, totalCasesForOwner: 5, - filesAggregations: filesRes, - owner: 'securitySolution', + filesAggregations: { averageSize: 500 }, }).attachmentFramework.files ).toMatchInlineSnapshot(` Object { @@ -587,33 +546,11 @@ describe('utils', () => { }, }; - const aggs: AttachmentAggregationResult = { - securitySolution: { ...attachmentFramework }, - observability: { ...attachmentFramework }, - cases: { ...attachmentFramework }, - participants: { - value: 1, - }, - }; - - const filesRes: FileAttachmentAggregationResult = { - securitySolution: { - averageSize: 500, - }, - observability: { - averageSize: 500, - }, - cases: { - averageSize: 500, - }, - }; - expect( getAttachmentsFrameworkStats({ - attachmentAggregations: aggs, - filesAggregations: filesRes, + attachmentAggregations: attachmentFramework, + filesAggregations: { averageSize: 500 }, totalCasesForOwner: 5, - owner: 'securitySolution', }).attachmentFramework.files ).toMatchInlineSnapshot(` Object { diff --git a/x-pack/plugins/cases/server/telemetry/queries/utils.ts b/x-pack/plugins/cases/server/telemetry/queries/utils.ts index de578cc27013c..43b5149e6333d 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/utils.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/utils.ts @@ -25,6 +25,8 @@ import type { AttachmentStats, FileAttachmentStats, FileAttachmentAggregationResult, + FileAttachmentAverageSize, + AttachmentFrameworkAggsResult, } from '../types'; import { buildFilter } from '../../client/utils'; import type { Owner } from '../../../common/constants/types'; @@ -178,15 +180,16 @@ export const getSolutionValues = ({ }); const totalCasesForOwner = findValueInBuckets(aggregationsBuckets.totalsByOwner, owner); + const attachmentsAggsForOwner = attachmentAggregations?.[owner]; + const fileAttachmentsForOwner = filesAggregations?.[owner]; return { total: totalCasesForOwner, ...getCountsFromBuckets(aggregationsBuckets[`${owner}.counts`]), ...getAttachmentsFrameworkStats({ - attachmentAggregations, - filesAggregations, + attachmentAggregations: attachmentsAggsForOwner, + filesAggregations: fileAttachmentsForOwner, totalCasesForOwner, - owner, }), assignees: { total: caseAggregations?.[owner].totalAssignees.value ?? 0, @@ -219,31 +222,28 @@ export const getAttachmentsFrameworkStats = ({ attachmentAggregations, filesAggregations, totalCasesForOwner, - owner, }: { - attachmentAggregations?: AttachmentAggregationResult; - filesAggregations?: FileAttachmentAggregationResult; + attachmentAggregations?: AttachmentFrameworkAggsResult; + filesAggregations?: FileAttachmentAverageSize; totalCasesForOwner: number; - owner: Owner; }): AttachmentFramework => { if (!attachmentAggregations) { return emptyAttachmentFramework(); } - const attachmentOwnerStats = attachmentAggregations[owner]; - const averageFileSize = filesAggregations?.[owner]?.averageSize; + const averageFileSize = filesAggregations?.averageSize; return { attachmentFramework: { externalAttachments: getAttachmentRegistryStats( - attachmentOwnerStats.externalReferenceTypes, + attachmentAggregations.externalReferenceTypes, totalCasesForOwner ), persistableAttachments: getAttachmentRegistryStats( - attachmentOwnerStats.persistableReferenceTypes, + attachmentAggregations.persistableReferenceTypes, totalCasesForOwner ), files: getFileAttachmentStats({ - registryResults: attachmentOwnerStats.externalReferenceTypes, + registryResults: attachmentAggregations.externalReferenceTypes, averageFileSize, totalCasesForOwner, }), @@ -349,6 +349,7 @@ export const getTelemetryDataEmptyState = (): CasesTelemetry => ({ updatedAt: null, closedAt: null, }, + ...emptyAttachmentFramework(), }, sec: { total: 0, diff --git a/x-pack/plugins/cases/server/telemetry/schema.ts b/x-pack/plugins/cases/server/telemetry/schema.ts index f21599e298237..7e04c070ac3aa 100644 --- a/x-pack/plugins/cases/server/telemetry/schema.ts +++ b/x-pack/plugins/cases/server/telemetry/schema.ts @@ -76,6 +76,7 @@ export const casesSchema: CasesTelemetrySchema = { cases: { all: { ...countSchema, + attachmentFramework: attachmentFrameworkSchema, assignees: assigneesSchema, status: statusSchema, syncAlertsOn: long, diff --git a/x-pack/plugins/cases/server/telemetry/types.ts b/x-pack/plugins/cases/server/telemetry/types.ts index 59a57d6fd655c..e2af2519b1e95 100644 --- a/x-pack/plugins/cases/server/telemetry/types.ts +++ b/x-pack/plugins/cases/server/telemetry/types.ts @@ -61,7 +61,8 @@ export interface FileAttachmentAverageSize { averageSize: number; } -export type FileAttachmentAggregationResult = Record; +export type FileAttachmentAggregationResult = Record & + FileAttachmentAverageSize; export interface BucketsWithMaxOnCase { buckets: Array< @@ -79,7 +80,7 @@ export interface AttachmentFrameworkAggsResult { export type AttachmentAggregationResult = Record & { participants: Cardinality; -}; +} & AttachmentFrameworkAggsResult; export type CaseAggregationResult = Record< Owner, @@ -145,18 +146,19 @@ export interface LatestDates { export interface CasesTelemetry { cases: { - all: Count & { - assignees: Assignees; - status: Status; - syncAlertsOn: number; - syncAlertsOff: number; - totalUsers: number; - totalParticipants: number; - totalTags: number; - totalWithAlerts: number; - totalWithConnectors: number; - latestDates: LatestDates; - }; + all: Count & + AttachmentFramework & { + assignees: Assignees; + status: Status; + syncAlertsOn: number; + syncAlertsOff: number; + totalUsers: number; + totalParticipants: number; + totalTags: number; + totalWithAlerts: number; + totalWithConnectors: number; + latestDates: LatestDates; + }; sec: SolutionTelemetry; obs: SolutionTelemetry; main: SolutionTelemetry; diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 99e466ebbb357..5a08e6af7930a 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -4796,6 +4796,36 @@ } } }, + "attachmentFramework": { + "properties": { + "persistableAttachments": { + "type": "array", + "items": { + "average": "long", + "maxOnACase": "long", + "total": "long", + "type": "string" + } + }, + "externalAttachments": { + "type": "array", + "items": { + "average": "long", + "maxOnACase": "long", + "total": "long", + "type": "string" + } + }, + "files": { + "properties": { + "average": "long", + "averageSize": "long", + "maxOnACase": "long", + "total": "long" + } + } + } + }, "status": { "properties": { "open": { @@ -4871,6 +4901,36 @@ "type": "long" } } + }, + "attachmentFramework": { + "properties": { + "persistableAttachments": { + "type": "array", + "items": { + "average": "long", + "maxOnACase": "long", + "total": "long", + "type": "string" + } + }, + "externalAttachments": { + "type": "array", + "items": { + "average": "long", + "maxOnACase": "long", + "total": "long", + "type": "string" + } + }, + "files": { + "properties": { + "average": "long", + "averageSize": "long", + "maxOnACase": "long", + "total": "long" + } + } + } } } }, @@ -4900,6 +4960,36 @@ "type": "long" } } + }, + "attachmentFramework": { + "properties": { + "persistableAttachments": { + "type": "array", + "items": { + "average": "long", + "maxOnACase": "long", + "total": "long", + "type": "string" + } + }, + "externalAttachments": { + "type": "array", + "items": { + "average": "long", + "maxOnACase": "long", + "total": "long", + "type": "string" + } + }, + "files": { + "properties": { + "average": "long", + "averageSize": "long", + "maxOnACase": "long", + "total": "long" + } + } + } } } }, @@ -4929,6 +5019,36 @@ "type": "long" } } + }, + "attachmentFramework": { + "properties": { + "persistableAttachments": { + "type": "array", + "items": { + "average": "long", + "maxOnACase": "long", + "total": "long", + "type": "string" + } + }, + "externalAttachments": { + "type": "array", + "items": { + "average": "long", + "maxOnACase": "long", + "total": "long", + "type": "string" + } + }, + "files": { + "properties": { + "average": "long", + "averageSize": "long", + "maxOnACase": "long", + "total": "long" + } + } + } } } } From 960e3dde600bfe1cc2e073e90f6b41eec63d59e8 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner Date: Thu, 9 Mar 2023 12:43:18 -0500 Subject: [PATCH 3/6] Fixing type errors --- x-pack/plugins/cases/server/telemetry/schema.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/cases/server/telemetry/schema.ts b/x-pack/plugins/cases/server/telemetry/schema.ts index 7e04c070ac3aa..59506c932a51f 100644 --- a/x-pack/plugins/cases/server/telemetry/schema.ts +++ b/x-pack/plugins/cases/server/telemetry/schema.ts @@ -15,6 +15,7 @@ import type { SolutionTelemetrySchema, AssigneesSchema, AttachmentFrameworkSchema, + AttachmentItemsSchema, } from './types'; const long: TypeLong = { type: 'long' }; @@ -27,8 +28,13 @@ const countSchema: CountSchema = { daily: long, }; -const attachmentSchema = { - type: 'array' as const, +interface AttachmentRegistrySchema { + type: 'array'; + items: AttachmentItemsSchema; +} + +const attachmentRegistrySchema: AttachmentRegistrySchema = { + type: 'array', items: { average: long, maxOnACase: long, @@ -38,8 +44,8 @@ const attachmentSchema = { }; const attachmentFrameworkSchema: AttachmentFrameworkSchema = { - persistableAttachments: attachmentSchema, - externalAttachments: attachmentSchema, + persistableAttachments: attachmentRegistrySchema, + externalAttachments: attachmentRegistrySchema, files: { average: long, averageSize: long, From c12d75a01352b48e90875286ad54b54c3aeb3133 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner Date: Thu, 9 Mar 2023 12:43:36 -0500 Subject: [PATCH 4/6] Forgot type --- x-pack/plugins/cases/server/telemetry/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/cases/server/telemetry/types.ts b/x-pack/plugins/cases/server/telemetry/types.ts index e2af2519b1e95..5a3f03d4dc691 100644 --- a/x-pack/plugins/cases/server/telemetry/types.ts +++ b/x-pack/plugins/cases/server/telemetry/types.ts @@ -196,4 +196,5 @@ export type LatestDatesSchema = MakeSchemaFrom; export type CasesTelemetrySchema = MakeSchemaFrom; export type AssigneesSchema = MakeSchemaFrom; export type AttachmentFrameworkSchema = MakeSchemaFrom; +export type AttachmentItemsSchema = MakeSchemaFrom; export type SolutionTelemetrySchema = MakeSchemaFrom; From 73c89da6ff679cd77db637983e837969e87a5a36 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner Date: Thu, 9 Mar 2023 14:38:47 -0500 Subject: [PATCH 5/6] Fixing telemetry mapping errors --- .../schema/xpack_plugins.json | 234 +++++++++++++----- 1 file changed, 173 insertions(+), 61 deletions(-) diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 5a08e6af7930a..fc3f0ac44d7f3 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -4783,49 +4783,77 @@ "daily": { "type": "long" }, - "assignees": { - "properties": { - "total": { - "type": "long" - }, - "totalWithZero": { - "type": "long" - }, - "totalWithAtLeastOne": { - "type": "long" - } - } - }, "attachmentFramework": { "properties": { "persistableAttachments": { "type": "array", "items": { - "average": "long", - "maxOnACase": "long", - "total": "long", - "type": "string" + "properties": { + "average": { + "type": "long" + }, + "maxOnACase": { + "type": "long" + }, + "total": { + "type": "long" + }, + "type": { + "type": "keyword" + } + } } }, "externalAttachments": { "type": "array", "items": { - "average": "long", - "maxOnACase": "long", - "total": "long", - "type": "string" + "properties": { + "average": { + "type": "long" + }, + "maxOnACase": { + "type": "long" + }, + "total": { + "type": "long" + }, + "type": { + "type": "keyword" + } + } } }, "files": { "properties": { - "average": "long", - "averageSize": "long", - "maxOnACase": "long", - "total": "long" + "average": { + "type": "long" + }, + "averageSize": { + "type": "long" + }, + "maxOnACase": { + "type": "long" + }, + "total": { + "type": "long" + } } } } }, + "assignees": { + "properties": { + "total": { + "type": "long" + }, + "totalWithZero": { + "type": "long" + }, + "totalWithAtLeastOne": { + "type": "long" + } + } + }, "status": { "properties": { "open": { @@ -4907,27 +4935,55 @@ "persistableAttachments": { "type": "array", "items": { - "average": "long", - "maxOnACase": "long", - "total": "long", - "type": "string" + "properties": { + "average": { + "type": "long" + }, + "maxOnACase": { + "type": "long" + }, + "total": { + "type": "long" + }, + "type": { + "type": "keyword" + } + } } }, "externalAttachments": { "type": "array", "items": { - "average": "long", - "maxOnACase": "long", - "total": "long", - "type": "string" + "properties": { + "average": { + "type": "long" + }, + "maxOnACase": { + "type": "long" + }, + "total": { + "type": "long" + }, + "type": { + "type": "keyword" + } + } } }, "files": { "properties": { - "average": "long", - "averageSize": "long", - "maxOnACase": "long", - "total": "long" + "average": { + "type": "long" + }, + "averageSize": { + "type": "long" + }, + "maxOnACase": { + "type": "long" + }, + "total": { + "type": "long" + } } } } @@ -4966,27 +5022,55 @@ "persistableAttachments": { "type": "array", "items": { - "average": "long", - "maxOnACase": "long", - "total": "long", - "type": "string" + "properties": { + "average": { + "type": "long" + }, + "maxOnACase": { + "type": "long" + }, + "total": { + "type": "long" + }, + "type": { + "type": "keyword" + } + } } }, "externalAttachments": { "type": "array", "items": { - "average": "long", - "maxOnACase": "long", - "total": "long", - "type": "string" + "properties": { + "average": { + "type": "long" + }, + "maxOnACase": { + "type": "long" + }, + "total": { + "type": "long" + }, + "type": { + "type": "keyword" + } + } } }, "files": { "properties": { - "average": "long", - "averageSize": "long", - "maxOnACase": "long", - "total": "long" + "average": { + "type": "long" + }, + "averageSize": { + "type": "long" + }, + "maxOnACase": { + "type": "long" + }, + "total": { + "type": "long" + } } } } @@ -5025,27 +5109,55 @@ "persistableAttachments": { "type": "array", "items": { - "average": "long", - "maxOnACase": "long", - "total": "long", - "type": "string" + "properties": { + "average": { + "type": "long" + }, + "maxOnACase": { + "type": "long" + }, + "total": { + "type": "long" + }, + "type": { + "type": "keyword" + } + } } }, "externalAttachments": { "type": "array", "items": { - "average": "long", - "maxOnACase": "long", - "total": "long", - "type": "string" + "properties": { + "average": { + "type": "long" + }, + "maxOnACase": { + "type": "long" + }, + "total": { + "type": "long" + }, + "type": { + "type": "keyword" + } + } } }, "files": { "properties": { - "average": "long", - "averageSize": "long", - "maxOnACase": "long", - "total": "long" + "average": { + "type": "long" + }, + "averageSize": { + "type": "long" + }, + "maxOnACase": { + "type": "long" + }, + "total": { + "type": "long" + } } } } From a6bbf17417fdec42ab6cd53a05479964798b7443 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner Date: Thu, 9 Mar 2023 16:39:01 -0500 Subject: [PATCH 6/6] Fixing schema failures from null --- .../cases/server/telemetry/queries/cases.ts | 6 +- .../cases/server/telemetry/queries/utils.ts | 84 ------------------- .../plugins/cases/server/telemetry/types.ts | 6 +- 3 files changed, 6 insertions(+), 90 deletions(-) diff --git a/x-pack/plugins/cases/server/telemetry/queries/cases.ts b/x-pack/plugins/cases/server/telemetry/queries/cases.ts index a918089cbe0e6..0e999721ae105 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/cases.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/cases.ts @@ -57,9 +57,9 @@ export const getLatestCasesDates = async ({ ]); return { - createdAt: savedObjects?.[0]?.saved_objects?.[0]?.attributes?.created_at ?? null, - updatedAt: savedObjects?.[1]?.saved_objects?.[0]?.attributes?.updated_at ?? null, - closedAt: savedObjects?.[2]?.saved_objects?.[0]?.attributes?.closed_at ?? null, + createdAt: savedObjects?.[0]?.saved_objects?.[0]?.attributes?.created_at ?? '', + updatedAt: savedObjects?.[1]?.saved_objects?.[0]?.attributes?.updated_at ?? '', + closedAt: savedObjects?.[2]?.saved_objects?.[0]?.attributes?.closed_at ?? '', }; }; diff --git a/x-pack/plugins/cases/server/telemetry/queries/utils.ts b/x-pack/plugins/cases/server/telemetry/queries/utils.ts index 43b5149e6333d..0c0a4f7bbf87d 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/utils.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/utils.ts @@ -16,7 +16,6 @@ import { import type { CaseAggregationResult, Buckets, - CasesTelemetry, MaxBucketOnCaseAggregation, SolutionTelemetry, AttachmentFramework, @@ -320,89 +319,6 @@ export const getOnlyConnectorsFilter = () => type: CASE_USER_ACTION_SAVED_OBJECT, }); -export const getTelemetryDataEmptyState = (): CasesTelemetry => ({ - cases: { - all: { - assignees: { - total: 0, - totalWithZero: 0, - totalWithAtLeastOne: 0, - }, - total: 0, - monthly: 0, - weekly: 0, - daily: 0, - status: { - open: 0, - inProgress: 0, - closed: 0, - }, - syncAlertsOn: 0, - syncAlertsOff: 0, - totalUsers: 0, - totalParticipants: 0, - totalTags: 0, - totalWithAlerts: 0, - totalWithConnectors: 0, - latestDates: { - createdAt: null, - updatedAt: null, - closedAt: null, - }, - ...emptyAttachmentFramework(), - }, - sec: { - total: 0, - monthly: 0, - weekly: 0, - daily: 0, - assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 }, - ...emptyAttachmentFramework(), - }, - obs: { - total: 0, - monthly: 0, - weekly: 0, - daily: 0, - assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 }, - ...emptyAttachmentFramework(), - }, - main: { - total: 0, - monthly: 0, - weekly: 0, - daily: 0, - assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 }, - ...emptyAttachmentFramework(), - }, - }, - userActions: { all: { total: 0, monthly: 0, weekly: 0, daily: 0, maxOnACase: 0 } }, - comments: { all: { total: 0, monthly: 0, weekly: 0, daily: 0, maxOnACase: 0 } }, - alerts: { all: { total: 0, monthly: 0, weekly: 0, daily: 0, maxOnACase: 0 } }, - connectors: { - all: { - all: { totalAttached: 0 }, - itsm: { totalAttached: 0 }, - sir: { totalAttached: 0 }, - jira: { totalAttached: 0 }, - resilient: { totalAttached: 0 }, - swimlane: { totalAttached: 0 }, - maxAttachedToACase: 0, - }, - }, - pushes: { - all: { total: 0, maxOnACase: 0 }, - }, - configuration: { - all: { - closure: { - manually: 0, - automatic: 0, - }, - }, - }, -}); - const emptyAttachmentFramework = (): AttachmentFramework => ({ attachmentFramework: { persistableAttachments: [], diff --git a/x-pack/plugins/cases/server/telemetry/types.ts b/x-pack/plugins/cases/server/telemetry/types.ts index 5a3f03d4dc691..e28c3abaf4d52 100644 --- a/x-pack/plugins/cases/server/telemetry/types.ts +++ b/x-pack/plugins/cases/server/telemetry/types.ts @@ -139,9 +139,9 @@ export interface Status { } export interface LatestDates { - createdAt: string | null; - updatedAt: string | null; - closedAt: string | null; + createdAt: string; + updatedAt: string; + closedAt: string; } export interface CasesTelemetry {