[Cases] Adding mime type telemetry (#154679)

This PR adds telemetry for collecting statistics for the mimetypes of
the files uploaded to cases.

It only looks for the top 20 mime types using a terms aggregation. It
does this across all cases and broken down by owner. On the telemetry
server we'll need to create a single document per entry in the array
here so we can do filtering for the visualizations.

Example

```

  "topMimeTypes": [
    {
      "count": 2,
      "name": "image/png"
    },
    {
      "count": 1,
      "name": "application/json"
    },
    {
      "count": 1,
      "name": "text/plain"
    }
  ]

```

<details><summary>Example telemetry result</summary>

                         "cases": {
                            "cases": {
                                "all": {
                                    "total": 1,
                                    "daily": 1,
                                    "weekly": 1,
                                    "monthly": 1,
                                    "status": {
                                        "open": 1,
                                        "inProgress": 0,
                                        "closed": 0
                                    },
                                    "syncAlertsOn": 1,
                                    "syncAlertsOff": 0,
                                    "totalUsers": 1,
                                    "totalParticipants": 1,
                                    "totalTags": 1,
                                    "totalWithAlerts": 0,
                                    "totalWithConnectors": 0,
                                    "latestDates": {
"createdAt": "2023-04-11T16:07:00.565Z",
"updatedAt": "2023-04-11T16:07:17.712Z",
                                        "closedAt": ""
                                    },
                                    "assignees": {
                                        "total": 0,
                                        "totalWithZero": 1,
                                        "totalWithAtLeastOne": 0
                                    },
                                    "attachmentFramework": {
                                        "externalAttachments": [
                                            {
                                                "type": ".files",
                                                "average": 4,
                                                "maxOnACase": 4,
                                                "total": 4
                                            }
                                        ],
                                        "persistableAttachments": [],
                                        "files": {
                                            "averageSize": 3,
                                            "average": 4,
                                            "maxOnACase": 4,
                                            "total": 4,
                                            "topMimeTypes": [
                                                {
                                                    "count": 2,
                                                    "name": "image/png"
                                                },
                                                {
                                                    "count": 1,
"name": "application/json"
                                                },
                                                {
                                                    "count": 1,
                                                    "name": "text/plain"
                                                }
                                            ]
                                        }
                                    }
                                },
                                "sec": {
                                    "total": 0,
                                    "daily": 0,
                                    "weekly": 0,
                                    "monthly": 0,
                                    "attachmentFramework": {
                                        "externalAttachments": [],
                                        "persistableAttachments": [],
                                        "files": {
                                            "averageSize": 0,
                                            "average": 0,
                                            "maxOnACase": 0,
                                            "total": 0,
                                            "topMimeTypes": []
                                        }
                                    },
                                    "assignees": {
                                        "total": 0,
                                        "totalWithZero": 0,
                                        "totalWithAtLeastOne": 0
                                    }
                                },
                                "obs": {
                                    "total": 0,
                                    "daily": 0,
                                    "weekly": 0,
                                    "monthly": 0,
                                    "attachmentFramework": {
                                        "externalAttachments": [],
                                        "persistableAttachments": [],
                                        "files": {
                                            "averageSize": 0,
                                            "average": 0,
                                            "maxOnACase": 0,
                                            "total": 0,
                                            "topMimeTypes": []
                                        }
                                    },
                                    "assignees": {
                                        "total": 0,
                                        "totalWithZero": 0,
                                        "totalWithAtLeastOne": 0
                                    }
                                },
                                "main": {
                                    "total": 1,
                                    "daily": 1,
                                    "weekly": 1,
                                    "monthly": 1,
                                    "attachmentFramework": {
                                        "externalAttachments": [
                                            {
                                                "type": ".files",
                                                "average": 4,
                                                "maxOnACase": 4,
                                                "total": 4
                                            }
                                        ],
                                        "persistableAttachments": [],
                                        "files": {
                                            "averageSize": 3,
                                            "average": 4,
                                            "maxOnACase": 4,
                                            "total": 4,
                                            "topMimeTypes": [
                                                {
                                                    "count": 2,
                                                    "name": "image/png"
                                                },
                                                {
                                                    "count": 1,
"name": "application/json"
                                                },
                                                {
                                                    "count": 1,
                                                    "name": "text/plain"
                                                }
                                            ]
                                        }
                                    },
                                    "assignees": {
                                        "total": 0,
                                        "totalWithZero": 1,
                                        "totalWithAtLeastOne": 0
                                    }
                                }
                            },
                            "userActions": {
                                "all": {
                                    "total": 5,
                                    "daily": 5,
                                    "weekly": 5,
                                    "monthly": 5,
                                    "maxOnACase": 5
                                }
                            },
                            "comments": {
                                "all": {
                                    "total": 0,
                                    "daily": 0,
                                    "weekly": 0,
                                    "monthly": 0,
                                    "maxOnACase": 0
                                }
                            },
                            "alerts": {
                                "all": {
                                    "total": 0,
                                    "daily": 0,
                                    "weekly": 0,
                                    "monthly": 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
                                    }
                                }
                            }
                        }
</details>

## Testing

To test modify this file:
https://github.com/elastic/kibana/blob/main/x-pack/plugins/cases/server/telemetry/schedule_telemetry_task.ts

With:

```
export const scheduleCasesTelemetryTask = (
  taskManager: TaskManagerStartContract,
  logger: Logger
) => {
  (async () => {
    await taskManager
      .ensureScheduled({
        id: CASES_TELEMETRY_TASK_NAME,
        taskType: CASES_TELEMETRY_TASK_NAME,
        schedule: {
          interval: `${MINUTES_ON_HALF_DAY}m`,
        },
        scope: ['cases'],
        params: {},
        state: {},
      })
      .catch((err) => {
        logger.debug(
          `Error scheduling cases task with ID ${CASES_TELEMETRY_TASK_NAME} and type ${CASES_TELEMETRY_TASK_NAME}. Received ${err.message}`
        );
      });

    await taskManager.runSoon(CASES_TELEMETRY_TASK_NAME);
  })();
};
```

This will cause the telemetry to be sent as soon as the server is
restarted.

To generate files and attachments to add stats to the telemetry I
created this python script:
https://github.com/elastic/cases-files-generator

To retrieve the telemetry:

```
POST http://localhost:5601/api/telemetry/v2/clusters/_stats
{
   "refreshCache": true,
   "unencrypted": true
}
```
This commit is contained in:
Jonathan Buttner 2023-04-13 08:23:02 -04:00 committed by GitHub
parent 07a7e765f6
commit fe9985b566
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 385 additions and 45 deletions

View file

@ -12,7 +12,7 @@ import type {
AttachmentAggregationResult, AttachmentAggregationResult,
AttachmentFrameworkAggsResult, AttachmentFrameworkAggsResult,
CaseAggregationResult, CaseAggregationResult,
FileAttachmentAggregationResult, FileAttachmentAggregationResults,
} from '../types'; } from '../types';
import { getCasesTelemetryData } from './cases'; import { getCasesTelemetryData } from './cases';
@ -187,17 +187,65 @@ describe('getCasesTelemetryData', () => {
...attachmentFramework, ...attachmentFramework,
}; };
const filesRes: FileAttachmentAggregationResult = { const filesRes: FileAttachmentAggregationResults = {
securitySolution: { securitySolution: {
averageSize: 500, averageSize: { value: 500 },
topMimeTypes: {
buckets: [
{
doc_count: 5,
key: 'image/png',
},
{
doc_count: 1,
key: 'application/json',
},
],
},
}, },
observability: { observability: {
averageSize: 500, averageSize: { value: 500 },
topMimeTypes: {
buckets: [
{
doc_count: 5,
key: 'image/png',
},
{
doc_count: 1,
key: 'application/json',
},
],
},
}, },
cases: { 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); mockFind(caseAggsResult);
@ -259,6 +307,16 @@ describe('getCasesTelemetryData', () => {
average, average,
maxOnACase: 10, maxOnACase: 10,
total, total,
topMimeTypes: [
{
count: 5,
name: 'image/png',
},
{
count: 1,
name: 'application/json',
},
],
}, },
}, },
}; };
@ -644,6 +702,7 @@ describe('getCasesTelemetryData', () => {
}, },
"terms": Object { "terms": Object {
"field": "cases-comments.attributes.externalReferenceAttachmentTypeId", "field": "cases-comments.attributes.externalReferenceAttachmentTypeId",
"size": 10,
}, },
}, },
"persistableReferenceTypes": Object { "persistableReferenceTypes": Object {
@ -677,6 +736,7 @@ describe('getCasesTelemetryData', () => {
}, },
"terms": Object { "terms": Object {
"field": "cases-comments.attributes.persistableStateAttachmentTypeId", "field": "cases-comments.attributes.persistableStateAttachmentTypeId",
"size": 10,
}, },
}, },
}, },
@ -717,6 +777,7 @@ describe('getCasesTelemetryData', () => {
}, },
"terms": Object { "terms": Object {
"field": "cases-comments.attributes.externalReferenceAttachmentTypeId", "field": "cases-comments.attributes.externalReferenceAttachmentTypeId",
"size": 10,
}, },
}, },
"observability": Object { "observability": Object {
@ -752,6 +813,7 @@ describe('getCasesTelemetryData', () => {
}, },
"terms": Object { "terms": Object {
"field": "cases-comments.attributes.externalReferenceAttachmentTypeId", "field": "cases-comments.attributes.externalReferenceAttachmentTypeId",
"size": 10,
}, },
}, },
"persistableReferenceTypes": Object { "persistableReferenceTypes": Object {
@ -785,6 +847,7 @@ describe('getCasesTelemetryData', () => {
}, },
"terms": Object { "terms": Object {
"field": "cases-comments.attributes.persistableStateAttachmentTypeId", "field": "cases-comments.attributes.persistableStateAttachmentTypeId",
"size": 10,
}, },
}, },
}, },
@ -830,6 +893,7 @@ describe('getCasesTelemetryData', () => {
}, },
"terms": Object { "terms": Object {
"field": "cases-comments.attributes.persistableStateAttachmentTypeId", "field": "cases-comments.attributes.persistableStateAttachmentTypeId",
"size": 10,
}, },
}, },
"securitySolution": Object { "securitySolution": Object {
@ -865,6 +929,7 @@ describe('getCasesTelemetryData', () => {
}, },
"terms": Object { "terms": Object {
"field": "cases-comments.attributes.externalReferenceAttachmentTypeId", "field": "cases-comments.attributes.externalReferenceAttachmentTypeId",
"size": 10,
}, },
}, },
"persistableReferenceTypes": Object { "persistableReferenceTypes": Object {
@ -898,6 +963,7 @@ describe('getCasesTelemetryData', () => {
}, },
"terms": Object { "terms": Object {
"field": "cases-comments.attributes.persistableStateAttachmentTypeId", "field": "cases-comments.attributes.persistableStateAttachmentTypeId",
"size": 10,
}, },
}, },
}, },
@ -1031,6 +1097,12 @@ describe('getCasesTelemetryData', () => {
"field": "file.attributes.size", "field": "file.attributes.size",
}, },
}, },
"topMimeTypes": Object {
"terms": Object {
"field": "file.attributes.mime_type",
"size": 20,
},
},
}, },
"filter": Object { "filter": Object {
"term": Object { "term": Object {
@ -1045,6 +1117,12 @@ describe('getCasesTelemetryData', () => {
"field": "file.attributes.size", "field": "file.attributes.size",
}, },
}, },
"topMimeTypes": Object {
"terms": Object {
"field": "file.attributes.mime_type",
"size": 20,
},
},
}, },
"filter": Object { "filter": Object {
"term": Object { "term": Object {
@ -1059,6 +1137,12 @@ describe('getCasesTelemetryData', () => {
"field": "file.attributes.size", "field": "file.attributes.size",
}, },
}, },
"topMimeTypes": Object {
"terms": Object {
"field": "file.attributes.mime_type",
"size": 20,
},
},
}, },
"filter": Object { "filter": Object {
"term": Object { "term": Object {
@ -1066,13 +1150,19 @@ describe('getCasesTelemetryData', () => {
}, },
}, },
}, },
"topMimeTypes": Object {
"terms": Object {
"field": "file.attributes.mime_type",
"size": 20,
},
},
}, },
"filter": Object { "filter": Object {
"arguments": Array [ "arguments": Array [
Object { Object {
"isQuoted": false, "isQuoted": false,
"type": "literal", "type": "literal",
"value": "file.attributes.Meta.caseId", "value": "file.attributes.Meta.caseIds",
}, },
Object { Object {
"type": "wildcard", "type": "wildcard",

View file

@ -23,7 +23,7 @@ import type {
LatestDates, LatestDates,
CaseAggregationResult, CaseAggregationResult,
AttachmentAggregationResult, AttachmentAggregationResult,
FileAttachmentAggregationResult, FileAttachmentAggregationResults,
} from '../types'; } from '../types';
import { import {
findValueInBuckets, findValueInBuckets,
@ -234,6 +234,7 @@ const getCommentsSavedObjectTelemetry = async (
externalReferenceTypes: { externalReferenceTypes: {
terms: { terms: {
field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.externalReferenceAttachmentTypeId`, field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.externalReferenceAttachmentTypeId`,
size: 10,
}, },
aggs: { aggs: {
...getMaxBucketOnCaseAggregationQuery(CASE_COMMENT_SAVED_OBJECT), ...getMaxBucketOnCaseAggregationQuery(CASE_COMMENT_SAVED_OBJECT),
@ -242,6 +243,7 @@ const getCommentsSavedObjectTelemetry = async (
persistableReferenceTypes: { persistableReferenceTypes: {
terms: { terms: {
field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.persistableStateAttachmentTypeId`, field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.persistableStateAttachmentTypeId`,
size: 10,
}, },
aggs: { aggs: {
...getMaxBucketOnCaseAggregationQuery(CASE_COMMENT_SAVED_OBJECT), ...getMaxBucketOnCaseAggregationQuery(CASE_COMMENT_SAVED_OBJECT),
@ -284,7 +286,7 @@ const getCommentsSavedObjectTelemetry = async (
const getFilesTelemetry = async ( const getFilesTelemetry = async (
savedObjectsClient: ISavedObjectsRepository savedObjectsClient: ISavedObjectsRepository
): Promise<SavedObjectsFindResponse<unknown, FileAttachmentAggregationResult>> => { ): Promise<SavedObjectsFindResponse<unknown, FileAttachmentAggregationResults>> => {
const averageSize = () => ({ const averageSize = () => ({
averageSize: { averageSize: {
avg: { 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( const filesByOwnerAggregationQuery = OWNERS.reduce(
(aggQuery, owner) => ({ (aggQuery, owner) => ({
...aggQuery, ...aggQuery,
@ -304,20 +315,21 @@ const getFilesTelemetry = async (
}, },
aggs: { aggs: {
...averageSize(), ...averageSize(),
...top20MimeTypes(),
}, },
}, },
}), }),
{} {}
); );
const filterCaseIdExists = fromKueryExpression(`${FILE_SO_TYPE}.attributes.Meta.caseId: *`); const filterCaseIdExists = fromKueryExpression(`${FILE_SO_TYPE}.attributes.Meta.caseIds: *`);
return savedObjectsClient.find<unknown, FileAttachmentAggregationResult>({ return savedObjectsClient.find<unknown, FileAttachmentAggregationResults>({
page: 0, page: 0,
perPage: 0, perPage: 0,
type: FILE_SO_TYPE, type: FILE_SO_TYPE,
filter: filterCaseIdExists, filter: filterCaseIdExists,
aggs: { ...filesByOwnerAggregationQuery, ...averageSize() }, aggs: { ...filesByOwnerAggregationQuery, ...averageSize(), ...top20MimeTypes() },
}); });
}; };

View file

@ -10,7 +10,7 @@ import type {
AttachmentAggregationResult, AttachmentAggregationResult,
AttachmentFrameworkAggsResult, AttachmentFrameworkAggsResult,
CaseAggregationResult, CaseAggregationResult,
FileAttachmentAggregationResult, FileAttachmentAggregationResults,
} from '../types'; } from '../types';
import { import {
findValueInBuckets, findValueInBuckets,
@ -168,17 +168,65 @@ describe('utils', () => {
...attachmentFramework, ...attachmentFramework,
}; };
const filesRes: FileAttachmentAggregationResult = { const filesRes: FileAttachmentAggregationResults = {
securitySolution: { securitySolution: {
averageSize: 500, averageSize: { value: 500 },
topMimeTypes: {
buckets: [
{
doc_count: 5,
key: 'image/png',
},
{
doc_count: 1,
key: 'application/json',
},
],
},
}, },
observability: { observability: {
averageSize: 500, averageSize: { value: 500 },
topMimeTypes: {
buckets: [
{
doc_count: 5,
key: 'image/png',
},
{
doc_count: 1,
key: 'application/json',
},
],
},
}, },
cases: { 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', () => { it('constructs the solution values correctly', () => {
@ -215,6 +263,16 @@ describe('utils', () => {
"average": 1, "average": 1,
"averageSize": 500, "averageSize": 500,
"maxOnACase": 10, "maxOnACase": 10,
"topMimeTypes": Array [
Object {
"count": 5,
"name": "image/png",
},
Object {
"count": 1,
"name": "application/json",
},
],
"total": 5, "total": 5,
}, },
"persistableAttachments": Array [ "persistableAttachments": Array [
@ -271,6 +329,16 @@ describe('utils', () => {
"average": 5, "average": 5,
"averageSize": 500, "averageSize": 500,
"maxOnACase": 10, "maxOnACase": 10,
"topMimeTypes": Array [
Object {
"count": 5,
"name": "image/png",
},
Object {
"count": 1,
"name": "application/json",
},
],
"total": 5, "total": 5,
}, },
"persistableAttachments": Array [ "persistableAttachments": Array [
@ -327,6 +395,16 @@ describe('utils', () => {
"average": 5, "average": 5,
"averageSize": 500, "averageSize": 500,
"maxOnACase": 10, "maxOnACase": 10,
"topMimeTypes": Array [
Object {
"count": 5,
"name": "image/png",
},
Object {
"count": 1,
"name": "application/json",
},
],
"total": 5, "total": 5,
}, },
"persistableAttachments": Array [ "persistableAttachments": Array [
@ -363,6 +441,7 @@ describe('utils', () => {
"average": 0, "average": 0,
"averageSize": 0, "averageSize": 0,
"maxOnACase": 0, "maxOnACase": 0,
"topMimeTypes": Array [],
"total": 0, "total": 0,
}, },
"persistableAttachments": Array [], "persistableAttachments": Array [],
@ -486,7 +565,7 @@ describe('utils', () => {
}); });
describe('files', () => { 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 = { const attachmentFramework: AttachmentFrameworkAggsResult = {
externalReferenceTypes: { externalReferenceTypes: {
buckets: [ buckets: [
@ -512,19 +591,25 @@ describe('utils', () => {
getAttachmentsFrameworkStats({ getAttachmentsFrameworkStats({
attachmentAggregations: attachmentFramework, attachmentAggregations: attachmentFramework,
totalCasesForOwner: 5, totalCasesForOwner: 5,
filesAggregations: { averageSize: 500 }, filesAggregations: {
averageSize: { value: 0 },
topMimeTypes: {
buckets: [],
},
},
}).attachmentFramework.files }).attachmentFramework.files
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
Object { Object {
"average": 0, "average": 0,
"averageSize": 0, "averageSize": 0,
"maxOnACase": 0, "maxOnACase": 0,
"topMimeTypes": Array [],
"total": 0, "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 = { const attachmentFramework: AttachmentFrameworkAggsResult = {
externalReferenceTypes: { externalReferenceTypes: {
buckets: [ buckets: [
@ -549,7 +634,21 @@ describe('utils', () => {
expect( expect(
getAttachmentsFrameworkStats({ getAttachmentsFrameworkStats({
attachmentAggregations: attachmentFramework, 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, totalCasesForOwner: 5,
}).attachmentFramework.files }).attachmentFramework.files
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
@ -557,10 +656,70 @@ describe('utils', () => {
"average": 1, "average": 1,
"averageSize": 500, "averageSize": 500,
"maxOnACase": 10, "maxOnACase": 10,
"topMimeTypes": Array [
Object {
"count": 5,
"name": "image/png",
},
Object {
"count": 1,
"name": "application/json",
},
],
"total": 5, "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,
}
`);
});
}); });
}); });

View file

@ -23,8 +23,8 @@ import type {
BucketsWithMaxOnCase, BucketsWithMaxOnCase,
AttachmentStats, AttachmentStats,
FileAttachmentStats, FileAttachmentStats,
FileAttachmentAggregationResult, FileAttachmentAggregationResults,
FileAttachmentAverageSize, FileAttachmentAggsResult,
AttachmentFrameworkAggsResult, AttachmentFrameworkAggsResult,
} from '../types'; } from '../types';
import { buildFilter } from '../../client/utils'; import { buildFilter } from '../../client/utils';
@ -170,7 +170,7 @@ export const getSolutionValues = ({
}: { }: {
caseAggregations?: CaseAggregationResult; caseAggregations?: CaseAggregationResult;
attachmentAggregations?: AttachmentAggregationResult; attachmentAggregations?: AttachmentAggregationResult;
filesAggregations?: FileAttachmentAggregationResult; filesAggregations?: FileAttachmentAggregationResults;
owner: Owner; owner: Owner;
}): SolutionTelemetry => { }): SolutionTelemetry => {
const aggregationsBuckets = getAggregationsBuckets({ const aggregationsBuckets = getAggregationsBuckets({
@ -223,13 +223,15 @@ export const getAttachmentsFrameworkStats = ({
totalCasesForOwner, totalCasesForOwner,
}: { }: {
attachmentAggregations?: AttachmentFrameworkAggsResult; attachmentAggregations?: AttachmentFrameworkAggsResult;
filesAggregations?: FileAttachmentAverageSize; filesAggregations?: FileAttachmentAggsResult;
totalCasesForOwner: number; totalCasesForOwner: number;
}): AttachmentFramework => { }): AttachmentFramework => {
if (!attachmentAggregations) { if (!attachmentAggregations) {
return emptyAttachmentFramework(); return emptyAttachmentFramework();
} }
const averageFileSize = filesAggregations?.averageSize;
const averageFileSize = filesAggregations?.averageSize?.value;
const topMimeTypes = filesAggregations?.topMimeTypes;
return { return {
attachmentFramework: { attachmentFramework: {
@ -245,6 +247,7 @@ export const getAttachmentsFrameworkStats = ({
registryResults: attachmentAggregations.externalReferenceTypes, registryResults: attachmentAggregations.externalReferenceTypes,
averageFileSize, averageFileSize,
totalCasesForOwner, totalCasesForOwner,
topMimeTypes,
}), }),
}, },
}; };
@ -272,8 +275,8 @@ const getAttachmentRegistryStats = (
return stats; return stats;
}; };
const calculateTypePerCaseAverage = (typeDocCount: number, totalCases: number) => { const calculateTypePerCaseAverage = (typeDocCount: number | undefined, totalCases: number) => {
if (totalCases === 0) { if (typeDocCount == null || totalCases === 0) {
return 0; return 0;
} }
@ -284,22 +287,27 @@ const getFileAttachmentStats = ({
registryResults, registryResults,
averageFileSize, averageFileSize,
totalCasesForOwner, totalCasesForOwner,
topMimeTypes,
}: { }: {
registryResults: BucketsWithMaxOnCase; registryResults: BucketsWithMaxOnCase;
averageFileSize?: number; averageFileSize?: number;
totalCasesForOwner: number; totalCasesForOwner: number;
topMimeTypes?: Buckets<string>;
}): FileAttachmentStats => { }): FileAttachmentStats => {
const fileBucket = registryResults.buckets.find((bucket) => bucket.key === FILE_ATTACHMENT_TYPE); const fileBucket = registryResults.buckets.find((bucket) => bucket.key === FILE_ATTACHMENT_TYPE);
if (!fileBucket || averageFileSize == null) { const mimeTypes =
return emptyFileAttachment(); topMimeTypes?.buckets.map((mimeType) => ({
} count: mimeType.doc_count,
name: mimeType.key,
})) ?? [];
return { return {
averageSize: averageFileSize, averageSize: averageFileSize ?? 0,
average: calculateTypePerCaseAverage(fileBucket.doc_count, totalCasesForOwner), average: calculateTypePerCaseAverage(fileBucket?.doc_count, totalCasesForOwner),
maxOnACase: fileBucket.references.cases.max.value, maxOnACase: fileBucket?.references.cases.max.value ?? 0,
total: fileBucket.doc_count, total: fileBucket?.doc_count ?? 0,
topMimeTypes: mimeTypes,
}; };
}; };
@ -332,4 +340,5 @@ const emptyFileAttachment = (): FileAttachmentStats => ({
averageSize: 0, averageSize: 0,
maxOnACase: 0, maxOnACase: 0,
total: 0, total: 0,
topMimeTypes: [],
}); });

View file

@ -51,6 +51,13 @@ const attachmentFrameworkSchema: AttachmentFrameworkSchema = {
averageSize: long, averageSize: long,
maxOnACase: long, maxOnACase: long,
total: long, total: long,
topMimeTypes: {
type: 'array',
items: {
count: long,
name: string,
},
},
}, },
}; };

View file

@ -9,11 +9,15 @@ import type { ISavedObjectsRepository, Logger } from '@kbn/core/server';
import type { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server'; import type { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server';
import type { Owner } from '../../common/constants/types'; import type { Owner } from '../../common/constants/types';
export interface Buckets { export type BucketKeyString = Omit<Bucket, 'key'> & { key: string };
buckets: Array<{
doc_count: number; interface Bucket<T extends string | number = string | number> {
key: number | string; doc_count: number;
}>; key: T;
}
export interface Buckets<T extends string | number = string | number> {
buckets: Array<Bucket<T>>;
} }
export interface Cardinality { export interface Cardinality {
@ -57,12 +61,15 @@ export interface AssigneesFilters {
}; };
} }
export interface FileAttachmentAverageSize { export interface FileAttachmentAggsResult {
averageSize: number; averageSize: {
value: number;
};
topMimeTypes: Buckets<string>;
} }
export type FileAttachmentAggregationResult = Record<Owner, FileAttachmentAverageSize> & export type FileAttachmentAggregationResults = Record<Owner, FileAttachmentAggsResult> &
FileAttachmentAverageSize; FileAttachmentAggsResult;
export interface BucketsWithMaxOnCase { export interface BucketsWithMaxOnCase {
buckets: Array< buckets: Array<
@ -118,6 +125,10 @@ export interface AttachmentStats extends CommonAttachmentStats {
export interface FileAttachmentStats extends CommonAttachmentStats { export interface FileAttachmentStats extends CommonAttachmentStats {
averageSize: number; averageSize: number;
topMimeTypes: Array<{
name: string;
count: number;
}>;
} }
export interface AttachmentFramework { export interface AttachmentFramework {

View file

@ -4839,6 +4839,19 @@
}, },
"total": { "total": {
"type": "long" "type": "long"
},
"topMimeTypes": {
"type": "array",
"items": {
"properties": {
"count": {
"type": "long"
},
"name": {
"type": "keyword"
}
}
}
} }
} }
} }
@ -4986,6 +4999,19 @@
}, },
"total": { "total": {
"type": "long" "type": "long"
},
"topMimeTypes": {
"type": "array",
"items": {
"properties": {
"count": {
"type": "long"
},
"name": {
"type": "keyword"
}
}
}
} }
} }
} }
@ -5073,6 +5099,19 @@
}, },
"total": { "total": {
"type": "long" "type": "long"
},
"topMimeTypes": {
"type": "array",
"items": {
"properties": {
"count": {
"type": "long"
},
"name": {
"type": "keyword"
}
}
}
} }
} }
} }
@ -5160,6 +5199,19 @@
}, },
"total": { "total": {
"type": "long" "type": "long"
},
"topMimeTypes": {
"type": "array",
"items": {
"properties": {
"count": {
"type": "long"
},
"name": {
"type": "keyword"
}
}
}
} }
} }
} }