[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 { 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
|
||||
|
@ -55,6 +58,7 @@ export class DashboardPlugin
|
|||
);
|
||||
core.capabilities.registerProvider(capabilitiesProvider);
|
||||
|
||||
registerDashboardUsageCollector(plugins.usageCollection, plugins.embeddable);
|
||||
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": {
|
||||
"dashboard": {
|
||||
"properties": {
|
||||
"panels": {
|
||||
"type": "long"
|
||||
},
|
||||
"panelsByValue": {
|
||||
"type": "long"
|
||||
},
|
||||
"lensByValue": {
|
||||
"properties": {
|
||||
"DYNAMIC_KEY": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"visualizationByValue": {
|
||||
"properties": {
|
||||
"DYNAMIC_KEY": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kql": {
|
||||
"properties": {
|
||||
"optInCount": {
|
||||
|
|
Loading…
Reference in a new issue