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

View file

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

View file

@ -12,6 +12,7 @@ import {
getReportingUsageCollector, getReportingUsageCollector,
} from './reporting_usage_collector'; } from './reporting_usage_collector';
import { ReportingConfig } from '../types'; import { ReportingConfig } from '../types';
import { SearchResponse } from './types';
const exportTypesRegistry = getExportTypesRegistry(); const exportTypesRegistry = getExportTypesRegistry();
@ -61,7 +62,7 @@ const getMockReportingConfig = () => ({
get: () => {}, get: () => {},
kbnConfig: { get: () => '' }, kbnConfig: { get: () => '' },
}); });
const getResponseMock = (customization = {}) => customization; const getResponseMock = (base = {}) => base;
describe('license checks', () => { describe('license checks', () => {
let mockConfig: ReportingConfig; let mockConfig: ReportingConfig;
@ -206,212 +207,143 @@ describe('data modeling', () => {
ranges: { ranges: {
buckets: { buckets: {
all: { all: {
doc_count: 54, doc_count: 12,
layoutTypes: { jobTypes: { buckets: [ { doc_count: 9, key: 'printable_pdf' }, { doc_count: 3, key: 'PNG' }, ], },
doc_count: 23, layoutTypes: { doc_count: 9, pdf: { buckets: [{ doc_count: 9, key: 'preserve_layout' }] }, },
pdf: { objectTypes: { doc_count: 9, pdf: { buckets: [ { doc_count: 6, key: 'canvas workpad' }, { doc_count: 3, key: 'visualization' }, ], }, },
doc_count_error_upper_bound: 0, 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', }, ], },
sum_other_doc_count: 0, statusTypes: { buckets: [ { doc_count: 10, key: 'completed' }, { doc_count: 1, key: 'completed_with_warnings' }, { doc_count: 1, key: 'failed' }, ], },
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 },
],
},
}, },
last7Days: { last7Days: {
doc_count: 27, doc_count: 1,
layoutTypes: { jobTypes: { buckets: [{ doc_count: 1, key: 'PNG' }] },
doc_count: 13, layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
pdf: { objectTypes: { doc_count: 0, pdf: { buckets: [] } },
doc_count_error_upper_bound: 0, statusByApp: { buckets: [ { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed_with_warnings', }, ], },
sum_other_doc_count: 0, statusTypes: { buckets: [{ doc_count: 1, key: 'completed_with_warnings' }] },
buckets: [ },
{ key: 'print', doc_count: 10 }, lastDay: {
{ key: 'preserve_layout', doc_count: 3 }, doc_count: 1,
], jobTypes: { buckets: [{ doc_count: 1, key: 'PNG' }] },
}, layoutTypes: { doc_count: 0, pdf: { buckets: [] } },
}, objectTypes: { doc_count: 0, pdf: { buckets: [] } },
objectTypes: { statusByApp: { buckets: [ { doc_count: 1, jobTypes: { buckets: [ { appNames: { buckets: [{ doc_count: 1, key: 'dashboard' }] }, doc_count: 1, key: 'PNG', }, ], }, key: 'completed_with_warnings', }, ], },
doc_count: 13, statusTypes: { buckets: [{ doc_count: 1, key: 'completed_with_warnings' }] },
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 },
],
},
}, },
}, },
}, },
}, },
}) } as SearchResponse) // prettier-ignore
) )
); );
const usageStats = await fetch(callClusterMock as any); const usageStats = await fetch(callClusterMock as any);
expect(usageStats).toMatchInlineSnapshot(` expect(usageStats).toMatchSnapshot();
Object { });
"PNG": Object {
"available": true, test('with sparse data', async () => {
"total": 4, const mockConfig = getMockReportingConfig();
}, const plugins = getPluginsMock();
"_all": 54, const { fetch } = getReportingUsageCollector(
"available": true, mockConfig,
"browser_type": undefined, plugins.usageCollection,
"csv": Object { plugins.__LEGACY.plugins.xpack_main.info,
"available": true, exportTypesRegistry,
"total": 27, function isReady() {
}, return Promise.resolve(true);
"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,
},
} }
`); );
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. * 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 { export interface KeyCountBucket {
key: string; key: string;
doc_count: number; doc_count: number;
@ -20,22 +11,53 @@ export interface KeyCountBucket {
export interface AggregationBuckets { export interface AggregationBuckets {
buckets: KeyCountBucket[]; buckets: KeyCountBucket[];
pdf?: { }
buckets: KeyCountBucket[];
export interface StatusByAppBucket {
key: string;
doc_count: number;
jobTypes: {
buckets: Array<{
doc_count: number;
key: string;
appNames: AggregationBuckets;
}>;
}; };
} }
/* export interface AggregationResultBuckets {
* Mapped Types and Intersection Types jobTypes: AggregationBuckets;
*/ layoutTypes: {
doc_count: number;
type AggregationKeys = 'jobTypes' | 'layoutTypes' | 'objectTypes' | 'statusTypes'; pdf: AggregationBuckets;
export type AggregationResults = { [K in AggregationKeys]: AggregationBuckets } & { };
objectTypes: {
doc_count: number;
pdf: AggregationBuckets;
};
statusTypes: AggregationBuckets;
statusByApp: {
buckets: StatusByAppBucket[];
};
doc_count: number; doc_count: number;
}; }
type RangeAggregationKeys = 'all' | 'lastDay' | 'last7Days'; export interface SearchResponse {
export type RangeAggregationResults = { [K in RangeAggregationKeys]?: AggregationResults }; aggregations: {
ranges: {
buckets: {
all: AggregationResultBuckets;
last7Days: AggregationResultBuckets;
lastDay: AggregationResultBuckets;
};
};
};
}
export interface AvailableTotal {
available: boolean;
total: number;
}
type BaseJobTypeKeys = 'csv' | 'PNG'; type BaseJobTypeKeys = 'csv' | 'PNG';
export type JobTypes = { [K in BaseJobTypeKeys]: AvailableTotal } & { 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 & { export type RangeStats = JobTypes & {
_all: number; _all: number;
status: StatusCounts; status: StatusCounts;
statuses: StatusByAppCounts;
}; };
export type ExportType = 'csv' | 'printable_pdf' | 'PNG'; export type ExportType = 'csv' | 'printable_pdf' | 'PNG';