mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Step 1 ] VisEditors Telemetry enhancements (remove legacy agg-based telemetries) (#135634)
* [Step 1 ] VisEditors Telemetry enhancements (remove legacy agg-based telemetries) * remove legacy trackUiMetric telemetries Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e33f4b0f80
commit
ded1fcb279
59 changed files with 10 additions and 1886 deletions
|
@ -10112,122 +10112,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"vis_type_table": {
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
"total_split": {
|
||||
"type": "long"
|
||||
},
|
||||
"split_columns": {
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"split_rows": {
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"vis_type_timeseries": {
|
||||
"properties": {
|
||||
"timeseries_use_last_value_mode_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "Number of TSVB visualizations using \"last value\" as a time range"
|
||||
}
|
||||
},
|
||||
"timeseries_use_es_indices_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "Number of TSVB visualizations using elasticsearch indices"
|
||||
}
|
||||
},
|
||||
"timeseries_table_use_aggregate_function": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "Number of TSVB table visualizations using aggregate function"
|
||||
}
|
||||
},
|
||||
"timeseries_types": {
|
||||
"properties": {
|
||||
"table": {
|
||||
"type": "long"
|
||||
},
|
||||
"gauge": {
|
||||
"type": "long"
|
||||
},
|
||||
"markdown": {
|
||||
"type": "long"
|
||||
},
|
||||
"top_n": {
|
||||
"type": "long"
|
||||
},
|
||||
"timeseries": {
|
||||
"type": "long"
|
||||
},
|
||||
"metric": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"vis_type_vega": {
|
||||
"properties": {
|
||||
"vega_lib_specs_total": {
|
||||
"type": "long"
|
||||
},
|
||||
"vega_lite_lib_specs_total": {
|
||||
"type": "long"
|
||||
},
|
||||
"vega_use_map_total": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"visualization_types": {
|
||||
"properties": {
|
||||
"DYNAMIC_KEY": {
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
"spaces_min": {
|
||||
"type": "long"
|
||||
},
|
||||
"spaces_max": {
|
||||
"type": "long"
|
||||
},
|
||||
"spaces_avg": {
|
||||
"type": "long"
|
||||
},
|
||||
"saved_7_days_total": {
|
||||
"type": "long"
|
||||
},
|
||||
"saved_30_days_total": {
|
||||
"type": "long"
|
||||
},
|
||||
"saved_90_days_total": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,13 +13,12 @@ import { HeatmapVisParams, HeatmapTypeProps } from '../../types';
|
|||
const HeatmapOptionsLazy = lazy(() => import('./heatmap'));
|
||||
|
||||
export const getHeatmapOptions =
|
||||
({ showElasticChartsOptions, palettes, trackUiMetric }: HeatmapTypeProps) =>
|
||||
({ showElasticChartsOptions, palettes }: HeatmapTypeProps) =>
|
||||
(props: VisEditorOptionsProps<HeatmapVisParams>) =>
|
||||
(
|
||||
<HeatmapOptionsLazy
|
||||
{...props}
|
||||
palettes={palettes}
|
||||
showElasticChartsOptions={showElasticChartsOptions}
|
||||
trackUiMetric={trackUiMetric}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -34,16 +34,10 @@ export class VisTypeHeatmapPlugin {
|
|||
{ visualizations, charts, usageCollection }: VisTypeHeatmapSetupDependencies
|
||||
) {
|
||||
if (!core.uiSettings.get(LEGACY_HEATMAP_CHARTS_LIBRARY)) {
|
||||
const trackUiMetric = usageCollection?.reportUiCounter.bind(
|
||||
usageCollection,
|
||||
'vis_type_heatmap'
|
||||
);
|
||||
|
||||
visualizations.createBaseVisualization(
|
||||
heatmapVisType({
|
||||
showElasticChartsOptions: true,
|
||||
palettes: charts.palettes,
|
||||
trackUiMetric,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { UiCounterMetricType } from '@kbn/analytics';
|
||||
import type { Position } from '@elastic/charts';
|
||||
import type { ChartsPluginSetup, Style, Labels, ColorSchemas } from '@kbn/charts-plugin/public';
|
||||
import { Range } from '@kbn/expressions-plugin/public';
|
||||
|
@ -14,7 +13,6 @@ import { LegendSize } from '@kbn/visualizations-plugin/public';
|
|||
export interface HeatmapTypeProps {
|
||||
showElasticChartsOptions?: boolean;
|
||||
palettes?: ChartsPluginSetup['palettes'];
|
||||
trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void;
|
||||
}
|
||||
|
||||
export interface HeatmapVisParams {
|
||||
|
|
|
@ -20,7 +20,6 @@ import { SplitTooltip } from './split_tooltip';
|
|||
export const getHeatmapVisTypeDefinition = ({
|
||||
showElasticChartsOptions = false,
|
||||
palettes,
|
||||
trackUiMetric,
|
||||
}: HeatmapTypeProps): VisTypeDefinition<HeatmapVisParams> => ({
|
||||
name: 'heatmap',
|
||||
title: i18n.translate('visTypeHeatmap.heatmap.heatmapTitle', { defaultMessage: 'Heat map' }),
|
||||
|
@ -68,7 +67,6 @@ export const getHeatmapVisTypeDefinition = ({
|
|||
optionsTemplate: getHeatmapOptions({
|
||||
showElasticChartsOptions,
|
||||
palettes,
|
||||
trackUiMetric,
|
||||
}),
|
||||
schemas: [
|
||||
{
|
||||
|
|
|
@ -14,13 +14,12 @@ import { PieTypeProps } from '../../types';
|
|||
const PieOptionsLazy = lazy(() => import('./pie'));
|
||||
|
||||
export const getPieOptions =
|
||||
({ showElasticChartsOptions, palettes, trackUiMetric }: PieTypeProps) =>
|
||||
({ showElasticChartsOptions, palettes }: PieTypeProps) =>
|
||||
(props: VisEditorOptionsProps<PartitionVisParams>) =>
|
||||
(
|
||||
<PieOptionsLazy
|
||||
{...props}
|
||||
palettes={palettes}
|
||||
showElasticChartsOptions={showElasticChartsOptions}
|
||||
trackUiMetric={trackUiMetric}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import {
|
||||
EuiPanel,
|
||||
EuiTitle,
|
||||
|
@ -223,9 +222,6 @@ const PieOptions = (props: PieOptionsProps) => {
|
|||
value={stateParams.nestedLegend}
|
||||
disabled={stateParams.legendDisplay === LegendDisplay.HIDE}
|
||||
setValue={(paramName, value) => {
|
||||
if (props.trackUiMetric) {
|
||||
props.trackUiMetric(METRIC_TYPE.CLICK, 'nested_legend_switched');
|
||||
}
|
||||
setValue(paramName, value);
|
||||
}}
|
||||
data-test-subj="visTypePieNestedLegendSwitch"
|
||||
|
@ -253,9 +249,6 @@ const PieOptions = (props: PieOptionsProps) => {
|
|||
activePalette={stateParams.palette}
|
||||
paramName="palette"
|
||||
setPalette={(paramName, value) => {
|
||||
if (props.trackUiMetric) {
|
||||
props.trackUiMetric(METRIC_TYPE.CLICK, 'palette_selected');
|
||||
}
|
||||
setValue(paramName, value);
|
||||
}}
|
||||
/>
|
||||
|
@ -296,9 +289,6 @@ const PieOptions = (props: PieOptionsProps) => {
|
|||
: stateParams.labels.position || LabelPositions.DEFAULT
|
||||
}
|
||||
setValue={(paramName, value) => {
|
||||
if (props.trackUiMetric) {
|
||||
props.trackUiMetric(METRIC_TYPE.CLICK, 'label_position_selected');
|
||||
}
|
||||
setLabels(paramName, value);
|
||||
}}
|
||||
data-test-subj="visTypePieLabelPositionSelect"
|
||||
|
@ -338,9 +328,6 @@ const PieOptions = (props: PieOptionsProps) => {
|
|||
paramName="valuesFormat"
|
||||
value={stateParams.labels.valuesFormat || ValueFormats.PERCENT}
|
||||
setValue={(paramName, value) => {
|
||||
if (props.trackUiMetric) {
|
||||
props.trackUiMetric(METRIC_TYPE.CLICK, 'values_format_selected');
|
||||
}
|
||||
setLabels(paramName, value);
|
||||
}}
|
||||
data-test-subj="visTypePieValueFormatsSelect"
|
||||
|
|
|
@ -43,12 +43,10 @@ export class VisTypePiePlugin {
|
|||
{ visualizations, charts, usageCollection }: VisTypePieSetupDependencies
|
||||
) {
|
||||
if (!core.uiSettings.get(LEGACY_PIE_CHARTS_LIBRARY, false)) {
|
||||
const trackUiMetric = usageCollection?.reportUiCounter.bind(usageCollection, 'vis_type_pie');
|
||||
visualizations.createBaseVisualization(
|
||||
pieVisType({
|
||||
showElasticChartsOptions: true,
|
||||
palettes: charts.palettes,
|
||||
trackUiMetric,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { UiCounterMetricType } from '@kbn/analytics';
|
||||
import { SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import { ChartsPluginSetup } from '@kbn/charts-plugin/public';
|
||||
|
||||
|
@ -28,5 +27,4 @@ export interface Dimensions {
|
|||
export interface PieTypeProps {
|
||||
showElasticChartsOptions?: boolean;
|
||||
palettes?: ChartsPluginSetup['palettes'];
|
||||
trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import { getPieOptions } from '../editor/components';
|
|||
export const getPieVisTypeDefinition = ({
|
||||
showElasticChartsOptions = false,
|
||||
palettes,
|
||||
trackUiMetric,
|
||||
}: PieTypeProps): VisTypeDefinition<PartitionVisParams> => ({
|
||||
name: 'pie',
|
||||
title: i18n.translate('visTypePie.pie.pieTitle', { defaultMessage: 'Pie' }),
|
||||
|
@ -68,7 +67,6 @@ export const getPieVisTypeDefinition = ({
|
|||
optionsTemplate: getPieOptions({
|
||||
showElasticChartsOptions,
|
||||
palettes,
|
||||
trackUiMetric,
|
||||
}),
|
||||
schemas: [
|
||||
{
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
"share",
|
||||
"visDefaultEditor"
|
||||
],
|
||||
"optionalPlugins": ["usageCollection"],
|
||||
"owner": {
|
||||
"name": "Vis Editors",
|
||||
"githubTeam": "kibana-vis-editors"
|
||||
|
|
|
@ -6,21 +6,14 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { CoreSetup, PluginConfigDescriptor } from '@kbn/core/server';
|
||||
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
|
||||
|
||||
import { PluginConfigDescriptor } from '@kbn/core/server';
|
||||
import { configSchema, ConfigSchema } from '../config';
|
||||
import { registerVisTypeTableUsageCollector } from './usage_collector';
|
||||
|
||||
export const config: PluginConfigDescriptor<ConfigSchema> = {
|
||||
schema: configSchema,
|
||||
};
|
||||
|
||||
export const plugin = () => ({
|
||||
setup(core: CoreSetup, plugins: { usageCollection?: UsageCollectionSetup }) {
|
||||
if (plugins.usageCollection) {
|
||||
registerVisTypeTableUsageCollector(plugins.usageCollection);
|
||||
}
|
||||
},
|
||||
setup() {},
|
||||
start() {},
|
||||
});
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* 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 { getStats } from './get_stats';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
|
||||
const mockVisualizations = {
|
||||
saved_objects: [
|
||||
{
|
||||
attributes: {
|
||||
visState:
|
||||
'{"type": "table","aggs": [{ "schema": "metric" }, { "schema": "bucket" }, { "schema": "split", "enabled": true }], "params": { "row": true }}',
|
||||
},
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
visState:
|
||||
'{"type": "table","aggs": [{ "schema": "metric" }, { "schema": "bucket" }, { "schema": "split", "enabled": false }], "params": { "row": true }}',
|
||||
},
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
visState:
|
||||
'{"type": "table","aggs": [{ "schema": "metric" }, { "schema": "split", "enabled": true }], "params": { "row": false }}',
|
||||
},
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
visState: '{"type": "table","aggs": [{ "schema": "metric" }, { "schema": "bucket" }]}',
|
||||
},
|
||||
},
|
||||
{
|
||||
attributes: { visState: '{"type": "histogram"}' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('vis_type_table getStats', () => {
|
||||
const mockSoClient = {
|
||||
createPointInTimeFinder: jest.fn().mockResolvedValue({
|
||||
close: jest.fn(),
|
||||
find: function* asyncGenerator() {
|
||||
yield mockVisualizations;
|
||||
},
|
||||
}),
|
||||
} as unknown as SavedObjectsClientContract;
|
||||
|
||||
test('Returns stats from saved objects for table vis only', async () => {
|
||||
const result = await getStats(mockSoClient);
|
||||
|
||||
expect(mockSoClient.createPointInTimeFinder).toHaveBeenCalledWith({
|
||||
type: 'visualization',
|
||||
perPage: 1000,
|
||||
namespaces: ['*'],
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
total: 4,
|
||||
total_split: 3,
|
||||
split_columns: {
|
||||
total: 1,
|
||||
enabled: 1,
|
||||
},
|
||||
split_rows: {
|
||||
total: 2,
|
||||
enabled: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* 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 type {
|
||||
ISavedObjectsRepository,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsFindResult,
|
||||
} from '@kbn/core/server';
|
||||
import type { SavedVisState } from '@kbn/visualizations-plugin/common';
|
||||
import { VIS_TYPE_TABLE } from '../../common';
|
||||
|
||||
export interface VisTypeTableUsage {
|
||||
/**
|
||||
* Total number of table type visualizations
|
||||
*/
|
||||
total: number;
|
||||
/**
|
||||
* Total number of table visualizations, using "Split table" agg
|
||||
*/
|
||||
total_split: number;
|
||||
/**
|
||||
* Split table by columns stats
|
||||
*/
|
||||
split_columns: {
|
||||
total: number;
|
||||
enabled: number;
|
||||
};
|
||||
/**
|
||||
* Split table by rows stats
|
||||
*/
|
||||
split_rows: {
|
||||
total: number;
|
||||
enabled: number;
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the response data into telemetry payload
|
||||
*/
|
||||
export async function getStats(
|
||||
soClient: SavedObjectsClientContract | ISavedObjectsRepository
|
||||
): Promise<VisTypeTableUsage | undefined> {
|
||||
const finder = await soClient.createPointInTimeFinder({
|
||||
type: 'visualization',
|
||||
perPage: 1000,
|
||||
namespaces: ['*'],
|
||||
});
|
||||
|
||||
const stats: VisTypeTableUsage = {
|
||||
total: 0,
|
||||
total_split: 0,
|
||||
split_columns: {
|
||||
total: 0,
|
||||
enabled: 0,
|
||||
},
|
||||
split_rows: {
|
||||
total: 0,
|
||||
enabled: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const doTelemetry = ({ aggs, params }: SavedVisState) => {
|
||||
stats.total += 1;
|
||||
|
||||
const hasSplitAgg = aggs.find((agg) => agg.schema === 'split');
|
||||
|
||||
if (hasSplitAgg) {
|
||||
stats.total_split += 1;
|
||||
|
||||
const isSplitRow = params.row;
|
||||
const isSplitEnabled = hasSplitAgg.enabled;
|
||||
const container = isSplitRow ? stats.split_rows : stats.split_columns;
|
||||
|
||||
container.total += 1;
|
||||
container.enabled = isSplitEnabled ? container.enabled + 1 : container.enabled;
|
||||
}
|
||||
};
|
||||
|
||||
for await (const response of finder.find()) {
|
||||
(response.saved_objects || []).forEach(({ attributes }: SavedObjectsFindResult<any>) => {
|
||||
if (attributes?.visState) {
|
||||
try {
|
||||
const visState: SavedVisState = JSON.parse(attributes.visState);
|
||||
|
||||
if (visState.type === VIS_TYPE_TABLE) {
|
||||
doTelemetry(visState);
|
||||
}
|
||||
} catch {
|
||||
// nothing to be here, "so" not valid
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
await finder.close();
|
||||
|
||||
return stats;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { registerVisTypeTableUsageCollector } from './register_usage_collector';
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
createUsageCollectionSetupMock,
|
||||
createCollectorFetchContextMock,
|
||||
} from '@kbn/usage-collection-plugin/server/mocks';
|
||||
import { registerVisTypeTableUsageCollector } from './register_usage_collector';
|
||||
import { getStats } from './get_stats';
|
||||
|
||||
jest.mock('./get_stats', () => ({
|
||||
getStats: jest.fn().mockResolvedValue({ somestat: 1 }),
|
||||
}));
|
||||
|
||||
describe('registerVisTypeTableUsageCollector', () => {
|
||||
test('Usage collector configs fit the shape', () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerVisTypeTableUsageCollector(mockCollectorSet);
|
||||
expect(mockCollectorSet.makeUsageCollector).toBeCalledTimes(1);
|
||||
expect(mockCollectorSet.registerCollector).toBeCalledTimes(1);
|
||||
expect(mockCollectorSet.makeUsageCollector).toHaveBeenCalledWith({
|
||||
type: 'vis_type_table',
|
||||
isReady: expect.any(Function),
|
||||
fetch: expect.any(Function),
|
||||
schema: {
|
||||
total: { type: 'long' },
|
||||
total_split: { type: 'long' },
|
||||
split_columns: {
|
||||
total: { type: 'long' },
|
||||
enabled: { type: 'long' },
|
||||
},
|
||||
split_rows: {
|
||||
total: { type: 'long' },
|
||||
enabled: { type: 'long' },
|
||||
},
|
||||
},
|
||||
});
|
||||
const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0];
|
||||
expect(usageCollectorConfig.isReady()).toBe(true);
|
||||
});
|
||||
|
||||
test('Usage collector config.fetch calls getStats', async () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerVisTypeTableUsageCollector(mockCollectorSet);
|
||||
const usageCollector = mockCollectorSet.makeUsageCollector.mock.results[0].value;
|
||||
const mockCollectorFetchContext = createCollectorFetchContextMock();
|
||||
const fetchResult = await usageCollector.fetch(mockCollectorFetchContext);
|
||||
expect(getStats).toBeCalledTimes(1);
|
||||
expect(getStats).toBeCalledWith(mockCollectorFetchContext.soClient);
|
||||
expect(fetchResult).toEqual({ somestat: 1 });
|
||||
});
|
||||
});
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* 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 type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
|
||||
import { getStats, VisTypeTableUsage } from './get_stats';
|
||||
|
||||
export function registerVisTypeTableUsageCollector(collectorSet: UsageCollectionSetup) {
|
||||
const collector = collectorSet.makeUsageCollector<VisTypeTableUsage | undefined>({
|
||||
type: 'vis_type_table',
|
||||
isReady: () => true,
|
||||
schema: {
|
||||
total: { type: 'long' },
|
||||
total_split: { type: 'long' },
|
||||
split_columns: {
|
||||
total: { type: 'long' },
|
||||
enabled: { type: 'long' },
|
||||
},
|
||||
split_rows: {
|
||||
total: { type: 'long' },
|
||||
enabled: { type: 'long' },
|
||||
},
|
||||
},
|
||||
fetch: ({ soClient }) => getStats(soClient),
|
||||
});
|
||||
collectorSet.registerCollector(collector);
|
||||
}
|
|
@ -17,7 +17,6 @@
|
|||
{ "path": "../../data/tsconfig.json" },
|
||||
{ "path": "../../visualizations/tsconfig.json" },
|
||||
{ "path": "../../share/tsconfig.json" },
|
||||
{ "path": "../../usage_collection/tsconfig.json" },
|
||||
{ "path": "../../expressions/tsconfig.json" },
|
||||
{ "path": "../../kibana_utils/tsconfig.json" },
|
||||
{ "path": "../../kibana_react/tsconfig.json" },
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"server": true,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["charts", "data", "expressions", "visualizations", "inspector", "dataViews"],
|
||||
"optionalPlugins": ["home","usageCollection"],
|
||||
"optionalPlugins": ["home"],
|
||||
"requiredBundles": ["unifiedSearch", "kibanaUtils", "kibanaReact", "fieldFormats"],
|
||||
"owner": {
|
||||
"name": "Vis Editors",
|
||||
|
|
|
@ -18,7 +18,6 @@ import {
|
|||
import { firstValueFrom, Observable } from 'rxjs';
|
||||
import { Server } from '@hapi/hapi';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
|
||||
import { HomeServerPluginSetup } from '@kbn/home-plugin/server';
|
||||
import { PluginStart } from '@kbn/data-plugin/server';
|
||||
import type { DataViewsService } from '@kbn/data-views-plugin/common';
|
||||
|
@ -41,14 +40,11 @@ import {
|
|||
} from './lib/search_strategies';
|
||||
import type { TimeseriesVisData, VisPayload } from '../common/types';
|
||||
|
||||
import { registerTimeseriesUsageCollector } from './usage_collector';
|
||||
|
||||
export interface LegacySetup {
|
||||
server: Server;
|
||||
}
|
||||
|
||||
interface VisTypeTimeseriesPluginSetupDependencies {
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
home?: HomeServerPluginSetup;
|
||||
}
|
||||
|
||||
|
@ -128,10 +124,6 @@ export class VisTypeTimeseriesPlugin implements Plugin<VisTypeTimeseriesSetup> {
|
|||
visDataRoutes(router, framework);
|
||||
fieldsRoutes(router, framework);
|
||||
|
||||
if (plugins.usageCollection) {
|
||||
registerTimeseriesUsageCollector(plugins.usageCollection, plugins.home);
|
||||
}
|
||||
|
||||
return {
|
||||
getVisData: async (
|
||||
requestContext: VisTypeTimeseriesRequestHandlerContext,
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const mockStats = { somestat: 1 };
|
||||
export const mockGetStats = jest.fn().mockResolvedValue(mockStats);
|
||||
|
||||
jest.doMock('./get_usage_collector', () => ({
|
||||
getStats: mockGetStats,
|
||||
}));
|
|
@ -1,264 +0,0 @@
|
|||
/*
|
||||
* 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 { getStats } from './get_usage_collector';
|
||||
import { createCollectorFetchContextMock } from '@kbn/usage-collection-plugin/server/mocks';
|
||||
import type { SavedObjectsClientContract, SavedObjectsFindResponse } from '@kbn/core/server';
|
||||
import { TIME_RANGE_DATA_MODES } from '../../common/enums';
|
||||
|
||||
const mockedSavedObject = {
|
||||
saved_objects: [
|
||||
{
|
||||
attributes: {
|
||||
visState: JSON.stringify({
|
||||
type: 'metrics',
|
||||
title: 'TSVB visualization 1',
|
||||
params: {
|
||||
type: 'gauge',
|
||||
time_range_mode: TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE,
|
||||
use_kibana_indexes: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
visState: JSON.stringify({
|
||||
type: 'metrics',
|
||||
title: 'TSVB visualization 2',
|
||||
params: {
|
||||
type: 'top_n',
|
||||
time_range_mode: TIME_RANGE_DATA_MODES.LAST_VALUE,
|
||||
use_kibana_indexes: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
visState: JSON.stringify({
|
||||
type: 'metrics',
|
||||
title: 'TSVB visualization 3',
|
||||
params: {
|
||||
type: 'markdown',
|
||||
time_range_mode: undefined,
|
||||
use_kibana_indexes: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
visState: JSON.stringify({
|
||||
type: 'metrics',
|
||||
title: 'TSVB visualization 4',
|
||||
params: {
|
||||
type: 'table',
|
||||
series: [
|
||||
{
|
||||
aggregate_by: 'test',
|
||||
aggregate_function: 'max',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
} as SavedObjectsFindResponse;
|
||||
|
||||
const mockedSavedObjectsByValue = [
|
||||
{
|
||||
attributes: {
|
||||
panelsJSON: JSON.stringify({
|
||||
type: 'visualization',
|
||||
embeddableConfig: {
|
||||
savedVis: {
|
||||
type: 'metrics',
|
||||
params: {
|
||||
type: 'markdown',
|
||||
time_range_mode: TIME_RANGE_DATA_MODES.LAST_VALUE,
|
||||
use_kibana_indexes: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
panelsJSON: JSON.stringify({
|
||||
type: 'visualization',
|
||||
embeddableConfig: {
|
||||
savedVis: {
|
||||
type: 'metrics',
|
||||
params: {
|
||||
type: 'timeseries',
|
||||
time_range_mode: TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE,
|
||||
use_kibana_indexes: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
panelsJSON: JSON.stringify({
|
||||
type: 'visualization',
|
||||
embeddableConfig: {
|
||||
savedVis: {
|
||||
type: 'metrics',
|
||||
params: {
|
||||
type: 'table',
|
||||
series: [
|
||||
{
|
||||
aggregate_by: 'test1',
|
||||
aggregate_function: 'sum',
|
||||
},
|
||||
],
|
||||
use_kibana_indexes: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const getMockCollectorFetchContext = (
|
||||
savedObjects: SavedObjectsFindResponse,
|
||||
savedObjectsByValue: unknown[] = []
|
||||
) => {
|
||||
const fetchParamsMock = createCollectorFetchContextMock();
|
||||
|
||||
fetchParamsMock.soClient = {
|
||||
find: jest.fn().mockResolvedValue({
|
||||
saved_objects: savedObjectsByValue,
|
||||
}),
|
||||
createPointInTimeFinder: jest.fn().mockResolvedValue({
|
||||
close: jest.fn(),
|
||||
find: function* asyncGenerator() {
|
||||
yield savedObjects;
|
||||
},
|
||||
}),
|
||||
} as unknown as SavedObjectsClientContract;
|
||||
return fetchParamsMock;
|
||||
};
|
||||
|
||||
describe('Timeseries visualization usage collector', () => {
|
||||
test('Returns undefined when no results found (undefined)', async () => {
|
||||
const mockCollectorFetchContext = getMockCollectorFetchContext(
|
||||
{ saved_objects: [] } as unknown as SavedObjectsFindResponse,
|
||||
[]
|
||||
);
|
||||
const result = await getStats(mockCollectorFetchContext.soClient);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('Returns undefined when no timeseries saved objects found', async () => {
|
||||
const mockCollectorFetchContext = getMockCollectorFetchContext({
|
||||
saved_objects: [
|
||||
{
|
||||
attributes: { visState: '{"type": "area"}' },
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
panelsJSON: JSON.stringify({
|
||||
type: 'visualization',
|
||||
embeddableConfig: {
|
||||
savedVis: {
|
||||
type: 'area',
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
} as SavedObjectsFindResponse);
|
||||
|
||||
const result = await getStats(mockCollectorFetchContext.soClient);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('Returns undefined when aggregate function is null', async () => {
|
||||
const mockCollectorFetchContext = getMockCollectorFetchContext({
|
||||
saved_objects: [
|
||||
{
|
||||
attributes: {
|
||||
panelsJSON: JSON.stringify({
|
||||
type: 'visualization',
|
||||
embeddableConfig: {
|
||||
savedVis: {
|
||||
type: 'metrics',
|
||||
params: {
|
||||
type: 'table',
|
||||
series: [
|
||||
{
|
||||
aggregate_by: null,
|
||||
aggregate_function: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
panelsJSON: JSON.stringify({
|
||||
type: 'visualization',
|
||||
embeddableConfig: {
|
||||
savedVis: {
|
||||
type: 'metrics',
|
||||
params: {
|
||||
type: 'table',
|
||||
series: [
|
||||
{
|
||||
axis_position: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
} as SavedObjectsFindResponse);
|
||||
|
||||
const result = await getStats(mockCollectorFetchContext.soClient);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('Summarizes visualizations response data', async () => {
|
||||
const mockCollectorFetchContext = getMockCollectorFetchContext(
|
||||
mockedSavedObject,
|
||||
mockedSavedObjectsByValue
|
||||
);
|
||||
const result = await getStats(mockCollectorFetchContext.soClient);
|
||||
|
||||
expect(result).toStrictEqual({
|
||||
timeseries_use_last_value_mode_total: 5,
|
||||
timeseries_use_es_indices_total: 4,
|
||||
timeseries_table_use_aggregate_function: 2,
|
||||
timeseries_types: {
|
||||
gauge: 1,
|
||||
markdown: 2,
|
||||
metric: 0,
|
||||
table: 2,
|
||||
timeseries: 1,
|
||||
top_n: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,182 +0,0 @@
|
|||
/*
|
||||
* 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 { findByValueEmbeddables } from '@kbn/dashboard-plugin/server';
|
||||
|
||||
import type {
|
||||
SavedObjectsClientContract,
|
||||
ISavedObjectsRepository,
|
||||
SavedObjectsFindResult,
|
||||
} from '@kbn/core/server';
|
||||
import type { HomeServerPluginSetup } from '@kbn/home-plugin/server';
|
||||
import type { SavedVisState } from '@kbn/visualizations-plugin/common';
|
||||
import { TIME_RANGE_DATA_MODES } from '../../common/enums';
|
||||
import type { Panel } from '../../common/types';
|
||||
|
||||
export interface TimeseriesUsage {
|
||||
timeseries_use_last_value_mode_total: number;
|
||||
timeseries_use_es_indices_total: number;
|
||||
timeseries_table_use_aggregate_function: number;
|
||||
timeseries_types: {
|
||||
table: number;
|
||||
gauge: number;
|
||||
markdown: number;
|
||||
top_n: number;
|
||||
timeseries: number;
|
||||
metric: number;
|
||||
};
|
||||
}
|
||||
|
||||
const doTelemetryFoVisualizations = async (
|
||||
soClient: SavedObjectsClientContract | ISavedObjectsRepository,
|
||||
calculateTelemetry: (savedVis: SavedVisState<Panel>) => void
|
||||
) => {
|
||||
const finder = await soClient.createPointInTimeFinder({
|
||||
type: 'visualization',
|
||||
perPage: 1000,
|
||||
namespaces: ['*'],
|
||||
});
|
||||
|
||||
for await (const response of finder.find()) {
|
||||
(response.saved_objects || []).forEach(({ attributes }: SavedObjectsFindResult<any>) => {
|
||||
if (attributes?.visState) {
|
||||
try {
|
||||
const visState: SavedVisState<Panel> = JSON.parse(attributes.visState);
|
||||
|
||||
calculateTelemetry(visState);
|
||||
} catch {
|
||||
// nothing to be here, "so" not valid
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
await finder.close();
|
||||
};
|
||||
|
||||
const doTelemetryForByValueVisualizations = async (
|
||||
soClient: SavedObjectsClientContract | ISavedObjectsRepository,
|
||||
telemetryUseLastValueMode: (savedVis: SavedVisState<Panel>) => void
|
||||
) => {
|
||||
const byValueVisualizations = await findByValueEmbeddables(soClient, 'visualization');
|
||||
|
||||
for (const item of byValueVisualizations) {
|
||||
telemetryUseLastValueMode(item.savedVis as unknown as SavedVisState<Panel>);
|
||||
}
|
||||
};
|
||||
|
||||
const getDefaultTSVBVisualizations = (home?: HomeServerPluginSetup) => {
|
||||
const titles: string[] = [];
|
||||
const sampleDataSets = home?.sampleData.getSampleDatasets() ?? [];
|
||||
|
||||
sampleDataSets.forEach((sampleDataSet) =>
|
||||
sampleDataSet.savedObjects.forEach((savedObject) => {
|
||||
try {
|
||||
if (savedObject.type === 'visualization') {
|
||||
const visState = JSON.parse(savedObject.attributes?.visState);
|
||||
|
||||
if (visState.type === 'metrics') {
|
||||
titles.push(visState.title);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Let it go, visState is invalid and we'll don't need to handle it
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return titles;
|
||||
};
|
||||
|
||||
export const getStats = async (
|
||||
soClient: SavedObjectsClientContract | ISavedObjectsRepository,
|
||||
home?: HomeServerPluginSetup
|
||||
): Promise<TimeseriesUsage | undefined> => {
|
||||
const timeseriesUsage = {
|
||||
timeseries_use_last_value_mode_total: 0,
|
||||
timeseries_use_es_indices_total: 0,
|
||||
timeseries_table_use_aggregate_function: 0,
|
||||
timeseries_types: {
|
||||
gauge: 0,
|
||||
markdown: 0,
|
||||
metric: 0,
|
||||
table: 0,
|
||||
timeseries: 0,
|
||||
top_n: 0,
|
||||
},
|
||||
};
|
||||
|
||||
// we want to exclude the TSVB Sample Data visualizations from the stats
|
||||
// in order to have more accurate results
|
||||
const excludedFromStatsVisualizations = getDefaultTSVBVisualizations(home);
|
||||
|
||||
function telemetryUseLastValueMode(visState: SavedVisState<Panel>) {
|
||||
if (
|
||||
visState.type === 'metrics' &&
|
||||
visState.params.type !== 'timeseries' &&
|
||||
(!visState.params.time_range_mode ||
|
||||
visState.params.time_range_mode === TIME_RANGE_DATA_MODES.LAST_VALUE) &&
|
||||
!excludedFromStatsVisualizations.includes(visState.title)
|
||||
) {
|
||||
timeseriesUsage.timeseries_use_last_value_mode_total++;
|
||||
}
|
||||
}
|
||||
|
||||
function telemetryUseESIndices(visState: SavedVisState<Panel>) {
|
||||
if (
|
||||
visState.type === 'metrics' &&
|
||||
!visState.params.use_kibana_indexes &&
|
||||
!excludedFromStatsVisualizations.includes(visState.title)
|
||||
) {
|
||||
timeseriesUsage.timeseries_use_es_indices_total++;
|
||||
}
|
||||
}
|
||||
|
||||
function telemetryTableAggFunction(visState: SavedVisState<Panel>) {
|
||||
if (
|
||||
visState.type === 'metrics' &&
|
||||
visState.params.type === 'table' &&
|
||||
visState.params.series &&
|
||||
visState.params.series.length > 0 &&
|
||||
!excludedFromStatsVisualizations.includes(visState.title)
|
||||
) {
|
||||
const usesAggregateFunction = visState.params.series.some(
|
||||
(s) => s.aggregate_by && s.aggregate_function
|
||||
);
|
||||
if (usesAggregateFunction) {
|
||||
timeseriesUsage.timeseries_table_use_aggregate_function++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function telemetryPanelTypes(visState: SavedVisState<Panel>) {
|
||||
if (visState.type === 'metrics' && !excludedFromStatsVisualizations.includes(visState.title)) {
|
||||
timeseriesUsage.timeseries_types[visState.params.type]++;
|
||||
}
|
||||
}
|
||||
await Promise.all([
|
||||
// last value usage telemetry
|
||||
doTelemetryFoVisualizations(soClient, telemetryUseLastValueMode),
|
||||
doTelemetryForByValueVisualizations(soClient, telemetryUseLastValueMode),
|
||||
// elasticsearch indices usage telemetry
|
||||
doTelemetryFoVisualizations(soClient, telemetryUseESIndices),
|
||||
doTelemetryForByValueVisualizations(soClient, telemetryUseESIndices),
|
||||
// table aggregate function telemetry
|
||||
doTelemetryFoVisualizations(soClient, telemetryTableAggFunction),
|
||||
doTelemetryForByValueVisualizations(soClient, telemetryTableAggFunction),
|
||||
// panel types usage telemetry
|
||||
doTelemetryFoVisualizations(soClient, telemetryPanelTypes),
|
||||
doTelemetryForByValueVisualizations(soClient, telemetryPanelTypes),
|
||||
]);
|
||||
|
||||
return timeseriesUsage.timeseries_use_last_value_mode_total ||
|
||||
timeseriesUsage.timeseries_use_es_indices_total ||
|
||||
timeseriesUsage.timeseries_table_use_aggregate_function ||
|
||||
Object.values(timeseriesUsage.timeseries_types).some((visualizationCount) => visualizationCount)
|
||||
? timeseriesUsage
|
||||
: undefined;
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { registerTimeseriesUsageCollector } from './register_timeseries_collector';
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* 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 { mockStats, mockGetStats } from './get_usage_collector.mock';
|
||||
import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks';
|
||||
import { createCollectorFetchContextMock } from '@kbn/usage-collection-plugin/server/mocks';
|
||||
import { registerTimeseriesUsageCollector } from './register_timeseries_collector';
|
||||
|
||||
describe('registerTimeseriesUsageCollector', () => {
|
||||
it('makes a usage collector and registers it`', () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerTimeseriesUsageCollector(mockCollectorSet);
|
||||
expect(mockCollectorSet.makeUsageCollector).toBeCalledTimes(1);
|
||||
expect(mockCollectorSet.registerCollector).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('makeUsageCollector configs fit the shape', () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerTimeseriesUsageCollector(mockCollectorSet);
|
||||
expect(mockCollectorSet.makeUsageCollector).toHaveBeenCalledWith({
|
||||
type: 'vis_type_timeseries',
|
||||
isReady: expect.any(Function),
|
||||
fetch: expect.any(Function),
|
||||
schema: expect.any(Object),
|
||||
});
|
||||
const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0];
|
||||
expect(usageCollectorConfig.isReady()).toBe(true);
|
||||
});
|
||||
|
||||
it('makeUsageCollector config.isReady returns true', () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerTimeseriesUsageCollector(mockCollectorSet);
|
||||
const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0];
|
||||
expect(usageCollectorConfig.isReady()).toBe(true);
|
||||
});
|
||||
|
||||
it('makeUsageCollector config.fetch calls getStats', async () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerTimeseriesUsageCollector(mockCollectorSet);
|
||||
const usageCollector = mockCollectorSet.makeUsageCollector.mock.results[0].value;
|
||||
const mockedCollectorFetchContext = createCollectorFetchContextMock();
|
||||
const fetchResult = await usageCollector.fetch(mockedCollectorFetchContext);
|
||||
expect(mockGetStats).toBeCalledTimes(1);
|
||||
expect(mockGetStats).toBeCalledWith(mockedCollectorFetchContext.soClient, undefined);
|
||||
expect(fetchResult).toBe(mockStats);
|
||||
});
|
||||
});
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* 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 type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
|
||||
import type { HomeServerPluginSetup } from '@kbn/home-plugin/server';
|
||||
import { getStats, TimeseriesUsage } from './get_usage_collector';
|
||||
|
||||
export function registerTimeseriesUsageCollector(
|
||||
collectorSet: UsageCollectionSetup,
|
||||
home?: HomeServerPluginSetup
|
||||
) {
|
||||
const collector = collectorSet.makeUsageCollector<TimeseriesUsage | undefined>({
|
||||
type: 'vis_type_timeseries',
|
||||
isReady: () => true,
|
||||
schema: {
|
||||
timeseries_use_last_value_mode_total: {
|
||||
type: 'long',
|
||||
_meta: { description: 'Number of TSVB visualizations using "last value" as a time range' },
|
||||
},
|
||||
timeseries_use_es_indices_total: {
|
||||
type: 'long',
|
||||
_meta: { description: 'Number of TSVB visualizations using elasticsearch indices' },
|
||||
},
|
||||
timeseries_table_use_aggregate_function: {
|
||||
type: 'long',
|
||||
_meta: { description: 'Number of TSVB table visualizations using aggregate function' },
|
||||
},
|
||||
timeseries_types: {
|
||||
table: { type: 'long' },
|
||||
gauge: { type: 'long' },
|
||||
markdown: { type: 'long' },
|
||||
top_n: { type: 'long' },
|
||||
timeseries: { type: 'long' },
|
||||
metric: { type: 'long' },
|
||||
},
|
||||
},
|
||||
fetch: async ({ soClient }) => await getStats(soClient, home),
|
||||
});
|
||||
|
||||
collectorSet.registerCollector(collector);
|
||||
}
|
|
@ -23,7 +23,6 @@
|
|||
{ "path": "../../dashboard/tsconfig.json" },
|
||||
{ "path": "../../kibana_utils/tsconfig.json" },
|
||||
{ "path": "../../kibana_react/tsconfig.json" },
|
||||
{ "path": "../../usage_collection/tsconfig.json" },
|
||||
{ "path": "../../unified_search/tsconfig.json" }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"server": true,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["data", "visualizations", "mapsEms", "expressions", "inspector", "dataViews"],
|
||||
"optionalPlugins": ["home","usageCollection"],
|
||||
"optionalPlugins": ["home"],
|
||||
"requiredBundles": ["kibanaUtils", "kibanaReact", "visDefaultEditor"],
|
||||
"owner": {
|
||||
"name": "Vis Editors",
|
||||
|
|
|
@ -7,25 +7,16 @@
|
|||
*/
|
||||
|
||||
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '@kbn/core/server';
|
||||
import { registerVegaUsageCollector } from './usage_collector';
|
||||
import {
|
||||
ConfigObservable,
|
||||
VisTypeVegaPluginSetupDependencies,
|
||||
VisTypeVegaPluginSetup,
|
||||
VisTypeVegaPluginStart,
|
||||
} from './types';
|
||||
|
||||
export class VisTypeVegaPlugin implements Plugin<VisTypeVegaPluginSetup, VisTypeVegaPluginStart> {
|
||||
private readonly config: ConfigObservable;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
this.config = initializerContext.config.legacy.globalConfig$;
|
||||
}
|
||||
constructor(initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(core: CoreSetup, { home, usageCollection }: VisTypeVegaPluginSetupDependencies) {
|
||||
if (usageCollection) {
|
||||
registerVegaUsageCollector(usageCollection, this.config, { home });
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const mockStats = { somestat: 1 };
|
||||
export const mockGetStats = jest.fn().mockResolvedValue(mockStats);
|
||||
|
||||
jest.doMock('./get_usage_collector', () => ({
|
||||
getStats: mockGetStats,
|
||||
}));
|
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* 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 { getStats } from './get_usage_collector';
|
||||
import { createCollectorFetchContextMock } from '@kbn/usage-collection-plugin/server/mocks';
|
||||
import type { HomeServerPluginSetup } from '@kbn/home-plugin/server';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
|
||||
const mockedSavedObjects = [
|
||||
// vega-lite lib spec
|
||||
{
|
||||
attributes: {
|
||||
visState: JSON.stringify({
|
||||
type: 'vega',
|
||||
params: {
|
||||
spec: '{"$schema": "https://vega.github.io/schema/vega-lite/v5.json" }',
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
// vega lib spec
|
||||
{
|
||||
attributes: {
|
||||
visState: JSON.stringify({
|
||||
type: 'vega',
|
||||
params: {
|
||||
spec: '{"$schema": "https://vega.github.io/schema/vega/v5.json" }',
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
// map layout
|
||||
{
|
||||
attributes: {
|
||||
visState: JSON.stringify({
|
||||
type: 'vega',
|
||||
params: {
|
||||
spec: '{"$schema": "https://vega.github.io/schema/vega/v3.json" \n "config": { "kibana" : { "type": "map" }} }',
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const getMockCollectorFetchContext = (savedObjects?: unknown[]) => {
|
||||
const fetchParamsMock = createCollectorFetchContextMock();
|
||||
|
||||
fetchParamsMock.soClient = {
|
||||
createPointInTimeFinder: jest.fn().mockResolvedValue({
|
||||
close: jest.fn(),
|
||||
find: function* asyncGenerator() {
|
||||
yield { saved_objects: savedObjects };
|
||||
},
|
||||
}),
|
||||
} as unknown as SavedObjectsClientContract;
|
||||
|
||||
return fetchParamsMock;
|
||||
};
|
||||
|
||||
describe('Vega visualization usage collector', () => {
|
||||
const mockDeps = {
|
||||
home: {
|
||||
sampleData: {
|
||||
getSampleDatasets: jest.fn().mockReturnValue([
|
||||
{
|
||||
savedObjects: [
|
||||
{
|
||||
type: 'visualization',
|
||||
attributes: {
|
||||
visState: JSON.stringify({
|
||||
type: 'vega',
|
||||
title: 'sample vega visualization',
|
||||
params: {
|
||||
spec: '{"$schema": "https://vega.github.io/schema/vega/v5.json" }',
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
},
|
||||
} as unknown as HomeServerPluginSetup,
|
||||
};
|
||||
|
||||
test('Returns undefined when no results found (undefined)', async () => {
|
||||
const result = await getStats(getMockCollectorFetchContext().soClient, mockDeps);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('Returns undefined when no results found (0 results)', async () => {
|
||||
const result = await getStats(getMockCollectorFetchContext([]).soClient, mockDeps);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('Returns undefined when no vega saved objects found', async () => {
|
||||
const mockCollectorFetchContext = getMockCollectorFetchContext([
|
||||
{
|
||||
_id: 'visualization:myvis-123',
|
||||
_source: {
|
||||
type: 'visualization',
|
||||
visualization: { visState: '{"type": "area"}' },
|
||||
},
|
||||
},
|
||||
]);
|
||||
const result = await getStats(mockCollectorFetchContext.soClient, mockDeps);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('Should ingnore sample data visualizations', async () => {
|
||||
const mockCollectorFetchContext = getMockCollectorFetchContext([
|
||||
{
|
||||
attributes: {
|
||||
visState: JSON.stringify({
|
||||
type: 'vega',
|
||||
title: 'sample vega visualization',
|
||||
params: {
|
||||
spec: '{"$schema": "https://vega.github.io/schema/vega/v5.json" }',
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await getStats(mockCollectorFetchContext.soClient, mockDeps);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('Summarizes visualizations response data', async () => {
|
||||
const mockCollectorFetchContext = getMockCollectorFetchContext(mockedSavedObjects);
|
||||
const result = await getStats(mockCollectorFetchContext.soClient, mockDeps);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
vega_lib_specs_total: 2,
|
||||
vega_lite_lib_specs_total: 1,
|
||||
vega_use_map_total: 1,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
* 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 { parse } from 'hjson';
|
||||
|
||||
import type { SavedObjectsClientContract, SavedObjectsFindResult } from '@kbn/core/server';
|
||||
import type { SavedVisState } from '@kbn/visualizations-plugin/common';
|
||||
import type { VegaSavedObjectAttributes, VisTypeVegaPluginSetupDependencies } from '../types';
|
||||
|
||||
type UsageCollectorDependencies = Pick<VisTypeVegaPluginSetupDependencies, 'home'>;
|
||||
type VegaType = 'vega' | 'vega-lite';
|
||||
|
||||
export interface VegaUsage {
|
||||
vega_lib_specs_total: number;
|
||||
vega_lite_lib_specs_total: number;
|
||||
vega_use_map_total: number;
|
||||
}
|
||||
|
||||
function isVegaType(attributes: any): attributes is VegaSavedObjectAttributes {
|
||||
return attributes && attributes.type === 'vega' && attributes.params?.spec;
|
||||
}
|
||||
|
||||
const checkVegaSchemaType = (schemaURL: string, type: VegaType) =>
|
||||
schemaURL.includes(`//vega.github.io/schema/${type}/`);
|
||||
|
||||
const getDefaultVegaVisualizations = (home: UsageCollectorDependencies['home']) => {
|
||||
const titles: string[] = [];
|
||||
const sampleDataSets = home?.sampleData.getSampleDatasets() ?? [];
|
||||
|
||||
sampleDataSets.forEach((sampleDataSet) =>
|
||||
sampleDataSet.savedObjects.forEach((savedObject) => {
|
||||
try {
|
||||
if (savedObject.type === 'visualization') {
|
||||
const visState = JSON.parse(savedObject.attributes?.visState);
|
||||
|
||||
if (isVegaType(visState)) {
|
||||
titles.push(visState.title);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Let it go, visState is invalid and we'll don't need to handle it
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return titles;
|
||||
};
|
||||
|
||||
export const getStats = async (
|
||||
soClient: SavedObjectsClientContract,
|
||||
{ home }: UsageCollectorDependencies
|
||||
): Promise<VegaUsage | undefined> => {
|
||||
let shouldPublishTelemetry = false;
|
||||
|
||||
const vegaUsage = {
|
||||
vega_lib_specs_total: 0,
|
||||
vega_lite_lib_specs_total: 0,
|
||||
vega_use_map_total: 0,
|
||||
};
|
||||
|
||||
// we want to exclude the Vega Sample Data visualizations from the stats
|
||||
// in order to have more accurate results
|
||||
const excludedFromStatsVisualizations = getDefaultVegaVisualizations(home);
|
||||
|
||||
const finder = await soClient.createPointInTimeFinder({
|
||||
type: 'visualization',
|
||||
perPage: 1000,
|
||||
namespaces: ['*'],
|
||||
});
|
||||
|
||||
const doTelemetry = ({ params }: SavedVisState) => {
|
||||
try {
|
||||
const spec = parse(params.spec as string, { legacyRoot: false });
|
||||
|
||||
if (spec) {
|
||||
shouldPublishTelemetry = true;
|
||||
|
||||
if (checkVegaSchemaType(spec.$schema, 'vega')) {
|
||||
vegaUsage.vega_lib_specs_total++;
|
||||
}
|
||||
if (checkVegaSchemaType(spec.$schema, 'vega-lite')) {
|
||||
vegaUsage.vega_lite_lib_specs_total++;
|
||||
}
|
||||
if (spec.config?.kibana?.type === 'map') {
|
||||
vegaUsage.vega_use_map_total++;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Let it go, the data is invalid and we'll don't need to handle it
|
||||
}
|
||||
};
|
||||
|
||||
for await (const response of finder.find()) {
|
||||
(response.saved_objects || []).forEach(({ attributes }: SavedObjectsFindResult<any>) => {
|
||||
if (attributes?.visState) {
|
||||
try {
|
||||
const visState: SavedVisState = JSON.parse(attributes.visState);
|
||||
|
||||
if (isVegaType(visState) && !excludedFromStatsVisualizations.includes(visState.title)) {
|
||||
doTelemetry(visState);
|
||||
}
|
||||
} catch {
|
||||
// nothing to be here, "so" not valid
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
await finder.close();
|
||||
|
||||
return shouldPublishTelemetry ? vegaUsage : undefined;
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { registerVegaUsageCollector } from './register_vega_collector';
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
createUsageCollectionSetupMock,
|
||||
createCollectorFetchContextMock,
|
||||
} from '@kbn/usage-collection-plugin/server/mocks';
|
||||
import { mockStats, mockGetStats } from './get_usage_collector.mock';
|
||||
import { registerVegaUsageCollector } from './register_vega_collector';
|
||||
|
||||
import type { HomeServerPluginSetup } from '@kbn/home-plugin/server';
|
||||
import type { ConfigObservable } from '../types';
|
||||
|
||||
describe('registerVegaUsageCollector', () => {
|
||||
const mockDeps = { home: {} as unknown as HomeServerPluginSetup };
|
||||
const mockConfig = {} as ConfigObservable;
|
||||
|
||||
test('makes a usage collector and registers it`', () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerVegaUsageCollector(mockCollectorSet, mockConfig, mockDeps);
|
||||
expect(mockCollectorSet.makeUsageCollector).toBeCalledTimes(1);
|
||||
expect(mockCollectorSet.registerCollector).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('makeUsageCollector configs fit the shape', () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerVegaUsageCollector(mockCollectorSet, mockConfig, mockDeps);
|
||||
expect(mockCollectorSet.makeUsageCollector).toHaveBeenCalledWith({
|
||||
type: 'vis_type_vega',
|
||||
isReady: expect.any(Function),
|
||||
fetch: expect.any(Function),
|
||||
schema: expect.any(Object),
|
||||
});
|
||||
const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0];
|
||||
expect(usageCollectorConfig.isReady()).toBe(true);
|
||||
});
|
||||
|
||||
test('makeUsageCollector config.isReady returns true', () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerVegaUsageCollector(mockCollectorSet, mockConfig, mockDeps);
|
||||
const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0];
|
||||
expect(usageCollectorConfig.isReady()).toBe(true);
|
||||
});
|
||||
|
||||
test('makeUsageCollector config.fetch calls getStats', async () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerVegaUsageCollector(mockCollectorSet, mockConfig, mockDeps);
|
||||
const usageCollector = mockCollectorSet.makeUsageCollector.mock.results[0].value;
|
||||
const mockedCollectorFetchContext = createCollectorFetchContextMock();
|
||||
const fetchResult = await usageCollector.fetch(mockedCollectorFetchContext);
|
||||
expect(mockGetStats).toBeCalledTimes(1);
|
||||
expect(mockGetStats).toBeCalledWith(mockedCollectorFetchContext.soClient, mockDeps);
|
||||
expect(fetchResult).toBe(mockStats);
|
||||
});
|
||||
});
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* 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 type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
|
||||
import { getStats, VegaUsage } from './get_usage_collector';
|
||||
import type { ConfigObservable, VisTypeVegaPluginSetupDependencies } from '../types';
|
||||
|
||||
export function registerVegaUsageCollector(
|
||||
collectorSet: UsageCollectionSetup,
|
||||
config: ConfigObservable,
|
||||
dependencies: Pick<VisTypeVegaPluginSetupDependencies, 'home'>
|
||||
) {
|
||||
const collector = collectorSet.makeUsageCollector<VegaUsage | undefined>({
|
||||
type: 'vis_type_vega',
|
||||
isReady: () => true,
|
||||
schema: {
|
||||
vega_lib_specs_total: { type: 'long' },
|
||||
vega_lite_lib_specs_total: { type: 'long' },
|
||||
vega_use_map_total: { type: 'long' },
|
||||
},
|
||||
fetch: async ({ soClient }) => await getStats(soClient, dependencies),
|
||||
});
|
||||
|
||||
collectorSet.registerCollector(collector);
|
||||
}
|
|
@ -23,7 +23,6 @@
|
|||
{ "path": "../../expressions/tsconfig.json" },
|
||||
{ "path": "../../inspector/tsconfig.json" },
|
||||
{ "path": "../../home/tsconfig.json" },
|
||||
{ "path": "../../usage_collection/tsconfig.json" },
|
||||
{ "path": "../../kibana_utils/tsconfig.json" },
|
||||
{ "path": "../../kibana_react/tsconfig.json" },
|
||||
{ "path": "../../vis_default_editor/tsconfig.json" },
|
||||
|
|
|
@ -9,20 +9,18 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import type { PaletteRegistry } from '@kbn/coloring';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { EuiFormRow, EuiRange } from '@elastic/eui';
|
||||
import { SelectOption, SwitchOption, PalettePicker } from '@kbn/vis-default-editor-plugin/public';
|
||||
|
||||
import { ChartType } from '../../../../../common';
|
||||
import { VisParams } from '../../../../types';
|
||||
import { ValidationVisOptionsProps } from '../../common';
|
||||
import { getPalettesService, getTrackUiMetric } from '../../../../services';
|
||||
import { getPalettesService } from '../../../../services';
|
||||
import { getFittingFunctions } from '../../../collections';
|
||||
|
||||
const fittingFunctions = getFittingFunctions();
|
||||
|
||||
export function ElasticChartsOptions(props: ValidationVisOptionsProps<VisParams>) {
|
||||
const trackUiMetric = getTrackUiMetric();
|
||||
const [palettesRegistry, setPalettesRegistry] = useState<PaletteRegistry | null>(null);
|
||||
const { stateParams, setValue, aggs } = props;
|
||||
|
||||
|
@ -58,9 +56,6 @@ export function ElasticChartsOptions(props: ValidationVisOptionsProps<VisParams>
|
|||
paramName="detailedTooltip"
|
||||
value={stateParams.detailedTooltip}
|
||||
setValue={(paramName, value) => {
|
||||
if (trackUiMetric) {
|
||||
trackUiMetric(METRIC_TYPE.CLICK, 'detailed_tooltip_switched');
|
||||
}
|
||||
setValue(paramName, value);
|
||||
}}
|
||||
/>
|
||||
|
@ -75,9 +70,6 @@ export function ElasticChartsOptions(props: ValidationVisOptionsProps<VisParams>
|
|||
paramName="fittingFunction"
|
||||
value={stateParams.fittingFunction ?? fittingFunctions[2].value}
|
||||
setValue={(paramName, value) => {
|
||||
if (trackUiMetric) {
|
||||
trackUiMetric(METRIC_TYPE.CLICK, 'fitting_function_selected');
|
||||
}
|
||||
setValue(paramName, value);
|
||||
}}
|
||||
/>
|
||||
|
@ -89,9 +81,6 @@ export function ElasticChartsOptions(props: ValidationVisOptionsProps<VisParams>
|
|||
activePalette={stateParams.palette}
|
||||
paramName="palette"
|
||||
setPalette={(paramName, value) => {
|
||||
if (trackUiMetric) {
|
||||
trackUiMetric(METRIC_TYPE.CLICK, 'palette_selected');
|
||||
}
|
||||
setValue(paramName, value);
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -16,7 +16,6 @@ import { ChartType } from '../../../../../common';
|
|||
import { getAggs, getVis, getStateParams } from './point_series.mocks';
|
||||
|
||||
jest.mock('../../../../services', () => ({
|
||||
getTrackUiMetric: jest.fn(() => null),
|
||||
getPalettesService: jest.fn(() => {
|
||||
return {
|
||||
getPalettes: jest.fn(),
|
||||
|
|
|
@ -19,7 +19,6 @@ import {
|
|||
setUISettings,
|
||||
setDocLinks,
|
||||
setPalettesService,
|
||||
setTrackUiMetric,
|
||||
setActiveCursor,
|
||||
} from './services';
|
||||
|
||||
|
@ -85,9 +84,6 @@ export class VisTypeXyPlugin
|
|||
expressions.registerFunction(expressionFunctions.visScale);
|
||||
|
||||
visTypesDefinitions.forEach(visualizations.createBaseVisualization);
|
||||
|
||||
setTrackUiMetric(usageCollection?.reportUiCounter.bind(usageCollection, 'vis_type_xy'));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { UiCounterMetricType } from '@kbn/analytics';
|
||||
import { CoreSetup, DocLinksStart } from '@kbn/core/public';
|
||||
import { createGetterSetter } from '@kbn/kibana-utils-plugin/public';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
|
@ -31,8 +30,3 @@ export const [getPalettesService, setPalettesService] =
|
|||
createGetterSetter<ChartsPluginSetup['palettes']>('xy charts.palette');
|
||||
|
||||
export const [getDocLinks, setDocLinks] = createGetterSetter<DocLinksStart>('DocLinks');
|
||||
|
||||
export const [getTrackUiMetric, setTrackUiMetric] =
|
||||
createGetterSetter<(metricType: UiCounterMetricType, eventName: string | string[]) => void>(
|
||||
'trackUiMetric'
|
||||
);
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"dataViews",
|
||||
"dataViewEditor"
|
||||
],
|
||||
"optionalPlugins": ["home", "share", "usageCollection", "spaces", "savedObjectsTaggingOss"],
|
||||
"optionalPlugins": ["home", "share", "spaces", "savedObjectsTaggingOss"],
|
||||
"requiredBundles": ["kibanaUtils", "discover", "kibanaReact"],
|
||||
"extraPublicDirs": ["common/constants", "common/utils", "common/expression_functions"],
|
||||
"owner": {
|
||||
|
|
|
@ -14,7 +14,6 @@ import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks';
|
|||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { indexPatternEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks';
|
||||
import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/public/mocks';
|
||||
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
|
||||
import { inspectorPluginMock } from '@kbn/inspector-plugin/public/mocks';
|
||||
import { savedObjectsPluginMock } from '@kbn/saved-objects-plugin/public/mocks';
|
||||
|
@ -52,7 +51,6 @@ const createInstance = async () => {
|
|||
embeddable: embeddablePluginMock.createSetupContract(),
|
||||
expressions: expressionsPluginMock.createSetupContract(),
|
||||
inspector: inspectorPluginMock.createSetupContract(),
|
||||
usageCollection: usageCollectionPluginMock.createSetupContract(),
|
||||
urlForwarding: urlForwardingPluginMock.createSetupContract(),
|
||||
uiActions: uiActionsPluginMock.createSetupContract(),
|
||||
});
|
||||
|
|
|
@ -33,7 +33,6 @@ import type {
|
|||
ApplicationStart,
|
||||
SavedObjectsClientContract,
|
||||
} from '@kbn/core/public';
|
||||
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import type { UiActionsStart, UiActionsSetup } from '@kbn/ui-actions-plugin/public';
|
||||
import type { SavedObjectsStart } from '@kbn/saved-objects-plugin/public';
|
||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
|
@ -50,7 +49,6 @@ import type { NavigationPublicPluginStart as NavigationStart } from '@kbn/naviga
|
|||
import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import type { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public';
|
||||
import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
|
||||
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
|
||||
import type { ScreenshotModePluginStart } from '@kbn/screenshot-mode-plugin/public';
|
||||
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
|
||||
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
|
@ -78,7 +76,6 @@ import {
|
|||
setHttp,
|
||||
setSearch,
|
||||
setSavedObjects,
|
||||
setUsageCollector,
|
||||
setExpressions,
|
||||
setUiActions,
|
||||
setTimeFilter,
|
||||
|
@ -112,7 +109,6 @@ export interface VisualizationsSetupDeps {
|
|||
expressions: ExpressionsSetup;
|
||||
inspector: InspectorSetup;
|
||||
uiActions: UiActionsSetup;
|
||||
usageCollection: UsageCollectionSetup;
|
||||
urlForwarding: UrlForwardingSetup;
|
||||
home?: HomePublicPluginSetup;
|
||||
share?: SharePluginSetup;
|
||||
|
@ -136,7 +132,6 @@ export interface VisualizationsStartDeps {
|
|||
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
|
||||
share?: SharePluginStart;
|
||||
urlForwarding: UrlForwardingStart;
|
||||
usageCollection?: UsageCollectionStart;
|
||||
screenshotMode: ScreenshotModePluginStart;
|
||||
fieldFormats: FieldFormatsStart;
|
||||
}
|
||||
|
@ -171,7 +166,6 @@ export class VisualizationsPlugin
|
|||
{
|
||||
expressions,
|
||||
embeddable,
|
||||
usageCollection,
|
||||
data,
|
||||
home,
|
||||
urlForwarding,
|
||||
|
@ -289,7 +283,6 @@ export class VisualizationsPlugin
|
|||
setHeaderActionMenu: params.setHeaderActionMenu,
|
||||
savedObjectsTagging: pluginsStart.savedObjectsTaggingOss?.getTaggingApi(),
|
||||
presentationUtil: pluginsStart.presentationUtil,
|
||||
usageCollection: pluginsStart.usageCollection,
|
||||
getKibanaVersion: () => this.initializerContext.env.packageInfo.version,
|
||||
spaces: pluginsStart.spaces,
|
||||
visEditorsRegistry,
|
||||
|
@ -335,7 +328,6 @@ export class VisualizationsPlugin
|
|||
}
|
||||
|
||||
setUISettings(core.uiSettings);
|
||||
setUsageCollector(usageCollection);
|
||||
setTheme(core.theme);
|
||||
|
||||
expressions.registerFunction(rangeExpressionFunction);
|
||||
|
@ -361,7 +353,6 @@ export class VisualizationsPlugin
|
|||
savedObjects,
|
||||
spaces,
|
||||
savedObjectsTaggingOss,
|
||||
usageCollection,
|
||||
fieldFormats,
|
||||
}: VisualizationsStartDeps
|
||||
): VisualizationsStart {
|
||||
|
|
|
@ -20,7 +20,6 @@ import type {
|
|||
ExecutionContextSetup,
|
||||
} from '@kbn/core/public';
|
||||
import type { DataPublicPluginStart, TimefilterContract } from '@kbn/data-plugin/public';
|
||||
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import type { ExpressionsStart } from '@kbn/expressions-plugin/public';
|
||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
|
@ -54,11 +53,6 @@ export const [getTimeFilter, setTimeFilter] = createGetterSetter<TimefilterContr
|
|||
|
||||
export const [getSearch, setSearch] = createGetterSetter<DataPublicPluginStart['search']>('Search');
|
||||
|
||||
export const [getUsageCollector, setUsageCollector] = createGetterSetter<UsageCollectionSetup>(
|
||||
'UsageCollection',
|
||||
false
|
||||
);
|
||||
|
||||
export const [getExpressions, setExpressions] = createGetterSetter<ExpressionsStart>('Expressions');
|
||||
|
||||
export const [getUiActions, setUiActions] = createGetterSetter<UiActionsStart>('UiActions');
|
||||
|
|
|
@ -37,7 +37,6 @@ import type { UrlForwardingStart } from '@kbn/url-forwarding-plugin/public';
|
|||
import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
|
||||
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
|
||||
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
|
||||
import type { SavedSearch } from '@kbn/discover-plugin/public';
|
||||
import type {
|
||||
Vis,
|
||||
|
@ -107,7 +106,6 @@ export interface VisualizeServices extends CoreStart {
|
|||
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
|
||||
savedObjectsTagging?: SavedObjectsTaggingApi;
|
||||
presentationUtil: PresentationUtilPluginStart;
|
||||
usageCollection?: UsageCollectionStart;
|
||||
getKibanaVersion: () => string;
|
||||
spaces?: SpacesPluginStart;
|
||||
theme: ThemeServiceStart;
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import {
|
||||
EuiBetaBadge,
|
||||
EuiButton,
|
||||
|
@ -25,16 +24,6 @@ import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plug
|
|||
import { RedirectAppLinks } from '@kbn/kibana-react-plugin/public';
|
||||
import { VisualizationListItem } from '../..';
|
||||
import { getVisualizeListItemLink } from './get_visualize_list_item_link';
|
||||
import { getUsageCollector } from '../../services';
|
||||
import { VISUALIZE_APP_NAME } from '../../../common/constants';
|
||||
|
||||
const doTelemetryForAddEvent = (visType?: string) => {
|
||||
const usageCollection = getUsageCollector();
|
||||
|
||||
if (usageCollection && visType) {
|
||||
usageCollection.reportUiCounter(VISUALIZE_APP_NAME, METRIC_TYPE.CLICK, `${visType}:add`);
|
||||
}
|
||||
};
|
||||
|
||||
const getBadge = (item: VisualizationListItem) => {
|
||||
if (item.stage === 'beta') {
|
||||
|
@ -106,12 +95,8 @@ export const getTableColumns = (
|
|||
// In case an error occurs i.e. the vis has wrong type, we render the vis but without the link
|
||||
!error ? (
|
||||
<RedirectAppLinks application={application} className="visListingTable__titleLink">
|
||||
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
|
||||
<EuiLink
|
||||
href={getVisualizeListItemLink(application, kbnUrlStateStorage, editApp, editUrl)}
|
||||
onClick={() => {
|
||||
doTelemetryForAddEvent(typeof type === 'string' ? type : type?.name);
|
||||
}}
|
||||
data-test-subj={`visListingTitleLink-${title.split(' ').join('-')}`}
|
||||
>
|
||||
{field}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { EuiBetaBadgeProps } from '@elastic/eui';
|
||||
import { parse } from 'query-string';
|
||||
|
||||
|
@ -40,7 +39,7 @@ import {
|
|||
VisualizeAppStateContainer,
|
||||
VisualizeEditorVisInstance,
|
||||
} from '../types';
|
||||
import { VISUALIZE_APP_NAME, VisualizeConstants } from '../../../common/constants';
|
||||
import { VisualizeConstants } from '../../../common/constants';
|
||||
import { getEditBreadcrumbs } from './breadcrumbs';
|
||||
import { VISUALIZE_APP_LOCATOR, VisualizeLocatorParams } from '../../../common/locator';
|
||||
import { getUiActions } from '../../services';
|
||||
|
@ -121,7 +120,6 @@ export const getTopNavConfig = (
|
|||
i18n: { Context: I18nContext },
|
||||
savedObjectsTagging,
|
||||
presentationUtil,
|
||||
usageCollection,
|
||||
getKibanaVersion,
|
||||
savedObjects,
|
||||
}: VisualizeServices
|
||||
|
@ -129,16 +127,6 @@ export const getTopNavConfig = (
|
|||
const { vis, embeddableHandler } = visInstance;
|
||||
const savedVis = visInstance.savedVis;
|
||||
|
||||
const doTelemetryForSaveEvent = (visType: string) => {
|
||||
if (usageCollection) {
|
||||
usageCollection.reportUiCounter(
|
||||
originatingApp ?? VISUALIZE_APP_NAME,
|
||||
METRIC_TYPE.CLICK,
|
||||
`${visType}:save`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the user clicks "Save" button.
|
||||
*/
|
||||
|
@ -523,8 +511,6 @@ export const getTopNavConfig = (
|
|||
return { id: true };
|
||||
}
|
||||
|
||||
doTelemetryForSaveEvent(vis.type.name);
|
||||
|
||||
// We're adding the viz to a library so we need to save it and then
|
||||
// add to a dashboard if necessary
|
||||
const response = await doSave(saveOptions);
|
||||
|
@ -642,8 +628,6 @@ export const getTopNavConfig = (
|
|||
}
|
||||
},
|
||||
run: async () => {
|
||||
doTelemetryForSaveEvent(vis.type.name);
|
||||
|
||||
if (!savedVis?.id) {
|
||||
return createVisReference();
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import {
|
|||
SavedObjectsStart,
|
||||
DocLinksStart,
|
||||
} from '@kbn/core/public';
|
||||
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import { EmbeddableStateTransfer } from '@kbn/embeddable-plugin/public';
|
||||
import { SearchSelection } from './search_selection';
|
||||
import { GroupSelection } from './group_selection';
|
||||
|
@ -36,7 +35,6 @@ interface TypeSelectionProps {
|
|||
uiSettings: IUiSettingsClient;
|
||||
docLinks: DocLinksStart;
|
||||
savedObjects: SavedObjectsStart;
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
application: ApplicationStart;
|
||||
outsideVisualizeApp?: boolean;
|
||||
stateTransfer?: EmbeddableStateTransfer;
|
||||
|
@ -75,11 +73,6 @@ class NewVisModal extends React.Component<TypeSelectionProps, TypeSelectionState
|
|||
showGroups: !this.props.showAggsSelection,
|
||||
visType: this.props.selectedVisType,
|
||||
};
|
||||
|
||||
this.trackUiMetric = this.props.usageCollection?.reportUiCounter.bind(
|
||||
this.props.usageCollection,
|
||||
'visualize'
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
getSavedObjects,
|
||||
getTypes,
|
||||
getUISettings,
|
||||
getUsageCollector,
|
||||
getApplication,
|
||||
getEmbeddable,
|
||||
getDocLinks,
|
||||
|
@ -83,7 +82,6 @@ export function showNewVisModal({
|
|||
addBasePath={getHttp().basePath.prepend}
|
||||
uiSettings={getUISettings()}
|
||||
savedObjects={getSavedObjects()}
|
||||
usageCollection={getUsageCollector()}
|
||||
application={getApplication()}
|
||||
docLinks={getDocLinks()}
|
||||
showAggsSelection={showAggsSelection}
|
||||
|
|
|
@ -17,10 +17,8 @@ import type {
|
|||
Plugin,
|
||||
Logger,
|
||||
} from '@kbn/core/server';
|
||||
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
|
||||
import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server';
|
||||
import { VISUALIZE_ENABLE_LABS_SETTING } from '../common/constants';
|
||||
import { registerVisualizationsCollector } from './usage_collector';
|
||||
import { capabilitiesProvider } from './capabilities_provider';
|
||||
|
||||
import type { VisualizationsPluginSetup, VisualizationsPluginStart } from './types';
|
||||
|
@ -39,7 +37,6 @@ export class VisualizationsPlugin
|
|||
public setup(
|
||||
core: CoreSetup,
|
||||
plugins: {
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
embeddable: EmbeddableSetup;
|
||||
data: DataPluginSetup;
|
||||
}
|
||||
|
@ -66,10 +63,6 @@ export class VisualizationsPlugin
|
|||
},
|
||||
});
|
||||
|
||||
if (plugins.usageCollection) {
|
||||
registerVisualizationsCollector(plugins.usageCollection);
|
||||
}
|
||||
|
||||
plugins.embeddable.registerEmbeddableFactory(
|
||||
makeVisualizeEmbeddableFactory(getSearchSourceMigrations)()
|
||||
);
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* 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 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);
|
||||
});
|
||||
});
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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));
|
||||
};
|
|
@ -1,13 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const mockStats = { somestat: 1 };
|
||||
export const mockGetStats = jest.fn().mockResolvedValue(mockStats);
|
||||
jest.doMock('./get_usage_collector', () => ({
|
||||
getStats: mockGetStats,
|
||||
}));
|
|
@ -1,168 +0,0 @@
|
|||
/*
|
||||
* 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 moment from 'moment';
|
||||
import { getStats } from './get_usage_collector';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
|
||||
const defaultMockSavedObjects = [
|
||||
{
|
||||
id: 'visualization:coolviz-123',
|
||||
attributes: { visState: '{"type": "shell_beads"}' },
|
||||
updated_at: moment().subtract(7, 'days').startOf('day').toString(),
|
||||
},
|
||||
];
|
||||
|
||||
const enlargedMockSavedObjects = [
|
||||
// default space
|
||||
{
|
||||
id: 'visualization:coolviz-123',
|
||||
namespaces: ['default'],
|
||||
attributes: { visState: '{"type": "cave_painting"}' },
|
||||
updated_at: moment().subtract(7, 'days').startOf('day').toString(),
|
||||
},
|
||||
{
|
||||
id: 'visualization:coolviz-456',
|
||||
namespaces: ['default'],
|
||||
attributes: { visState: '{"type": "printing_press"}' },
|
||||
updated_at: moment().subtract(20, 'days').startOf('day').toString(),
|
||||
},
|
||||
{
|
||||
id: 'meat:visualization:coolviz-789',
|
||||
namespaces: ['default'],
|
||||
attributes: { visState: '{"type": "floppy_disk"}' },
|
||||
updated_at: moment().subtract(2, 'months').startOf('day').toString(),
|
||||
},
|
||||
// meat space
|
||||
{
|
||||
id: 'meat:visualization:coolviz-789',
|
||||
namespaces: ['meat'],
|
||||
attributes: { visState: '{"type": "cave_painting"}' },
|
||||
updated_at: moment().subtract(89, 'days').startOf('day').toString(),
|
||||
},
|
||||
{
|
||||
id: 'meat:visualization:coolviz-789',
|
||||
namespaces: ['meat'],
|
||||
attributes: { visState: '{"type": "cuneiform"}' },
|
||||
updated_at: moment().subtract(5, 'months').startOf('day').toString(),
|
||||
},
|
||||
{
|
||||
id: 'meat:visualization:coolviz-789',
|
||||
namespaces: ['meat'],
|
||||
attributes: { visState: '{"type": "cuneiform"}' },
|
||||
updated_at: moment().subtract(2, 'days').startOf('day').toString(),
|
||||
},
|
||||
{
|
||||
id: 'meat:visualization:coolviz-789',
|
||||
attributes: { visState: '{"type": "floppy_disk"}' },
|
||||
updated_at: moment().subtract(7, 'days').startOf('day').toString(),
|
||||
},
|
||||
// cyber space
|
||||
{
|
||||
id: 'cyber:visualization:coolviz-789',
|
||||
namespaces: ['cyber'],
|
||||
attributes: { visState: '{"type": "floppy_disk"}' },
|
||||
updated_at: moment().subtract(7, 'months').startOf('day').toString(),
|
||||
},
|
||||
{
|
||||
id: 'cyber:visualization:coolviz-789',
|
||||
namespaces: ['cyber'],
|
||||
attributes: { visState: '{"type": "floppy_disk"}' },
|
||||
updated_at: moment().subtract(3, 'days').startOf('day').toString(),
|
||||
},
|
||||
{
|
||||
id: 'cyber:visualization:coolviz-123',
|
||||
namespaces: ['cyber'],
|
||||
attributes: { visState: '{"type": "cave_painting"}' },
|
||||
updated_at: moment().subtract(15, 'days').startOf('day').toString(),
|
||||
},
|
||||
];
|
||||
|
||||
describe('Visualizations usage collector', () => {
|
||||
const getMockCallCluster = (savedObjects: unknown[]) =>
|
||||
({
|
||||
createPointInTimeFinder: jest.fn().mockResolvedValue({
|
||||
close: jest.fn(),
|
||||
find: function* asyncGenerator() {
|
||||
yield { saved_objects: savedObjects };
|
||||
},
|
||||
}),
|
||||
} as unknown as SavedObjectsClientContract);
|
||||
|
||||
test('Returns undefined when no results found (undefined)', async () => {
|
||||
const result = await getStats(getMockCallCluster(undefined as any));
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('Returns undefined when no results found (0 results)', async () => {
|
||||
const result = await getStats(getMockCallCluster([]));
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('Summarizes visualizations response data', async () => {
|
||||
const result = await getStats(getMockCallCluster(defaultMockSavedObjects));
|
||||
|
||||
expect(result).toMatchObject({
|
||||
shell_beads: {
|
||||
spaces_avg: 1,
|
||||
spaces_max: 1,
|
||||
spaces_min: 1,
|
||||
total: 1,
|
||||
saved_7_days_total: 1,
|
||||
saved_30_days_total: 1,
|
||||
saved_90_days_total: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Summarizes visualizations response data per Space', async () => {
|
||||
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 result = await getStats(getMockCallCluster(enlargedMockSavedObjects));
|
||||
|
||||
expect(result).toMatchObject(expectedStats);
|
||||
});
|
||||
});
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* 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 { countBy, groupBy, mapValues, max, min, values } from 'lodash';
|
||||
import type { SavedObjectsClientContract, SavedObjectsFindResult } from '@kbn/core/server';
|
||||
import { getPastDays } from './get_past_days';
|
||||
|
||||
import type { SavedVisState } from '../../common';
|
||||
|
||||
interface VisSummary {
|
||||
type: string;
|
||||
space: string;
|
||||
past_days: number;
|
||||
}
|
||||
|
||||
export interface VisualizationUsage {
|
||||
[x: string]: {
|
||||
total: number;
|
||||
spaces_min?: number;
|
||||
spaces_max?: number;
|
||||
spaces_avg: number;
|
||||
saved_7_days_total: number;
|
||||
saved_30_days_total: number;
|
||||
saved_90_days_total: number;
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the response data into telemetry payload
|
||||
*/
|
||||
export async function getStats(
|
||||
soClient: SavedObjectsClientContract
|
||||
): Promise<VisualizationUsage | undefined> {
|
||||
const finder = await soClient.createPointInTimeFinder({
|
||||
type: 'visualization',
|
||||
perPage: 1000,
|
||||
namespaces: ['*'],
|
||||
});
|
||||
|
||||
const visSummaries: VisSummary[] = [];
|
||||
|
||||
for await (const response of finder.find()) {
|
||||
(response.saved_objects || []).forEach((so: SavedObjectsFindResult<any>) => {
|
||||
if (so.attributes?.visState) {
|
||||
const visState: SavedVisState = JSON.parse(so.attributes.visState);
|
||||
|
||||
visSummaries.push({
|
||||
type: visState.type ?? '_na_',
|
||||
space: so.namespaces?.[0] ?? 'default',
|
||||
past_days: getPastDays(so.updated_at!),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
await finder.close();
|
||||
|
||||
if (visSummaries.length) {
|
||||
// organize stats per type
|
||||
const visTypes = groupBy(visSummaries, 'type');
|
||||
|
||||
// get the final result
|
||||
return mapValues(visTypes, (curr) => {
|
||||
const total = curr.length;
|
||||
const spacesBreakdown = countBy(curr, 'space');
|
||||
const spaceCounts: number[] = values(spacesBreakdown);
|
||||
|
||||
return {
|
||||
total,
|
||||
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,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { registerVisualizationsCollector } from './register_visualizations_collector';
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
createUsageCollectionSetupMock,
|
||||
createCollectorFetchContextMock,
|
||||
} from '@kbn/usage-collection-plugin/server/mocks';
|
||||
import { mockStats, mockGetStats } from './get_usage_collector.mock';
|
||||
import { registerVisualizationsCollector } from './register_visualizations_collector';
|
||||
|
||||
describe('registerVisualizationsCollector', () => {
|
||||
test('makes a usage collector and registers it`', () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerVisualizationsCollector(mockCollectorSet);
|
||||
expect(mockCollectorSet.makeUsageCollector).toBeCalledTimes(1);
|
||||
expect(mockCollectorSet.registerCollector).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('makeUsageCollector configs fit the shape', () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerVisualizationsCollector(mockCollectorSet);
|
||||
expect(mockCollectorSet.makeUsageCollector).toHaveBeenCalledWith({
|
||||
type: 'visualization_types',
|
||||
isReady: expect.any(Function),
|
||||
fetch: expect.any(Function),
|
||||
schema: expect.any(Object),
|
||||
});
|
||||
const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0];
|
||||
expect(usageCollectorConfig.isReady()).toBe(true);
|
||||
});
|
||||
|
||||
test('makeUsageCollector config.isReady returns true', () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerVisualizationsCollector(mockCollectorSet);
|
||||
const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0];
|
||||
expect(usageCollectorConfig.isReady()).toBe(true);
|
||||
});
|
||||
|
||||
test('makeUsageCollector config.fetch calls getStats', async () => {
|
||||
const mockCollectorSet = createUsageCollectionSetupMock();
|
||||
registerVisualizationsCollector(mockCollectorSet);
|
||||
const usageCollector = mockCollectorSet.makeUsageCollector.mock.results[0].value;
|
||||
const mockCollectorFetchContext = createCollectorFetchContextMock();
|
||||
const fetchResult = await usageCollector.fetch(mockCollectorFetchContext);
|
||||
expect(mockGetStats).toBeCalledTimes(1);
|
||||
expect(mockGetStats).toBeCalledWith(mockCollectorFetchContext.soClient);
|
||||
expect(fetchResult).toBe(mockStats);
|
||||
});
|
||||
});
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* 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 type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
|
||||
import { getStats, VisualizationUsage } from './get_usage_collector';
|
||||
|
||||
export function registerVisualizationsCollector(collectorSet: UsageCollectionSetup) {
|
||||
const collector = collectorSet.makeUsageCollector<VisualizationUsage | undefined>({
|
||||
type: 'visualization_types',
|
||||
isReady: () => true,
|
||||
schema: {
|
||||
DYNAMIC_KEY: {
|
||||
total: { type: 'long' },
|
||||
spaces_min: { type: 'long' },
|
||||
spaces_max: { type: 'long' },
|
||||
spaces_avg: { type: 'long' },
|
||||
saved_7_days_total: { type: 'long' },
|
||||
saved_30_days_total: { type: 'long' },
|
||||
saved_90_days_total: { type: 'long' },
|
||||
},
|
||||
},
|
||||
fetch: async ({ soClient }) => await getStats(soClient),
|
||||
});
|
||||
collectorSet.registerCollector(collector);
|
||||
}
|
|
@ -22,7 +22,6 @@
|
|||
{ "path": "../inspector/tsconfig.json" },
|
||||
{ "path": "../saved_objects/tsconfig.json" },
|
||||
{ "path": "../saved_objects_tagging_oss/tsconfig.json" },
|
||||
{ "path": "../usage_collection/tsconfig.json" },
|
||||
{ "path": "../kibana_utils/tsconfig.json" },
|
||||
{ "path": "../kibana_react/tsconfig.json" },
|
||||
{ "path": "../discover/tsconfig.json" },
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue