[Dashboard] Adds dashboard collector for byValue panels (#85867)
* Adds dashboard collector for byValue panels * Fix telemetry schema * Remove unused import
This commit is contained in:
parent
74ae348f2d
commit
6672e26fe5
|
@ -30,9 +30,12 @@ import { capabilitiesProvider } from './capabilities_provider';
|
||||||
|
|
||||||
import { DashboardPluginSetup, DashboardPluginStart } from './types';
|
import { DashboardPluginSetup, DashboardPluginStart } from './types';
|
||||||
import { EmbeddableSetup } from '../../embeddable/server';
|
import { EmbeddableSetup } from '../../embeddable/server';
|
||||||
|
import { UsageCollectionSetup } from '../../usage_collection/server';
|
||||||
|
import { registerDashboardUsageCollector } from './usage/register_collector';
|
||||||
|
|
||||||
interface SetupDeps {
|
interface SetupDeps {
|
||||||
embeddable: EmbeddableSetup;
|
embeddable: EmbeddableSetup;
|
||||||
|
usageCollection: UsageCollectionSetup;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DashboardPlugin
|
export class DashboardPlugin
|
||||||
|
@ -55,6 +58,7 @@ export class DashboardPlugin
|
||||||
);
|
);
|
||||||
core.capabilities.registerProvider(capabilitiesProvider);
|
core.capabilities.registerProvider(capabilitiesProvider);
|
||||||
|
|
||||||
|
registerDashboardUsageCollector(plugins.usageCollection, plugins.embeddable);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
177
src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts
Normal file
177
src/plugins/dashboard/server/usage/dashboard_telemetry.test.ts
Normal file
|
@ -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
src/plugins/dashboard/server/usage/dashboard_telemetry.ts
Normal file
151
src/plugins/dashboard/server/usage/dashboard_telemetry.ts
Normal file
|
@ -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;
|
||||||
|
}
|
52
src/plugins/dashboard/server/usage/register_collector.ts
Normal file
52
src/plugins/dashboard/server/usage/register_collector.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* 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 { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
|
||||||
|
import { EmbeddablePersistableStateService } from 'src/plugins/embeddable/common';
|
||||||
|
|
||||||
|
import { collectDashboardTelemetry, DashboardCollectorData } from './dashboard_telemetry';
|
||||||
|
|
||||||
|
export function registerDashboardUsageCollector(
|
||||||
|
usageCollection: UsageCollectionSetup,
|
||||||
|
embeddableService: EmbeddablePersistableStateService
|
||||||
|
) {
|
||||||
|
const dashboardCollector = usageCollection.makeUsageCollector<DashboardCollectorData>({
|
||||||
|
type: 'dashboard',
|
||||||
|
isReady: () => true,
|
||||||
|
fetch: async ({ soClient }) => {
|
||||||
|
return await collectDashboardTelemetry(soClient, embeddableService);
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
panels: { type: 'long' },
|
||||||
|
panelsByValue: { type: 'long' },
|
||||||
|
lensByValue: {
|
||||||
|
DYNAMIC_KEY: {
|
||||||
|
type: 'long',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
visualizationByValue: {
|
||||||
|
DYNAMIC_KEY: {
|
||||||
|
type: 'long',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
usageCollection.registerCollector(dashboardCollector);
|
||||||
|
}
|
|
@ -1,5 +1,29 @@
|
||||||
{
|
{
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"dashboard": {
|
||||||
|
"properties": {
|
||||||
|
"panels": {
|
||||||
|
"type": "long"
|
||||||
|
},
|
||||||
|
"panelsByValue": {
|
||||||
|
"type": "long"
|
||||||
|
},
|
||||||
|
"lensByValue": {
|
||||||
|
"properties": {
|
||||||
|
"DYNAMIC_KEY": {
|
||||||
|
"type": "long"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualizationByValue": {
|
||||||
|
"properties": {
|
||||||
|
"DYNAMIC_KEY": {
|
||||||
|
"type": "long"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"kql": {
|
"kql": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"optInCount": {
|
"optInCount": {
|
||||||
|
|
Loading…
Reference in a new issue