Skip to content

Commit

Permalink
[Dashboard] Adds dashboard collector for byValue panels (#85867)
Browse files Browse the repository at this point in the history
* Adds dashboard collector for byValue panels

* Fix telemetry schema

* Remove unused import
  • Loading branch information
Corey Robertson authored Dec 15, 2020
1 parent 74ae348 commit 6672e26
Show file tree
Hide file tree
Showing 5 changed files with 408 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/plugins/dashboard/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ import { capabilitiesProvider } from './capabilities_provider';

import { DashboardPluginSetup, DashboardPluginStart } from './types';
import { EmbeddableSetup } from '../../embeddable/server';
import { UsageCollectionSetup } from '../../usage_collection/server';
import { registerDashboardUsageCollector } from './usage/register_collector';

interface SetupDeps {
embeddable: EmbeddableSetup;
usageCollection: UsageCollectionSetup;
}

export class DashboardPlugin
Expand All @@ -55,6 +58,7 @@ export class DashboardPlugin
);
core.capabilities.registerProvider(capabilitiesProvider);

registerDashboardUsageCollector(plugins.usageCollection, plugins.embeddable);
return {};
}

Expand Down
177 changes: 177 additions & 0 deletions src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { SavedDashboardPanel730ToLatest } from '../../common';
import {
collectDashboardInfo,
getEmptyTelemetryData,
collectByValueVisualizationInfo,
collectByValueLensInfo,
} from './dashboard_telemetry';

const visualizationType1ByValue = ({
embeddableConfig: {
savedVis: {
type: 'type1',
},
},
type: 'visualization',
} as unknown) as SavedDashboardPanel730ToLatest;

const visualizationType2ByValue = ({
embeddableConfig: {
savedVis: {
type: 'type2',
},
},
type: 'visualization',
} as unknown) as SavedDashboardPanel730ToLatest;
const visualizationType2ByReference = {
...visualizationType2ByValue,
id: '11111',
};

const lensTypeAByValue = ({
type: 'lens',
embeddableConfig: {
attributes: {
visualizationType: 'a',
},
},
} as unknown) as SavedDashboardPanel730ToLatest;
const lensTypeAByReference = {
...lensTypeAByValue,
id: '22222',
};

const lensXYSeriesA = ({
type: 'lens',
embeddableConfig: {
attributes: {
visualizationType: 'lnsXY',
state: {
visualization: {
preferredSeriesType: 'seriesA',
},
},
},
},
} as unknown) as SavedDashboardPanel730ToLatest;

const lensXYSeriesB = ({
type: 'lens',
embeddableConfig: {
attributes: {
visualizationType: 'lnsXY',
state: {
visualization: {
preferredSeriesType: 'seriesB',
},
},
},
},
} as unknown) as SavedDashboardPanel730ToLatest;

describe('dashboard telemetry', () => {
it('collects information about dashboard panels', () => {
const panels = [
visualizationType1ByValue,
visualizationType2ByValue,
visualizationType2ByReference,
];
const collectorData = getEmptyTelemetryData();

collectDashboardInfo(panels, collectorData);

expect(collectorData.panels).toBe(panels.length);
expect(collectorData.panelsByValue).toBe(2);
});

describe('visualizations', () => {
it('collects information about by value visualizations', () => {
const panels = [
visualizationType1ByValue,
visualizationType1ByValue,
visualizationType2ByValue,
visualizationType2ByReference,
];

const collectorData = getEmptyTelemetryData();

collectByValueVisualizationInfo(panels, collectorData);

expect(collectorData.visualizationByValue.type1).toBe(2);
expect(collectorData.visualizationByValue.type2).toBe(1);
});

it('handles misshapen visualization panels without errors', () => {
const badVisualizationPanel = ({
embeddableConfig: {},
type: 'visualization',
} as unknown) as SavedDashboardPanel730ToLatest;

const panels = [badVisualizationPanel, visualizationType1ByValue];

const collectorData = getEmptyTelemetryData();

collectByValueVisualizationInfo(panels, collectorData);

expect(Object.keys(collectorData.visualizationByValue)).toHaveLength(1);
});
});

describe('lens', () => {
it('collects information about by value lens', () => {
const panels = [
lensTypeAByValue,
lensTypeAByValue,
lensTypeAByValue,
lensTypeAByReference,
lensXYSeriesA,
lensXYSeriesA,
lensXYSeriesB,
];

const collectorData = getEmptyTelemetryData();

collectByValueLensInfo(panels, collectorData);

expect(collectorData.lensByValue.a).toBe(3);
expect(collectorData.lensByValue.seriesA).toBe(2);
expect(collectorData.lensByValue.seriesB).toBe(1);
});

it('handles misshapen lens panels', () => {
const badPanel = ({
type: 'lens',
embeddableConfig: {
oops: 'no visualization type',
},
} as unknown) as SavedDashboardPanel730ToLatest;

const panels = [badPanel, lensTypeAByValue];

const collectorData = getEmptyTelemetryData();

collectByValueLensInfo(panels, collectorData);

expect(collectorData.lensByValue.a).toBe(1);
});
});
});
151 changes: 151 additions & 0 deletions src/plugins/dashboard/server/usage/dashboard_telemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { ISavedObjectsRepository, SavedObjectAttributes } from 'src/core/server';
import { EmbeddablePersistableStateService } from 'src/plugins/embeddable/common';
import { SavedDashboardPanel730ToLatest } from '../../common';
import { injectReferences } from '../../common/saved_dashboard_references';

interface VisualizationPanel extends SavedDashboardPanel730ToLatest {
embeddableConfig: {
savedVis?: {
type?: string;
};
};
}

interface LensPanel extends SavedDashboardPanel730ToLatest {
embeddableConfig: {
attributes?: {
visualizationType?: string;
state?: {
visualization?: {
preferredSeriesType?: string;
};
};
};
};
}

export interface DashboardCollectorData {
panels: number;
panelsByValue: number;
lensByValue: {
[key: string]: number;
};
visualizationByValue: {
[key: string]: number;
};
}

export const getEmptyTelemetryData = (): DashboardCollectorData => ({
panels: 0,
panelsByValue: 0,
lensByValue: {},
visualizationByValue: {},
});

type DashboardCollectorFunction = (
panels: SavedDashboardPanel730ToLatest[],
collectorData: DashboardCollectorData
) => void;

export const collectDashboardInfo: DashboardCollectorFunction = (panels, collectorData) => {
collectorData.panels += panels.length;
collectorData.panelsByValue += panels.filter((panel) => panel.id === undefined).length;
};

export const collectByValueVisualizationInfo: DashboardCollectorFunction = (
panels,
collectorData
) => {
const byValueVisualizations = panels.filter(
(panel) => panel.id === undefined && panel.type === 'visualization'
);

for (const panel of byValueVisualizations) {
const visPanel = panel as VisualizationPanel;

if (
visPanel.embeddableConfig.savedVis !== undefined &&
visPanel.embeddableConfig.savedVis.type !== undefined
) {
const type = visPanel.embeddableConfig.savedVis.type;

if (!collectorData.visualizationByValue[type]) {
collectorData.visualizationByValue[type] = 0;
}

collectorData.visualizationByValue[type] = collectorData.visualizationByValue[type] + 1;
}
}
};

export const collectByValueLensInfo: DashboardCollectorFunction = (panels, collectorData) => {
const byValueLens = panels.filter((panel) => panel.id === undefined && panel.type === 'lens');

for (const panel of byValueLens) {
const lensPanel = panel as LensPanel;

if (lensPanel.embeddableConfig.attributes?.visualizationType !== undefined) {
let type = lensPanel.embeddableConfig.attributes.visualizationType;

if (type === 'lnsXY') {
type =
lensPanel.embeddableConfig.attributes.state?.visualization?.preferredSeriesType || type;
}

if (!collectorData.lensByValue[type]) {
collectorData.lensByValue[type] = 0;
}

collectorData.lensByValue[type] = collectorData.lensByValue[type] + 1;
}
}
};

export const collectForPanels: DashboardCollectorFunction = (panels, collectorData) => {
collectDashboardInfo(panels, collectorData);
collectByValueVisualizationInfo(panels, collectorData);
collectByValueLensInfo(panels, collectorData);
};

export async function collectDashboardTelemetry(
savedObjectClient: Pick<ISavedObjectsRepository, 'find'>,
embeddableService: EmbeddablePersistableStateService
) {
const collectorData = getEmptyTelemetryData();
const dashboards = await savedObjectClient.find<SavedObjectAttributes>({
type: 'dashboard',
});

for (const dashboard of dashboards.saved_objects) {
const attributes = injectReferences(dashboard, {
embeddablePersistableStateService: embeddableService,
});

const panels = (JSON.parse(
attributes.panelsJSON as string
) as unknown) as SavedDashboardPanel730ToLatest[];

collectForPanels(panels, collectorData);
}

return collectorData;
}
Loading

0 comments on commit 6672e26

Please sign in to comment.