[Reporting/telemetry] Add 'statuses' object to usage to show status counts by jobType & appType (#63922)

* [Reporting] Additional status by app data for usage

* --wip-- [skip ci]

* clean up types

* add a prettier-ignore

* fix types

* --wip-- [skip ci]

* fix typo

* more tests

* Tweak the data model

* fix the comments and type keys to reflect the data model

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Tim Sullivan 2020-04-27 11:49:51 -07:00 committed by GitHub
parent 75fa843652
commit 6b5a96557f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 584 additions and 237 deletions

View file

@ -0,0 +1,343 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`data modeling with empty data 1`] = `
Object {
"PNG": Object {
"available": true,
"total": 0,
},
"_all": 0,
"available": true,
"browser_type": undefined,
"csv": Object {
"available": true,
"total": 0,
},
"enabled": true,
"last7Days": Object {
"PNG": Object {
"available": true,
"total": 0,
},
"_all": 0,
"csv": Object {
"available": true,
"total": 0,
},
"printable_pdf": Object {
"app": Object {
"dashboard": 0,
"visualization": 0,
},
"available": true,
"layout": Object {
"preserve_layout": 0,
"print": 0,
},
"total": 0,
},
"status": Object {
"completed": 0,
"failed": 0,
},
"statuses": Object {},
},
"lastDay": Object {
"PNG": Object {
"available": true,
"total": 0,
},
"_all": 0,
"csv": Object {
"available": true,
"total": 0,
},
"printable_pdf": Object {
"app": Object {
"dashboard": 0,
"visualization": 0,
},
"available": true,
"layout": Object {
"preserve_layout": 0,
"print": 0,
},
"total": 0,
},
"status": Object {
"completed": 0,
"failed": 0,
},
"statuses": Object {},
},
"printable_pdf": Object {
"app": Object {
"dashboard": 0,
"visualization": 0,
},
"available": true,
"layout": Object {
"preserve_layout": 0,
"print": 0,
},
"total": 0,
},
"status": Object {
"completed": 0,
"failed": 0,
},
"statuses": Object {},
}
`;
exports[`data modeling with normal looking usage data 1`] = `
Object {
"PNG": Object {
"available": true,
"total": 3,
},
"_all": 12,
"available": true,
"browser_type": undefined,
"csv": Object {
"available": true,
"total": 0,
},
"enabled": true,
"last7Days": Object {
"PNG": Object {
"available": true,
"total": 1,
},
"_all": 1,
"csv": Object {
"available": true,
"total": 0,
},
"printable_pdf": Object {
"app": Object {
"dashboard": 0,
"visualization": 0,
},
"available": true,
"layout": Object {
"preserve_layout": 0,
"print": 0,
},
"total": 0,
},
"status": Object {
"completed": 0,
"completed_with_warnings": 1,
"failed": 0,
},
"statuses": Object {
"completed_with_warnings": Object {
"PNG": Object {
"dashboard": 1,
},
},
},
},
"lastDay": Object {
"PNG": Object {
"available": true,
"total": 1,
},
"_all": 1,
"csv": Object {
"available": true,
"total": 0,
},
"printable_pdf": Object {
"app": Object {
"dashboard": 0,
"visualization": 0,
},
"available": true,
"layout": Object {
"preserve_layout": 0,
"print": 0,
},
"total": 0,
},
"status": Object {
"completed": 0,
"completed_with_warnings": 1,
"failed": 0,
},
"statuses": Object {
"completed_with_warnings": Object {
"PNG": Object {
"dashboard": 1,
},
},
},
},
"printable_pdf": Object {
"app": Object {
"canvas workpad": 6,
"dashboard": 0,
"visualization": 3,
},
"available": true,
"layout": Object {
"preserve_layout": 9,
"print": 0,
},
"total": 9,
},
"status": Object {
"completed": 10,
"completed_with_warnings": 1,
"failed": 1,
},
"statuses": Object {
"completed": Object {
"PNG": Object {
"visualization": 1,
},
"printable_pdf": Object {
"canvas workpad": 6,
"visualization": 3,
},
},
"completed_with_warnings": Object {
"PNG": Object {
"dashboard": 1,
},
},
"failed": Object {
"PNG": Object {
"dashboard": 1,
},
},
},
}
`;
exports[`data modeling with sparse data 1`] = `
Object {
"PNG": Object {
"available": true,
"total": 1,
},
"_all": 4,
"available": true,
"browser_type": undefined,
"csv": Object {
"available": true,
"total": 1,
},
"enabled": true,
"last7Days": Object {
"PNG": Object {
"available": true,
"total": 1,
},
"_all": 4,
"csv": Object {
"available": true,
"total": 1,
},
"printable_pdf": Object {
"app": Object {
"canvas workpad": 1,
"dashboard": 1,
"visualization": 0,
},
"available": true,
"layout": Object {
"preserve_layout": 2,
"print": 0,
},
"total": 2,
},
"status": Object {
"completed": 4,
"failed": 0,
},
"statuses": Object {
"completed": Object {
"PNG": Object {
"dashboard": 1,
},
"csv": Object {},
"printable_pdf": Object {
"canvas workpad": 1,
"dashboard": 1,
},
},
},
},
"lastDay": Object {
"PNG": Object {
"available": true,
"total": 1,
},
"_all": 4,
"csv": Object {
"available": true,
"total": 1,
},
"printable_pdf": Object {
"app": Object {
"canvas workpad": 1,
"dashboard": 1,
"visualization": 0,
},
"available": true,
"layout": Object {
"preserve_layout": 2,
"print": 0,
},
"total": 2,
},
"status": Object {
"completed": 4,
"failed": 0,
},
"statuses": Object {
"completed": Object {
"PNG": Object {
"dashboard": 1,
},
"csv": Object {},
"printable_pdf": Object {
"canvas workpad": 1,
"dashboard": 1,
},
},
},
},
"printable_pdf": Object {
"app": Object {
"canvas workpad": 1,
"dashboard": 1,
"visualization": 0,
},
"available": true,
"layout": Object {
"preserve_layout": 2,
"print": 0,
},
"total": 2,
},
"status": Object {
"completed": 4,
"failed": 0,
},
"statuses": Object {
"completed": Object {
"PNG": Object {
"dashboard": 1,
},
"csv": Object {},
"printable_pdf": Object {
"canvas workpad": 1,
"dashboard": 1,
},
},
},
}
`;

View file

@ -6,7 +6,7 @@
import { uniq } from 'lodash';
import { CSV_JOB_TYPE, PDF_JOB_TYPE, PNG_JOB_TYPE } from '../../common/constants';
import { AvailableTotal, FeatureAvailabilityMap, RangeStats, ExportType } from './types';
import { AvailableTotal, ExportType, FeatureAvailabilityMap, RangeStats } from './types';
function getForFeature(
range: Partial<RangeStats>,
@ -47,6 +47,7 @@ export const decorateRangeStats = (
const {
_all: rangeAll,
status: rangeStatus,
statuses: rangeStatusByApp,
[PDF_JOB_TYPE]: rangeStatsPdf,
...rangeStatsBasic
} = rangeStats;
@ -73,6 +74,7 @@ export const decorateRangeStats = (
const resultStats = {
_all: rangeAll || 0,
status: { completed: 0, failed: 0, ...rangeStatus },
statuses: rangeStatusByApp,
...rangePdf,
...rangeBasic,
} as RangeStats;

View file

@ -11,13 +11,13 @@ import { ReportingConfig } from '../types';
import { decorateRangeStats } from './decorate_range_stats';
import { getExportTypesHandler } from './get_export_type_handler';
import {
AggregationBuckets,
AggregationResults,
AggregationResultBuckets,
FeatureAvailabilityMap,
JobTypes,
KeyCountBucket,
RangeAggregationResults,
RangeStats,
SearchResponse,
StatusByAppBucket,
} from './types';
type XPackInfo = XPackMainPlugin['info'];
@ -29,6 +29,7 @@ const LAYOUT_TYPES_FIELD = 'meta.layout.keyword';
const OBJECT_TYPES_KEY = 'objectTypes';
const OBJECT_TYPES_FIELD = 'meta.objectType.keyword';
const STATUS_TYPES_KEY = 'statusTypes';
const STATUS_BY_APP_KEY = 'statusByApp';
const STATUS_TYPES_FIELD = 'status';
const DEFAULT_TERMS_SIZE = 10;
@ -38,16 +39,30 @@ const PRINTABLE_PDF_JOBTYPE = 'printable_pdf';
const getKeyCount = (buckets: KeyCountBucket[]): { [key: string]: number } =>
buckets.reduce((accum, { key, doc_count: count }) => ({ ...accum, [key]: count }), {});
function getAggStats(aggs: AggregationResults) {
const { buckets: jobBuckets } = aggs[JOB_TYPES_KEY] as AggregationBuckets;
const jobTypes: JobTypes = jobBuckets.reduce(
// indexes some key/count buckets by statusType > jobType > appName: statusCount
const getAppStatuses = (buckets: StatusByAppBucket[]) =>
buckets.reduce((statuses, statusBucket) => {
return {
...statuses,
[statusBucket.key]: statusBucket.jobTypes.buckets.reduce((jobTypes, job) => {
return {
...jobTypes,
[job.key]: job.appNames.buckets.reduce((apps, app) => {
return {
...apps,
[app.key]: app.doc_count,
};
}, {}),
};
}, {}),
};
}, {});
function getAggStats(aggs: AggregationResultBuckets): RangeStats {
const { buckets: jobBuckets } = aggs[JOB_TYPES_KEY];
const jobTypes = jobBuckets.reduce(
(accum: JobTypes, { key, doc_count: count }: { key: string; doc_count: number }) => {
return {
...accum,
[key]: {
total: count,
},
};
return { ...accum, [key]: { total: count } };
},
{} as JobTypes
);
@ -55,8 +70,8 @@ function getAggStats(aggs: AggregationResults) {
// merge pdf stats into pdf jobtype key
const pdfJobs = jobTypes[PRINTABLE_PDF_JOBTYPE];
if (pdfJobs) {
const pdfAppBuckets = get(aggs[OBJECT_TYPES_KEY], '.pdf.buckets', []);
const pdfLayoutBuckets = get(aggs[LAYOUT_TYPES_KEY], '.pdf.buckets', []);
const pdfAppBuckets = get<KeyCountBucket[]>(aggs[OBJECT_TYPES_KEY], '.pdf.buckets', []);
const pdfLayoutBuckets = get<KeyCountBucket[]>(aggs[LAYOUT_TYPES_KEY], '.pdf.buckets', []);
pdfJobs.app = getKeyCount(pdfAppBuckets) as {
visualization: number;
dashboard: number;
@ -69,26 +84,35 @@ function getAggStats(aggs: AggregationResults) {
const all = aggs.doc_count as number;
let statusTypes = {};
const statusBuckets = get(aggs[STATUS_TYPES_KEY], 'buckets', []);
const statusBuckets = get<KeyCountBucket[]>(aggs[STATUS_TYPES_KEY], 'buckets', []);
if (statusBuckets) {
statusTypes = getKeyCount(statusBuckets);
}
return { _all: all, status: statusTypes, ...jobTypes };
let statusByApp = {};
const statusAppBuckets = get<StatusByAppBucket[]>(aggs[STATUS_BY_APP_KEY], 'buckets', []);
if (statusAppBuckets) {
statusByApp = getAppStatuses(statusAppBuckets);
}
return { _all: all, status: statusTypes, statuses: statusByApp, ...jobTypes };
}
type SearchAggregation = SearchResponse['aggregations']['ranges']['buckets'];
type RangeStatSets = Partial<
RangeStats & {
lastDay: RangeStats;
last7Days: RangeStats;
}
>;
async function handleResponse(response: AggregationResults): Promise<RangeStatSets> {
const buckets = get(response, 'aggregations.ranges.buckets');
async function handleResponse(response: SearchResponse): Promise<RangeStatSets> {
const buckets = get<SearchAggregation>(response, 'aggregations.ranges.buckets');
if (!buckets) {
return {};
}
const { lastDay, last7Days, all } = buckets as RangeAggregationResults;
const { lastDay, last7Days, all } = buckets;
const lastDayUsage = lastDay ? getAggStats(lastDay) : ({} as RangeStats);
const last7DaysUsage = last7Days ? getAggStats(last7Days) : ({} as RangeStats);
@ -126,6 +150,17 @@ export async function getReportingUsage(
aggs: {
[JOB_TYPES_KEY]: { terms: { field: JOB_TYPES_FIELD, size: DEFAULT_TERMS_SIZE } },
[STATUS_TYPES_KEY]: { terms: { field: STATUS_TYPES_FIELD, size: DEFAULT_TERMS_SIZE } },
[STATUS_BY_APP_KEY]: {
terms: { field: 'status', size: DEFAULT_TERMS_SIZE },
aggs: {
jobTypes: {
terms: { field: JOB_TYPES_FIELD, size: DEFAULT_TERMS_SIZE },
aggs: {
appNames: { terms: { field: OBJECT_TYPES_FIELD, size: DEFAULT_TERMS_SIZE } }, // NOTE Discover/CSV export is missing the 'meta.objectType' field, so Discover/CSV results are missing for this agg
},
},
},
},
[OBJECT_TYPES_KEY]: {
filter: { term: { jobtype: PRINTABLE_PDF_JOBTYPE } },
aggs: { pdf: { terms: { field: OBJECT_TYPES_FIELD, size: DEFAULT_TERMS_SIZE } } },
@ -141,7 +176,7 @@ export async function getReportingUsage(
};
return callCluster('search', params)
.then((response: AggregationResults) => handleResponse(response))
.then((response: SearchResponse) => handleResponse(response))
.then((usage: RangeStatSets) => {
// Allow this to explicitly throw an exception if/when this config is deprecated,
// because we shouldn't collect browserType in that case!

View file

@ -12,6 +12,7 @@ import {
getReportingUsageCollector,
} from './reporting_usage_collector';
import { ReportingConfig } from '../types';
import { SearchResponse } from './types';
const exportTypesRegistry = getExportTypesRegistry();
@ -61,7 +62,7 @@ const getMockReportingConfig = () => ({
get: () => {},
kbnConfig: { get: () => '' },
});
const getResponseMock = (customization = {}) => customization;
const getResponseMock = (base = {}) => base;
describe('license checks', () => {
let mockConfig: ReportingConfig;
@ -206,212 +207,143 @@ describe('data modeling', () => {
ranges: {
buckets: {
all: {
doc_count: 54,
layoutTypes: {
doc_count: 23,
pdf: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [
{ key: 'preserve_layout', doc_count: 13 },
{ key: 'print', doc_count: 10 },
],
},
},
objectTypes: {
doc_count: 23,
pdf: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [{ key: 'dashboard', doc_count: 23 }],
},
},
statusTypes: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [
{ key: 'pending', doc_count: 33 },
{ key: 'completed', doc_count: 20 },
{ key: 'processing', doc_count: 1 },
],
},
jobTypes: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [
{ key: 'csv', doc_count: 27 },
{ key: 'printable_pdf', doc_count: 23 },
{ key: 'PNG', doc_count: 4 },
],
},
},
lastDay: {
doc_count: 11,
layoutTypes: {
doc_count: 2,
pdf: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [{ key: 'print', doc_count: 2 }],
},
},
objectTypes: {
doc_count: 2,
pdf: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [{ key: 'dashboard', doc_count: 2 }],
},
},
statusTypes: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [{ key: 'pending', doc_count: 11 }],
},
jobTypes: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [
{ key: 'csv', doc_count: 5 },
{ key: 'PNG', doc_count: 4 },
{ key: 'printable_pdf', doc_count: 2 },
],
},
doc_count: 12,
jobTypes: { buckets: [ { doc_count: 9, key: 'printable_pdf' }, { doc_count: 3, key: 'PNG' }, ], },
layoutTypes: { doc_count: 9, pdf: { buckets: [{ doc_count: 9, key: 'preserve_layout' }] }, },
objectTypes: { doc_count: 9, pdf: { buckets: [ { doc_count: 6, key: 'canvas workpad' }, { doc_count: 3, key: 'visualization' }, ], }, },
statusByApp: { buckets: [ { doc_count: 10, jobTypes: { buckets: [ { appNames: { buckets: [ { doc_count: 6, key: 'canvas workpad' }, { doc_count: 3, key: 'visualization' }, ], }, doc_count: 9, key: 'printable_pdf', }, { appNames: { buckets: [{ doc_count: 1, key: 'visualization' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed', }, { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed_with_warnings', }, { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'failed', }, ], },
statusTypes: { buckets: [ { doc_count: 10, key: 'completed' }, { doc_count: 1, key: 'completed_with_warnings' }, { doc_count: 1, key: 'failed' }, ], },
},
last7Days: {
doc_count: 27,
layoutTypes: {
doc_count: 13,
pdf: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [
{ key: 'print', doc_count: 10 },
{ key: 'preserve_layout', doc_count: 3 },
],
},
},
objectTypes: {
doc_count: 13,
pdf: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [{ key: 'dashboard', doc_count: 13 }],
},
},
statusTypes: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [{ key: 'pending', doc_count: 27 }],
},
jobTypes: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [
{ key: 'printable_pdf', doc_count: 13 },
{ key: 'csv', doc_count: 10 },
{ key: 'PNG', doc_count: 4 },
],
},
doc_count: 1,
jobTypes: { buckets: [{ doc_count: 1, key: 'PNG' }] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [ { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed_with_warnings', }, ], },
statusTypes: { buckets: [{ doc_count: 1, key: 'completed_with_warnings' }] },
},
lastDay: {
doc_count: 1,
jobTypes: { buckets: [{ doc_count: 1, key: 'PNG' }] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [ { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed_with_warnings', }, ], },
statusTypes: { buckets: [{ doc_count: 1, key: 'completed_with_warnings' }] },
},
},
},
},
})
} as SearchResponse) // prettier-ignore
)
);
const usageStats = await fetch(callClusterMock as any);
expect(usageStats).toMatchInlineSnapshot(`
Object {
"PNG": Object {
"available": true,
"total": 4,
},
"_all": 54,
"available": true,
"browser_type": undefined,
"csv": Object {
"available": true,
"total": 27,
},
"enabled": true,
"last7Days": Object {
"PNG": Object {
"available": true,
"total": 4,
},
"_all": 27,
"csv": Object {
"available": true,
"total": 10,
},
"printable_pdf": Object {
"app": Object {
"dashboard": 13,
"visualization": 0,
},
"available": true,
"layout": Object {
"preserve_layout": 3,
"print": 10,
},
"total": 13,
},
"status": Object {
"completed": 0,
"failed": 0,
"pending": 27,
},
},
"lastDay": Object {
"PNG": Object {
"available": true,
"total": 4,
},
"_all": 11,
"csv": Object {
"available": true,
"total": 5,
},
"printable_pdf": Object {
"app": Object {
"dashboard": 2,
"visualization": 0,
},
"available": true,
"layout": Object {
"preserve_layout": 0,
"print": 2,
},
"total": 2,
},
"status": Object {
"completed": 0,
"failed": 0,
"pending": 11,
},
},
"printable_pdf": Object {
"app": Object {
"dashboard": 23,
"visualization": 0,
},
"available": true,
"layout": Object {
"preserve_layout": 13,
"print": 10,
},
"total": 23,
},
"status": Object {
"completed": 20,
"failed": 0,
"pending": 33,
"processing": 1,
},
expect(usageStats).toMatchSnapshot();
});
test('with sparse data', async () => {
const mockConfig = getMockReportingConfig();
const plugins = getPluginsMock();
const { fetch } = getReportingUsageCollector(
mockConfig,
plugins.usageCollection,
plugins.__LEGACY.plugins.xpack_main.info,
exportTypesRegistry,
function isReady() {
return Promise.resolve(true);
}
`);
);
const callClusterMock = jest.fn(() =>
Promise.resolve(
getResponseMock({
aggregations: {
ranges: {
buckets: {
all: {
doc_count: 4,
layoutTypes: { doc_count: 2, pdf: { buckets: [{ key: 'preserve_layout', doc_count: 2 }] }, },
statusByApp: { buckets: [ { key: 'completed', doc_count: 4, jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2, appNames: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, }, { key: 'PNG', doc_count: 1, appNames: { buckets: [{ key: 'dashboard', doc_count: 1 }] }, }, { key: 'csv', doc_count: 1, appNames: { buckets: [] } }, ], }, }, ], },
objectTypes: { doc_count: 2, pdf: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, },
statusTypes: { buckets: [{ key: 'completed', doc_count: 4 }] },
jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2 }, { key: 'PNG', doc_count: 1 }, { key: 'csv', doc_count: 1 }, ], },
},
last7Days: {
doc_count: 4,
layoutTypes: { doc_count: 2, pdf: { buckets: [{ key: 'preserve_layout', doc_count: 2 }] }, },
statusByApp: { buckets: [ { key: 'completed', doc_count: 4, jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2, appNames: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, }, { key: 'PNG', doc_count: 1, appNames: { buckets: [{ key: 'dashboard', doc_count: 1 }] }, }, { key: 'csv', doc_count: 1, appNames: { buckets: [] } }, ], }, }, ], },
objectTypes: { doc_count: 2, pdf: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, },
statusTypes: { buckets: [{ key: 'completed', doc_count: 4 }] },
jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2 }, { key: 'PNG', doc_count: 1 }, { key: 'csv', doc_count: 1 }, ], },
},
lastDay: {
doc_count: 4,
layoutTypes: { doc_count: 2, pdf: { buckets: [{ key: 'preserve_layout', doc_count: 2 }] }, },
statusByApp: { buckets: [ { key: 'completed', doc_count: 4, jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2, appNames: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, }, { key: 'PNG', doc_count: 1, appNames: { buckets: [{ key: 'dashboard', doc_count: 1 }] }, }, { key: 'csv', doc_count: 1, appNames: { buckets: [] } }, ], }, }, ], },
objectTypes: { doc_count: 2, pdf: { buckets: [ { key: 'canvas workpad', doc_count: 1 }, { key: 'dashboard', doc_count: 1 }, ], }, },
statusTypes: { buckets: [{ key: 'completed', doc_count: 4 }] },
jobTypes: { buckets: [ { key: 'printable_pdf', doc_count: 2 }, { key: 'PNG', doc_count: 1 }, { key: 'csv', doc_count: 1 }, ], },
},
},
},
},
} as SearchResponse) // prettier-ignore
)
);
const usageStats = await fetch(callClusterMock as any);
expect(usageStats).toMatchSnapshot();
});
test('with empty data', async () => {
const mockConfig = getMockReportingConfig();
const plugins = getPluginsMock();
const { fetch } = getReportingUsageCollector(
mockConfig,
plugins.usageCollection,
plugins.__LEGACY.plugins.xpack_main.info,
exportTypesRegistry,
function isReady() {
return Promise.resolve(true);
}
);
const callClusterMock = jest.fn(() =>
Promise.resolve(
getResponseMock({
aggregations: {
ranges: {
buckets: {
all: {
doc_count: 0,
jobTypes: { buckets: [] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [] },
statusTypes: { buckets: [] },
},
last7Days: {
doc_count: 0,
jobTypes: { buckets: [] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [] },
statusTypes: { buckets: [] },
},
lastDay: {
doc_count: 0,
jobTypes: { buckets: [] },
layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { doc_count: 0, pdf: { buckets: [] } },
statusByApp: { buckets: [] },
statusTypes: { buckets: [] },
},
},
},
},
} as SearchResponse)
)
);
const usageStats = await fetch(callClusterMock as any);
expect(usageStats).toMatchSnapshot();
});
});

View file

@ -4,15 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
export interface AvailableTotal {
available: boolean;
total: number;
}
interface StatusCounts {
[statusType: string]: number;
}
export interface KeyCountBucket {
key: string;
doc_count: number;
@ -20,22 +11,53 @@ export interface KeyCountBucket {
export interface AggregationBuckets {
buckets: KeyCountBucket[];
pdf?: {
buckets: KeyCountBucket[];
}
export interface StatusByAppBucket {
key: string;
doc_count: number;
jobTypes: {
buckets: Array<{
doc_count: number;
key: string;
appNames: AggregationBuckets;
}>;
};
}
/*
* Mapped Types and Intersection Types
*/
type AggregationKeys = 'jobTypes' | 'layoutTypes' | 'objectTypes' | 'statusTypes';
export type AggregationResults = { [K in AggregationKeys]: AggregationBuckets } & {
export interface AggregationResultBuckets {
jobTypes: AggregationBuckets;
layoutTypes: {
doc_count: number;
pdf: AggregationBuckets;
};
objectTypes: {
doc_count: number;
pdf: AggregationBuckets;
};
statusTypes: AggregationBuckets;
statusByApp: {
buckets: StatusByAppBucket[];
};
doc_count: number;
};
}
type RangeAggregationKeys = 'all' | 'lastDay' | 'last7Days';
export type RangeAggregationResults = { [K in RangeAggregationKeys]?: AggregationResults };
export interface SearchResponse {
aggregations: {
ranges: {
buckets: {
all: AggregationResultBuckets;
last7Days: AggregationResultBuckets;
lastDay: AggregationResultBuckets;
};
};
};
}
export interface AvailableTotal {
available: boolean;
total: number;
}
type BaseJobTypeKeys = 'csv' | 'PNG';
export type JobTypes = { [K in BaseJobTypeKeys]: AvailableTotal } & {
@ -51,9 +73,22 @@ export type JobTypes = { [K in BaseJobTypeKeys]: AvailableTotal } & {
};
};
interface StatusCounts {
[statusType: string]: number;
}
interface StatusByAppCounts {
[statusType: string]: {
[jobType: string]: {
[appName: string]: number;
};
};
}
export type RangeStats = JobTypes & {
_all: number;
status: StatusCounts;
statuses: StatusByAppCounts;
};
export type ExportType = 'csv' | 'printable_pdf' | 'PNG';