Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
mikecote committed Jul 13, 2023
1 parent 7af4c38 commit 882d627
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 14 deletions.
7 changes: 5 additions & 2 deletions src/plugins/dashboard/server/usage/dashboard_telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import { EmbeddablePersistableStateService } from '@kbn/embeddable-plugin/common
import { type ControlGroupTelemetry, CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/common';

import { DashboardAttributes, SavedDashboardPanel } from '../../common/content_management';
import { TASK_ID, DashboardTelemetryTaskState } from './dashboard_telemetry_collection_task';
import { TASK_ID } from './dashboard_telemetry_collection_task';
import { type LatestTaskStateSchema } from './task_state';

// TODO: Merge with LatestTaskStateSchema
export interface DashboardCollectorData {
panels: {
total: number;
Expand Down Expand Up @@ -127,7 +130,7 @@ export async function collectDashboardTelemetry(taskManager: TaskManagerStartCon
const latestTaskState = await getLatestTaskState(taskManager);

if (latestTaskState !== null) {
const state = latestTaskState[0].state as DashboardTelemetryTaskState;
const state = latestTaskState[0].state as LatestTaskStateSchema;
return state.telemetry;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import {
} from '@kbn/task-manager-plugin/server';
import { EmbeddableSetup } from '@kbn/embeddable-plugin/server';
import { CoreSetup, Logger, SavedObjectReference } from '@kbn/core/server';
import { stateSchemaByVersion, emptyState, type LatestTaskStateSchema } from './task_state';

import {
controlsCollectorFactory,
collectPanelsByType,
getEmptyDashboardData,
DashboardCollectorData,
} from './dashboard_telemetry';
import { injectReferences } from '../../common';
import { DashboardAttributesAndReferences } from '../../common/types';
Expand All @@ -32,11 +32,6 @@ import { DashboardAttributes, SavedDashboardPanel } from '../../common/content_m
const TELEMETRY_TASK_TYPE = 'dashboard_telemetry';
export const TASK_ID = `Dashboard-${TELEMETRY_TASK_TYPE}`;

export interface DashboardTelemetryTaskState {
runs: number;
telemetry: DashboardCollectorData;
}

export function initializeDashboardTelemetryTask(
logger: Logger,
core: CoreSetup,
Expand All @@ -60,6 +55,7 @@ function registerDashboardTelemetryTask(
[TELEMETRY_TASK_TYPE]: {
title: 'Dashboard telemetry collection task',
timeout: '2m',
stateSchemaByVersion,
createTaskRunner: dashboardTaskRunner(logger, core, embeddable),
},
});
Expand All @@ -70,7 +66,7 @@ async function scheduleTasks(logger: Logger, taskManager: TaskManagerStartContra
return await taskManager.ensureScheduled({
id: TASK_ID,
taskType: TELEMETRY_TASK_TYPE,
state: { byDate: {}, suggestionsByDate: {}, saved: {}, runs: 0 },
state: emptyState,
params: {},
});
} catch (e) {
Expand All @@ -80,7 +76,7 @@ async function scheduleTasks(logger: Logger, taskManager: TaskManagerStartContra

export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable: EmbeddableSetup) {
return ({ taskInstance }: RunContext) => {
const { state } = taskInstance;
const state = taskInstance.state as LatestTaskStateSchema;

const getEsClient = async () => {
const [coreStart] = await core.getStartServices();
Expand Down Expand Up @@ -172,11 +168,12 @@ export function dashboardTaskRunner(logger: Logger, core: CoreSetup, embeddable:
);
}

const updatedState: LatestTaskStateSchema = {
runs: state.runs + 1,
telemetry: dashboardData,
};
return {
state: {
runs: (state.runs || 0) + 1,
telemetry: dashboardData,
},
state: updatedState,
runAt: getNextMidnight(),
};
} catch (e) {
Expand Down
66 changes: 66 additions & 0 deletions src/plugins/dashboard/server/usage/task_state.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { cloneDeep } from 'lodash';
import { stateSchemaByVersion } from './task_state';

describe('telemetry task state', () => {
describe('v1', () => {
const v1 = stateSchemaByVersion[1];
it('should work on empty object when running the up migration', () => {
const result = v1.up({});
expect(result).toMatchInlineSnapshot(`
Object {
"runs": 0,
"telemetry": undefined,
}
`);
});

it(`shouldn't overwrite properties when running the up migration`, () => {
const state = {
runs: 1,
telemetry: {
panels: {
total: 2,
by_reference: 3,
by_value: 4,
by_type: {
foo: 5,
},
},
controls: {
total: 6,
chaining_system: { foo: 7 },
label_position: { foo: 8 },
ignore_settings: { foo: 9 },
by_type: { foo: 10 },
},
},
};
const result = v1.up(cloneDeep(state));
expect(result).toEqual(state);
});

it(`should migrate the old default state that didn't match the schema`, () => {
const result = v1.up({ byDate: {}, suggestionsByDate: {}, saved: {}, runs: 0 });
expect(result).toMatchInlineSnapshot(`
Object {
"runs": 0,
"telemetry": undefined,
}
`);
});

it('should drop unknown properties when running the up migration', () => {
const state = { foo: true };
const result = v1.up(state);
expect(result).not.toHaveProperty('foo');
});
});
});
85 changes: 85 additions & 0 deletions src/plugins/dashboard/server/usage/task_state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { schema, type TypeOf } from '@kbn/config-schema';

/**
* WARNING: Do not modify the existing versioned schema(s) below, instead define a new version (ex: 2, 3, 4).
* This is required to support zero-downtime upgrades and rollbacks. See https://github.com/elastic/kibana/issues/155764.
*
* As you add a new schema version, don't forget to change latestTaskStateSchema variable to reference the latest schema.
* For example, changing stateSchemaByVersion[1].schema to stateSchemaByVersion[2].schema.
*/
export const stateSchemaByVersion = {
1: {
// A task that was created < 8.10 will go through this "up" migration
// to ensure it matches the v1 schema.
up: (state: Record<string, any>) => ({
runs: typeof state.runs === 'number' ? state.runs : 0,
telemetry: state.telemetry
? {
panels: {
total: state.telemetry.panels?.total || 0,
by_reference: state.telemetry.panels?.by_reference || 0,
by_value: state.telemetry.panels?.by_value || 0,
by_type: state.telemetry.panels?.by_type || {},
},
controls: {
total: state.telemetry.controls?.total || 0,
chaining_system: state.telemetry.controls?.chaining_system || {},
label_position: state.telemetry.controls?.label_position || {},
ignore_settings: state.telemetry.controls?.ignore_settings || {},
by_type: state.telemetry.controls?.by_type || {},
},
}
: undefined,
}),
schema: schema.object({
runs: schema.number(),
telemetry: schema.maybe(
schema.object({
panels: schema.object({
total: schema.number(),
by_reference: schema.number(),
by_value: schema.number(),
by_type: schema.recordOf(
schema.string(),
schema.object({
total: schema.number(),
by_reference: schema.number(),
by_value: schema.number(),
details: schema.recordOf(schema.string(), schema.number()),
})
),
}),
controls: schema.object({
total: schema.number(),
chaining_system: schema.recordOf(schema.string(), schema.number()),
label_position: schema.recordOf(schema.string(), schema.number()),
ignore_settings: schema.recordOf(schema.string(), schema.number()),
by_type: schema.recordOf(
schema.string(),
schema.object({
total: schema.number(),
details: schema.recordOf(schema.string(), schema.number()),
})
),
}),
})
),
}),
},
};

const latestTaskStateSchema = stateSchemaByVersion[1].schema;
export type LatestTaskStateSchema = TypeOf<typeof latestTaskStateSchema>;

export const emptyState: LatestTaskStateSchema = {
runs: 0,
telemetry: undefined,
};

0 comments on commit 882d627

Please sign in to comment.