From 4c78a3d4a84d1df43e6d3599f477c097c19679f8 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Mon, 4 May 2020 15:19:58 -0700 Subject: [PATCH] [Reporting] Improve TS and update Reporting usage data model (#64841) * Stricter types for Reporting Usage data * Allow more type inference * remove lastDay filter * update ts and add test * fix tests * fix test Co-authored-by: Elastic Machine --- .../reporting_usage_collector.test.ts.snap | 103 ----------------- .../server/usage/get_reporting_usage.ts | 87 +++++++------- .../usage/reporting_usage_collector.test.ts | 107 +++++++++++++++++- .../plugins/reporting/server/usage/types.d.ts | 56 +++++---- .../test/reporting/services/reporting_api.js | 3 - 5 files changed, 182 insertions(+), 174 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap b/x-pack/legacy/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap index aa22c3f66df18..ed2637d7a1bcb 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap +++ b/x-pack/legacy/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap @@ -42,34 +42,6 @@ Object { }, "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, @@ -139,41 +111,6 @@ Object { }, }, }, - "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, @@ -270,46 +207,6 @@ Object { }, }, }, - "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, diff --git a/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts b/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts index 2c3bb8f4bf71c..eb907d52c5f96 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts +++ b/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts @@ -16,8 +16,11 @@ import { JobTypes, KeyCountBucket, RangeStats, + ReportingUsageType, SearchResponse, StatusByAppBucket, + AppCounts, + LayoutCounts, } from './types'; type XPackInfo = XPackMainPlugin['info']; @@ -36,8 +39,11 @@ const DEFAULT_TERMS_SIZE = 10; const PRINTABLE_PDF_JOBTYPE = 'printable_pdf'; // indexes some key/count buckets by the "key" property -const getKeyCount = (buckets: KeyCountBucket[]): { [key: string]: number } => - buckets.reduce((accum, { key, doc_count: count }) => ({ ...accum, [key]: count }), {}); +const getKeyCount = (buckets: KeyCountBucket[]): BucketType => + buckets.reduce( + (accum, { key, doc_count: count }) => ({ ...accum, [key]: count }), + {} as BucketType + ); // indexes some key/count buckets by statusType > jobType > appName: statusCount const getAppStatuses = (buckets: StatusByAppBucket[]) => @@ -58,7 +64,7 @@ const getAppStatuses = (buckets: StatusByAppBucket[]) => }; }, {}); -function getAggStats(aggs: AggregationResultBuckets): RangeStats { +function getAggStats(aggs: AggregationResultBuckets): Partial { const { buckets: jobBuckets } = aggs[JOB_TYPES_KEY]; const jobTypes = jobBuckets.reduce( (accum: JobTypes, { key, doc_count: count }: { key: string; doc_count: number }) => { @@ -72,17 +78,11 @@ function getAggStats(aggs: AggregationResultBuckets): RangeStats { if (pdfJobs) { const pdfAppBuckets = get(aggs[OBJECT_TYPES_KEY], '.pdf.buckets', []); const pdfLayoutBuckets = get(aggs[LAYOUT_TYPES_KEY], '.pdf.buckets', []); - pdfJobs.app = getKeyCount(pdfAppBuckets) as { - visualization: number; - dashboard: number; - }; - pdfJobs.layout = getKeyCount(pdfLayoutBuckets) as { - print: number; - preserve_layout: number; - }; + pdfJobs.app = getKeyCount(pdfAppBuckets); + pdfJobs.layout = getKeyCount(pdfLayoutBuckets); } - const all = aggs.doc_count as number; + const all = aggs.doc_count; let statusTypes = {}; const statusBuckets = get(aggs[STATUS_TYPES_KEY], 'buckets', []); if (statusBuckets) { @@ -100,27 +100,22 @@ function getAggStats(aggs: AggregationResultBuckets): RangeStats { type SearchAggregation = SearchResponse['aggregations']['ranges']['buckets']; -type RangeStatSets = Partial< - RangeStats & { - lastDay: RangeStats; - last7Days: RangeStats; - } ->; +type RangeStatSets = Partial & { + last7Days: Partial; +}; -async function handleResponse(response: SearchResponse): Promise { +async function handleResponse(response: SearchResponse): Promise> { const buckets = get(response, 'aggregations.ranges.buckets'); if (!buckets) { return {}; } - const { lastDay, last7Days, all } = buckets; + const { last7Days, all } = buckets; - const lastDayUsage = lastDay ? getAggStats(lastDay) : ({} as RangeStats); - const last7DaysUsage = last7Days ? getAggStats(last7Days) : ({} as RangeStats); - const allUsage = all ? getAggStats(all) : ({} as RangeStats); + const last7DaysUsage = last7Days ? getAggStats(last7Days) : {}; + const allUsage = all ? getAggStats(all) : {}; return { last7Days: last7DaysUsage, - lastDay: lastDayUsage, ...allUsage, }; } @@ -143,7 +138,6 @@ export async function getReportingUsage( filters: { filters: { all: { match_all: {} }, - lastDay: { range: { created_at: { gte: 'now-1d/d' } } }, last7Days: { range: { created_at: { gte: 'now-7d/d' } } }, }, }, @@ -177,25 +171,26 @@ export async function getReportingUsage( return callCluster('search', params) .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! - const browserType = config.get('capture', 'browser', 'type'); - - const exportTypesHandler = getExportTypesHandler(exportTypesRegistry); - const availability = exportTypesHandler.getAvailability( - xpackMainInfo - ) as FeatureAvailabilityMap; - - const { lastDay, last7Days, ...all } = usage; - - return { - available: true, - browser_type: browserType, - enabled: true, - lastDay: decorateRangeStats(lastDay, availability), - last7Days: decorateRangeStats(last7Days, availability), - ...decorateRangeStats(all, availability), - }; - }); + .then( + (usage: Partial): ReportingUsageType => { + // Allow this to explicitly throw an exception if/when this config is deprecated, + // because we shouldn't collect browserType in that case! + const browserType = config.get('capture', 'browser', 'type'); + + const exportTypesHandler = getExportTypesHandler(exportTypesRegistry); + const availability = exportTypesHandler.getAvailability( + xpackMainInfo + ) as FeatureAvailabilityMap; + + const { last7Days, ...all } = usage; + + return { + available: true, + browser_type: browserType, + enabled: true, + last7Days: decorateRangeStats(last7Days, availability), + ...decorateRangeStats(all, availability), + }; + } + ); } diff --git a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.ts b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.ts index 61b736a3e4d8c..8509eb8b5c47a 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.ts +++ b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.ts @@ -12,7 +12,7 @@ import { getReportingUsageCollector, } from './reporting_usage_collector'; import { ReportingConfig } from '../types'; -import { SearchResponse } from './types'; +import { SearchResponse, ReportingUsageType } from './types'; const exportTypesRegistry = getExportTypesRegistry(); @@ -345,6 +345,111 @@ describe('data modeling', () => { const usageStats = await fetch(callClusterMock as any); expect(usageStats).toMatchSnapshot(); }); + + test('Cast various example data to the TypeScript definition', () => { + const check = (obj: ReportingUsageType) => { + return typeof obj; + }; + + // just check that the example objects can be cast to ReportingUsageType + check({ + PNG: { available: true, total: 7 }, + _all: 21, + available: true, + browser_type: 'chromium', + csv: { available: true, total: 4 }, + enabled: true, + last7Days: { + PNG: { available: true, total: 0 }, + _all: 0, + csv: { available: true, total: 0 }, + printable_pdf: { + app: { dashboard: 0, visualization: 0 }, + available: true, + layout: { preserve_layout: 0, print: 0 }, + total: 0, + }, + status: { completed: 0, failed: 0 }, + statuses: {}, + }, + printable_pdf: { + app: { 'canvas workpad': 3, dashboard: 3, visualization: 4 }, + available: true, + layout: { preserve_layout: 7, print: 3 }, + total: 10, + }, + status: { completed: 21, failed: 0 }, + statuses: { + completed: { + PNG: { dashboard: 3, visualization: 4 }, + csv: {}, + printable_pdf: { 'canvas workpad': 3, dashboard: 3, visualization: 4 }, + }, + }, + }); + check({ + PNG: { available: true, total: 3 }, + _all: 4, + available: true, + browser_type: 'chromium', + csv: { available: true, total: 0 }, + enabled: true, + last7Days: { + PNG: { available: true, total: 3 }, + _all: 4, + csv: { available: true, total: 0 }, + printable_pdf: { + app: { 'canvas workpad': 1, dashboard: 0, visualization: 0 }, + available: true, + layout: { preserve_layout: 1, print: 0 }, + total: 1, + }, + status: { completed: 4, failed: 0 }, + statuses: { + completed: { PNG: { visualization: 3 }, printable_pdf: { 'canvas workpad': 1 } }, + }, + }, + printable_pdf: { + app: { 'canvas workpad': 1, dashboard: 0, visualization: 0 }, + available: true, + layout: { preserve_layout: 1, print: 0 }, + total: 1, + }, + status: { completed: 4, failed: 0 }, + statuses: { + completed: { PNG: { visualization: 3 }, printable_pdf: { 'canvas workpad': 1 } }, + }, + }); + check({ + available: true, + browser_type: 'chromium', + enabled: true, + last7Days: { + _all: 0, + status: { completed: 0, failed: 0 }, + statuses: {}, + printable_pdf: { + available: true, + total: 0, + app: { dashboard: 0, visualization: 0 }, + layout: { preserve_layout: 0, print: 0 }, + }, + csv: { available: true, total: 0 }, + PNG: { available: true, total: 0 }, + }, + _all: 0, + status: { completed: 0, failed: 0 }, + statuses: {}, + printable_pdf: { + available: true, + total: 0, + app: { dashboard: 0, visualization: 0 }, + layout: { preserve_layout: 0, print: 0 }, + }, + csv: { available: true, total: 0 }, + PNG: { available: true, total: 0 }, + }); + }); }); describe('Ready for collection observable', () => { diff --git a/x-pack/legacy/plugins/reporting/server/usage/types.d.ts b/x-pack/legacy/plugins/reporting/server/usage/types.d.ts index 83f1701863355..4d7a1a33239b2 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/types.d.ts +++ b/x-pack/legacy/plugins/reporting/server/usage/types.d.ts @@ -48,7 +48,6 @@ export interface SearchResponse { buckets: { all: AggregationResultBuckets; last7Days: AggregationResultBuckets; - lastDay: AggregationResultBuckets; }; }; }; @@ -59,31 +58,39 @@ export interface AvailableTotal { total: number; } -type BaseJobTypeKeys = 'csv' | 'PNG'; -export type JobTypes = { [K in BaseJobTypeKeys]: AvailableTotal } & { +type BaseJobTypes = 'csv' | 'PNG' | 'printable_pdf'; +export interface LayoutCounts { + print: number; + preserve_layout: number; +} + +type AppNames = 'canvas workpad' | 'dashboard' | 'visualization'; +export type AppCounts = { + [A in AppNames]?: number; +}; + +export type JobTypes = { [K in BaseJobTypes]: AvailableTotal } & { printable_pdf: AvailableTotal & { - app: { - visualization: number; - dashboard: number; - }; - layout: { - print: number; - preserve_layout: number; - }; + app: AppCounts; + layout: LayoutCounts; }; }; -interface StatusCounts { - [statusType: string]: number; -} - -interface StatusByAppCounts { - [statusType: string]: { - [jobType: string]: { - [appName: string]: number; - }; +type Statuses = + | 'cancelled' + | 'completed' + | 'completed_with_warnings' + | 'failed' + | 'pending' + | 'processing'; +type StatusCounts = { + [S in Statuses]?: number; +}; +type StatusByAppCounts = { + [S in Statuses]?: { + [J in BaseJobTypes]?: AppCounts; }; -} +}; export type RangeStats = JobTypes & { _all: number; @@ -91,5 +98,12 @@ export type RangeStats = JobTypes & { statuses: StatusByAppCounts; }; +export type ReportingUsageType = RangeStats & { + available: boolean; + browser_type: string; + enabled: boolean; + last7Days: RangeStats; +}; + export type ExportType = 'csv' | 'printable_pdf' | 'PNG'; export type FeatureAvailabilityMap = { [F in ExportType]: boolean }; diff --git a/x-pack/test/reporting/services/reporting_api.js b/x-pack/test/reporting/services/reporting_api.js index e81f02c8a8ae4..82fe6aea08ac3 100644 --- a/x-pack/test/reporting/services/reporting_api.js +++ b/x-pack/test/reporting/services/reporting_api.js @@ -97,7 +97,6 @@ export function ReportingAPIProvider({ getService }) { }, expectRecentPdfAppStats(stats, app, count) { - expect(stats.reporting.last_day.printable_pdf.app[app]).to.be(count); expect(stats.reporting.last_7_days.printable_pdf.app[app]).to.be(count); }, @@ -106,7 +105,6 @@ export function ReportingAPIProvider({ getService }) { }, expectRecentPdfLayoutStats(stats, layout, count) { - expect(stats.reporting.last_day.printable_pdf.layout[layout]).to.be(count); expect(stats.reporting.last_7_days.printable_pdf.layout[layout]).to.be(count); }, @@ -115,7 +113,6 @@ export function ReportingAPIProvider({ getService }) { }, expectRecentJobTypeTotalStats(stats, jobType, count) { - expect(stats.reporting.last_day[jobType].total).to.be(count); expect(stats.reporting.last_7_days[jobType].total).to.be(count); },