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 ca6049885b298..b97235ff82247 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts @@ -12,7 +12,7 @@ import type { AttachmentAggregationResult, AttachmentFrameworkAggsResult, CaseAggregationResult, - FileAttachmentAggregationResult, + FileAttachmentAggregationResults, } from '../types'; import { getCasesTelemetryData } from './cases'; @@ -187,17 +187,65 @@ describe('getCasesTelemetryData', () => { ...attachmentFramework, }; - const filesRes: FileAttachmentAggregationResult = { + const filesRes: FileAttachmentAggregationResults = { securitySolution: { - averageSize: 500, + averageSize: { value: 500 }, + topMimeTypes: { + buckets: [ + { + doc_count: 5, + key: 'image/png', + }, + { + doc_count: 1, + key: 'application/json', + }, + ], + }, }, observability: { - averageSize: 500, + averageSize: { value: 500 }, + topMimeTypes: { + buckets: [ + { + doc_count: 5, + key: 'image/png', + }, + { + doc_count: 1, + key: 'application/json', + }, + ], + }, }, cases: { - averageSize: 500, + averageSize: { value: 500 }, + topMimeTypes: { + buckets: [ + { + doc_count: 5, + key: 'image/png', + }, + { + doc_count: 1, + key: 'application/json', + }, + ], + }, + }, + averageSize: { value: 500 }, + topMimeTypes: { + buckets: [ + { + doc_count: 5, + key: 'image/png', + }, + { + doc_count: 1, + key: 'application/json', + }, + ], }, - averageSize: 500, }; mockFind(caseAggsResult); @@ -259,6 +307,16 @@ describe('getCasesTelemetryData', () => { average, maxOnACase: 10, total, + topMimeTypes: [ + { + count: 5, + name: 'image/png', + }, + { + count: 1, + name: 'application/json', + }, + ], }, }, }; @@ -644,6 +702,7 @@ describe('getCasesTelemetryData', () => { }, "terms": Object { "field": "cases-comments.attributes.externalReferenceAttachmentTypeId", + "size": 10, }, }, "persistableReferenceTypes": Object { @@ -677,6 +736,7 @@ describe('getCasesTelemetryData', () => { }, "terms": Object { "field": "cases-comments.attributes.persistableStateAttachmentTypeId", + "size": 10, }, }, }, @@ -717,6 +777,7 @@ describe('getCasesTelemetryData', () => { }, "terms": Object { "field": "cases-comments.attributes.externalReferenceAttachmentTypeId", + "size": 10, }, }, "observability": Object { @@ -752,6 +813,7 @@ describe('getCasesTelemetryData', () => { }, "terms": Object { "field": "cases-comments.attributes.externalReferenceAttachmentTypeId", + "size": 10, }, }, "persistableReferenceTypes": Object { @@ -785,6 +847,7 @@ describe('getCasesTelemetryData', () => { }, "terms": Object { "field": "cases-comments.attributes.persistableStateAttachmentTypeId", + "size": 10, }, }, }, @@ -830,6 +893,7 @@ describe('getCasesTelemetryData', () => { }, "terms": Object { "field": "cases-comments.attributes.persistableStateAttachmentTypeId", + "size": 10, }, }, "securitySolution": Object { @@ -865,6 +929,7 @@ describe('getCasesTelemetryData', () => { }, "terms": Object { "field": "cases-comments.attributes.externalReferenceAttachmentTypeId", + "size": 10, }, }, "persistableReferenceTypes": Object { @@ -898,6 +963,7 @@ describe('getCasesTelemetryData', () => { }, "terms": Object { "field": "cases-comments.attributes.persistableStateAttachmentTypeId", + "size": 10, }, }, }, @@ -1031,6 +1097,12 @@ describe('getCasesTelemetryData', () => { "field": "file.attributes.size", }, }, + "topMimeTypes": Object { + "terms": Object { + "field": "file.attributes.mime_type", + "size": 20, + }, + }, }, "filter": Object { "term": Object { @@ -1045,6 +1117,12 @@ describe('getCasesTelemetryData', () => { "field": "file.attributes.size", }, }, + "topMimeTypes": Object { + "terms": Object { + "field": "file.attributes.mime_type", + "size": 20, + }, + }, }, "filter": Object { "term": Object { @@ -1059,6 +1137,12 @@ describe('getCasesTelemetryData', () => { "field": "file.attributes.size", }, }, + "topMimeTypes": Object { + "terms": Object { + "field": "file.attributes.mime_type", + "size": 20, + }, + }, }, "filter": Object { "term": Object { @@ -1066,13 +1150,19 @@ describe('getCasesTelemetryData', () => { }, }, }, + "topMimeTypes": Object { + "terms": Object { + "field": "file.attributes.mime_type", + "size": 20, + }, + }, }, "filter": Object { "arguments": Array [ Object { "isQuoted": false, "type": "literal", - "value": "file.attributes.Meta.caseId", + "value": "file.attributes.Meta.caseIds", }, Object { "type": "wildcard", diff --git a/x-pack/plugins/cases/server/telemetry/queries/cases.ts b/x-pack/plugins/cases/server/telemetry/queries/cases.ts index 0e999721ae105..59576fa1f45c0 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/cases.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/cases.ts @@ -23,7 +23,7 @@ import type { LatestDates, CaseAggregationResult, AttachmentAggregationResult, - FileAttachmentAggregationResult, + FileAttachmentAggregationResults, } from '../types'; import { findValueInBuckets, @@ -234,6 +234,7 @@ const getCommentsSavedObjectTelemetry = async ( externalReferenceTypes: { terms: { field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.externalReferenceAttachmentTypeId`, + size: 10, }, aggs: { ...getMaxBucketOnCaseAggregationQuery(CASE_COMMENT_SAVED_OBJECT), @@ -242,6 +243,7 @@ const getCommentsSavedObjectTelemetry = async ( persistableReferenceTypes: { terms: { field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.persistableStateAttachmentTypeId`, + size: 10, }, aggs: { ...getMaxBucketOnCaseAggregationQuery(CASE_COMMENT_SAVED_OBJECT), @@ -284,7 +286,7 @@ const getCommentsSavedObjectTelemetry = async ( const getFilesTelemetry = async ( savedObjectsClient: ISavedObjectsRepository -): Promise> => { +): Promise> => { const averageSize = () => ({ averageSize: { avg: { @@ -293,6 +295,15 @@ const getFilesTelemetry = async ( }, }); + const top20MimeTypes = () => ({ + topMimeTypes: { + terms: { + field: `${FILE_SO_TYPE}.attributes.mime_type`, + size: 20, + }, + }, + }); + const filesByOwnerAggregationQuery = OWNERS.reduce( (aggQuery, owner) => ({ ...aggQuery, @@ -304,20 +315,21 @@ const getFilesTelemetry = async ( }, aggs: { ...averageSize(), + ...top20MimeTypes(), }, }, }), {} ); - const filterCaseIdExists = fromKueryExpression(`${FILE_SO_TYPE}.attributes.Meta.caseId: *`); + const filterCaseIdExists = fromKueryExpression(`${FILE_SO_TYPE}.attributes.Meta.caseIds: *`); - return savedObjectsClient.find({ + return savedObjectsClient.find({ page: 0, perPage: 0, type: FILE_SO_TYPE, filter: filterCaseIdExists, - aggs: { ...filesByOwnerAggregationQuery, ...averageSize() }, + aggs: { ...filesByOwnerAggregationQuery, ...averageSize(), ...top20MimeTypes() }, }); }; 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 d7c6a0e9bf7b9..359aa621798f0 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts @@ -10,7 +10,7 @@ import type { AttachmentAggregationResult, AttachmentFrameworkAggsResult, CaseAggregationResult, - FileAttachmentAggregationResult, + FileAttachmentAggregationResults, } from '../types'; import { findValueInBuckets, @@ -168,17 +168,65 @@ describe('utils', () => { ...attachmentFramework, }; - const filesRes: FileAttachmentAggregationResult = { + const filesRes: FileAttachmentAggregationResults = { securitySolution: { - averageSize: 500, + averageSize: { value: 500 }, + topMimeTypes: { + buckets: [ + { + doc_count: 5, + key: 'image/png', + }, + { + doc_count: 1, + key: 'application/json', + }, + ], + }, }, observability: { - averageSize: 500, + averageSize: { value: 500 }, + topMimeTypes: { + buckets: [ + { + doc_count: 5, + key: 'image/png', + }, + { + doc_count: 1, + key: 'application/json', + }, + ], + }, }, cases: { - averageSize: 500, + averageSize: { value: 500 }, + topMimeTypes: { + buckets: [ + { + doc_count: 5, + key: 'image/png', + }, + { + doc_count: 1, + key: 'application/json', + }, + ], + }, + }, + averageSize: { value: 500 }, + topMimeTypes: { + buckets: [ + { + doc_count: 5, + key: 'image/png', + }, + { + doc_count: 1, + key: 'application/json', + }, + ], }, - averageSize: 500, }; it('constructs the solution values correctly', () => { @@ -215,6 +263,16 @@ describe('utils', () => { "average": 1, "averageSize": 500, "maxOnACase": 10, + "topMimeTypes": Array [ + Object { + "count": 5, + "name": "image/png", + }, + Object { + "count": 1, + "name": "application/json", + }, + ], "total": 5, }, "persistableAttachments": Array [ @@ -271,6 +329,16 @@ describe('utils', () => { "average": 5, "averageSize": 500, "maxOnACase": 10, + "topMimeTypes": Array [ + Object { + "count": 5, + "name": "image/png", + }, + Object { + "count": 1, + "name": "application/json", + }, + ], "total": 5, }, "persistableAttachments": Array [ @@ -327,6 +395,16 @@ describe('utils', () => { "average": 5, "averageSize": 500, "maxOnACase": 10, + "topMimeTypes": Array [ + Object { + "count": 5, + "name": "image/png", + }, + Object { + "count": 1, + "name": "application/json", + }, + ], "total": 5, }, "persistableAttachments": Array [ @@ -363,6 +441,7 @@ describe('utils', () => { "average": 0, "averageSize": 0, "maxOnACase": 0, + "topMimeTypes": Array [], "total": 0, }, "persistableAttachments": Array [], @@ -486,7 +565,7 @@ describe('utils', () => { }); describe('files', () => { - it('sets the files stats to empty when it cannot find a files entry', () => { + it('sets the files stats to empty when the file aggregation results is the empty version', () => { const attachmentFramework: AttachmentFrameworkAggsResult = { externalReferenceTypes: { buckets: [ @@ -512,19 +591,25 @@ describe('utils', () => { getAttachmentsFrameworkStats({ attachmentAggregations: attachmentFramework, totalCasesForOwner: 5, - filesAggregations: { averageSize: 500 }, + filesAggregations: { + averageSize: { value: 0 }, + topMimeTypes: { + buckets: [], + }, + }, }).attachmentFramework.files ).toMatchInlineSnapshot(` Object { "average": 0, "averageSize": 0, "maxOnACase": 0, + "topMimeTypes": Array [], "total": 0, } `); }); - it('sets the files stats when it finds a files entry', () => { + it('sets the files stats using the file aggregation result', () => { const attachmentFramework: AttachmentFrameworkAggsResult = { externalReferenceTypes: { buckets: [ @@ -549,7 +634,21 @@ describe('utils', () => { expect( getAttachmentsFrameworkStats({ attachmentAggregations: attachmentFramework, - filesAggregations: { averageSize: 500 }, + filesAggregations: { + averageSize: { value: 500 }, + topMimeTypes: { + buckets: [ + { + doc_count: 5, + key: 'image/png', + }, + { + doc_count: 1, + key: 'application/json', + }, + ], + }, + }, totalCasesForOwner: 5, }).attachmentFramework.files ).toMatchInlineSnapshot(` @@ -557,10 +656,70 @@ describe('utils', () => { "average": 1, "averageSize": 500, "maxOnACase": 10, + "topMimeTypes": Array [ + Object { + "count": 5, + "name": "image/png", + }, + Object { + "count": 1, + "name": "application/json", + }, + ], "total": 5, } `); }); + + it('sets the top mime types when a file entry is not found', () => { + const attachmentFramework: AttachmentFrameworkAggsResult = { + externalReferenceTypes: { + buckets: [], + }, + persistableReferenceTypes: { + buckets: [], + }, + }; + + expect( + getAttachmentsFrameworkStats({ + attachmentAggregations: attachmentFramework, + filesAggregations: { + averageSize: { value: 0 }, + topMimeTypes: { + buckets: [ + { + doc_count: 5, + key: 'image/png', + }, + { + doc_count: 1, + key: 'application/json', + }, + ], + }, + }, + totalCasesForOwner: 5, + }).attachmentFramework.files + ).toMatchInlineSnapshot(` + Object { + "average": 0, + "averageSize": 0, + "maxOnACase": 0, + "topMimeTypes": Array [ + Object { + "count": 5, + "name": "image/png", + }, + Object { + "count": 1, + "name": "application/json", + }, + ], + "total": 0, + } + `); + }); }); }); diff --git a/x-pack/plugins/cases/server/telemetry/queries/utils.ts b/x-pack/plugins/cases/server/telemetry/queries/utils.ts index 0c0a4f7bbf87d..7896c2bdac760 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/utils.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/utils.ts @@ -23,8 +23,8 @@ import type { BucketsWithMaxOnCase, AttachmentStats, FileAttachmentStats, - FileAttachmentAggregationResult, - FileAttachmentAverageSize, + FileAttachmentAggregationResults, + FileAttachmentAggsResult, AttachmentFrameworkAggsResult, } from '../types'; import { buildFilter } from '../../client/utils'; @@ -170,7 +170,7 @@ export const getSolutionValues = ({ }: { caseAggregations?: CaseAggregationResult; attachmentAggregations?: AttachmentAggregationResult; - filesAggregations?: FileAttachmentAggregationResult; + filesAggregations?: FileAttachmentAggregationResults; owner: Owner; }): SolutionTelemetry => { const aggregationsBuckets = getAggregationsBuckets({ @@ -223,13 +223,15 @@ export const getAttachmentsFrameworkStats = ({ totalCasesForOwner, }: { attachmentAggregations?: AttachmentFrameworkAggsResult; - filesAggregations?: FileAttachmentAverageSize; + filesAggregations?: FileAttachmentAggsResult; totalCasesForOwner: number; }): AttachmentFramework => { if (!attachmentAggregations) { return emptyAttachmentFramework(); } - const averageFileSize = filesAggregations?.averageSize; + + const averageFileSize = filesAggregations?.averageSize?.value; + const topMimeTypes = filesAggregations?.topMimeTypes; return { attachmentFramework: { @@ -245,6 +247,7 @@ export const getAttachmentsFrameworkStats = ({ registryResults: attachmentAggregations.externalReferenceTypes, averageFileSize, totalCasesForOwner, + topMimeTypes, }), }, }; @@ -272,8 +275,8 @@ const getAttachmentRegistryStats = ( return stats; }; -const calculateTypePerCaseAverage = (typeDocCount: number, totalCases: number) => { - if (totalCases === 0) { +const calculateTypePerCaseAverage = (typeDocCount: number | undefined, totalCases: number) => { + if (typeDocCount == null || totalCases === 0) { return 0; } @@ -284,22 +287,27 @@ const getFileAttachmentStats = ({ registryResults, averageFileSize, totalCasesForOwner, + topMimeTypes, }: { registryResults: BucketsWithMaxOnCase; averageFileSize?: number; totalCasesForOwner: number; + topMimeTypes?: Buckets; }): FileAttachmentStats => { const fileBucket = registryResults.buckets.find((bucket) => bucket.key === FILE_ATTACHMENT_TYPE); - if (!fileBucket || averageFileSize == null) { - return emptyFileAttachment(); - } + const mimeTypes = + topMimeTypes?.buckets.map((mimeType) => ({ + count: mimeType.doc_count, + name: mimeType.key, + })) ?? []; return { - averageSize: averageFileSize, - average: calculateTypePerCaseAverage(fileBucket.doc_count, totalCasesForOwner), - maxOnACase: fileBucket.references.cases.max.value, - total: fileBucket.doc_count, + averageSize: averageFileSize ?? 0, + average: calculateTypePerCaseAverage(fileBucket?.doc_count, totalCasesForOwner), + maxOnACase: fileBucket?.references.cases.max.value ?? 0, + total: fileBucket?.doc_count ?? 0, + topMimeTypes: mimeTypes, }; }; @@ -332,4 +340,5 @@ const emptyFileAttachment = (): FileAttachmentStats => ({ averageSize: 0, maxOnACase: 0, total: 0, + topMimeTypes: [], }); diff --git a/x-pack/plugins/cases/server/telemetry/schema.ts b/x-pack/plugins/cases/server/telemetry/schema.ts index 59506c932a51f..33d44101c5a6d 100644 --- a/x-pack/plugins/cases/server/telemetry/schema.ts +++ b/x-pack/plugins/cases/server/telemetry/schema.ts @@ -51,6 +51,13 @@ const attachmentFrameworkSchema: AttachmentFrameworkSchema = { averageSize: long, maxOnACase: long, total: long, + topMimeTypes: { + type: 'array', + items: { + count: long, + name: string, + }, + }, }, }; diff --git a/x-pack/plugins/cases/server/telemetry/types.ts b/x-pack/plugins/cases/server/telemetry/types.ts index e28c3abaf4d52..b38d46d04283b 100644 --- a/x-pack/plugins/cases/server/telemetry/types.ts +++ b/x-pack/plugins/cases/server/telemetry/types.ts @@ -9,11 +9,15 @@ import type { ISavedObjectsRepository, Logger } from '@kbn/core/server'; import type { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server'; import type { Owner } from '../../common/constants/types'; -export interface Buckets { - buckets: Array<{ - doc_count: number; - key: number | string; - }>; +export type BucketKeyString = Omit & { key: string }; + +interface Bucket { + doc_count: number; + key: T; +} + +export interface Buckets { + buckets: Array>; } export interface Cardinality { @@ -57,12 +61,15 @@ export interface AssigneesFilters { }; } -export interface FileAttachmentAverageSize { - averageSize: number; +export interface FileAttachmentAggsResult { + averageSize: { + value: number; + }; + topMimeTypes: Buckets; } -export type FileAttachmentAggregationResult = Record & - FileAttachmentAverageSize; +export type FileAttachmentAggregationResults = Record & + FileAttachmentAggsResult; export interface BucketsWithMaxOnCase { buckets: Array< @@ -118,6 +125,10 @@ export interface AttachmentStats extends CommonAttachmentStats { export interface FileAttachmentStats extends CommonAttachmentStats { averageSize: number; + topMimeTypes: Array<{ + name: string; + count: number; + }>; } export interface AttachmentFramework { 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 517c52ed4fb71..594373a71e188 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -4839,6 +4839,19 @@ }, "total": { "type": "long" + }, + "topMimeTypes": { + "type": "array", + "items": { + "properties": { + "count": { + "type": "long" + }, + "name": { + "type": "keyword" + } + } + } } } } @@ -4986,6 +4999,19 @@ }, "total": { "type": "long" + }, + "topMimeTypes": { + "type": "array", + "items": { + "properties": { + "count": { + "type": "long" + }, + "name": { + "type": "keyword" + } + } + } } } } @@ -5073,6 +5099,19 @@ }, "total": { "type": "long" + }, + "topMimeTypes": { + "type": "array", + "items": { + "properties": { + "count": { + "type": "long" + }, + "name": { + "type": "keyword" + } + } + } } } } @@ -5160,6 +5199,19 @@ }, "total": { "type": "long" + }, + "topMimeTypes": { + "type": "array", + "items": { + "properties": { + "count": { + "type": "long" + }, + "name": { + "type": "keyword" + } + } + } } } }