diff --git a/x-pack/plugins/oss_telemetry/server/lib/get_past_days.test.ts b/x-pack/plugins/oss_telemetry/server/lib/get_past_days.test.ts new file mode 100644 index 00000000000000..28909779343a54 --- /dev/null +++ b/x-pack/plugins/oss_telemetry/server/lib/get_past_days.test.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; +import { getPastDays } from './get_past_days'; + +describe('getPastDays', () => { + test('Returns 2 days that have passed from the current date', () => { + const pastDate = moment().subtract(2, 'days').startOf('day').toString(); + + expect(getPastDays(pastDate)).toEqual(2); + }); + + test('Returns 30 days that have passed from the current date', () => { + const pastDate = moment().subtract(30, 'days').startOf('day').toString(); + + expect(getPastDays(pastDate)).toEqual(30); + }); +}); diff --git a/x-pack/plugins/oss_telemetry/server/lib/get_past_days.ts b/x-pack/plugins/oss_telemetry/server/lib/get_past_days.ts new file mode 100644 index 00000000000000..4f25ef147ad43d --- /dev/null +++ b/x-pack/plugins/oss_telemetry/server/lib/get_past_days.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export const getPastDays = (dateString: string): number => { + const date = new Date(dateString); + const today = new Date(); + const diff = Math.abs(date.getTime() - today.getTime()); + return Math.trunc(diff / (1000 * 60 * 60 * 24)); +}; diff --git a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts index 6a47983a6f4d94..c064f39f4bc6a6 100644 --- a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts +++ b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts @@ -13,6 +13,7 @@ import { import { visualizationsTaskRunner } from './task_runner'; import { TaskInstance } from '../../../../../task_manager/server'; import { getNextMidnight } from '../../get_next_midnight'; +import moment from 'moment'; describe('visualizationsTaskRunner', () => { let mockTaskInstance: TaskInstance; @@ -55,6 +56,9 @@ describe('visualizationsTaskRunner', () => { spaces_max: 1, spaces_min: 1, total: 1, + saved_7_days_total: 1, + saved_30_days_total: 1, + saved_90_days_total: 1, }, }, }, @@ -69,6 +73,7 @@ describe('visualizationsTaskRunner', () => { _source: { type: 'visualization', visualization: { visState: '{"type": "cave_painting"}' }, + updated_at: moment().subtract(7, 'days').startOf('day').toString(), }, }, { @@ -76,11 +81,16 @@ describe('visualizationsTaskRunner', () => { _source: { type: 'visualization', visualization: { visState: '{"type": "printing_press"}' }, + updated_at: moment().subtract(20, 'days').startOf('day').toString(), }, }, { _id: 'meat:visualization:coolviz-789', - _source: { type: 'visualization', visualization: { visState: '{"type": "floppy_disk"}' } }, + _source: { + type: 'visualization', + visualization: { visState: '{"type": "floppy_disk"}' }, + updated_at: moment().subtract(2, 'months').startOf('day').toString(), + }, }, // meat space { @@ -88,38 +98,99 @@ describe('visualizationsTaskRunner', () => { _source: { type: 'visualization', visualization: { visState: '{"type": "cave_painting"}' }, + updated_at: moment().subtract(89, 'days').startOf('day').toString(), }, }, { _id: 'meat:visualization:coolviz-789', - _source: { type: 'visualization', visualization: { visState: '{"type": "cuneiform"}' } }, + _source: { + type: 'visualization', + visualization: { visState: '{"type": "cuneiform"}' }, + updated_at: moment().subtract(5, 'months').startOf('day').toString(), + }, }, { _id: 'meat:visualization:coolviz-789', - _source: { type: 'visualization', visualization: { visState: '{"type": "cuneiform"}' } }, + _source: { + type: 'visualization', + visualization: { visState: '{"type": "cuneiform"}' }, + updated_at: moment().subtract(2, 'days').startOf('day').toString(), + }, }, { _id: 'meat:visualization:coolviz-789', - _source: { type: 'visualization', visualization: { visState: '{"type": "floppy_disk"}' } }, + _source: { + type: 'visualization', + visualization: { visState: '{"type": "floppy_disk"}' }, + updated_at: moment().subtract(7, 'days').startOf('day').toString(), + }, }, // cyber space { _id: 'cyber:visualization:coolviz-789', - _source: { type: 'visualization', visualization: { visState: '{"type": "floppy_disk"}' } }, + _source: { + type: 'visualization', + visualization: { visState: '{"type": "floppy_disk"}' }, + updated_at: moment().subtract(7, 'months').startOf('day').toString(), + }, }, { _id: 'cyber:visualization:coolviz-789', - _source: { type: 'visualization', visualization: { visState: '{"type": "floppy_disk"}' } }, + _source: { + type: 'visualization', + visualization: { visState: '{"type": "floppy_disk"}' }, + updated_at: moment().subtract(3, 'days').startOf('day').toString(), + }, }, { _id: 'cyber:visualization:coolviz-123', _source: { type: 'visualization', visualization: { visState: '{"type": "cave_painting"}' }, + updated_at: moment().subtract(15, 'days').startOf('day').toString(), }, }, ]); + const expectedStats = { + cave_painting: { + total: 3, + spaces_min: 1, + spaces_max: 1, + spaces_avg: 1, + saved_7_days_total: 1, + saved_30_days_total: 2, + saved_90_days_total: 3, + }, + printing_press: { + total: 1, + spaces_min: 1, + spaces_max: 1, + spaces_avg: 1, + saved_7_days_total: 0, + saved_30_days_total: 1, + saved_90_days_total: 1, + }, + cuneiform: { + total: 2, + spaces_min: 2, + spaces_max: 2, + spaces_avg: 2, + saved_7_days_total: 1, + saved_30_days_total: 1, + saved_90_days_total: 1, + }, + floppy_disk: { + total: 4, + spaces_min: 2, + spaces_max: 2, + spaces_avg: 2, + saved_7_days_total: 2, + saved_30_days_total: 2, + saved_90_days_total: 3, + }, + }; + const runner = visualizationsTaskRunner( mockTaskInstance, getMockConfig(), @@ -131,13 +202,10 @@ describe('visualizationsTaskRunner', () => { error: undefined, state: { runs: 1, - stats: { - cave_painting: { total: 3, spaces_min: 1, spaces_max: 1, spaces_avg: 1 }, - printing_press: { total: 1, spaces_min: 1, spaces_max: 1, spaces_avg: 1 }, - cuneiform: { total: 2, spaces_min: 2, spaces_max: 2, spaces_avg: 2 }, - floppy_disk: { total: 4, spaces_min: 2, spaces_max: 2, spaces_avg: 2 }, - }, + stats: expectedStats, }, }); + + expect(result.state.stats).toMatchObject(expectedStats); }); }); diff --git a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts index b15ead36a75f69..5f7f70ee5d62a6 100644 --- a/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts +++ b/x-pack/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts @@ -10,12 +10,14 @@ import { first } from 'rxjs/operators'; import { APICaller, IClusterClient } from 'src/core/server'; import { getNextMidnight } from '../../get_next_midnight'; +import { getPastDays } from '../../get_past_days'; import { TaskInstance } from '../../../../../task_manager/server'; import { ESSearchHit } from '../../../../../apm/typings/elasticsearch'; interface VisSummary { type: string; space: string; + past_days: number; } /* @@ -26,7 +28,11 @@ async function getStats(callCluster: APICaller, index: string) { size: 10000, // elasticsearch index.max_result_window default value index, ignoreUnavailable: true, - filterPath: ['hits.hits._id', 'hits.hits._source.visualization'], + filterPath: [ + 'hits.hits._id', + 'hits.hits._source.visualization', + 'hits.hits._source.updated_at', + ], body: { query: { bool: { filter: { term: { type: 'visualization' } } }, @@ -43,13 +49,14 @@ async function getStats(callCluster: APICaller, index: string) { const visSummaries: VisSummary[] = esResponse.hits.hits.map( (hit: ESSearchHit<{ visState: string }>) => { const spacePhrases: string[] = hit._id.split(':'); + const lastUpdated: string = _.get(hit, '_source.updated_at'); const space = spacePhrases.length === 3 ? spacePhrases[0] : 'default'; // if in a custom space, the format of a saved object ID is space:type:id const visualization = _.get(hit, '_source.visualization', { visState: '{}' }); const visState: { type?: string } = JSON.parse(visualization.visState); - return { type: visState.type || '_na_', space, + past_days: getPastDays(lastUpdated), }; } ); @@ -68,6 +75,9 @@ async function getStats(callCluster: APICaller, index: string) { spaces_min: _.min(spaceCounts), spaces_max: _.max(spaceCounts), spaces_avg: total / spaceCounts.length, + saved_7_days_total: curr.filter((c) => c.past_days <= 7).length, + saved_30_days_total: curr.filter((c) => c.past_days <= 30).length, + saved_90_days_total: curr.filter((c) => c.past_days <= 90).length, }; }); } diff --git a/x-pack/plugins/oss_telemetry/server/test_utils/index.ts b/x-pack/plugins/oss_telemetry/server/test_utils/index.ts index fd0906ae620f10..5dd7338d278807 100644 --- a/x-pack/plugins/oss_telemetry/server/test_utils/index.ts +++ b/x-pack/plugins/oss_telemetry/server/test_utils/index.ts @@ -6,6 +6,7 @@ import { APICaller } from 'src/core/server'; import { of } from 'rxjs'; +import moment from 'moment'; import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; import { ConcreteTaskInstance, @@ -37,6 +38,7 @@ const defaultMockSavedObjects = [ _source: { type: 'visualization', visualization: { visState: '{"type": "shell_beads"}' }, + updated_at: moment().subtract(7, 'days').startOf('day').toString(), }, }, ];