diff --git a/package.json b/package.json index 2793164ab2a21..f680bfa092c0a 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "@elastic/apm-rum": "^5.12.0", "@elastic/apm-rum-react": "^1.4.2", "@elastic/apm-synthtrace": "link:bazel-bin/packages/elastic-apm-synthtrace", - "@elastic/charts": "46.13.0", + "@elastic/charts": "47.0.0", "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.3.0-canary.1", "@elastic/ems-client": "8.3.3", @@ -525,7 +525,7 @@ "uuid": "3.3.2", "vega": "^5.22.1", "vega-interpreter": "^1.0.4", - "vega-lite": "^5.2.0", + "vega-lite": "^5.3.0", "vega-schema-url-parser": "^2.2.0", "vega-spec-injector": "^0.0.2", "vega-tooltip": "^0.28.0", diff --git a/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.test.tsx b/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.test.tsx index 389c6c7571b24..26c1c7ff785a2 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.test.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.test.tsx @@ -32,6 +32,7 @@ const getSeriesIdentifier = ({ seriesSplitAccessors: Map; }): XYChartSeriesIdentifier => ({ specId: generateSeriesId({ layerId, xAccessor }, splitAccessors, yAccessor), + xAccessor: xAccessor ?? 'x', yAccessor: yAccessor ?? 'a', splitAccessors: seriesSplitAccessors, seriesKeys: [], diff --git a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.test.tsx b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.test.tsx index d09eedfaaf7b7..4de0f274697b0 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.test.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.test.tsx @@ -1896,6 +1896,7 @@ describe('XYChart component', () => { seriesKeys: [], key: '', specId: 'a', + xAccessor: '', yAccessor: '', splitAccessors: new Map(), }; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 4dbc715f3a977..4927723395e13 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -10118,122 +10118,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" - } - } - } - } } } } diff --git a/src/plugins/vis_types/heatmap/public/editor/components/index.tsx b/src/plugins/vis_types/heatmap/public/editor/components/index.tsx index 59db84b0db665..cac64d00af9cd 100644 --- a/src/plugins/vis_types/heatmap/public/editor/components/index.tsx +++ b/src/plugins/vis_types/heatmap/public/editor/components/index.tsx @@ -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) => ( ); diff --git a/src/plugins/vis_types/heatmap/public/plugin.ts b/src/plugins/vis_types/heatmap/public/plugin.ts index c84c8830318c2..44357cceaa86b 100644 --- a/src/plugins/vis_types/heatmap/public/plugin.ts +++ b/src/plugins/vis_types/heatmap/public/plugin.ts @@ -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, }) ); } diff --git a/src/plugins/vis_types/heatmap/public/types.ts b/src/plugins/vis_types/heatmap/public/types.ts index 9d41a132f00b1..e29b77746b070 100644 --- a/src/plugins/vis_types/heatmap/public/types.ts +++ b/src/plugins/vis_types/heatmap/public/types.ts @@ -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 { diff --git a/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx b/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx index ee2893f2cb190..e5a92ca03f5cc 100644 --- a/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx +++ b/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx @@ -20,7 +20,6 @@ import { SplitTooltip } from './split_tooltip'; export const getHeatmapVisTypeDefinition = ({ showElasticChartsOptions = false, palettes, - trackUiMetric, }: HeatmapTypeProps): VisTypeDefinition => ({ name: 'heatmap', title: i18n.translate('visTypeHeatmap.heatmap.heatmapTitle', { defaultMessage: 'Heat map' }), @@ -68,7 +67,6 @@ export const getHeatmapVisTypeDefinition = ({ optionsTemplate: getHeatmapOptions({ showElasticChartsOptions, palettes, - trackUiMetric, }), schemas: [ { diff --git a/src/plugins/vis_types/pie/public/editor/components/index.tsx b/src/plugins/vis_types/pie/public/editor/components/index.tsx index f21af20f6b2af..4290634cdeb78 100644 --- a/src/plugins/vis_types/pie/public/editor/components/index.tsx +++ b/src/plugins/vis_types/pie/public/editor/components/index.tsx @@ -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) => ( ); diff --git a/src/plugins/vis_types/pie/public/editor/components/pie.tsx b/src/plugins/vis_types/pie/public/editor/components/pie.tsx index cd1e565861d78..a22f29415d5b5 100644 --- a/src/plugins/vis_types/pie/public/editor/components/pie.tsx +++ b/src/plugins/vis_types/pie/public/editor/components/pie.tsx @@ -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" diff --git a/src/plugins/vis_types/pie/public/plugin.ts b/src/plugins/vis_types/pie/public/plugin.ts index ac219d29b5479..480cf0c49db63 100644 --- a/src/plugins/vis_types/pie/public/plugin.ts +++ b/src/plugins/vis_types/pie/public/plugin.ts @@ -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, }) ); } diff --git a/src/plugins/vis_types/pie/public/types/types.ts b/src/plugins/vis_types/pie/public/types/types.ts index 968e04fd59075..a13ed368d8dec 100644 --- a/src/plugins/vis_types/pie/public/types/types.ts +++ b/src/plugins/vis_types/pie/public/types/types.ts @@ -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; } diff --git a/src/plugins/vis_types/pie/public/vis_type/pie.ts b/src/plugins/vis_types/pie/public/vis_type/pie.ts index 113c277d5e210..6d48507cf47a3 100644 --- a/src/plugins/vis_types/pie/public/vis_type/pie.ts +++ b/src/plugins/vis_types/pie/public/vis_type/pie.ts @@ -25,7 +25,6 @@ import { getPieOptions } from '../editor/components'; export const getPieVisTypeDefinition = ({ showElasticChartsOptions = false, palettes, - trackUiMetric, }: PieTypeProps): VisTypeDefinition => ({ name: 'pie', title: i18n.translate('visTypePie.pie.pieTitle', { defaultMessage: 'Pie' }), @@ -68,7 +67,6 @@ export const getPieVisTypeDefinition = ({ optionsTemplate: getPieOptions({ showElasticChartsOptions, palettes, - trackUiMetric, }), schemas: [ { diff --git a/src/plugins/vis_types/table/kibana.json b/src/plugins/vis_types/table/kibana.json index a56965a214349..28b21fc55f453 100644 --- a/src/plugins/vis_types/table/kibana.json +++ b/src/plugins/vis_types/table/kibana.json @@ -14,7 +14,6 @@ "share", "visDefaultEditor" ], - "optionalPlugins": ["usageCollection"], "owner": { "name": "Vis Editors", "githubTeam": "kibana-vis-editors" diff --git a/src/plugins/vis_types/table/server/index.ts b/src/plugins/vis_types/table/server/index.ts index 0d1eade453f6c..a7f63648ba422 100644 --- a/src/plugins/vis_types/table/server/index.ts +++ b/src/plugins/vis_types/table/server/index.ts @@ -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 = { schema: configSchema, }; export const plugin = () => ({ - setup(core: CoreSetup, plugins: { usageCollection?: UsageCollectionSetup }) { - if (plugins.usageCollection) { - registerVisTypeTableUsageCollector(plugins.usageCollection); - } - }, + setup() {}, start() {}, }); diff --git a/src/plugins/vis_types/table/server/usage_collector/get_stats.test.ts b/src/plugins/vis_types/table/server/usage_collector/get_stats.test.ts deleted file mode 100644 index cba5bce0c5bbf..0000000000000 --- a/src/plugins/vis_types/table/server/usage_collector/get_stats.test.ts +++ /dev/null @@ -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, - }, - }); - }); -}); diff --git a/src/plugins/vis_types/table/server/usage_collector/get_stats.ts b/src/plugins/vis_types/table/server/usage_collector/get_stats.ts deleted file mode 100644 index 288a48a20cc81..0000000000000 --- a/src/plugins/vis_types/table/server/usage_collector/get_stats.ts +++ /dev/null @@ -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 { - 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) => { - 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; -} diff --git a/src/plugins/vis_types/table/server/usage_collector/index.ts b/src/plugins/vis_types/table/server/usage_collector/index.ts deleted file mode 100644 index 6a2e893a354e0..0000000000000 --- a/src/plugins/vis_types/table/server/usage_collector/index.ts +++ /dev/null @@ -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'; diff --git a/src/plugins/vis_types/table/server/usage_collector/register_usage_collector.test.ts b/src/plugins/vis_types/table/server/usage_collector/register_usage_collector.test.ts deleted file mode 100644 index 7d6c4e54ff8fd..0000000000000 --- a/src/plugins/vis_types/table/server/usage_collector/register_usage_collector.test.ts +++ /dev/null @@ -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 }); - }); -}); diff --git a/src/plugins/vis_types/table/server/usage_collector/register_usage_collector.ts b/src/plugins/vis_types/table/server/usage_collector/register_usage_collector.ts deleted file mode 100644 index 75cc7c0b11f1a..0000000000000 --- a/src/plugins/vis_types/table/server/usage_collector/register_usage_collector.ts +++ /dev/null @@ -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({ - 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); -} diff --git a/src/plugins/vis_types/table/tsconfig.json b/src/plugins/vis_types/table/tsconfig.json index 578b10fb09be8..6df0a83853142 100644 --- a/src/plugins/vis_types/table/tsconfig.json +++ b/src/plugins/vis_types/table/tsconfig.json @@ -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" }, diff --git a/src/plugins/vis_types/timeseries/kibana.json b/src/plugins/vis_types/timeseries/kibana.json index 8f12e7c4d3ca5..199b0e00242d3 100644 --- a/src/plugins/vis_types/timeseries/kibana.json +++ b/src/plugins/vis_types/timeseries/kibana.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", diff --git a/src/plugins/vis_types/timeseries/server/plugin.ts b/src/plugins/vis_types/timeseries/server/plugin.ts index 31ca2667ed3a7..6ca50032aa689 100644 --- a/src/plugins/vis_types/timeseries/server/plugin.ts +++ b/src/plugins/vis_types/timeseries/server/plugin.ts @@ -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 { visDataRoutes(router, framework); fieldsRoutes(router, framework); - if (plugins.usageCollection) { - registerTimeseriesUsageCollector(plugins.usageCollection, plugins.home); - } - return { getVisData: async ( requestContext: VisTypeTimeseriesRequestHandlerContext, diff --git a/src/plugins/vis_types/timeseries/server/usage_collector/get_usage_collector.mock.ts b/src/plugins/vis_types/timeseries/server/usage_collector/get_usage_collector.mock.ts deleted file mode 100644 index bb52d215c67e8..0000000000000 --- a/src/plugins/vis_types/timeseries/server/usage_collector/get_usage_collector.mock.ts +++ /dev/null @@ -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, -})); diff --git a/src/plugins/vis_types/timeseries/server/usage_collector/get_usage_collector.test.ts b/src/plugins/vis_types/timeseries/server/usage_collector/get_usage_collector.test.ts deleted file mode 100644 index 6304e7e6518ba..0000000000000 --- a/src/plugins/vis_types/timeseries/server/usage_collector/get_usage_collector.test.ts +++ /dev/null @@ -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, - }, - }); - }); -}); diff --git a/src/plugins/vis_types/timeseries/server/usage_collector/get_usage_collector.ts b/src/plugins/vis_types/timeseries/server/usage_collector/get_usage_collector.ts deleted file mode 100644 index 1a6c060cd7f96..0000000000000 --- a/src/plugins/vis_types/timeseries/server/usage_collector/get_usage_collector.ts +++ /dev/null @@ -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) => void -) => { - const finder = await soClient.createPointInTimeFinder({ - type: 'visualization', - perPage: 1000, - namespaces: ['*'], - }); - - for await (const response of finder.find()) { - (response.saved_objects || []).forEach(({ attributes }: SavedObjectsFindResult) => { - if (attributes?.visState) { - try { - const visState: SavedVisState = 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) => void -) => { - const byValueVisualizations = await findByValueEmbeddables(soClient, 'visualization'); - - for (const item of byValueVisualizations) { - telemetryUseLastValueMode(item.savedVis as unknown as SavedVisState); - } -}; - -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 => { - 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) { - 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) { - if ( - visState.type === 'metrics' && - !visState.params.use_kibana_indexes && - !excludedFromStatsVisualizations.includes(visState.title) - ) { - timeseriesUsage.timeseries_use_es_indices_total++; - } - } - - function telemetryTableAggFunction(visState: SavedVisState) { - 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) { - 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; -}; diff --git a/src/plugins/vis_types/timeseries/server/usage_collector/index.ts b/src/plugins/vis_types/timeseries/server/usage_collector/index.ts deleted file mode 100644 index 7f72662e154ea..0000000000000 --- a/src/plugins/vis_types/timeseries/server/usage_collector/index.ts +++ /dev/null @@ -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'; diff --git a/src/plugins/vis_types/timeseries/server/usage_collector/register_timeseries_collector.test.ts b/src/plugins/vis_types/timeseries/server/usage_collector/register_timeseries_collector.test.ts deleted file mode 100644 index 2c4f0f08d216c..0000000000000 --- a/src/plugins/vis_types/timeseries/server/usage_collector/register_timeseries_collector.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/src/plugins/vis_types/timeseries/server/usage_collector/register_timeseries_collector.ts b/src/plugins/vis_types/timeseries/server/usage_collector/register_timeseries_collector.ts deleted file mode 100644 index d06bf7f3a64b5..0000000000000 --- a/src/plugins/vis_types/timeseries/server/usage_collector/register_timeseries_collector.ts +++ /dev/null @@ -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({ - 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); -} diff --git a/src/plugins/vis_types/timeseries/tsconfig.json b/src/plugins/vis_types/timeseries/tsconfig.json index 43d66c3bd030e..be96a71b9a580 100644 --- a/src/plugins/vis_types/timeseries/tsconfig.json +++ b/src/plugins/vis_types/timeseries/tsconfig.json @@ -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" } ] } diff --git a/src/plugins/vis_types/vega/kibana.json b/src/plugins/vis_types/vega/kibana.json index d3e0da54d848f..423f044aa3275 100644 --- a/src/plugins/vis_types/vega/kibana.json +++ b/src/plugins/vis_types/vega/kibana.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", diff --git a/src/plugins/vis_types/vega/server/plugin.ts b/src/plugins/vis_types/vega/server/plugin.ts index e5e9bdaefe987..6f43a79c99262 100644 --- a/src/plugins/vis_types/vega/server/plugin.ts +++ b/src/plugins/vis_types/vega/server/plugin.ts @@ -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 { - 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 {}; } diff --git a/src/plugins/vis_types/vega/server/usage_collector/get_usage_collector.mock.ts b/src/plugins/vis_types/vega/server/usage_collector/get_usage_collector.mock.ts deleted file mode 100644 index bb52d215c67e8..0000000000000 --- a/src/plugins/vis_types/vega/server/usage_collector/get_usage_collector.mock.ts +++ /dev/null @@ -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, -})); diff --git a/src/plugins/vis_types/vega/server/usage_collector/get_usage_collector.test.ts b/src/plugins/vis_types/vega/server/usage_collector/get_usage_collector.test.ts deleted file mode 100644 index e7154ea2b7da6..0000000000000 --- a/src/plugins/vis_types/vega/server/usage_collector/get_usage_collector.test.ts +++ /dev/null @@ -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, - }); - }); -}); diff --git a/src/plugins/vis_types/vega/server/usage_collector/get_usage_collector.ts b/src/plugins/vis_types/vega/server/usage_collector/get_usage_collector.ts deleted file mode 100644 index 21afe9f141b88..0000000000000 --- a/src/plugins/vis_types/vega/server/usage_collector/get_usage_collector.ts +++ /dev/null @@ -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; -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 => { - 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) => { - 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; -}; diff --git a/src/plugins/vis_types/vega/server/usage_collector/index.ts b/src/plugins/vis_types/vega/server/usage_collector/index.ts deleted file mode 100644 index 3e7903f960d64..0000000000000 --- a/src/plugins/vis_types/vega/server/usage_collector/index.ts +++ /dev/null @@ -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'; diff --git a/src/plugins/vis_types/vega/server/usage_collector/register_vega_collector.test.ts b/src/plugins/vis_types/vega/server/usage_collector/register_vega_collector.test.ts deleted file mode 100644 index 098515d15d9df..0000000000000 --- a/src/plugins/vis_types/vega/server/usage_collector/register_vega_collector.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/src/plugins/vis_types/vega/server/usage_collector/register_vega_collector.ts b/src/plugins/vis_types/vega/server/usage_collector/register_vega_collector.ts deleted file mode 100644 index adf35f81396d1..0000000000000 --- a/src/plugins/vis_types/vega/server/usage_collector/register_vega_collector.ts +++ /dev/null @@ -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 -) { - const collector = collectorSet.makeUsageCollector({ - 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); -} diff --git a/src/plugins/vis_types/vega/tsconfig.json b/src/plugins/vis_types/vega/tsconfig.json index ccb4bbfb34454..7aa32cbda7201 100644 --- a/src/plugins/vis_types/vega/tsconfig.json +++ b/src/plugins/vis_types/vega/tsconfig.json @@ -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" }, diff --git a/src/plugins/vis_types/xy/public/editor/components/options/point_series/elastic_charts_options.tsx b/src/plugins/vis_types/xy/public/editor/components/options/point_series/elastic_charts_options.tsx index 8a633e9bda53e..ed57c5427027b 100644 --- a/src/plugins/vis_types/xy/public/editor/components/options/point_series/elastic_charts_options.tsx +++ b/src/plugins/vis_types/xy/public/editor/components/options/point_series/elastic_charts_options.tsx @@ -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) { - const trackUiMetric = getTrackUiMetric(); const [palettesRegistry, setPalettesRegistry] = useState(null); const { stateParams, setValue, aggs } = props; @@ -58,9 +56,6 @@ export function ElasticChartsOptions(props: ValidationVisOptionsProps 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 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 activePalette={stateParams.palette} paramName="palette" setPalette={(paramName, value) => { - if (trackUiMetric) { - trackUiMetric(METRIC_TYPE.CLICK, 'palette_selected'); - } setValue(paramName, value); }} /> diff --git a/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.test.tsx b/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.test.tsx index dbd1bed390574..ae8327c6c9df0 100644 --- a/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.test.tsx +++ b/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.test.tsx @@ -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(), diff --git a/src/plugins/vis_types/xy/public/plugin.ts b/src/plugins/vis_types/xy/public/plugin.ts index 00b036594e4d4..81689401a8750 100644 --- a/src/plugins/vis_types/xy/public/plugin.ts +++ b/src/plugins/vis_types/xy/public/plugin.ts @@ -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 {}; } diff --git a/src/plugins/vis_types/xy/public/services.ts b/src/plugins/vis_types/xy/public/services.ts index 6ee38238c9cf4..b8a4cc94294d0 100644 --- a/src/plugins/vis_types/xy/public/services.ts +++ b/src/plugins/vis_types/xy/public/services.ts @@ -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('xy charts.palette'); export const [getDocLinks, setDocLinks] = createGetterSetter('DocLinks'); - -export const [getTrackUiMetric, setTrackUiMetric] = - createGetterSetter<(metricType: UiCounterMetricType, eventName: string | string[]) => void>( - 'trackUiMetric' - ); diff --git a/src/plugins/visualizations/kibana.json b/src/plugins/visualizations/kibana.json index 9223f36b36816..62627208561da 100644 --- a/src/plugins/visualizations/kibana.json +++ b/src/plugins/visualizations/kibana.json @@ -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": { diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts index f8975b705caf6..49a083647fbf8 100644 --- a/src/plugins/visualizations/public/mocks.ts +++ b/src/plugins/visualizations/public/mocks.ts @@ -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(), }); diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index 7c704a8916af8..2bd233a5db049 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -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 { diff --git a/src/plugins/visualizations/public/services.ts b/src/plugins/visualizations/public/services.ts index df2ed019c308b..f87597b07462b 100644 --- a/src/plugins/visualizations/public/services.ts +++ b/src/plugins/visualizations/public/services.ts @@ -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('Search'); -export const [getUsageCollector, setUsageCollector] = createGetterSetter( - 'UsageCollection', - false -); - export const [getExpressions, setExpressions] = createGetterSetter('Expressions'); export const [getUiActions, setUiActions] = createGetterSetter('UiActions'); diff --git a/src/plugins/visualizations/public/visualize_app/types.ts b/src/plugins/visualizations/public/visualize_app/types.ts index cf46e0fc4ff1e..e7e213d3a6416 100644 --- a/src/plugins/visualizations/public/visualize_app/types.ts +++ b/src/plugins/visualizations/public/visualize_app/types.ts @@ -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; diff --git a/src/plugins/visualizations/public/visualize_app/utils/get_table_columns.tsx b/src/plugins/visualizations/public/visualize_app/utils/get_table_columns.tsx index 5775d4e5bf535..a2b75d5868b64 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/get_table_columns.tsx +++ b/src/plugins/visualizations/public/visualize_app/utils/get_table_columns.tsx @@ -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 ? ( - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} { - doTelemetryForAddEvent(typeof type === 'string' ? type : type?.name); - }} data-test-subj={`visListingTitleLink-${title.split(' ').join('-')}`} > {field} diff --git a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx index 0fb7f9b818260..8b9b42201a442 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx +++ b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx @@ -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(); } diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx index 839ba364719a2..4ceb93b7ec061 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx @@ -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 { - 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); - }); -}); diff --git a/src/plugins/visualizations/server/usage_collector/get_past_days.ts b/src/plugins/visualizations/server/usage_collector/get_past_days.ts deleted file mode 100644 index 2ffd09ba3c7d9..0000000000000 --- a/src/plugins/visualizations/server/usage_collector/get_past_days.ts +++ /dev/null @@ -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)); -}; diff --git a/src/plugins/visualizations/server/usage_collector/get_usage_collector.mock.ts b/src/plugins/visualizations/server/usage_collector/get_usage_collector.mock.ts deleted file mode 100644 index b1dca4f7f71e4..0000000000000 --- a/src/plugins/visualizations/server/usage_collector/get_usage_collector.mock.ts +++ /dev/null @@ -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, -})); diff --git a/src/plugins/visualizations/server/usage_collector/get_usage_collector.test.ts b/src/plugins/visualizations/server/usage_collector/get_usage_collector.test.ts deleted file mode 100644 index 38e0036feec24..0000000000000 --- a/src/plugins/visualizations/server/usage_collector/get_usage_collector.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/src/plugins/visualizations/server/usage_collector/get_usage_collector.ts b/src/plugins/visualizations/server/usage_collector/get_usage_collector.ts deleted file mode 100644 index f53b4b01864c5..0000000000000 --- a/src/plugins/visualizations/server/usage_collector/get_usage_collector.ts +++ /dev/null @@ -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 { - 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) => { - 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, - }; - }); - } -} diff --git a/src/plugins/visualizations/server/usage_collector/index.ts b/src/plugins/visualizations/server/usage_collector/index.ts deleted file mode 100644 index ca19d049b31ad..0000000000000 --- a/src/plugins/visualizations/server/usage_collector/index.ts +++ /dev/null @@ -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'; diff --git a/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.test.ts b/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.test.ts deleted file mode 100644 index 9f0fbd6571cde..0000000000000 --- a/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.ts b/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.ts deleted file mode 100644 index bf00e83be4b87..0000000000000 --- a/src/plugins/visualizations/server/usage_collector/register_visualizations_collector.ts +++ /dev/null @@ -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({ - 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); -} diff --git a/src/plugins/visualizations/tsconfig.json b/src/plugins/visualizations/tsconfig.json index 9a9cb97d63764..e44374e23f623 100644 --- a/src/plugins/visualizations/tsconfig.json +++ b/src/plugins/visualizations/tsconfig.json @@ -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" }, diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts index bec2383629e7b..1caae58ad5af6 100644 --- a/test/functional/page_objects/visualize_chart_page.ts +++ b/test/functional/page_objects/visualize_chart_page.ts @@ -167,6 +167,8 @@ export class VisualizeChartPageObject extends FtrService { public async filterLegend(name: string, force = false) { await this.toggleLegend(force); await this.testSubjects.click(`legend-${name}`); + // wait for a short amount of time for popover to stabilize as there is no good way to check for that + await this.common.sleep(250); const filterIn = await this.testSubjects.find(`legend-${name}-filterIn`); await filterIn.click(); await this.waitForVisualizationRenderingStabilized(); diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 69e35f1146536..abeff2b867ca6 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -123,6 +123,13 @@ const dependenciesTitle = i18n.translate( } ); +const apmSettingsTitle = i18n.translate( + 'xpack.apm.navigation.apmSettingsTitle', + { + defaultMessage: 'Settings', + } +); + export class ApmPlugin implements Plugin { constructor( private readonly initializerContext: PluginInitializerContext @@ -303,6 +310,7 @@ export class ApmPlugin implements Plugin { title: dependenciesTitle, path: '/dependencies/inventory', }, + { id: 'settings', title: apmSettingsTitle, path: '/settings' }, ], async mount(appMountParameters: AppMountParameters) { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/get_filters.test.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/get_filters.test.ts new file mode 100644 index 0000000000000..39c88981a3698 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/get_filters.test.ts @@ -0,0 +1,56 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { CSP_LATEST_FINDINGS_DATA_VIEW } from '../../../common/constants'; +import { createStubDataView } from '@kbn/data-views-plugin/common/stubs'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { getFilters } from './get_filters'; + +describe('Get Filters', () => { + let dataViewMock: DataView; + const fieldKey = 'some_field_name'; + + beforeEach(() => { + dataViewMock = createStubDataView({ + spec: { + id: CSP_LATEST_FINDINGS_DATA_VIEW, + fields: { + a: { + searchable: false, + aggregatable: false, + name: fieldKey, + type: 'type', + }, + }, + }, + }); + }); + + it('negate an existing filter', () => { + const fields = { + dataView: dataViewMock, + field: fieldKey, + value: 'b', + }; + const initialFilters = getFilters({ + ...fields, + filters: [], + negate: false, + }); + + expect(initialFilters.length).toBe(1); + expect(initialFilters[0].meta.negate).toBe(false); + + const nextFilters = getFilters({ + ...fields, + filters: initialFilters, + negate: true, + }); + + expect(nextFilters.length).toBe(1); + expect(nextFilters[0].meta.negate).toBe(true); + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/get_filters.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/get_filters.ts new file mode 100644 index 0000000000000..fec0efdcaba4c --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/get_filters.ts @@ -0,0 +1,65 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + type Filter, + buildFilter, + FILTERS, + FilterStateStore, + compareFilters, + FilterCompareOptions, +} from '@kbn/es-query'; +import type { Serializable } from '@kbn/utility-types'; +import type { FindingsBaseProps } from './types'; + +const compareOptions: FilterCompareOptions = { + negate: false, +}; + +/** + * adds a new filter to a new filters array + * removes existing filter if negated filter is added + * + * @returns {Filter[]} a new array of filters to be added back to filterManager + */ +export const getFilters = ({ + filters: existingFilters, + dataView, + field, + value, + negate, +}: { + filters: Filter[]; + dataView: FindingsBaseProps['dataView']; + field: string; + value: Serializable; + negate: boolean; +}): Filter[] => { + const dataViewField = dataView.getFieldByName(field); + if (!dataViewField) return existingFilters; + + const phraseFilter = buildFilter( + dataView, + dataViewField, + FILTERS.PHRASE, + negate, + false, + value, + null, + FilterStateStore.APP_STATE + ); + + const nextFilters = [ + ...existingFilters.filter( + // Exclude existing filters that match the newly added 'phraseFilter' + (filter) => !compareFilters(filter, phraseFilter, compareOptions) + ), + phraseFilter, + ]; + + return nextFilters; +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx index c41b6835fb4ff..f72e4b61bd17c 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx @@ -18,7 +18,7 @@ import type { FindingsBaseURLQuery } from '../types'; import { FindingsDistributionBar } from '../layout/findings_distribution_bar'; import { getFindingsPageSizeInfo, - addFilter, + getFilters, getPaginationQuery, getPaginationTableParams, useBaseEsQuery, @@ -119,7 +119,7 @@ export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { onAddFilter={(field, value, negate) => setUrlQuery({ pageIndex: 0, - filters: addFilter({ + filters: getFilters({ filters: urlQuery.filters, dataView, field, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx index e55af94202e3c..6ed71372c4580 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx @@ -17,7 +17,7 @@ import { FindingsByResourceQuery, useFindingsByResource } from './use_findings_b import { FindingsByResourceTable } from './findings_by_resource_table'; import { getFindingsPageSizeInfo, - addFilter, + getFilters, getPaginationQuery, getPaginationTableParams, useBaseEsQuery, @@ -143,7 +143,7 @@ const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { onAddFilter={(field, value, negate) => setUrlQuery({ pageIndex: 0, - filters: addFilter({ + filters: getFilters({ filters: urlQuery.filters, dataView, field, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx index 1302dfafe6603..c3e23d2a865f1 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx @@ -19,7 +19,7 @@ import { useUrlQuery } from '../../../../common/hooks/use_url_query'; import type { FindingsBaseURLQuery, FindingsBaseProps, CspFinding } from '../../types'; import { getFindingsPageSizeInfo, - addFilter, + getFilters, getPaginationQuery, getPaginationTableParams, useBaseEsQuery, @@ -147,7 +147,7 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { onAddFilter={(field, value, negate) => setUrlQuery({ pageIndex: 0, - filters: addFilter({ + filters: getFilters({ filters: urlQuery.filters, dataView, field, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/utils.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/utils.ts index 8b965953826d3..6c4e1a7509f2d 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/utils.ts @@ -5,22 +5,14 @@ * 2.0. */ -import { - buildEsQuery, - type Filter, - buildFilter, - FILTERS, - FilterStateStore, - type Query, -} from '@kbn/es-query'; +import { buildEsQuery, type Query } from '@kbn/es-query'; import { EuiBasicTableProps, Pagination } from '@elastic/eui'; import { useCallback, useEffect, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import type { estypes } from '@elastic/elasticsearch'; -import type { Serializable } from '@kbn/utility-types'; import type { FindingsBaseProps, FindingsBaseURLQuery } from './types'; import { useKibana } from '../../common/hooks/use_kibana'; -import { isNonNullable } from '../../../common/utils/helpers'; +export { getFilters } from './get_filters'; const getBaseQuery = ({ dataView, query, filters }: FindingsBaseURLQuery & FindingsBaseProps) => { try { @@ -137,38 +129,6 @@ export const getAggregationCount = (buckets: estypes.AggregationsStringRareTerms }; }; -export const addFilter = ({ - filters, - dataView, - field, - value, - negate, -}: { - filters: Filter[]; - dataView: FindingsBaseProps['dataView']; - field: string; - value: Serializable; - negate: boolean; -}): Filter[] => { - const dataViewField = dataView.getFieldByName(field); - if (!dataViewField) return filters; - - const singleValue = Array.isArray(value) ? value[0] : value; - - const filter = buildFilter( - dataView, - dataViewField, - FILTERS.PHRASE, - negate, - false, - singleValue, - null, - FilterStateStore.APP_STATE - ); - - return [...filters, filter].filter(isNonNullable); -}; - const FIELDS_WITHOUT_KEYWORD_MAPPING = new Set([ '@timestamp', 'resource.sub_type', diff --git a/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts b/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts index 9921a2d97cdd9..5d5693fcbe93e 100644 --- a/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts +++ b/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts @@ -6,6 +6,7 @@ */ export const elasticAgentStandaloneManifest = `--- +# For more information refer to https://www.elastic.co/guide/en/fleet/current/running-on-kubernetes-standalone.html apiVersion: apps/v1 kind: DaemonSet metadata: @@ -22,6 +23,8 @@ spec: labels: app: elastic-agent spec: + # Tolerations are needed to run Elastic Agent on Kubernetes master nodes. + # Agents running on master nodes collect metrics from the control plane components (scheduler, controller manager) of Kubernetes tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule @@ -37,8 +40,11 @@ spec: "-d", "'*'", ] env: + # The basic authentication username used to connect to Elasticsearch + # This user needs the privileges required to publish events to Elasticsearch. - name: ES_USERNAME value: "elastic" + # The basic authentication password used to connect to Elasticsearch - name: ES_PASSWORD value: "changeme" - name: NODE_NAME @@ -67,6 +73,7 @@ spec: readOnly: true - name: etc-kubernetes mountPath: /hostfs/etc/kubernetes + readOnly: true - name: var-lib mountPath: /hostfs/var/lib readOnly: true @@ -85,7 +92,7 @@ spec: - name: group mountPath: /hostfs/etc/group readOnly: true - - name: systemd + - name: etcsysmd mountPath: /hostfs/etc/systemd readOnly: true volumes: @@ -96,15 +103,19 @@ spec: - name: proc hostPath: path: /proc + # Needed for cloudbeat - name: etc-kubernetes hostPath: path: /etc/kubernetes + # Needed for cloudbeat - name: var-lib hostPath: path: /var/lib + # Needed for cloudbeat - name: passwd hostPath: path: /etc/passwd + # Needed for cloudbeat - name: group hostPath: path: /etc/group @@ -117,7 +128,8 @@ spec: - name: varlog hostPath: path: /var/log - - name: systemd + # Needed for cloudbeat + - name: etcsysmd hostPath: path: /etc/systemd --- @@ -177,6 +189,7 @@ rules: - pods - services - configmaps + # Needed for cloudbeat - serviceaccounts - persistentvolumes - persistentvolumeclaims @@ -208,12 +221,12 @@ rules: - nodes/stats verbs: - get - # required for apiserver + # Needed for apiserver - nonResourceURLs: - "/metrics" verbs: - get - # required for cloudbeat + # Needed for cloudbeat - apiGroups: ["rbac.authorization.k8s.io"] resources: - clusterrolebindings @@ -221,11 +234,7 @@ rules: - rolebindings - roles verbs: ["get", "list", "watch"] - - apiGroups: ["networking.k8s.io"] - resources: - - ingressclasses - - ingresses - verbs: ["get", "list", "watch"] + # Needed for cloudbeat - apiGroups: ["policy"] resources: - podsecuritypolicies @@ -235,7 +244,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: elastic-agent - # should be the namespace where elastic-agent is running + # Should be the namespace where elastic-agent is running namespace: kube-system labels: k8s-app: elastic-agent @@ -272,6 +281,7 @@ metadata: `; export const elasticAgentManagedManifest = `--- +# For more information refer to https://www.elastic.co/guide/en/fleet/current/running-on-kubernetes-managed-by-fleet.html apiVersion: apps/v1 kind: DaemonSet metadata: @@ -288,31 +298,41 @@ spec: labels: app: elastic-agent spec: + # Tolerations are needed to run Elastic Agent on Kubernetes master nodes. + # Agents running on master nodes collect metrics from the control plane components (scheduler, controller manager) of Kubernetes tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule serviceAccountName: elastic-agent hostNetwork: true + # 'hostPID: true' enables the Elastic Security integration to observe all process exec events on the host. + # Sharing the host process ID namespace gives visibility of all processes running on the same host. + hostPID: true dnsPolicy: ClusterFirstWithHostNet containers: - name: elastic-agent image: docker.elastic.co/beats/elastic-agent:VERSION env: + # Set to 1 for enrollment into Fleet server. If not set, Elastic Agent is run in standalone mode - name: FLEET_ENROLL value: "1" - # Set to true in case of insecure or unverified HTTP + # Set to true to communicate with Fleet with either insecure HTTP or unverified HTTPS - name: FLEET_INSECURE value: "true" - # The ip:port pair of fleet server + # Fleet Server URL to enroll the Elastic Agent into + # FLEET_URL can be found in Kibana, go to Management > Fleet > Settings - name: FLEET_URL value: "https://fleet-server:8220" - # If left empty KIBANA_HOST, KIBANA_FLEET_USERNAME, KIBANA_FLEET_PASSWORD are needed + # Elasticsearch API key used to enroll Elastic Agents in Fleet (https://www.elastic.co/guide/en/fleet/current/fleet-enrollment-tokens.html#fleet-enrollment-tokens) + # If FLEET_ENROLLMENT_TOKEN is empty then KIBANA_HOST, KIBANA_FLEET_USERNAME, KIBANA_FLEET_PASSWORD are needed - name: FLEET_ENROLLMENT_TOKEN value: "token-id" - name: KIBANA_HOST value: "http://kibana:5601" + # The basic authentication username used to connect to Kibana and retrieve a service_token to enable Fleet - name: KIBANA_FLEET_USERNAME value: "elastic" + # The basic authentication password used to connect to Kibana and retrieve a service_token to enable Fleet - name: KIBANA_FLEET_PASSWORD value: "changeme" - name: NODE_NAME @@ -337,6 +357,7 @@ spec: readOnly: true - name: etc-kubernetes mountPath: /hostfs/etc/kubernetes + readOnly: true - name: var-lib mountPath: /hostfs/var/lib readOnly: true @@ -355,37 +376,51 @@ spec: - name: group mountPath: /hostfs/etc/group readOnly: true - - name: systemd + - name: etcsysmd mountPath: /hostfs/etc/systemd readOnly: true + - name: etc-mid + mountPath: /etc/machine-id + readOnly: true volumes: - name: proc hostPath: path: /proc + - name: cgroup + hostPath: + path: /sys/fs/cgroup + - name: varlibdockercontainers + hostPath: + path: /var/lib/docker/containers + - name: varlog + hostPath: + path: /var/log + # Needed for cloudbeat - name: etc-kubernetes hostPath: path: /etc/kubernetes + # Needed for cloudbeat - name: var-lib hostPath: path: /var/lib + # Needed for cloudbeat - name: passwd hostPath: path: /etc/passwd + # Needed for cloudbeat - name: group hostPath: path: /etc/group - - name: cgroup - hostPath: - path: /sys/fs/cgroup - - name: varlibdockercontainers - hostPath: - path: /var/lib/docker/containers - - name: varlog - hostPath: - path: /var/log - - name: systemd + # Needed for cloudbeat + - name: etcsysmd hostPath: path: /etc/systemd + # Mount /etc/machine-id from the host to determine host ID + # Needed for Elastic Security integration + - name: etc-mid + hostPath: + path: /etc/machine-id + type: File --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -443,6 +478,7 @@ rules: - pods - services - configmaps + # Needed for cloudbeat - serviceaccounts - persistentvolumes - persistentvolumeclaims @@ -474,12 +510,12 @@ rules: - jobs - cronjobs verbs: [ "get", "list", "watch" ] - # required for apiserver + # Needed for apiserver - nonResourceURLs: - "/metrics" verbs: - get - # required for cloudbeat + # Needed for cloudbeat - apiGroups: ["rbac.authorization.k8s.io"] resources: - clusterrolebindings @@ -487,11 +523,7 @@ rules: - rolebindings - roles verbs: ["get", "list", "watch"] - - apiGroups: ["networking.k8s.io"] - resources: - - ingressclasses - - ingresses - verbs: ["get", "list", "watch"] + # Needed for cloudbeat - apiGroups: ["policy"] resources: - podsecuritypolicies @@ -501,7 +533,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: elastic-agent - # should be the namespace where elastic-agent is running + # Should be the namespace where elastic-agent is running namespace: kube-system labels: k8s-app: elastic-agent diff --git a/x-pack/plugins/infra/common/alerting/metrics/alert_link.test.ts b/x-pack/plugins/infra/common/alerting/metrics/alert_link.test.ts index 80bac365d3562..5e0a9e38c1cf2 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/alert_link.test.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/alert_link.test.ts @@ -7,9 +7,70 @@ import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common/parse_technical_fields'; import { ALERT_RULE_PARAMETERS, TIMESTAMP } from '@kbn/rule-data-utils'; -import { getInventoryViewInAppUrl } from './alert_link'; +import { getInventoryViewInAppUrl, flatAlertRuleParams } from './alert_link'; describe('Inventory Threshold Rule', () => { + describe('flatAlertRuleParams', () => { + it('flat ALERT_RULE_PARAMETERS', () => { + expect( + flatAlertRuleParams( + { + sourceId: 'default', + criteria: [ + { + comparator: '>', + timeSize: 1, + metric: 'cpu', + threshold: [5], + customMetric: { + field: '', + aggregation: 'avg', + id: 'alert-custom-metric', + type: 'custom', + }, + timeUnit: 'm', + }, + ], + nodeType: 'host', + }, + ALERT_RULE_PARAMETERS + ) + ).toMatchInlineSnapshot(` + Object { + "kibana.alert.rule.parameters.criteria.comparator": Array [ + ">", + ], + "kibana.alert.rule.parameters.criteria.customMetric.aggregation": Array [ + "avg", + ], + "kibana.alert.rule.parameters.criteria.customMetric.field": Array [ + "", + ], + "kibana.alert.rule.parameters.criteria.customMetric.id": Array [ + "alert-custom-metric", + ], + "kibana.alert.rule.parameters.criteria.customMetric.type": Array [ + "custom", + ], + "kibana.alert.rule.parameters.criteria.metric": Array [ + "cpu", + ], + "kibana.alert.rule.parameters.criteria.timeSize": Array [ + 1, + ], + "kibana.alert.rule.parameters.criteria.timeUnit": Array [ + "m", + ], + "kibana.alert.rule.parameters.nodeType": Array [ + "host", + ], + "kibana.alert.rule.parameters.sourceId": Array [ + "default", + ], + } + `); + }); + }); describe('getInventoryViewInAppUrl', () => { it('should work with custom metrics', () => { const fields = { @@ -36,5 +97,61 @@ describe('Inventory Threshold Rule', () => { '/app/metrics/link-to/inventory?customMetric=&metric=%28type%3Acpu%29&nodeType=h×tamp=1640995200000' ); }); + + it('should work with custom metrics when ALERT_RULE_PARAMETERS is an object', () => { + const fields = { + '@timestamp': '2022-01-01T00:00:00.000Z', + 'kibana.alert.rule.parameters': { + sourceId: 'default', + criteria: [ + { + comparator: '>', + timeSize: 1, + metric: 'custom', + threshold: [5], + customMetric: { + field: 'system.cpu.user.pct', + aggregation: 'avg', + id: 'alert-custom-metric', + type: 'custom', + }, + timeUnit: 'm', + }, + ], + nodeType: 'host', + }, + _id: 'eaa439aa-a4bb-4e7c-b7f8-fbe532ca7366', + _index: '.internal.alerts-observability.metrics.alerts-default-000001', + } as unknown as ParsedTechnicalFields & Record; + const url = getInventoryViewInAppUrl(fields); + expect(url).toEqual( + '/app/metrics/link-to/inventory?customMetric=%28aggregation%3Aavg%2Cfield%3Asystem.cpu.user.pct%2Cid%3Aalert-custom-metric%2Ctype%3Acustom%29&metric=%28aggregation%3Aavg%2Cfield%3Asystem.cpu.user.pct%2Cid%3Aalert-custom-metric%2Ctype%3Acustom%29&nodeType=host×tamp=1640995200000' + ); + }); + + it('should work with non-custom metrics when ALERT_RULE_PARAMETERS is an object', () => { + const fields = { + '@timestamp': '2022-01-01T00:00:00.000Z', + 'kibana.alert.rule.parameters': { + sourceId: 'default', + criteria: [ + { + comparator: '>', + timeSize: 1, + metric: 'cpu', + threshold: [5], + timeUnit: 'm', + }, + ], + nodeType: 'host', + }, + _id: 'eaa439aa-a4bb-4e7c-b7f8-fbe532ca7366', + _index: '.internal.alerts-observability.metrics.alerts-default-000001', + } as unknown as ParsedTechnicalFields & Record; + const url = getInventoryViewInAppUrl(fields); + expect(url).toEqual( + '/app/metrics/link-to/inventory?customMetric=&metric=%28type%3Acpu%29&nodeType=host×tamp=1640995200000' + ); + }); }); }); diff --git a/x-pack/plugins/infra/common/alerting/metrics/alert_link.ts b/x-pack/plugins/infra/common/alerting/metrics/alert_link.ts index 11068df93b926..a531911b7746b 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/alert_link.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/alert_link.ts @@ -10,26 +10,68 @@ import { encode } from 'rison-node'; import { stringify } from 'query-string'; import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common/parse_technical_fields'; +export const flatAlertRuleParams = (params: {}, pKey = ''): Record => { + return Object.entries(params).reduce((acc, [key, field]) => { + const objectKey = pKey.length ? `${pKey}.${key}` : key; + if (typeof field === 'object' && field != null) { + if (Array.isArray(field) && field.length > 0) { + return { + ...acc, + ...flatAlertRuleParams(field[0] as {}, objectKey), + }; + } else { + return { + ...acc, + ...flatAlertRuleParams(field as {}, objectKey), + }; + } + } + return { + ...acc, + [objectKey]: Array.isArray(field) ? field : [field], + }; + }, {}); +}; + export const getInventoryViewInAppUrl = ( fields: ParsedTechnicalFields & Record ): string => { + let inventoryFields = fields; + + /* Temporary Solution -> https://github.com/elastic/kibana/issues/137033 + * In the alert table from timelines plugin (old table), we are using an API who is flattening all the response + * from elasticsearch to Record, The new alert table API from TriggersActionUI is not doing that + * anymore, it is trusting and returning the way it has been done from the field API from elasticsearch. I think + * it is better to trust elasticsearch and the mapping of the doc. When o11y will only use the new alert table from + * triggersActionUI then we will stop using this flattening way and we will update the code to work with fields API, + * it will be less magic. + */ + if (fields[ALERT_RULE_PARAMETERS]) { + inventoryFields = { + ...fields, + ...flatAlertRuleParams(fields[ALERT_RULE_PARAMETERS] as {}, ALERT_RULE_PARAMETERS), + }; + } + const nodeTypeField = `${ALERT_RULE_PARAMETERS}.nodeType`; - const nodeType = fields[nodeTypeField]; + const nodeType = inventoryFields[nodeTypeField]; let inventoryViewInAppUrl = '/app/metrics/link-to/inventory?'; + if (nodeType) { const linkToParams: Record = { - nodeType: fields[nodeTypeField][0], - timestamp: Date.parse(fields[TIMESTAMP]), + nodeType: inventoryFields[nodeTypeField][0], + timestamp: Date.parse(inventoryFields[TIMESTAMP]), customMetric: '', }; // We always pick the first criteria metric for the URL - const criteriaMetric = fields[`${ALERT_RULE_PARAMETERS}.criteria.metric`][0]; + const criteriaMetric = inventoryFields[`${ALERT_RULE_PARAMETERS}.criteria.metric`][0]; if (criteriaMetric === 'custom') { - const criteriaCustomMetricId = fields[`${ALERT_RULE_PARAMETERS}.criteria.customMetric.id`][0]; + const criteriaCustomMetricId = + inventoryFields[`${ALERT_RULE_PARAMETERS}.criteria.customMetric.id`][0]; const criteriaCustomMetricAggregation = - fields[`${ALERT_RULE_PARAMETERS}.criteria.customMetric.aggregation`][0]; + inventoryFields[`${ALERT_RULE_PARAMETERS}.criteria.customMetric.aggregation`][0]; const criteriaCustomMetricField = - fields[`${ALERT_RULE_PARAMETERS}.criteria.customMetric.field`][0]; + inventoryFields[`${ALERT_RULE_PARAMETERS}.criteria.customMetric.field`][0]; const customMetric = encode({ id: criteriaCustomMetricId, diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index f8dba39f20dd9..5dd2368d2b83a 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -15,7 +15,6 @@ import { tableHasFormulas } from '@kbn/data-plugin/common'; import { exporters, getEsQueryConfig } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { trackUiEvent } from '../lens_ui_telemetry'; import type { StateSetter } from '../types'; import { LensAppServices, @@ -559,7 +558,6 @@ export const LensTopNavMenu = ({ const currentRange = data.query.timefilter.timefilter.getTime(); if (dateRange.from !== currentRange.from || dateRange.to !== currentRange.to) { data.query.timefilter.timefilter.setTime(dateRange); - trackUiEvent('app_date_change'); } else { // Query has changed, renew the session id. // recalculate resolvedDateRange (relevant for relative time range) @@ -567,7 +565,6 @@ export const LensTopNavMenu = ({ searchSessionId: data.search.session.start(), resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), }); - trackUiEvent('app_query_change'); } if (newQuery) { if (!isEqual(newQuery, query)) { diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 8502485f374e1..17e2ae3e0b2ed 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -29,7 +29,6 @@ import { ACTION_CONVERT_TO_LENS } from '@kbn/visualizations-plugin/public'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { EuiLoadingSpinner } from '@elastic/eui'; import { syncQueryStateWithUrl } from '@kbn/data-plugin/public'; -import { LensReportManager, setReportManager, trackUiEvent } from '../lens_ui_telemetry'; import { App } from './app'; import { EditorFrameStart, LensTopNavMenuEntryGenerator } from '../types'; @@ -134,7 +133,7 @@ export async function mountApp( const lensServices = await getLensServices(coreStart, startDependencies, attributeService); - const { stateTransfer, data, storage } = lensServices; + const { stateTransfer, data } = lensServices; const embeddableEditorIncomingState = stateTransfer?.getIncomingEditorState(APP_ID); @@ -154,13 +153,6 @@ export async function mountApp( i18n.translate('xpack.lens.pageTitle', { defaultMessage: 'Lens' }) ); - setReportManager( - new LensReportManager({ - http: core.http, - storage, - }) - ); - const getInitialInput = (id?: string, editByValue?: boolean): LensEmbeddableInput | undefined => { if (editByValue) { return embeddableEditorIncomingState?.valueInput as LensByValueInput; @@ -282,7 +274,6 @@ export async function mountApp( initCallback(); })(); }, [initCallback, initialInput, props.history, redirectCallback]); - trackUiEvent('loaded'); if (editorState === 'loading') { return ; @@ -343,7 +334,6 @@ export async function mountApp( }; function NotFound() { - trackUiEvent('loaded_404'); return ; } // dispatch synthetic hash change event to update hash history objects diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx index 9107141f655de..6769010d8f721 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx @@ -7,7 +7,6 @@ import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { METRIC_TYPE } from '@kbn/analytics'; import { isFilterPinned } from '@kbn/es-query'; import type { SavedObjectReference } from '@kbn/core/public'; @@ -17,7 +16,6 @@ import type { SaveProps } from './app'; import { Document, checkForDuplicateTitle } from '../persistence'; import type { LensByReferenceInput, LensEmbeddableInput } from '../embeddable'; import { APP_ID, getFullPath, LENS_EMBEDDABLE_TYPE } from '../../common'; -import { trackUiEvent } from '../lens_ui_telemetry'; import type { LensAppState } from '../state_management'; import { getPersisted } from '../state_management/init_middleware/load_initial'; @@ -200,7 +198,6 @@ export const runSaveLensVisualization = async ( const { chrome, initialInput, - originatingApp, lastKnownDoc, persistedDoc, savedObjectsClient, @@ -208,7 +205,6 @@ export const runSaveLensVisualization = async ( notifications, stateTransfer, attributeService, - usageCollection, savedObjectsTagging, getIsByValueMode, redirectToOrigin, @@ -221,10 +217,6 @@ export const runSaveLensVisualization = async ( return; } - if (usageCollection) { - usageCollection.reportUiCounter(originatingApp || 'visualize', METRIC_TYPE.CLICK, 'lens:save'); - } - let references = lastKnownDoc.references; if (savedObjectsTagging) { @@ -345,7 +337,6 @@ export const runSaveLensVisualization = async ( } catch (e) { // eslint-disable-next-line no-console console.dir(e); - trackUiEvent('save_failed'); throw e; } }; diff --git a/x-pack/plugins/lens/public/app_plugin/settings_menu.tsx b/x-pack/plugins/lens/public/app_plugin/settings_menu.tsx index cf5d00585090b..e4cd670286ef6 100644 --- a/x-pack/plugins/lens/public/app_plugin/settings_menu.tsx +++ b/x-pack/plugins/lens/public/app_plugin/settings_menu.tsx @@ -24,7 +24,6 @@ import { useLensDispatch, useLensSelector, } from '../state_management'; -import { trackUiEvent } from '../lens_ui_telemetry'; import { writeToStorage } from '../settings_storage'; import { AUTO_APPLY_DISABLED_STORAGE_KEY } from '../editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper'; @@ -46,8 +45,6 @@ export function SettingsMenu({ const dispatch = useLensDispatch(); const toggleAutoApply = useCallback(() => { - trackUiEvent('toggle_autoapply'); - writeToStorage( new Storage(localStorage), AUTO_APPLY_DISABLED_STORAGE_KEY, diff --git a/x-pack/plugins/lens/public/async_services.ts b/x-pack/plugins/lens/public/async_services.ts index 09b434c648418..7c691dc431ab6 100644 --- a/x-pack/plugins/lens/public/async_services.ts +++ b/x-pack/plugins/lens/public/async_services.ts @@ -37,5 +37,4 @@ export * from './editor_frame_service'; export * from './embeddable'; export * from './app_plugin/mounter'; export * from './lens_attribute_service'; -export * from './lens_ui_telemetry'; export * from './app_plugin/save_modal_container'; diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx index d8a526380cd6d..83854b8328651 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx @@ -23,7 +23,6 @@ import { announce, Ghost, } from './providers'; -import { trackUiEvent } from '../lens_ui_telemetry'; import { DropType } from '../types'; export type DroppableEvent = React.DragEvent; @@ -385,7 +384,6 @@ const DragInner = memo(function DragInner({ const dropToActiveDropTarget = () => { if (activeDropTarget) { - trackUiEvent('drop_total'); const { dropType, humanData, onDrop: onTargetDrop } = activeDropTarget; setTimeout(() => setA11yMessage(announce.dropped(value.humanData, humanData, dropType))); onTargetDrop(value, dropType); @@ -953,7 +951,6 @@ const ReorderableDrop = memo(function ReorderableDrop( setKeyboardMode(false); if (onDrop && dragging) { - trackUiEvent('drop_total'); onDrop(dragging, 'reorder'); // setTimeout ensures it will run after dragEnd messaging setTimeout(() => diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index e00998a507cc2..ce4fbbba70236 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -14,7 +14,6 @@ import { } from '@kbn/unified-search-plugin/public'; import { Visualization } from '../../../types'; import { LayerPanel } from './layer_panel'; -import { trackUiEvent } from '../../../lens_ui_telemetry'; import { generateId } from '../../../id_generator'; import { ConfigPanelWrapperProps } from './types'; import { useFocusUpdate } from './use_focus_update'; @@ -222,7 +221,6 @@ export function LayerPanels( onAddLayerClick={(layerType) => { const layerId = generateId(); dispatchLens(addLayer({ layerId, layerType })); - trackUiEvent('layer_added'); setNextFocusedLayerId(layerId); }} /> diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 43ff541d9c92f..ecaf23bb34d41 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -28,7 +28,6 @@ import { } from '../../../types'; import { DragDropIdentifier, ReorderProvider } from '../../../drag_drop'; import { LayerSettings } from './layer_settings'; -import { trackUiEvent } from '../../../lens_ui_telemetry'; import { LayerPanelProps, ActiveDimensionState } from './types'; import { DimensionContainer } from './dimension_container'; import { RemoveLayerButton } from './remove_layer_button'; @@ -456,7 +455,6 @@ export function LayerPanel( }); }} onRemoveClick={(id: string) => { - trackUiEvent('indexpattern_dimension_removed'); if (datasourceId && layerDatasource) { props.updateAll( datasourceId, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 1b91d78f8f6f1..072db723ff326 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -17,7 +17,6 @@ import { WorkspacePanel } from './workspace_panel'; import { DragDropIdentifier, RootDragDropProvider } from '../../drag_drop'; import { EditorFrameStartPlugins } from '../service'; import { getTopSuggestionForField, switchToSuggestion } from './suggestion_helpers'; -import { trackUiEvent } from '../../lens_ui_telemetry'; import { useLensSelector, useLensDispatch, @@ -78,7 +77,6 @@ export function EditorFrame(props: EditorFrameProps) { (field) => { const suggestion = getSuggestionForField.current!(field); if (suggestion) { - trackUiEvent('drop_onto_workspace'); switchToSuggestion(dispatchLens, suggestion, { clearStagedPreview: true }); } }, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index f3f4ed3c39e4e..67403b93d7b8c 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -40,7 +40,6 @@ import { } from '../../types'; import { getSuggestions, switchToSuggestion } from './suggestion_helpers'; import { getDatasourceExpressionsByLayers } from './expression_helpers'; -import { trackUiEvent, trackSuggestionEvent } from '../../lens_ui_telemetry'; import { getMissingIndexPattern, validateDatasourceAndVisualization, @@ -345,7 +344,6 @@ export function SuggestionPanel({ function rollbackToCurrentVisualization() { if (lastSelectedSuggestion !== -1) { - trackSuggestionEvent('back_to_current'); setLastSelectedSuggestion(-1); dispatchLens(rollbackSuggestion()); dispatchLens(applyChanges()); @@ -411,7 +409,6 @@ export function SuggestionPanel({ ExpressionRenderer={AutoRefreshExpressionRenderer} key={index} onSelect={() => { - trackUiEvent('suggestion_clicked'); if (lastSelectedSuggestion === index) { rollbackToCurrentVisualization(); } else { @@ -455,7 +452,6 @@ export function SuggestionPanel({ size="xs" iconType="refresh" onClick={() => { - trackUiEvent('suggestion_confirmed'); dispatchLens(submitSuggestion()); }} > diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx index a611d6d43638d..8c41fc5da8aae 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx @@ -30,7 +30,6 @@ import { Suggestion, } from '../../../types'; import { getSuggestions, switchToSuggestion } from '../suggestion_helpers'; -import { trackUiEvent } from '../../../lens_ui_telemetry'; import { insertLayer, removeLayers, @@ -124,8 +123,6 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { const commitSelection = (selection: VisualizationSelection) => { setFlyoutOpen(false); - trackUiEvent(`chart_switch`); - switchToSuggestion( dispatchLens, { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index e45f23941b070..b18e007077553 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -47,7 +47,6 @@ import { import { DragDrop, DragContext, DragDropIdentifier } from '../../../drag_drop'; import { switchToSuggestion } from '../suggestion_helpers'; import { buildExpression } from '../expression_helpers'; -import { trackUiEvent } from '../../../lens_ui_telemetry'; import { WorkspacePanelWrapper } from './workspace_panel_wrapper'; import { DropIllustration } from '../../../assets/drop_illustration'; import applyChangesIllustrationDark from '../../../assets/render_dark@2x.png'; @@ -358,11 +357,9 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ const onDrop = useCallback(() => { if (suggestionForDraggedField) { - trackUiEvent('drop_onto_workspace'); - trackUiEvent(expressionExists ? 'drop_non_empty' : 'drop_empty'); switchToSuggestion(dispatchLens, suggestionForDraggedField, { clearStagedPreview: true }); } - }, [suggestionForDraggedField, expressionExists, dispatchLens]); + }, [suggestionForDraggedField, dispatchLens]); const IS_DARK_THEME = core.uiSettings.get('theme:darkMode'); @@ -625,7 +622,6 @@ export const VisualizationWrapper = ({ { - trackUiEvent('error_fix_action'); const newState = await validationError.fixAction?.newState({ ...framePublicAPI, ...context, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx index ae087221fd49a..256f452c2ab2a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx @@ -11,7 +11,6 @@ import { EuiPopover, EuiPopoverTitle, EuiSelectableProps } from '@elastic/eui'; import { ToolbarButton, ToolbarButtonProps } from '@kbn/kibana-react-plugin/public'; import { DataViewsList } from '@kbn/unified-search-plugin/public'; import { IndexPatternRef } from './types'; -import { trackUiEvent } from '../lens_ui_telemetry'; export type ChangeIndexPatternTriggerProps = ToolbarButtonProps & { label: string; @@ -81,7 +80,6 @@ export function ChangeIndexPattern({ { - trackUiEvent('indexpattern_changed'); onChangeIndexPattern(newId); setPopoverIsOpen(false); }} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 551e3f85a64fb..d907c25cbaf14 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -42,7 +42,6 @@ import type { IndexPatternField, IndexPatternRef, } from './types'; -import { trackUiEvent } from '../lens_ui_telemetry'; import { loadIndexPatterns, syncExistingFields } from './loader'; import { fieldExists } from './pure_helpers'; import { Loader } from '../loader'; @@ -533,7 +532,6 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ () => editPermission ? async (fieldName?: string, uiAction: 'edit' | 'add' = 'edit') => { - trackUiEvent(`open_field_editor_${uiAction}`); const indexPatternInstance = await dataViews.get(currentIndexPattern.id); closeFieldEditor.current = indexPatternFieldEditor.openEditor({ ctx: { @@ -541,7 +539,6 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ }, fieldName, onSave: async () => { - trackUiEvent(`save_field_${uiAction}`); await refreshFieldList(); }, }); @@ -554,7 +551,6 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ () => editPermission ? async (fieldName: string) => { - trackUiEvent('open_field_delete_modal'); const indexPatternInstance = await dataViews.get(currentIndexPattern.id); closeFieldEditor.current = indexPatternFieldEditor.openDeleteModal({ ctx: { @@ -562,7 +558,6 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ }, fieldName, onDelete: async () => { - trackUiEvent('delete_field'); await refreshFieldList(); }, }); @@ -616,7 +611,6 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ defaultMessage: 'Clear name and type filters', }), onClick: () => { - trackUiEvent('indexpattern_filters_cleared'); clearLocalState(); }, }} @@ -679,7 +673,6 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ icon={localState.typeFilter.includes(type) ? 'check' : 'empty'} data-test-subj={`typeFilter-${type}`} onClick={() => { - trackUiEvent('indexpattern_type_filter_toggled'); setLocalState((s) => ({ ...s, typeFilter: localState.typeFilter.includes(type) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 559c3ef7fff44..e1884808910d1 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -37,7 +37,6 @@ import { hasField } from '../pure_utils'; import { fieldIsInvalid } from '../utils'; import { BucketNestingEditor } from './bucket_nesting_editor'; import type { IndexPattern, IndexPatternField, IndexPatternLayer } from '../types'; -import { trackUiEvent } from '../../lens_ui_telemetry'; import { FormatSelector } from './format_selector'; import { ReferenceEditor } from './reference_editor'; import { TimeScaling } from './time_scaling'; @@ -193,7 +192,6 @@ export function DimensionEditor(props: DimensionEditorProps) { const addStaticValueColumn = (prevLayer = props.state.layers[props.layerId]) => { if (selectedColumn?.operationType !== staticValueOperationName) { - trackUiEvent(`indexpattern_dimension_operation_static_value`); const layer = insertOrReplaceColumn({ layer: prevLayer, indexPattern: currentIndexPattern, @@ -429,7 +427,6 @@ export function DimensionEditor(props: DimensionEditorProps) { setTemporaryState('none'); } setStateWrapper(newLayer); - trackUiEvent(`indexpattern_dimension_operation_${operationType}`); return; } else if (!selectedColumn || !compatibleWithCurrentField) { const possibleFields = fieldByOperation[operationType] || new Set(); @@ -465,7 +462,6 @@ export function DimensionEditor(props: DimensionEditorProps) { setTemporaryState('none'); } setStateWrapper(newLayer); - trackUiEvent(`indexpattern_dimension_operation_${operationType}`); return; } @@ -607,7 +603,6 @@ export function DimensionEditor(props: DimensionEditorProps) { ); }} onChooseField={(choice: FieldChoiceWithOperationType) => { - trackUiEvent('indexpattern_dimension_field_changed'); updateLayer( insertOrReplaceColumn({ layer, @@ -791,7 +786,6 @@ export function DimensionEditor(props: DimensionEditorProps) { visualizationGroups: dimensionGroups, }); setStateWrapper(newLayer); - trackUiEvent(`indexpattern_dimension_operation_formula`); } }, label: i18n.translate('xpack.lens.indexPattern.formulaLabel', { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/on_drop_handler.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/on_drop_handler.ts index 3d57e21e73387..b2c24cd5bf438 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/on_drop_handler.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/on_drop_handler.ts @@ -27,7 +27,6 @@ import { mergeLayer, mergeLayers } from '../../state_helpers'; import { isDraggedField } from '../../pure_utils'; import { getNewOperation, getField } from './get_drop_props'; import { IndexPatternPrivateState, DraggedField, DataViewDragDropOperation } from '../../types'; -import { trackUiEvent } from '../../../lens_ui_telemetry'; interface DropHandlerProps { state: IndexPatternPrivateState; @@ -156,10 +155,6 @@ function onFieldDrop(props: DropHandlerProps, shouldAddField?: boo shouldCombineField: shouldAddField, initialParams, }); - - trackUiEvent('drop_onto_dimension'); - const hasData = Object.values(state.layers).some(({ columns }) => columns.length); - trackUiEvent(hasData ? 'drop_non_empty' : 'drop_empty'); setState(mergeLayer({ state, layerId: target.layerId, newLayer })); return true; } @@ -271,8 +266,6 @@ function onMoveIncompatible( targetGroup: target.groupId, shouldResetLabel: true, }); - - trackUiEvent('drop_onto_dimension'); setState( mergeLayer({ state, @@ -292,8 +285,6 @@ function onMoveIncompatible( targetGroup: target.groupId, shouldResetLabel: true, }); - - trackUiEvent('drop_onto_dimension'); setState( mergeLayers({ state, @@ -352,8 +343,6 @@ function onSwapIncompatible({ targetGroup: source.groupId, shouldResetLabel: true, }); - - trackUiEvent('drop_onto_dimension'); setState( mergeLayer({ state, @@ -373,8 +362,6 @@ function onSwapIncompatible({ targetGroup: source.groupId, shouldResetLabel: true, }); - - trackUiEvent('drop_onto_dimension'); setState( mergeLayers({ state, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx index 16e70f5657db0..6ed035afbc4f3 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx @@ -10,7 +10,6 @@ import { partition } from 'lodash'; import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiComboBoxOptionOption, EuiComboBoxProps } from '@elastic/eui'; -import { trackUiEvent } from '../../lens_ui_telemetry'; import { fieldExists } from '../pure_helpers'; import type { OperationType } from '../indexpattern'; import type { OperationSupportMatrix } from './operation_support'; @@ -162,7 +161,6 @@ export function FieldSelect({ options={memoizedFieldOptions as Array>} onChoose={(choice) => { if (choice && choice.field !== selectedField) { - trackUiEvent('indexpattern_dimension_field_changed'); onChoose(choice); } }} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx index 57782fa157ca7..580d74e3ab63d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx @@ -33,7 +33,6 @@ import type { IndexPatternLayer, IndexPatternPrivateState, } from '../types'; -import { trackUiEvent } from '../../lens_ui_telemetry'; import type { ParamEditorCustomProps } from '../../types'; import type { IndexPatternDimensionEditorProps } from './dimension_panel'; import { FormRow } from '../operations/definitions/shared_components'; @@ -281,7 +280,6 @@ export const ReferenceEditor = (props: ReferenceEditorProps) => { : undefined; onChooseFunction(operationType, field); - trackUiEvent(`indexpattern_dimension_operation_${operationType}`); return; }} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index a0c2a182bee62..316f9c87c7c29 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -48,7 +48,6 @@ import { DatasourceDataPanelProps, DataType } from '../types'; import { BucketedAggregation, DOCUMENT_FIELD_NAME, FieldStatsResponse } from '../../common'; import { IndexPattern, IndexPatternField, DraggedField } from './types'; import { LensFieldIcon } from '../shared_components/field_picker/lens_field_icon'; -import { trackUiEvent } from '../lens_ui_telemetry'; import { VisualizeGeoFieldButton } from './visualize_geo_field_button'; import { getVisualizeGeoFieldMessage } from '../utils'; @@ -187,7 +186,6 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { function togglePopover() { setOpen(!infoIsOpen); if (!infoIsOpen) { - trackUiEvent('indexpattern_field_info_click'); fetchData(); } } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/help_popover.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/help_popover.tsx index a1bbc7ccd55f1..803899b3028fd 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/help_popover.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/help_popover.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { ReactNode, useEffect } from 'react'; +import React, { ReactNode } from 'react'; import { EuiIcon, EuiLink, @@ -17,7 +17,6 @@ import { EuiPopoverTitle, EuiText, } from '@elastic/eui'; -import { trackUiEvent } from '../lens_ui_telemetry'; import './help_popover.scss'; export const HelpPopoverButton = ({ @@ -53,11 +52,6 @@ export const HelpPopover = ({ isOpen: EuiPopoverProps['isOpen']; title?: string; }) => { - useEffect(() => { - if (isOpen) { - trackUiEvent('open_help_popover'); - } - }, [isOpen]); return ( { - useEffect(() => { - if (isOpen) { - trackUiEvent('open_help_popover'); - } - }, [isOpen]); return ( { - if (!isHelpOpen) { - trackUiEvent('open_formula_popover'); - } setIsHelpOpen(!isHelpOpen); }} iconType="documentation" diff --git a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts deleted file mode 100644 index 080e2cf5d495f..0000000000000 --- a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.test.ts +++ /dev/null @@ -1,110 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - LensReportManager, - setReportManager, - stopReportManager, - trackUiEvent, - trackSuggestionEvent, -} from './factory'; -import { coreMock } from '@kbn/core/public/mocks'; -import { HttpSetup } from '@kbn/core/public'; -import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; - -jest.useFakeTimers(); - -const createMockStorage = () => { - let lastData = { events: {}, suggestionEvents: {} }; - return { - get: jest.fn().mockImplementation(() => lastData), - set: jest.fn().mockImplementation((key, value) => { - lastData = value; - }), - remove: jest.fn(), - clear: jest.fn(), - }; -}; - -describe('Lens UI telemetry', () => { - let storage: jest.Mocked; - let http: jest.Mocked; - let dateSpy: jest.SpyInstance; - - beforeEach(() => { - dateSpy = jest - .spyOn(Date, 'now') - .mockImplementation(() => new Date(Date.UTC(2019, 9, 23)).valueOf()); - - storage = createMockStorage(); - http = coreMock.createSetup().http; - http.post.mockClear(); - const fakeManager = new LensReportManager({ - http, - storage, - }); - setReportManager(fakeManager); - }); - - afterEach(() => { - stopReportManager(); - dateSpy.mockRestore(); - }); - - it('should write immediately and track local state', () => { - trackUiEvent('loaded'); - - expect(storage.set).toHaveBeenCalledWith('lens-ui-telemetry', { - events: expect.any(Object), - suggestionEvents: {}, - }); - - trackSuggestionEvent('reload'); - - expect(storage.set).toHaveBeenLastCalledWith('lens-ui-telemetry', { - events: expect.any(Object), - suggestionEvents: expect.any(Object), - }); - }); - - it('should post the results after waiting 10 seconds, if there is data', async () => { - jest.runOnlyPendingTimers(); - - http.post.mockResolvedValue({}); - - expect(http.post).not.toHaveBeenCalled(); - expect(storage.set).toHaveBeenCalledTimes(0); - - trackUiEvent('load'); - expect(storage.set).toHaveBeenCalledTimes(1); - - jest.runOnlyPendingTimers(); - - expect(http.post).toHaveBeenCalledWith(`/api/lens/stats`, { - body: JSON.stringify({ - events: { - '2019-10-23': { - load: 1, - }, - }, - suggestionEvents: {}, - }), - }); - }); - - it('should keep its local state after an http error', () => { - http.post.mockRejectedValue('http error'); - - trackUiEvent('load'); - expect(storage.set).toHaveBeenCalledTimes(1); - - jest.runOnlyPendingTimers(); - - expect(http.post).toHaveBeenCalled(); - expect(storage.set).toHaveBeenCalledTimes(1); - }); -}); diff --git a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts deleted file mode 100644 index bad1bad481e8b..0000000000000 --- a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts +++ /dev/null @@ -1,129 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import moment from 'moment'; -import { HttpSetup } from '@kbn/core/public'; - -import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; -import { BASE_API_URL } from '../../common'; - -const STORAGE_KEY = 'lens-ui-telemetry'; - -let reportManager: LensReportManager; - -export function setReportManager(newManager: LensReportManager) { - if (reportManager) { - reportManager.stop(); - } - reportManager = newManager; -} - -export function stopReportManager() { - if (reportManager) { - reportManager.stop(); - } -} - -export function trackUiEvent(name: string) { - if (reportManager) { - reportManager.trackEvent(name); - } -} - -export function trackSuggestionEvent(name: string) { - if (reportManager) { - reportManager.trackSuggestionEvent(name); - } -} - -export class LensReportManager { - private events: Record> = {}; - private suggestionEvents: Record> = {}; - - private storage: IStorageWrapper; - private http: HttpSetup; - private timer: ReturnType; - - constructor({ storage, http }: { storage: IStorageWrapper; http: HttpSetup }) { - this.storage = storage; - this.http = http; - - this.readFromStorage(); - - this.timer = setInterval(() => { - this.postToServer(); - }, 10000); - } - - public trackEvent(name: string) { - this.readFromStorage(); - this.trackTo(this.events, name); - } - - public trackSuggestionEvent(name: string) { - this.readFromStorage(); - this.trackTo(this.suggestionEvents, name); - } - - public stop() { - if (this.timer) { - clearInterval(this.timer); - } - } - - private readFromStorage() { - const data = this.storage.get(STORAGE_KEY); - if (data && typeof data.events === 'object' && typeof data.suggestionEvents === 'object') { - this.events = data.events; - this.suggestionEvents = data.suggestionEvents; - } - } - - private async postToServer() { - this.readFromStorage(); - if (Object.keys(this.events).length || Object.keys(this.suggestionEvents).length) { - try { - await this.http.post(`${BASE_API_URL}/stats`, { - body: JSON.stringify({ - events: this.events, - suggestionEvents: this.suggestionEvents, - }), - }); - this.events = {}; - this.suggestionEvents = {}; - this.write(); - } catch (e) { - // Silent error because events will be reported during the next timer - - // If posting stats is forbidden for the current user, stop attempting to send them, - // but keep them in storage to push in case the user logs in with sufficient permissions at some point. - if (e.response && e.response.status === 403) { - this.stop(); - } - } - } - } - - private trackTo(target: Record>, name: string) { - const date = moment().utc().format('YYYY-MM-DD'); - if (!target[date]) { - target[date] = { - [name]: 1, - }; - } else if (!target[date][name]) { - target[date][name] = 1; - } else { - target[date][name] += 1; - } - - this.write(); - } - - private write() { - this.storage.set(STORAGE_KEY, { events: this.events, suggestionEvents: this.suggestionEvents }); - } -} diff --git a/x-pack/plugins/lens/public/lens_ui_telemetry/index.ts b/x-pack/plugins/lens/public/lens_ui_telemetry/index.ts deleted file mode 100644 index 974d07503a376..0000000000000 --- a/x-pack/plugins/lens/public/lens_ui_telemetry/index.ts +++ /dev/null @@ -1,8 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './factory'; diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index a312f338f8504..8dd2a5278d8ce 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -231,8 +231,6 @@ export class LensPlugin { private hasDiscoverAccess: boolean = false; private dataViewsService: DataViewsPublicPluginStart | undefined; - private stopReportManager?: () => void; - setup( core: CoreSetup, { @@ -335,10 +333,7 @@ export class LensPlugin { eventAnnotation ); - const { mountApp, stopReportManager, getLensAttributeService } = await import( - './async_services' - ); - this.stopReportManager = stopReportManager; + const { mountApp, getLensAttributeService } = await import('./async_services'); const frameStart = this.editorFrameService!.start(coreStart, deps); return mountApp(core, params, { @@ -508,9 +503,5 @@ export class LensPlugin { }; } - stop() { - if (this.stopReportManager) { - this.stopReportManager(); - } - } + stop() {} } diff --git a/x-pack/plugins/lens/public/state_management/context_middleware/subscribe_to_external_context.ts b/x-pack/plugins/lens/public/state_management/context_middleware/subscribe_to_external_context.ts index 60a4f5670070e..abc3f388db5e9 100644 --- a/x-pack/plugins/lens/public/state_management/context_middleware/subscribe_to_external_context.ts +++ b/x-pack/plugins/lens/public/state_management/context_middleware/subscribe_to_external_context.ts @@ -8,7 +8,6 @@ import { delay, finalize, switchMap, tap } from 'rxjs/operators'; import { debounce, isEqual } from 'lodash'; import { waitUntilNextSessionCompletes$, DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { trackUiEvent } from '../../lens_ui_telemetry'; import { setState, LensGetState, LensDispatch } from '..'; import { getResolvedDateRange } from '../../utils'; @@ -53,7 +52,6 @@ export function subscribeToExternalContext( const filterSubscription = filterManager.getUpdates$().subscribe({ next: () => { debounceDispatchFromExternal(); - trackUiEvent('app_filters_updated'); }, }); diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx index d3a7552954d13..d5523a0b10914 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx @@ -12,7 +12,6 @@ import { ToolbarButton } from '@kbn/kibana-react-plugin/public'; import type { VisualizationLayerWidgetProps, VisualizationType } from '../../types'; import { State, visualizationTypes, SeriesType } from '../types'; import { isHorizontalChart, isHorizontalSeries } from '../state_helpers'; -import { trackUiEvent } from '../../lens_ui_telemetry'; import { StaticHeader } from '../../shared_components'; import { LensIconChartBarReferenceLine } from '../../assets/chart_bar_reference_line'; import { LensIconChartBarAnnotations } from '../../assets/chart_bar_annotations'; @@ -107,7 +106,6 @@ function DataLayerHeader(props: VisualizationLayerWidgetProps) { return; } const id = chosenType.value!; - trackUiEvent('xy_change_layer_display'); props.setState(updateLayer(state, { ...layer, seriesType: id as SeriesType }, index)); setPopoverIsOpen(false); }} diff --git a/x-pack/plugins/lens/server/plugin.tsx b/x-pack/plugins/lens/server/plugin.tsx index f7b39832eca03..5671eb833ef66 100644 --- a/x-pack/plugins/lens/server/plugin.tsx +++ b/x-pack/plugins/lens/server/plugin.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import { Plugin, CoreSetup, CoreStart, PluginInitializerContext, Logger } from '@kbn/core/server'; -import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; +import { Plugin, CoreSetup, CoreStart, PluginInitializerContext } from '@kbn/core/server'; import { PluginStart as DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; import { PluginStart as DataPluginStart, @@ -23,18 +22,12 @@ import { import { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; import { setupRoutes } from './routes'; import { getUiSettings } from './ui_settings'; -import { - registerLensUsageCollector, - initializeLensTelemetry, - scheduleLensTelemetry, -} from './usage'; import { setupSavedObjects } from './saved_objects'; import { setupExpressions } from './expressions'; import { makeLensEmbeddableFactory } from './embeddable/make_lens_embeddable_factory'; import type { CustomVisualizationMigrations } from './migrations/types'; export interface PluginSetupContract { - usageCollection?: UsageCollectionSetup; taskManager?: TaskManagerSetupContract; embeddable: EmbeddableSetup; expressions: ExpressionsServerSetup; @@ -63,12 +56,9 @@ export interface LensServerPluginSetup { } export class LensServerPlugin implements Plugin { - private readonly telemetryLogger: Logger; private customVisualizationMigrations: CustomVisualizationMigrations = {}; - constructor(private initializerContext: PluginInitializerContext) { - this.telemetryLogger = initializerContext.logger.get('usage'); - } + constructor(private initializerContext: PluginInitializerContext) {} setup(core: CoreSetup, plugins: PluginSetupContract) { const getFilterMigrations = plugins.data.query.filterManager.getAllMigrations.bind( @@ -79,16 +69,6 @@ export class LensServerPlugin implements Plugin taskManager as TaskManagerStartContract) - ); - initializeLensTelemetry(this.telemetryLogger, core, plugins.taskManager); - } - const lensEmbeddableFactory = makeLensEmbeddableFactory( getFilterMigrations, this.customVisualizationMigrations @@ -109,9 +89,6 @@ export class LensServerPlugin implements Plugin, logger: Logger) { existingFieldsRoute(setup, logger); initFieldsRoute(setup); - initLensUsageRoute(setup); } diff --git a/x-pack/plugins/lens/server/routes/telemetry.ts b/x-pack/plugins/lens/server/routes/telemetry.ts deleted file mode 100644 index 01c843cf5489d..0000000000000 --- a/x-pack/plugins/lens/server/routes/telemetry.ts +++ /dev/null @@ -1,93 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { errors } from '@elastic/elasticsearch'; -import { CoreSetup } from '@kbn/core/server'; -import { schema } from '@kbn/config-schema'; -import { SavedObjectsErrorHelpers } from '@kbn/core/server'; -import { BASE_API_URL } from '../../common'; -import { PluginStartContract } from '../plugin'; - -// This route is responsible for taking a batch of click events from the browser -// and writing them to saved objects -export async function initLensUsageRoute(setup: CoreSetup) { - const router = setup.http.createRouter(); - router.post( - { - path: `${BASE_API_URL}/stats`, - validate: { - body: schema.object({ - events: schema.mapOf(schema.string(), schema.mapOf(schema.string(), schema.number())), - suggestionEvents: schema.mapOf( - schema.string(), - schema.mapOf(schema.string(), schema.number()) - ), - }), - }, - }, - async (context, req, res) => { - const { events, suggestionEvents } = req.body; - - try { - const client = (await context.core).savedObjects.client; - - const allEvents: Array<{ - type: 'lens-ui-telemetry'; - attributes: {}; - }> = []; - - events.forEach((subMap, date) => { - subMap.forEach((count, key) => { - allEvents.push({ - type: 'lens-ui-telemetry', - attributes: { - name: key, - date, - count, - type: 'regular', - }, - }); - }); - }); - suggestionEvents.forEach((subMap, date) => { - subMap.forEach((count, key) => { - allEvents.push({ - type: 'lens-ui-telemetry', - attributes: { - name: key, - date, - count, - type: 'suggestion', - }, - }); - }); - }); - - if (allEvents.length) { - await client.bulkCreate(allEvents); - } - - return res.ok({ body: {} }); - } catch (e) { - if (SavedObjectsErrorHelpers.isForbiddenError(e)) { - return res.forbidden(); - } - if (e instanceof errors.ResponseError && e.statusCode === 404) { - return res.notFound(); - } - if (e.isBoom) { - if (e.output.statusCode === 404) { - return res.notFound(); - } - throw new Error(e.output.message); - } else { - throw e; - } - } - } - ); -} diff --git a/x-pack/plugins/lens/server/usage/collectors.ts b/x-pack/plugins/lens/server/usage/collectors.ts deleted file mode 100644 index b857b31129398..0000000000000 --- a/x-pack/plugins/lens/server/usage/collectors.ts +++ /dev/null @@ -1,119 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import moment from 'moment'; -import { get } from 'lodash'; -import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; -import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; - -import { LensUsage, LensTelemetryState } from './types'; -import { lensUsageSchema } from './schema'; - -const emptyUsageCollection = { - saved_multiterms_overall: {}, - saved_multiterms_30_days: {}, - saved_multiterms_90_days: {}, - saved_overall: {}, - saved_30_days: {}, - saved_90_days: {}, - saved_overall_total: 0, - saved_30_days_total: 0, - saved_90_days_total: 0, - events_30_days: {}, - events_90_days: {}, - suggestion_events_30_days: {}, - suggestion_events_90_days: {}, -}; - -export function registerLensUsageCollector( - usageCollection: UsageCollectionSetup, - taskManager: Promise -) { - const lensUsageCollector = usageCollection.makeUsageCollector({ - type: 'lens', - async fetch() { - try { - const docs = await getLatestTaskState(await taskManager); - // get the accumulated state from the recurring task - const state: LensTelemetryState = get(docs, '[0].state'); - - const events = getDataByDate(state.byDate); - const suggestions = getDataByDate(state.suggestionsByDate); - - return { - ...emptyUsageCollection, - ...state.saved, - ...state.multiterms, - events_30_days: events.last30, - events_90_days: events.last90, - suggestion_events_30_days: suggestions.last30, - suggestion_events_90_days: suggestions.last90, - }; - } catch (err) { - return emptyUsageCollection; - } - }, - isReady: async () => { - await taskManager; - return true; - }, - schema: lensUsageSchema, - }); - - usageCollection.registerCollector(lensUsageCollector); -} - -function addEvents(prevEvents: Record, newEvents: Record) { - Object.keys(newEvents).forEach((key) => { - prevEvents[key] = (prevEvents[key] || 0) + newEvents[key]; - }); -} - -async function getLatestTaskState(taskManager: TaskManagerStartContract) { - try { - const result = await taskManager.fetch({ - query: { bool: { filter: { term: { _id: `task:Lens-lens_telemetry` } } } }, - }); - return result.docs; - } catch (err) { - const errMessage = err && err.message ? err.message : err.toString(); - /* - The usage service WILL to try to fetch from this collector before the task manager has been initialized, because the - task manager has to wait for all plugins to initialize first. It's fine to ignore it as next time around it will be - initialized (or it will throw a different type of error) - */ - if (!errMessage.includes('NotInitialized')) { - throw err; - } - } - - return null; -} - -function getDataByDate(dates: Record>) { - const byDate = Object.keys(dates || {}).map((dateStr) => parseInt(dateStr, 10)); - - const last30: Record = {}; - const last90: Record = {}; - - const last30Timestamp = moment().subtract(30, 'days').unix(); - const last90Timestamp = moment().subtract(90, 'days').unix(); - - byDate.forEach((dateKey) => { - if (dateKey >= last30Timestamp) { - addEvents(last30, dates[dateKey]); - addEvents(last90, dates[dateKey]); - } else if (dateKey > last90Timestamp) { - addEvents(last90, dates[dateKey]); - } - }); - - return { - last30, - last90, - }; -} diff --git a/x-pack/plugins/lens/server/usage/multiterms_count.ts b/x-pack/plugins/lens/server/usage/multiterms_count.ts deleted file mode 100644 index 26ebbb8393892..0000000000000 --- a/x-pack/plugins/lens/server/usage/multiterms_count.ts +++ /dev/null @@ -1,106 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ElasticsearchClient } from '@kbn/core/server'; -import { createMetricQuery } from './saved_objects_metric_factory'; -import { LensMultitermsUsage } from './types'; - -export async function getMultitermsCounts( - getEsClient: () => Promise, - kibanaIndex: string -): Promise { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function bucketsToObject(arg: any) { - const obj: Record = {}; - if (arg.multitermsDocs.doc_count > 0) { - obj.multiterms_docs = arg.multitermsDocs.doc_count; - obj.multiterms_terms_count = arg.multitermsTermsCount.value; - obj.multiterms_operations_count = arg.multitermsOperationsCount.value; - } - return obj; - } - - const forEachMultitermsOperationScript = (operationToApply: string) => { - return ` - try { - if(doc['lens.state'].size() == 0) return; - HashMap layers = params['_source'].get('lens').get('state').get('datasourceStates').get('indexpattern').get('layers'); - for(layerId in layers.keySet()) { - HashMap columns = layers.get(layerId).get('columns'); - for(columnId in columns.keySet()) { - if(columns.get(columnId).get('operationType') == 'terms'){ - if(columns.get(columnId).get('params').get('secondaryFields').size() > 0){ - ${operationToApply} - } - } - } - } - } catch(Exception e) {}`; - }; - - const fn = createMetricQuery(getEsClient, kibanaIndex); - - const result = await fn({ - aggregations: { - multitermsOperationsCount: { - sum: { - field: 'multiterms_operations_count', - }, - }, - multitermsTermsCount: { - sum: { - field: 'multiterms_count', - }, - }, - multitermsDocs: { - filter: { - match: { - operation_type: 'multiterms', - }, - }, - }, - }, - runtimeMappings: { - operation_type: { - type: 'keyword', - script: { - lang: 'painless', - source: forEachMultitermsOperationScript("emit('multiterms');"), - }, - }, - multiterms_count: { - type: 'double', - script: { - lang: 'painless', - source: ` - double terms = 0; - ${forEachMultitermsOperationScript( - "terms += columns.get(columnId).get('params').get('secondaryFields').size() + 1;" - )} - emit(terms);`, - }, - }, - multiterms_operations_count: { - type: 'double', - script: { - lang: 'painless', - source: ` - double operations = 0; - ${forEachMultitermsOperationScript('operations += 1;')} - emit(operations);`, - }, - }, - }, - bucketsToObject, - }); - // remap the result with the multiterms shape - return { - saved_multiterms_overall: result.saved_overall, - saved_multiterms_30_days: result.saved_30_days, - saved_multiterms_90_days: result.saved_90_days, - }; -} diff --git a/x-pack/plugins/lens/server/usage/saved_objects_metric_factory.ts b/x-pack/plugins/lens/server/usage/saved_objects_metric_factory.ts deleted file mode 100644 index 890c2ec29decc..0000000000000 --- a/x-pack/plugins/lens/server/usage/saved_objects_metric_factory.ts +++ /dev/null @@ -1,84 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - AggregationsAggregationContainer, - MappingRuntimeFields, -} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { ElasticsearchClient } from '@kbn/core/server'; -import { GenericSavedUsage } from './types'; - -export function createMetricQuery( - getEsClient: () => Promise, - kibanaIndex: string -) { - return async function ({ - aggregations, - runtimeMappings, - bucketsToObject, - }: { - aggregations: Record; - runtimeMappings?: MappingRuntimeFields; - bucketsToObject?: (arg: unknown) => Record; - }): Promise { - const esClient = await getEsClient(); - const results = await esClient.search({ - index: kibanaIndex, - body: { - query: { - bool: { - filter: [{ term: { type: 'lens' } }], - }, - }, - aggs: { - groups: { - filters: { - filters: { - last30: { bool: { filter: { range: { updated_at: { gte: 'now-30d' } } } } }, - last90: { bool: { filter: { range: { updated_at: { gte: 'now-90d' } } } } }, - overall: { match_all: {} }, - }, - }, - aggs: { - ...aggregations, - }, - }, - }, - runtime_mappings: { - ...runtimeMappings, - }, - size: 0, - }, - }); - - // @ts-expect-error specify aggregations type explicitly - const buckets = results.aggregations.groups.buckets; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function bucketsToObjectFallback(arg: any) { - const obj: Record = {}; - const key = Object.keys(arg).find((argKey) => arg[argKey]?.buckets?.length); - if (key) { - arg[key].buckets.forEach((bucket: { key: string; doc_count: number }) => { - obj[bucket.key] = bucket.doc_count + (obj[bucket.key] ?? 0); - }); - } - return obj; - } - - const mapFn = bucketsToObject ?? bucketsToObjectFallback; - - return { - saved_overall: mapFn(buckets.overall), - saved_30_days: mapFn(buckets.last30), - saved_90_days: mapFn(buckets.last90), - saved_overall_total: buckets.overall.doc_count, - saved_30_days_total: buckets.last30.doc_count, - saved_90_days_total: buckets.last90.doc_count, - }; - }; -} diff --git a/x-pack/plugins/lens/server/usage/schema.ts b/x-pack/plugins/lens/server/usage/schema.ts deleted file mode 100644 index 08264de0be416..0000000000000 --- a/x-pack/plugins/lens/server/usage/schema.ts +++ /dev/null @@ -1,262 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server'; -import { LensUsage } from './types'; - -const eventsSchema: MakeSchemaFrom = { - app_query_change: { type: 'long' }, - open_help_popover: { - type: 'long', - _meta: { description: 'Number of times the user opened one of the in-product help popovers.' }, - }, - error_fix_action: { - type: 'long', - _meta: { - description: - 'Number of times the user used the fix action of an error displayed in the workspace.', - }, - }, - open_formula_popover: { - type: 'long', - _meta: { description: 'Number of times the user opened the in-product formula help popover.' }, - }, - toggle_autoapply: { - type: 'long', - _meta: { - description: 'Number of times the user toggled auto-apply.', - }, - }, - toggle_fullscreen_formula: { - type: 'long', - _meta: { - description: 'Number of times the user toggled fullscreen mode on formula.', - }, - }, - indexpattern_field_info_click: { type: 'long' }, - loaded: { type: 'long' }, - app_filters_updated: { type: 'long' }, - app_date_change: { type: 'long' }, - save_failed: { type: 'long' }, - loaded_404: { type: 'long' }, - drop_total: { type: 'long' }, - chart_switch: { type: 'long' }, - suggestion_confirmed: { type: 'long' }, - suggestion_clicked: { type: 'long' }, - drop_onto_workspace: { type: 'long' }, - drop_non_empty: { type: 'long' }, - drop_empty: { type: 'long' }, - indexpattern_changed: { type: 'long' }, - indexpattern_filters_cleared: { type: 'long' }, - indexpattern_type_filter_toggled: { type: 'long' }, - indexpattern_existence_toggled: { type: 'long' }, - indexpattern_show_all_fields_clicked: { type: 'long' }, - drop_onto_dimension: { type: 'long' }, - indexpattern_dimension_removed: { type: 'long' }, - indexpattern_dimension_field_changed: { type: 'long' }, - xy_change_layer_display: { type: 'long' }, - xy_layer_removed: { type: 'long' }, - xy_layer_added: { type: 'long' }, - open_field_editor_edit: { - type: 'long', - _meta: { - description: - 'Number of times the user opened the editor flyout to edit a field from within Lens.', - }, - }, - open_field_editor_add: { - type: 'long', - _meta: { - description: - 'Number of times the user opened the editor flyout to add a field from within Lens.', - }, - }, - save_field_edit: { - type: 'long', - _meta: { - description: 'Number of times the user edited a field from within Lens.', - }, - }, - save_field_add: { - type: 'long', - _meta: { - description: 'Number of times the user added a field from within Lens.', - }, - }, - open_field_delete_modal: { - type: 'long', - _meta: { - description: 'Number of times the user opened the field delete modal from within Lens.', - }, - }, - delete_field: { - type: 'long', - _meta: { - description: 'Number of times the user deleted a field from within Lens.', - }, - }, - indexpattern_dimension_operation_terms: { - type: 'long', - _meta: { - description: 'Number of times the top values function was selected', - }, - }, - indexpattern_dimension_operation_date_histogram: { - type: 'long', - _meta: { - description: 'Number of times the date histogram function was selected', - }, - }, - indexpattern_dimension_operation_avg: { - type: 'long', - _meta: { - description: 'Number of times the average function was selected', - }, - }, - indexpattern_dimension_operation_min: { - type: 'long', - _meta: { - description: 'Number of times the min function was selected', - }, - }, - indexpattern_dimension_operation_max: { - type: 'long', - _meta: { - description: 'Number of times the max function was selected', - }, - }, - indexpattern_dimension_operation_sum: { - type: 'long', - _meta: { - description: 'Number of times the sum function was selected', - }, - }, - indexpattern_dimension_operation_count: { - type: 'long', - _meta: { - description: 'Number of times the count function was selected', - }, - }, - indexpattern_dimension_operation_cardinality: { - type: 'long', - _meta: { - description: 'Number of times the cardinality function was selected', - }, - }, - indexpattern_dimension_operation_filters: { - type: 'long', - _meta: { - description: 'Number of times the filters function was selected', - }, - }, - indexpattern_dimension_operation_range: { - type: 'long', - _meta: { description: 'Number of times the range function was selected' }, - }, - indexpattern_dimension_operation_median: { - type: 'long', - _meta: { description: 'Number of times the median function was selected' }, - }, - indexpattern_dimension_operation_percentile: { - type: 'long', - _meta: { description: 'Number of times the percentile function was selected' }, - }, - indexpattern_dimension_operation_last_value: { - type: 'long', - _meta: { description: 'Number of times the last value function was selected' }, - }, - indexpattern_dimension_operation_cumulative_sum: { - type: 'long', - _meta: { description: 'Number of times the cumulative sum function was selected' }, - }, - indexpattern_dimension_operation_counter_rate: { - type: 'long', - _meta: { description: 'Number of times the counter rate function was selected' }, - }, - indexpattern_dimension_operation_derivative: { - type: 'long', - _meta: { description: 'Number of times the derivative function was selected' }, - }, - indexpattern_dimension_operation_moving_average: { - type: 'long', - _meta: { description: 'Number of times the moving average function was selected' }, - }, - indexpattern_dimension_operation_formula: { - type: 'long', - _meta: { description: 'Number of times the formula function was selected' }, - }, -}; - -const suggestionEventsSchema: MakeSchemaFrom = { - back_to_current: { type: 'long' }, - reload: { type: 'long' }, -}; - -const savedSchema: MakeSchemaFrom = { - bar: { type: 'long' }, - bar_horizontal: { type: 'long' }, - line: { type: 'long' }, - area: { type: 'long' }, - bar_stacked: { type: 'long' }, - bar_percentage_stacked: { type: 'long' }, - bar_horizontal_stacked: { type: 'long' }, - bar_horizontal_percentage_stacked: { type: 'long' }, - area_stacked: { type: 'long' }, - area_percentage_stacked: { type: 'long' }, - lnsDatatable: { type: 'long' }, - lnsPie: { type: 'long' }, - lnsMetric: { type: 'long' }, - formula: { - type: 'long', - _meta: { - description: 'Number of saved lens visualizations which are using at least one formula', - }, - }, -}; - -const savedMultitermsSchema: MakeSchemaFrom = { - multiterms_docs: { - type: 'long', - _meta: { - description: - 'Number of saved lens visualizations which are using at least one multiterms operation', - }, - }, - multiterms_terms_count: { - type: 'long', - _meta: { - description: 'Sum of terms used for multiterms operations of saved lens visualizations', - }, - }, - multiterms_operations_count: { - type: 'long', - _meta: { - description: 'Sum of operations using multiterms of saved lens visualizations', - }, - }, -}; - -export const lensUsageSchema: MakeSchemaFrom = { - // LensClickUsage - events_30_days: eventsSchema, - events_90_days: eventsSchema, - suggestion_events_30_days: suggestionEventsSchema, - suggestion_events_90_days: suggestionEventsSchema, - - // LensVisualizationUsage - saved_overall_total: { type: 'long' }, - saved_30_days_total: { type: 'long' }, - saved_90_days_total: { type: 'long' }, - - saved_overall: savedSchema, - saved_30_days: savedSchema, - saved_90_days: savedSchema, - - saved_multiterms_overall: savedMultitermsSchema, - saved_multiterms_30_days: savedMultitermsSchema, - saved_multiterms_90_days: savedMultitermsSchema, -}; diff --git a/x-pack/plugins/lens/server/usage/task.ts b/x-pack/plugins/lens/server/usage/task.ts deleted file mode 100644 index f4a805bef7401..0000000000000 --- a/x-pack/plugins/lens/server/usage/task.ts +++ /dev/null @@ -1,215 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { CoreSetup, Logger, ElasticsearchClient } from '@kbn/core/server'; -import moment from 'moment'; -import { - RunContext, - TaskManagerSetupContract, - TaskManagerStartContract, -} from '@kbn/task-manager-plugin/server'; - -import { ESSearchResponse } from '@kbn/core/types/elasticsearch'; -import { getVisualizationCounts } from './visualization_counts'; -import { getMultitermsCounts } from './multiterms_count'; - -// This task is responsible for running daily and aggregating all the Lens click event objects -// into daily rolled-up documents, which will be used in reporting click stats - -const TELEMETRY_TASK_TYPE = 'lens_telemetry'; - -export const TASK_ID = `Lens-${TELEMETRY_TASK_TYPE}`; - -export function initializeLensTelemetry( - logger: Logger, - core: CoreSetup, - taskManager: TaskManagerSetupContract -) { - registerLensTelemetryTask(logger, core, taskManager); -} - -export function scheduleLensTelemetry(logger: Logger, taskManager?: TaskManagerStartContract) { - if (taskManager) { - scheduleTasks(logger, taskManager); - } -} - -function registerLensTelemetryTask( - logger: Logger, - core: CoreSetup, - taskManager: TaskManagerSetupContract -) { - taskManager.registerTaskDefinitions({ - [TELEMETRY_TASK_TYPE]: { - title: 'Lens usage fetch task', - timeout: '1m', - createTaskRunner: telemetryTaskRunner(logger, core), - }, - }); -} - -async function scheduleTasks(logger: Logger, taskManager: TaskManagerStartContract) { - try { - await taskManager.ensureScheduled({ - id: TASK_ID, - taskType: TELEMETRY_TASK_TYPE, - state: { byDate: {}, suggestionsByDate: {}, saved: {}, runs: 0 }, - params: {}, - }); - } catch (e) { - logger.debug(`Error scheduling task, received ${e.message}`); - } -} - -export async function getDailyEvents( - kibanaIndex: string, - getEsClient: () => Promise -): Promise<{ - byDate: Record>; - suggestionsByDate: Record>; -}> { - const esClient = await getEsClient(); - const aggs = { - daily: { - date_histogram: { - field: 'lens-ui-telemetry.date', - calendar_interval: '1d' as const, - min_doc_count: 1, - }, - aggs: { - groups: { - filters: { - filters: { - suggestionEvents: { - bool: { - filter: { - term: { 'lens-ui-telemetry.type': 'suggestion' }, - }, - }, - }, - regularEvents: { - bool: { - must_not: { - term: { 'lens-ui-telemetry.type': 'suggestion' }, - }, - }, - }, - }, - }, - aggs: { - names: { - terms: { field: 'lens-ui-telemetry.name', size: 100 }, - aggs: { - sums: { sum: { field: 'lens-ui-telemetry.count' } }, - }, - }, - }, - }, - }, - }, - }; - - const metrics = await esClient.search>( - { - index: kibanaIndex, - body: { - query: { - bool: { - filter: [ - { term: { type: 'lens-ui-telemetry' } }, - { range: { 'lens-ui-telemetry.date': { gte: 'now-90d/d' } } }, - ], - }, - }, - aggs, - }, - size: 0, - } - ); - - const byDateByType: Record> = {}; - const suggestionsByDate: Record> = {}; - - // @ts-expect-error no way to declare aggregations for search response - metrics.aggregations!.daily.buckets.forEach((daily) => { - const byType: Record = byDateByType[daily.key] || {}; - // @ts-expect-error no way to declare aggregations for search response - daily.groups.buckets.regularEvents.names.buckets.forEach((bucket) => { - byType[bucket.key] = (bucket.sums.value || 0) + (byType[daily.key] || 0); - }); - byDateByType[daily.key] = byType; - - const suggestionsByType: Record = suggestionsByDate[daily.key] || {}; - // @ts-expect-error no way to declare aggregations for search response - daily.groups.buckets.suggestionEvents.names.buckets.forEach((bucket) => { - suggestionsByType[bucket.key] = - (bucket.sums.value || 0) + (suggestionsByType[daily.key] || 0); - }); - suggestionsByDate[daily.key] = suggestionsByType; - }); - - // Always delete old date because we don't report it - await esClient.deleteByQuery({ - index: kibanaIndex, - wait_for_completion: true, - body: { - query: { - bool: { - filter: [ - { term: { type: 'lens-ui-telemetry' } }, - { range: { 'lens-ui-telemetry.date': { lt: 'now-90d/d' } } }, - ], - }, - }, - }, - }); - - return { - byDate: byDateByType, - suggestionsByDate, - }; -} - -export function telemetryTaskRunner(logger: Logger, core: CoreSetup) { - return ({ taskInstance }: RunContext) => { - const { state } = taskInstance; - const getEsClient = async () => { - const [coreStart] = await core.getStartServices(); - return coreStart.elasticsearch.client.asInternalUser; - }; - - return { - async run() { - const kibanaIndex = core.savedObjects.getKibanaIndex(); - - return Promise.all([ - getDailyEvents(kibanaIndex, getEsClient), - getVisualizationCounts(getEsClient, kibanaIndex), - getMultitermsCounts(getEsClient, kibanaIndex), - ]) - .then(([lensTelemetry, lensVisualizations, lensMultiterms]) => { - return { - state: { - runs: (state.runs || 0) + 1, - byDate: (lensTelemetry && lensTelemetry.byDate) || {}, - suggestionsByDate: (lensTelemetry && lensTelemetry.suggestionsByDate) || {}, - saved: lensVisualizations, - multiterms: lensMultiterms, - }, - runAt: getNextMidnight(), - }; - }) - .catch((errMsg) => logger.warn(`Error executing lens telemetry task: ${errMsg}`)); - }, - async cancel() {}, - }; - }; -} - -function getNextMidnight() { - return moment().add(1, 'day').startOf('day').toDate(); -} diff --git a/x-pack/plugins/lens/server/usage/types.ts b/x-pack/plugins/lens/server/usage/types.ts deleted file mode 100644 index f440232b233c3..0000000000000 --- a/x-pack/plugins/lens/server/usage/types.ts +++ /dev/null @@ -1,47 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export interface LensTelemetryState { - runs: number; - byDate: Record>; - suggestionsByDate: Record>; - saved: LensVisualizationUsage; - multiterms: LensMultitermsUsage; -} - -export interface LensVisualizationUsage { - saved_overall: Record; - saved_30_days: Record; - saved_90_days: Record; - saved_overall_total: number; - saved_30_days_total: number; - saved_90_days_total: number; -} - -export interface LensMultitermsUsage { - saved_multiterms_overall: Record; - saved_multiterms_30_days: Record; - saved_multiterms_90_days: Record; -} - -export interface LensClickUsage { - events_30_days: Record; - events_90_days: Record; - suggestion_events_30_days: Record; - suggestion_events_90_days: Record; -} - -export interface GenericSavedUsage { - saved_overall: Record; - saved_30_days: Record; - saved_90_days: Record; - saved_overall_total: number; - saved_30_days_total: number; - saved_90_days_total: number; -} - -export type LensUsage = LensVisualizationUsage & LensMultitermsUsage & LensClickUsage; diff --git a/x-pack/plugins/lens/server/usage/visualization_counts.ts b/x-pack/plugins/lens/server/usage/visualization_counts.ts deleted file mode 100644 index 34d9c2c4ee49e..0000000000000 --- a/x-pack/plugins/lens/server/usage/visualization_counts.ts +++ /dev/null @@ -1,73 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ElasticsearchClient } from '@kbn/core/server'; -import { createMetricQuery } from './saved_objects_metric_factory'; -import { LensVisualizationUsage } from './types'; - -export function getVisualizationCounts( - getEsClient: () => Promise, - kibanaIndex: string -): Promise { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function bucketsToObject(arg: any) { - const obj: Record = {}; - arg.byType.buckets.forEach((bucket: { key: string; doc_count: number }) => { - obj[bucket.key] = bucket.doc_count + (obj[bucket.key] ?? 0); - }); - if (arg.usesFormula.doc_count > 0) { - obj.formula = arg.usesFormula.doc_count; - } - return obj; - } - - return createMetricQuery( - getEsClient, - kibanaIndex - )({ - aggregations: { - byType: { - terms: { - // The script relies on having flattened keyword mapping for the Lens saved object, - // without this kind of mapping we would not be able to access `lens.state` in painless - script: ` - String visType = doc['lens.visualizationType'].value; - String niceType = visType == 'lnsXY' ? doc['lens.state.visualization.preferredSeriesType'].value : visType; - return niceType; - `, - size: 100, - }, - }, - usesFormula: { - filter: { - match: { - operation_type: 'formula', - }, - }, - }, - }, - runtimeMappings: { - operation_type: { - type: 'keyword', - script: { - lang: 'painless', - source: `try { - if(doc['lens.state'].size() == 0) return; - HashMap layers = params['_source'].get('lens').get('state').get('datasourceStates').get('indexpattern').get('layers'); - for(layerId in layers.keySet()) { - HashMap columns = layers.get(layerId).get('columns'); - for(columnId in columns.keySet()) { - emit(columns.get(columnId).get('operationType')) - } - } - } catch(Exception e) {}`, - }, - }, - }, - bucketsToObject, - }); -} diff --git a/x-pack/plugins/observability/public/config/register_alerts_table_configuration.tsx b/x-pack/plugins/observability/public/config/register_alerts_table_configuration.tsx index e2d3dc1130750..f119b07def3b7 100644 --- a/x-pack/plugins/observability/public/config/register_alerts_table_configuration.tsx +++ b/x-pack/plugins/observability/public/config/register_alerts_table_configuration.tsx @@ -7,10 +7,12 @@ import type { GetRenderCellValue } from '@kbn/triggers-actions-ui-plugin/public'; import { observabilityFeatureId } from '../../common'; +import { useBulkAddToCaseActions } from '../hooks/use_alert_bulk_case_actions'; import { TopAlert, useToGetInternalFlyout } from '../pages/alerts'; import { getRenderCellValue } from '../pages/alerts/components/render_cell_value'; import { addDisplayNames } from '../pages/alerts/containers/alerts_table_t_grid/add_display_names'; import { columns as alertO11yColumns } from '../pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid'; +import { getRowActions } from '../pages/alerts/containers/alerts_table_t_grid/get_row_actions'; import type { ObservabilityRuleTypeRegistry } from '../rules/create_observability_rule_type_registry'; const getO11yAlertsTableConfiguration = ( @@ -22,9 +24,11 @@ const getO11yAlertsTableConfiguration = ( const { header, body, footer } = useToGetInternalFlyout(observabilityRuleTypeRegistry); return { header, body, footer }; }, + useActionsColumn: getRowActions(observabilityRuleTypeRegistry), getRenderCellValue: (({ setFlyoutAlert }: { setFlyoutAlert: (data: TopAlert) => void }) => { return getRenderCellValue({ observabilityRuleTypeRegistry, setFlyoutAlert }); }) as unknown as GetRenderCellValue, + useBulkActions: useBulkAddToCaseActions, }); export { getO11yAlertsTableConfiguration }; diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx index 9afe75f2ae310..ed2c3b6ae192b 100644 --- a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx @@ -63,7 +63,7 @@ import { getRenderCellValue } from '../../components/render_cell_value'; import { observabilityAppId, observabilityFeatureId } from '../../../../../common'; import { useGetUserCasesPermissions } from '../../../../hooks/use_get_user_cases_permissions'; import { usePluginContext } from '../../../../hooks/use_plugin_context'; -import { LazyAlertsFlyout } from '../../../..'; +import { LazyAlertsFlyout, ObservabilityRuleTypeRegistry } from '../../../..'; import { parseAlert } from '../../components/parse_alert'; import { translations, paths } from '../../../../config'; import { addDisplayNames } from './add_display_names'; @@ -82,9 +82,13 @@ interface AlertsTableTGridProps { itemsPerPage?: number; } -interface ObservabilityActionsProps extends ActionProps { +export type ObservabilityActionsProps = Pick< + ActionProps, + 'data' | 'eventId' | 'ecsData' | 'setEventsDeleted' +> & { setFlyoutAlert: React.Dispatch>; -} + observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; +}; const EventsThContent = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__thContent ${className}`, @@ -142,13 +146,13 @@ const NO_ROW_RENDER: RowRenderer[] = []; const trailingControlColumns: never[] = []; -function ObservabilityActions({ +export function ObservabilityActions({ data, eventId, ecsData, + observabilityRuleTypeRegistry, setFlyoutAlert, }: ObservabilityActionsProps) { - const { observabilityRuleTypeRegistry } = usePluginContext(); const dataFieldEs = data.reduce((acc, d) => ({ ...acc, [d.field]: d.value }), {}); const [openActionsPopoverId, setActionsPopover] = useState(null); const { cases, http } = useKibana().services; @@ -263,42 +267,40 @@ function ObservabilityActions({ return ( <> - - - - - - - - - toggleActionsPopover(eventId)} - data-test-subj="alertsTableRowActionMore" - /> - - } - isOpen={openActionsPopoverId === eventId} - closePopover={closeActionsPopover} - panelPaddingSize="none" - anchorPosition="downLeft" - > - - - - + + + + + + + + toggleActionsPopover(eventId)} + data-test-subj="alertsTableRowActionMore" + /> + + } + isOpen={openActionsPopoverId === eventId} + closePopover={closeActionsPopover} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + ); } @@ -377,16 +379,19 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) { }, rowCellRender: (actionProps: ActionProps) => { return ( - + + + ); }, }, ]; - }, [setEventsDeleted]); + }, [setEventsDeleted, observabilityRuleTypeRegistry]); const onStateChange = useCallback( (state: TGridState) => { diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/get_row_actions.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/get_row_actions.tsx new file mode 100644 index 0000000000000..61fc0afbe16ef --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/get_row_actions.tsx @@ -0,0 +1,36 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EcsFieldsResponse } from '@kbn/rule-registry-plugin/common/search_strategy'; +import { ObservabilityRuleTypeRegistry } from '../../../../rules/create_observability_rule_type_registry'; +import { ObservabilityActions } from './alerts_table_t_grid'; +import type { ObservabilityActionsProps } from './alerts_table_t_grid'; + +const buildData = (alerts: EcsFieldsResponse): ObservabilityActionsProps['data'] => { + return Object.entries(alerts).reduce( + (acc, [field, value]) => [...acc, { field, value }], + [] + ); +}; +const fakeSetEventsDeleted = () => []; +export const getRowActions = (observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry) => { + return () => ({ + renderCustomActionsRow: (alert: EcsFieldsResponse, setFlyoutAlert: (data: unknown) => void) => { + return ( + + ); + }, + width: 120, + }); +}; diff --git a/x-pack/plugins/observability/public/pages/rule_details/index.tsx b/x-pack/plugins/observability/public/pages/rule_details/index.tsx index b99a20b8db07a..c0884dd6a134a 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/index.tsx +++ b/x-pack/plugins/observability/public/pages/rule_details/index.tsx @@ -34,6 +34,7 @@ import { import { ALERTS_FEATURE_ID, RuleExecutionStatusErrorReasons } from '@kbn/alerting-plugin/common'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { RuleDefinitionProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; import { DeleteModalConfirmation } from './components/delete_modal_confirmation'; import { CenterJustifiedSpinner } from './components/center_justified_spinner'; import { RuleDetailsPathParams, EVENT_LOG_LIST_TAB, ALERT_LIST_TAB } from './types'; @@ -42,15 +43,17 @@ import { usePluginContext } from '../../hooks/use_plugin_context'; import { useFetchRule } from '../../hooks/use_fetch_rule'; import { RULES_BREADCRUMB_TEXT } from '../rules/translations'; import { PageTitle } from './components'; -import { useKibana } from '../../utils/kibana_react'; import { getHealthColor } from './config'; import { hasExecuteActionsCapability, hasAllPrivilege } from './config'; import { paths } from '../../config/paths'; import { observabilityFeatureId } from '../../../common'; import { ALERT_STATUS_LICENSE_ERROR, rulesStatusesTranslationsMapping } from './translations'; +import { ObservabilityAppServices } from '../../application/types'; +import { useGetUserCasesPermissions } from '../../hooks/use_get_user_cases_permissions'; export function RuleDetailsPage() { const { + cases, http, triggersActionsUi: { alertsTableConfigurationRegistry, @@ -63,7 +66,7 @@ export function RuleDetailsPage() { }, application: { capabilities, navigateToUrl }, notifications: { toasts }, - } = useKibana().services; + } = useKibana().services; const { ruleId } = useParams(); const { ObservabilityPageTemplate, observabilityRuleTypeRegistry } = usePluginContext(); @@ -150,7 +153,13 @@ export function RuleDetailsPage() { ? !ruleTypeRegistry.get(rule.ruleTypeId).requiresAppContext : false); + const userPermissions = useGetUserCasesPermissions(); + const alertStateProps = { + cases: { + ui: cases.ui, + permissions: userPermissions, + }, alertsTableConfigurationRegistry, configurationId: observabilityFeatureId, id: `case-details-alerts-o11y`, diff --git a/x-pack/plugins/osquery/common/constants.ts b/x-pack/plugins/osquery/common/constants.ts index 5354332fd89f8..4d6be1f3f2ae8 100644 --- a/x-pack/plugins/osquery/common/constants.ts +++ b/x-pack/plugins/osquery/common/constants.ts @@ -9,3 +9,5 @@ export const DEFAULT_MAX_TABLE_QUERY_SIZE = 10000; export const DEFAULT_DARK_MODE = 'theme:darkMode'; export const OSQUERY_INTEGRATION_NAME = 'osquery_manager'; export const BASE_PATH = '/app/osquery'; +export const ACTIONS_INDEX = `.logs-${OSQUERY_INTEGRATION_NAME}.actions`; +export const ACTION_RESPONSES_INDEX = `.logs-${OSQUERY_INTEGRATION_NAME}.action.responses`; diff --git a/x-pack/plugins/osquery/common/schemas/common/schemas.ts b/x-pack/plugins/osquery/common/schemas/common/schemas.ts index 24eaa11a7bf84..fda6e2cec8b50 100644 --- a/x-pack/plugins/osquery/common/schemas/common/schemas.ts +++ b/x-pack/plugins/osquery/common/schemas/common/schemas.ts @@ -53,6 +53,20 @@ export type SavedQueryId = t.TypeOf; export const savedQueryIdOrUndefined = t.union([savedQueryId, t.undefined]); export type SavedQueryIdOrUndefined = t.TypeOf; +export const packId = t.string; +export type PackId = t.TypeOf; +export const packIdOrUndefined = t.union([packId, t.undefined]); +export type PackIdOrUndefined = t.TypeOf; + +export const executionContext = t.type({ + name: t.union([t.string, t.undefined]), + url: t.union([t.string, t.undefined]), +}); + +export type ExecutionContext = t.TypeOf; +export const executionContextOrUndefined = t.union([executionContext, t.undefined]); +export type ExecutionContextOrUndefined = t.TypeOf; + export const ecsMapping = t.record( t.string, t.partial({ @@ -63,3 +77,32 @@ export const ecsMapping = t.record( export type ECSMapping = t.TypeOf; export const ecsMappingOrUndefined = t.union([ecsMapping, t.undefined]); export type ECSMappingOrUndefined = t.TypeOf; + +export const stringArrayOrUndefined = t.union([t.array(t.string), t.undefined]); +export type StringArrayOrUndefined = t.TypeOf; + +export const arrayQueries = t.array( + t.type({ + id, + query, + ecsMapping, + version, + platform, + }) +); +export type ArrayQueries = t.TypeOf; +export const objectQueries = t.record( + t.string, + t.type({ + query, + ecsMapping: ecsMappingOrUndefined, + version: versionOrUndefined, + platform: platformOrUndefined, + saved_query_id: savedQueryIdOrUndefined, + }) +); +export type ObjectQueries = t.TypeOf; +export const queries = t.union([arrayQueries, objectQueries]); +export type Queries = t.TypeOf; +export const queriesOrUndefined = t.union([queries, t.undefined]); +export type QueriesOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/osquery/common/schemas/routes/action/create_action_request_body_schema.ts b/x-pack/plugins/osquery/common/schemas/routes/action/create_action_request_body_schema.ts deleted file mode 100644 index a85471a95a137..0000000000000 --- a/x-pack/plugins/osquery/common/schemas/routes/action/create_action_request_body_schema.ts +++ /dev/null @@ -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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { - query, - agentSelection, - ecsMappingOrUndefined, - savedQueryIdOrUndefined, -} from '../../common/schemas'; - -export const createActionRequestBodySchema = t.type({ - agentSelection, - query, - saved_query_id: savedQueryIdOrUndefined, - ecs_mapping: ecsMappingOrUndefined, -}); - -export type CreateActionRequestBodySchema = t.OutputOf; diff --git a/x-pack/plugins/osquery/common/schemas/routes/action/index.ts b/x-pack/plugins/osquery/common/schemas/routes/action/index.ts deleted file mode 100644 index 286aa2e5128b2..0000000000000 --- a/x-pack/plugins/osquery/common/schemas/routes/action/index.ts +++ /dev/null @@ -1,8 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './create_action_request_body_schema'; diff --git a/x-pack/plugins/osquery/common/schemas/routes/live_query/create_live_query_request_body_schema.ts b/x-pack/plugins/osquery/common/schemas/routes/live_query/create_live_query_request_body_schema.ts new file mode 100644 index 0000000000000..6d37ab6d56288 --- /dev/null +++ b/x-pack/plugins/osquery/common/schemas/routes/live_query/create_live_query_request_body_schema.ts @@ -0,0 +1,35 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; + +import { + ecsMappingOrUndefined, + savedQueryIdOrUndefined, + packIdOrUndefined, + queryOrUndefined, + queriesOrUndefined, + stringArrayOrUndefined, +} from '../../common/schemas'; + +export const createLiveQueryRequestBodySchema = t.type({ + agent_ids: stringArrayOrUndefined, + agent_all: t.union([t.boolean, t.undefined]), + agent_platforms: stringArrayOrUndefined, + agent_policy_ids: stringArrayOrUndefined, + query: queryOrUndefined, + queries: queriesOrUndefined, + saved_query_id: savedQueryIdOrUndefined, + ecs_mapping: ecsMappingOrUndefined, + pack_id: packIdOrUndefined, + alert_ids: stringArrayOrUndefined, + case_ids: stringArrayOrUndefined, + event_ids: stringArrayOrUndefined, + metadata: t.union([t.object, t.undefined]), +}); + +export type CreateLiveQueryRequestBodySchema = t.OutputOf; diff --git a/x-pack/plugins/lens/server/usage/index.ts b/x-pack/plugins/osquery/common/schemas/routes/live_query/index.ts similarity index 81% rename from x-pack/plugins/lens/server/usage/index.ts rename to x-pack/plugins/osquery/common/schemas/routes/live_query/index.ts index ea657f59cd77a..0438204e6c86e 100644 --- a/x-pack/plugins/lens/server/usage/index.ts +++ b/x-pack/plugins/osquery/common/schemas/routes/live_query/index.ts @@ -5,5 +5,4 @@ * 2.0. */ -export * from './collectors'; -export * from './task'; +export * from './create_live_query_request_body_schema'; diff --git a/x-pack/plugins/osquery/common/search_strategy/common/index.ts b/x-pack/plugins/osquery/common/search_strategy/common/index.ts index a0b7b5719cbcc..6139682935b66 100644 --- a/x-pack/plugins/osquery/common/search_strategy/common/index.ts +++ b/x-pack/plugins/osquery/common/search_strategy/common/index.ts @@ -20,12 +20,6 @@ export interface Inspect { dsl: string[]; } -export interface PageInfoPaginated { - activePage: number; - fakeTotalCount: number; - showMorePagesIndicator: boolean; -} - export interface CursorType { value?: Maybe; tiebreaker?: Maybe; @@ -64,8 +58,6 @@ export interface PaginationInputPaginated { activePage: number; /** The cursorStart parameter defines the start of the results to be displayed */ cursorStart: number; - /** The fakePossibleCount parameter determines the total count in order to show 5 additional pages */ - fakePossibleCount: number; /** The querySize parameter is the number of items to be returned */ querySize: number; } diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts index 217f150f514a4..283ae46269422 100644 --- a/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts @@ -6,9 +6,9 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { IEsSearchResponse, IKibanaSearchResponse } from '@kbn/data-plugin/common'; -import type { Inspect, Maybe, PageInfoPaginated } from '../../common'; +import type { Inspect, Maybe } from '../../common'; import type { RequestOptions, RequestOptionsPaginated } from '../..'; export type ActionEdges = estypes.SearchResponse['hits']['hits']; @@ -16,16 +16,40 @@ export type ActionEdges = estypes.SearchResponse['hits']['hits']; export type ActionResultEdges = estypes.SearchResponse['hits']['hits']; export interface ActionsStrategyResponse extends IEsSearchResponse { edges: ActionEdges; - totalCount: number; - pageInfo: PageInfoPaginated; inspect?: Maybe; } +export interface ActionDetails { + action_id: string; + expiration: string; + '@timestamp': string; + agent_all: boolean; + agent_ids: string[]; + agent_platforoms: string[]; + agent_policy_ids: string[]; + agents: string[]; + user_id?: string; + pack_id?: string; + pack_name?: string; + pack_prebuilt?: boolean; + status?: string; + queries?: Array<{ + action_id: string; + id: string; + query: string; + agents: string[]; + ecs_mapping?: unknown; + version?: string; + platform?: string; + saved_query_id?: string; + expiration?: string; + }>; +} + export type ActionsRequestOptions = RequestOptionsPaginated; export interface ActionDetailsStrategyResponse extends IEsSearchResponse { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - actionDetails: Record; + actionDetails: estypes.SearchHit; inspect?: Maybe; } @@ -33,10 +57,26 @@ export interface ActionDetailsRequestOptions extends RequestOptions { actionId: string; } -export interface ActionResultsStrategyResponse extends IEsSearchResponse { +export interface ActionResultsStrategyResponse + extends IKibanaSearchResponse< + estypes.SearchResponse< + object, + { + aggs: { + responses_by_action_id: estypes.AggregationsSingleBucketAggregateBase & { + rows_count: estypes.AggregationsSumAggregate; + responses: { + buckets: Array<{ + key: string; + doc_count: number; + }>; + }; + }; + }; + } + > + > { edges: ActionResultEdges; - totalCount: number; - pageInfo: PageInfoPaginated; inspect?: Maybe; } diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts index 53492f937db58..06447beb18eac 100644 --- a/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts @@ -7,14 +7,12 @@ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import type { Inspect, Maybe, PageInfoPaginated } from '../../common'; +import type { Inspect, Maybe } from '../../common'; import type { RequestOptionsPaginated } from '../..'; import type { Agent } from '../../../shared_imports'; export interface AgentsStrategyResponse extends IEsSearchResponse { edges: Agent[]; - totalCount: number; - pageInfo: PageInfoPaginated; inspect?: Maybe; } diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts index b089d1fcf6484..b8985297b3062 100644 --- a/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts @@ -40,6 +40,7 @@ export interface RequestBasicOptions extends IEsSearchRequest { aggregations?: Record; docValueFields?: DocValueFields[]; factoryQueryType?: FactoryQueryTypes; + componentTemplateExists?: boolean; } /** A mapping of semantic fields to their document counterparts */ diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts index 5a0e14f99ed70..d7777e389dde8 100644 --- a/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts @@ -8,15 +8,13 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import type { Inspect, Maybe, PageInfoPaginated, SortField } from '../../common'; +import type { Inspect, Maybe, SortField } from '../../common'; import type { RequestOptionsPaginated } from '../..'; export type ResultEdges = estypes.SearchResponse['hits']['hits']; export interface ResultsStrategyResponse extends IEsSearchResponse { edges: ResultEdges; - totalCount: number; - pageInfo: PageInfoPaginated; inspect?: Maybe; } diff --git a/x-pack/plugins/osquery/common/types.ts b/x-pack/plugins/osquery/common/types.ts index ef2c077056b86..981dbef21de7c 100644 --- a/x-pack/plugins/osquery/common/types.ts +++ b/x-pack/plugins/osquery/common/types.ts @@ -5,12 +5,6 @@ * 2.0. */ -import type { - PackagePolicy, - PackagePolicyInput, - PackagePolicyInputStream, -} from '@kbn/fleet-plugin/common'; - export const savedQuerySavedObjectType = 'osquery-saved-query'; export const packSavedObjectType = 'osquery-pack'; export const packAssetSavedObjectType = 'osquery-pack-asset'; @@ -36,42 +30,3 @@ export type RequiredKeepUndefined = { [K in keyof T]-?: [T[K]] } extends infe ? { [K in keyof U]: U[K][0] } : never : never; - -export interface OsqueryManagerPackagePolicyConfigRecordEntry { - type: string; - value: string; - frozen?: boolean; -} - -export interface OsqueryManagerPackagePolicyConfigRecord { - id: OsqueryManagerPackagePolicyConfigRecordEntry; - query: OsqueryManagerPackagePolicyConfigRecordEntry; - interval: OsqueryManagerPackagePolicyConfigRecordEntry; - platform?: OsqueryManagerPackagePolicyConfigRecordEntry; - version?: OsqueryManagerPackagePolicyConfigRecordEntry; - ecs_mapping?: - | { - value: Record< - string, - { - field: string; - } - >; - } - | undefined; -} - -export interface OsqueryManagerPackagePolicyInputStream - extends Omit { - config?: OsqueryManagerPackagePolicyConfigRecord; - vars?: OsqueryManagerPackagePolicyConfigRecord; -} - -export interface OsqueryManagerPackagePolicyInput extends Omit { - streams: OsqueryManagerPackagePolicyInputStream[]; -} - -export interface OsqueryManagerPackagePolicy extends Omit { - inputs: OsqueryManagerPackagePolicyInput[]; - read_only?: boolean; -} diff --git a/x-pack/plugins/osquery/common/utils/build_query/filters.ts b/x-pack/plugins/osquery/common/utils/build_query/filters.ts index a9a9b3319661b..0d82a581e27d9 100644 --- a/x-pack/plugins/osquery/common/utils/build_query/filters.ts +++ b/x-pack/plugins/osquery/common/utils/build_query/filters.ts @@ -6,8 +6,26 @@ */ import { isEmpty, isString } from 'lodash/fp'; - +import type { PaginationInputPaginated, Inspect } from '../../search_strategy'; import type { ESQuery } from '../../typed_json'; export const createQueryFilterClauses = (filterQuery: ESQuery | string | undefined) => !isEmpty(filterQuery) ? [isString(filterQuery) ? JSON.parse(filterQuery) : filterQuery] : []; + +export const createFilter = (filterQuery: ESQuery | string | undefined) => + isString(filterQuery) ? filterQuery : JSON.stringify(filterQuery); + +export type InspectResponse = Inspect & { response: string[] }; + +export const generateTablePaginationOptions = ( + activePage: number, + limit: number +): PaginationInputPaginated => { + const cursorStart = activePage * limit; + + return { + activePage, + cursorStart, + querySize: limit, + }; +}; diff --git a/x-pack/plugins/osquery/cypress/integration/all/add_integration.spec.ts b/x-pack/plugins/osquery/cypress/integration/all/add_integration.spec.ts index 1ad43e5003827..a6850e7fdbc03 100644 --- a/x-pack/plugins/osquery/cypress/integration/all/add_integration.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/all/add_integration.spec.ts @@ -76,7 +76,8 @@ describe('ALL - Add Integration', () => { addIntegration(); cy.contains('osquery_manager-'); }); - it('should have integration and packs copied when upgrading integration', () => { + + it.skip('should have integration and packs copied when upgrading integration', () => { const packageName = 'osquery_manager'; const oldVersion = '1.2.0'; const newVersion = '1.3.1'; diff --git a/x-pack/plugins/osquery/cypress/integration/all/alerts.spec.ts b/x-pack/plugins/osquery/cypress/integration/all/alerts.spec.ts index 4ef3e263df01c..80516f480e803 100644 --- a/x-pack/plugins/osquery/cypress/integration/all/alerts.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/all/alerts.spec.ts @@ -58,11 +58,11 @@ describe('Alert Event Details', () => { cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'true'); }); - it('should be able to run live query and add to timeline (-depending on the previous test)', () => { + it.skip('should be able to run live query and add to timeline (-depending on the previous test)', () => { const TIMELINE_NAME = 'Untitled timeline'; cy.visit('/app/security/alerts'); cy.getBySel('header-page-title').contains('Alerts').should('exist'); - cy.getBySel('timeline-context-menu-button').first().click({ force: true }); + cy.getBySel('timeline-context-menu-button').first().click(); cy.getBySel('osquery-action-item').should('exist').contains('Run Osquery'); cy.getBySel('expand-event').first().click(); cy.getBySel('take-action-dropdown-btn').click(); diff --git a/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts b/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts index 930d1e29c2ebf..731802a021ae4 100644 --- a/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts @@ -17,6 +17,7 @@ import { typeInOsqueryFieldInput, } from '../../tasks/live_query'; import { + LIVE_QUERY_EDITOR, RESULTS_TABLE, RESULTS_TABLE_BUTTON, RESULTS_TABLE_CELL_WRRAPER, @@ -92,4 +93,23 @@ describe('ALL - Live Query', () => { cy.react('ReactAce', { props: { value: 'select * from users' } }).should('exist'); }); + + it.skip('should run live pack', () => { + cy.contains('New live query').click(); + cy.contains('Run a set of queries in a pack.').click(); + cy.get(LIVE_QUERY_EDITOR).should('not.exist'); + cy.getBySel('select-live-pack').click(); + cy.contains('Integration').click(); + cy.contains('This table contains 1 rows.'); + cy.contains('Integration ('); + cy.contains('system_memory_linux_elastic'); + selectAllAgents(); + submitQuery(); + cy.getBySel('live-query-loading').should('exist'); + cy.getBySel('live-query-loading', { timeout: 10000 }).should('not.exist'); + cy.getBySel('toggleIcon-events').click(); + checkResults(); + navigateTo('/app/osquery'); + cy.contains('Integration'); + }); }); diff --git a/x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts b/x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts index 4a8842d21c9b1..260408ca428c1 100644 --- a/x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/all/packs.spec.ts @@ -96,7 +96,7 @@ describe('ALL - Packs', () => { cy.contains('ID must be unique').should('exist'); cy.react('EuiFlyoutFooter').react('EuiButtonEmpty').contains('Cancel').click(); }); - it('should open lens in new tab', () => { + it.skip('should open lens in new tab', () => { let lensUrl = ''; cy.window().then((win) => { cy.stub(win, 'open') diff --git a/x-pack/plugins/osquery/cypress/integration/roles/alert_test.spec.ts b/x-pack/plugins/osquery/cypress/integration/roles/alert_test.spec.ts index 5d25b6599b13c..b68cd0d17e466 100644 --- a/x-pack/plugins/osquery/cypress/integration/roles/alert_test.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/roles/alert_test.spec.ts @@ -13,7 +13,7 @@ import { preparePack } from '../../tasks/packs'; import { closeModalIfVisible } from '../../tasks/integrations'; import { navigateTo } from '../../tasks/navigation'; -describe('Alert_Test', () => { +describe.skip('Alert_Test', () => { before(() => { runKbnArchiverScript(ArchiverMethod.LOAD, 'pack'); runKbnArchiverScript(ArchiverMethod.LOAD, 'rule'); diff --git a/x-pack/plugins/osquery/cypress/integration/roles/t1_analyst.spec.ts b/x-pack/plugins/osquery/cypress/integration/roles/t1_analyst.spec.ts index 51270332e0a51..57995296cfb17 100644 --- a/x-pack/plugins/osquery/cypress/integration/roles/t1_analyst.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/roles/t1_analyst.spec.ts @@ -12,7 +12,7 @@ import { checkResults, selectAllAgents, submitQuery } from '../../tasks/live_que import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver'; import { getSavedQueriesDropdown, LIVE_QUERY_EDITOR } from '../../screens/live_query'; -describe('T1 Analyst - READ + runSavedQueries ', () => { +describe.skip('T1 Analyst - READ + runSavedQueries ', () => { const SAVED_QUERY_ID = 'Saved-Query-Id'; beforeEach(() => { @@ -50,7 +50,6 @@ describe('T1 Analyst - READ + runSavedQueries ', () => { cy.contains('select * from uptime'); cy.wait(1000); cy.react('EuiTableBody').first().react('DefaultItemAction').first().click(); - selectAllAgents(); cy.contains(SAVED_QUERY_ID); submitQuery(); checkResults(); diff --git a/x-pack/plugins/osquery/cypress/integration/roles/t2_analyst.spec.ts b/x-pack/plugins/osquery/cypress/integration/roles/t2_analyst.spec.ts index 901b18f1461c7..cf91a49e9dad6 100644 --- a/x-pack/plugins/osquery/cypress/integration/roles/t2_analyst.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/roles/t2_analyst.spec.ts @@ -19,7 +19,7 @@ import { import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver'; import { getSavedQueriesComplexTest } from '../../tasks/saved_queries'; -describe('T2 Analyst - READ + Write Live/Saved + runSavedQueries ', () => { +describe.skip('T2 Analyst - READ + Write Live/Saved + runSavedQueries ', () => { const SAVED_QUERY_ID = 'Saved-Query-Id'; const NEW_SAVED_QUERY_ID = 'Saved-Query-Id-T2'; const NEW_SAVED_QUERY_DESCRIPTION = 'Test saved query description T2'; diff --git a/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts b/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts index 66e606132b05e..d288584b6a169 100644 --- a/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts +++ b/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts @@ -75,7 +75,7 @@ export const getSavedQueriesComplexTest = (savedQueryId: string, savedQueryDescr // visit Status results cy.react('EuiTab', { props: { id: 'status' } }).click(); cy.react('EuiTableRow').should('have.lengthOf', 1); - cy.contains('Successful').siblings().contains(1); + // cy.contains('Successful').siblings().contains(1); // play saved query cy.contains('Saved queries').click(); diff --git a/x-pack/plugins/osquery/kibana.json b/x-pack/plugins/osquery/kibana.json index 1ea8468529b85..539c2f7dc18dc 100644 --- a/x-pack/plugins/osquery/kibana.json +++ b/x-pack/plugins/osquery/kibana.json @@ -12,6 +12,7 @@ "requiredPlugins": [ "actions", "data", + "dataViews", "discover", "features", "navigation", diff --git a/x-pack/plugins/osquery/public/action_results/action_agents_status.tsx b/x-pack/plugins/osquery/public/action_results/action_agents_status.tsx deleted file mode 100644 index f5b952af8acd4..0000000000000 --- a/x-pack/plugins/osquery/public/action_results/action_agents_status.tsx +++ /dev/null @@ -1,92 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useEffect, useMemo, useState } from 'react'; - -import { Direction } from '../../common/search_strategy'; -import { AgentStatusBar } from './action_agents_status_bar'; -import { ActionAgentsStatusBadges } from './action_agents_status_badges'; -import { useActionResults } from './use_action_results'; - -interface ActionAgentsStatusProps { - actionId: string; - expirationDate?: string; - agentIds?: string[]; -} - -const ActionAgentsStatusComponent: React.FC = ({ - actionId, - expirationDate, - agentIds, -}) => { - const [isLive, setIsLive] = useState(true); - const expired = useMemo( - () => (!expirationDate ? false : new Date(expirationDate) < new Date()), - [expirationDate] - ); - const { - // @ts-expect-error update types - data: { aggregations }, - } = useActionResults({ - actionId, - activePage: 0, - agentIds, - limit: 0, - direction: Direction.asc, - sortField: '@timestamp', - isLive, - }); - - const agentStatus = useMemo(() => { - const notRespondedCount = !agentIds?.length ? 0 : agentIds.length - aggregations.totalResponded; - - return { - success: aggregations.successful, - pending: notRespondedCount, - failed: aggregations.failed, - }; - }, [agentIds?.length, aggregations.failed, aggregations.successful, aggregations.totalResponded]); - - useEffect( - () => - setIsLive(() => { - if (!agentIds?.length || expired) return false; - - return !!(aggregations.totalResponded !== agentIds?.length); - }), - [agentIds?.length, aggregations.totalResponded, expired] - ); - - return ( - <> - - - - - - - - - - - - - - - - - ); -}; - -export const ActionAgentsStatus = React.memo(ActionAgentsStatusComponent); diff --git a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx index e04f783608420..29d823560d6e3 100644 --- a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx +++ b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx @@ -5,8 +5,6 @@ * 2.0. */ -/* eslint-disable @typescript-eslint/no-unused-vars */ - import { i18n } from '@kbn/i18n'; import { EuiInMemoryTable, EuiCodeBlock } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; @@ -33,10 +31,8 @@ const ActionResultsSummaryComponent: React.FC = ({ expirationDate, agentIds, }) => { - // @ts-expect-error update types - const [pageIndex, setPageIndex] = useState(0); - // @ts-expect-error update types - const [pageSize, setPageSize] = useState(50); + const [pageIndex] = useState(0); + const [pageSize] = useState(50); const expired = useMemo( () => (!expirationDate ? false : new Date(expirationDate) < new Date()), [expirationDate] diff --git a/x-pack/plugins/osquery/public/action_results/use_action_results.ts b/x-pack/plugins/osquery/public/action_results/use_action_results.ts index 629c490660a2e..964feb9eafb3b 100644 --- a/x-pack/plugins/osquery/public/action_results/use_action_results.ts +++ b/x-pack/plugins/osquery/public/action_results/use_action_results.ts @@ -5,9 +5,9 @@ * 2.0. */ +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { flatten, reverse, uniqBy } from 'lodash/fp'; import { useQuery } from 'react-query'; - import { i18n } from '@kbn/i18n'; import { lastValueFrom } from 'rxjs'; import type { InspectResponse } from '../common/helpers'; @@ -19,7 +19,6 @@ import { import { useKibana } from '../common/lib/kibana'; import type { ResultEdges, - PageInfoPaginated, ActionResultsRequestOptions, ActionResultsStrategyResponse, Direction, @@ -35,8 +34,6 @@ export interface ResultsArgs { id: string; inspect: InspectResponse; isInspected: boolean; - pageInfo: PageInfoPaginated; - totalCount: number; } export interface UseActionResults { @@ -65,7 +62,7 @@ export const useActionResults = ({ const { data } = useKibana().services; const setErrorToast = useErrorToast(); - return useQuery( + return useQuery<{}, Error, ActionResultsStrategyResponse>( ['actionResults', { actionId }], async () => { const responseData = await lastValueFrom( @@ -87,22 +84,23 @@ export const useActionResults = ({ ); const totalResponded = - // @ts-expect-error update types responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.doc_count ?? 0; const totalRowCount = - // @ts-expect-error update types responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.rows_count?.value ?? 0; const aggsBuckets = - // @ts-expect-error update types responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.responses.buckets; - const cachedData = queryClient.getQueryData(['actionResults', { actionId }]); + const cachedData = queryClient.getQueryData([ + 'actionResults', + { actionId }, + ]); - // @ts-expect-error update types const previousEdges = cachedData?.edges.length - ? // @ts-expect-error update types - cachedData?.edges - : agentIds?.map((agentId) => ({ fields: { agent_id: [agentId] } })) ?? []; + ? cachedData?.edges + : agentIds?.map( + (agentId) => + ({ fields: { agent_id: [agentId] } } as unknown as estypes.SearchHit) + ) ?? []; return { ...responseData, @@ -110,9 +108,7 @@ export const useActionResults = ({ aggregations: { totalRowCount, totalResponded, - // @ts-expect-error update types successful: aggsBuckets?.find((bucket) => bucket.key === 'success')?.doc_count ?? 0, - // @ts-expect-error update types failed: aggsBuckets?.find((bucket) => bucket.key === 'error')?.doc_count ?? 0, }, inspect: getInspectResponse(responseData, {} as InspectResponse), @@ -124,7 +120,6 @@ export const useActionResults = ({ aggregations: { totalResponded: 0, successful: 0, - // @ts-expect-error update types pending: agentIds?.length ?? 0, failed: 0, }, @@ -133,7 +128,7 @@ export const useActionResults = ({ keepPreviousData: true, enabled: !skip && !!agentIds?.length, onSuccess: () => setErrorToast(), - onError: (error: Error) => + onError: (error) => setErrorToast(error, { title: i18n.translate('xpack.osquery.action_results.fetchError', { defaultMessage: 'Error while fetching action results', diff --git a/x-pack/plugins/osquery/public/actions/actions_table.tsx b/x-pack/plugins/osquery/public/actions/actions_table.tsx index 2f81394bccde8..25c35d09e1ba0 100644 --- a/x-pack/plugins/osquery/public/actions/actions_table.tsx +++ b/x-pack/plugins/osquery/public/actions/actions_table.tsx @@ -7,7 +7,15 @@ import { isArray, isEmpty, pickBy } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { EuiBasicTable, EuiButtonIcon, EuiCodeBlock, formatDate } from '@elastic/eui'; +import { + EuiBasicTable, + EuiButtonIcon, + EuiCodeBlock, + formatDate, + EuiIcon, + EuiFlexItem, + EuiFlexGroup, +} from '@elastic/eui'; import React, { useState, useCallback, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; @@ -47,14 +55,24 @@ const ActionsTableComponent = () => { setPageSize(size); }, []); - const renderQueryColumn = useCallback( - (_, item) => ( + const renderQueryColumn = useCallback((_, item) => { + if (item._source.pack_name) { + return ( + + + + + {item._source.pack_name} + + ); + } + + return ( - {item._source.data.query} + {item._source.queries[0].query} - ), - [] - ); + ); + }, []); const renderAgentsColumn = useCallback((_, item) => <>{item.fields.agents?.length ?? 0}, []); @@ -71,18 +89,43 @@ const ActionsTableComponent = () => { ); const handlePlayClick = useCallback( - (item) => + (item) => { + const packId = item._source.pack_id; + + if (packId) { + return push('/live_queries/new', { + form: pickBy( + { + packId: item._source.pack_id, + agentSelection: { + agents: item._source.agent_ids, + allAgentsSelected: item._source.agent_all, + platformsSelected: item._source.agent_platforms, + policiesSelected: item._source.agent_policy_ids, + }, + }, + (value) => !isEmpty(value) + ), + }); + } + push('/live_queries/new', { form: pickBy( { - agentIds: item.fields.agents, - query: item._source.data.query, - ecs_mapping: item._source.data.ecs_mapping, - savedQueryId: item._source.data.saved_query_id, + query: item._source.queries[0].query, + ecs_mapping: item._source.queries[0].ecs_mapping, + savedQueryId: item._source.queries[0].saved_query_id, + agentSelection: { + agents: item._source.agent_ids, + allAgentsSelected: item._source.agent_all, + platformsSelected: item._source.agent_platforms, + policiesSelected: item._source.agent_policy_ids, + }, }, (value) => !isEmpty(value) ), - }), + }); + }, [push] ); const isPlayButtonAvailable = useCallback( @@ -156,10 +199,10 @@ const ActionsTableComponent = () => { () => ({ pageIndex, pageSize, - totalItemCount: actionsData?.totalCount ?? 0, + totalItemCount: actionsData?.total ?? 0, pageSizeOptions: [20, 50, 100], }), - [actionsData?.totalCount, pageIndex, pageSize] + [actionsData?.total, pageIndex, pageSize] ); return ( diff --git a/x-pack/plugins/osquery/public/actions/use_action_details.ts b/x-pack/plugins/osquery/public/actions/use_action_details.ts deleted file mode 100644 index 39abf4ac23852..0000000000000 --- a/x-pack/plugins/osquery/public/actions/use_action_details.ts +++ /dev/null @@ -1,70 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useQuery } from 'react-query'; - -import { i18n } from '@kbn/i18n'; -import { lastValueFrom } from 'rxjs'; -import { createFilter } from '../common/helpers'; -import { useKibana } from '../common/lib/kibana'; -import type { - ActionDetailsRequestOptions, - ActionDetailsStrategyResponse, -} from '../../common/search_strategy'; -import { OsqueryQueries } from '../../common/search_strategy'; -import type { ESTermQuery } from '../../common/typed_json'; -import { useErrorToast } from '../common/hooks/use_error_toast'; - -export interface ActionDetailsArgs { - actionDetails: Record; - id: string; -} - -interface UseActionDetails { - actionId: string; - filterQuery?: ESTermQuery | string; - skip?: boolean; -} - -export const useActionDetails = ({ actionId, filterQuery, skip = false }: UseActionDetails) => { - const { data } = useKibana().services; - const setErrorToast = useErrorToast(); - - return useQuery( - ['actionDetails', { actionId, filterQuery }], - async () => { - const responseData = await lastValueFrom( - data.search.search( - { - actionId, - factoryQueryType: OsqueryQueries.actionDetails, - filterQuery: createFilter(filterQuery), - }, - { - strategy: 'osquerySearchStrategy', - } - ) - ); - - if (!responseData.actionDetails) throw new Error(); - - return responseData; - }, - { - enabled: !skip, - onSuccess: () => setErrorToast(), - onError: (error: Error) => - setErrorToast(error, { - title: i18n.translate('xpack.osquery.action_details.fetchError', { - defaultMessage: 'Error while fetching action details', - }), - }), - refetchOnWindowFocus: false, - retryDelay: 1000, - } - ); -}; diff --git a/x-pack/plugins/osquery/public/actions/use_all_actions.ts b/x-pack/plugins/osquery/public/actions/use_all_actions.ts index a0a53ab4566bd..fc3f2a6d123ac 100644 --- a/x-pack/plugins/osquery/public/actions/use_all_actions.ts +++ b/x-pack/plugins/osquery/public/actions/use_all_actions.ts @@ -18,7 +18,6 @@ import { import { useKibana } from '../common/lib/kibana'; import type { ActionEdges, - PageInfoPaginated, ActionsRequestOptions, ActionsStrategyResponse, Direction, @@ -33,8 +32,6 @@ export interface ActionsArgs { id: string; inspect: InspectResponse; isInspected: boolean; - pageInfo: PageInfoPaginated; - totalCount: number; } interface UseAllActions { diff --git a/x-pack/plugins/osquery/public/actions/use_live_query_details.ts b/x-pack/plugins/osquery/public/actions/use_live_query_details.ts new file mode 100644 index 0000000000000..a31c493487057 --- /dev/null +++ b/x-pack/plugins/osquery/public/actions/use_live_query_details.ts @@ -0,0 +1,81 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from 'react-query'; + +import { i18n } from '@kbn/i18n'; +import { useKibana } from '../common/lib/kibana'; +import type { ESTermQuery } from '../../common/typed_json'; +import { useErrorToast } from '../common/hooks/use_error_toast'; + +export interface LiveQueryDetailsArgs { + actionDetails: Record; + id: string; +} + +interface UseLiveQueryDetails { + actionId?: string; + isLive?: boolean; + filterQuery?: ESTermQuery | string; + skip?: boolean; +} + +export interface LiveQueryDetailsItem { + action_id: string; + expiration: string; + '@timestamp': string; + agent_all: boolean; + agent_ids: string[]; + agent_platforoms: string[]; + agent_policy_ids: string[]; + agents: string[]; + user_id?: string; + pack_id?: string; + pack_name?: string; + pack_prebuilt?: boolean; + status?: string; + queries?: Array<{ + action_id: string; + id: string; + query: string; + agents: string[]; + ecs_mapping?: unknown; + version?: string; + platform?: string; + saved_query_id?: string; + expiration?: string; + }>; +} + +export const useLiveQueryDetails = ({ + actionId, + filterQuery, + isLive = false, + skip = false, +}: UseLiveQueryDetails) => { + const { http } = useKibana().services; + const setErrorToast = useErrorToast(); + + return useQuery<{ data: LiveQueryDetailsItem }, Error, LiveQueryDetailsItem>( + ['liveQueries', { actionId, filterQuery }], + () => http.get(`/api/osquery/live_queries/${actionId}`), + { + enabled: !skip && !!actionId, + refetchInterval: isLive ? 5000 : false, + onSuccess: () => setErrorToast(), + onError: (error) => + setErrorToast(error, { + title: i18n.translate('xpack.osquery.action_details.fetchError', { + defaultMessage: 'Error while fetching action details', + }), + }), + select: (response) => response.data, + refetchOnWindowFocus: false, + retryDelay: 5000, + } + ); +}; diff --git a/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts b/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts index 608357bd72912..f629a138f70f6 100644 --- a/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts +++ b/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts @@ -8,6 +8,7 @@ import { useQuery } from 'react-query'; import { i18n } from '@kbn/i18n'; +import type { AgentPolicy } from '@kbn/fleet-plugin/common'; import { useKibana } from '../common/lib/kibana'; import { useErrorToast } from '../common/hooks/use_error_toast'; @@ -21,8 +22,7 @@ export const useAgentPolicy = ({ policyId, skip, silent }: UseAgentPolicy) => { const { http } = useKibana().services; const setErrorToast = useErrorToast(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return useQuery( + return useQuery<{ item: AgentPolicy }, Error, AgentPolicy>( ['agentPolicy', { policyId }], () => http.get(`/internal/osquery/fleet_wrapper/agent_policies/${policyId}`), { diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index e892a7f7a4840..e11cb9b8277d9 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -88,7 +88,7 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh selectedGroups: SelectedGroups; } = generateAgentSelection(selection); if (newAgentSelection.allAgentsSelected) { - setNumAgentsSelected(agentGroupsData?.totalCount ?? 0); + setNumAgentsSelected(agentGroupsData?.total ?? 0); } else { const checkAgent = generateAgentCheck(selectedGroups); setNumAgentsSelected( @@ -135,11 +135,11 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh } } - if (agentSelection.policiesSelected.length) { + if (agentSelection.policiesSelected?.length) { handleSelectedOptions(agentSelection.policiesSelected, AGENT_POLICY_LABEL); } - if (agentSelection.agents.length) { + if (agentSelection.agents?.length) { handleSelectedOptions(agentSelection.agents, AGENT_SELECTION_LABEL); } } @@ -149,7 +149,7 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh if (agentsFetched && groupsFetched && agentGroupsData) { const grouper = new AgentGrouper(); // update the groups when groups or agents have changed - grouper.setTotalAgents(agentGroupsData?.totalCount); + grouper.setTotalAgents(agentGroupsData?.total); grouper.updateGroup(AGENT_GROUP_KEY.Platform, agentGroupsData?.groups.platforms); grouper.updateGroup(AGENT_GROUP_KEY.Policy, agentGroupsData?.groups.policies); // @ts-expect-error update types diff --git a/x-pack/plugins/osquery/public/agents/use_agent_groups.ts b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts index 5076082563ba3..497819a15031d 100644 --- a/x-pack/plugins/osquery/public/agents/use_agent_groups.ts +++ b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts @@ -29,7 +29,7 @@ export const useAgentGroups = () => { AgentsStrategyResponse, unknown, { - totalCount: number; + total: number; groups: ReturnType; } >( @@ -80,7 +80,7 @@ export const useAgentGroups = () => { ); return { - totalCount: response.totalCount, + total: response.total ?? 0, groups: { platforms, overlap, @@ -96,13 +96,8 @@ export const useAgentGroups = () => { }; }, placeholderData: { - totalCount: 0, + total: 0, edges: [], - pageInfo: { - activePage: 1, - fakeTotalCount: 100, - showMorePagesIndicator: true, - }, rawResponse: { took: 0, timed_out: false, diff --git a/x-pack/plugins/osquery/public/agents/use_all_agents.ts b/x-pack/plugins/osquery/public/agents/use_all_agents.ts index 39a77fa72fa4e..c5e7c2d703bcf 100644 --- a/x-pack/plugins/osquery/public/agents/use_all_agents.ts +++ b/x-pack/plugins/osquery/public/agents/use_all_agents.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { useQuery } from 'react-query'; -import type { GetAgentsResponse } from '@kbn/fleet-plugin/common'; +import type { ListResult, Agent } from '@kbn/fleet-plugin/common'; import { useErrorToast } from '../common/hooks/use_error_toast'; import { useKibana } from '../common/lib/kibana'; import { useOsqueryPolicies } from './use_osquery_policies'; @@ -26,7 +26,7 @@ export const useAllAgents = (searchValue = '', opts: RequestOptions = { perPage: const { data: osqueryPolicies, isFetched } = useOsqueryPolicies(); - return useQuery( + return useQuery, 'items'> & { agents: Agent[] }, unknown, Agent[]>( ['agents', osqueryPolicies, searchValue, perPage], () => { let kuery = ''; @@ -47,7 +47,6 @@ export const useAllAgents = (searchValue = '', opts: RequestOptions = { perPage: }); }, { - // @ts-expect-error update types select: (data) => data?.agents || [], enabled: isFetched && !!osqueryPolicies?.length, onSuccess: () => setErrorToast(), diff --git a/x-pack/plugins/osquery/public/assets/use_import_assets.ts b/x-pack/plugins/osquery/public/assets/use_import_assets.ts index f63f3e7096f03..feb2c48041567 100644 --- a/x-pack/plugins/osquery/public/assets/use_import_assets.ts +++ b/x-pack/plugins/osquery/public/assets/use_import_assets.ts @@ -23,20 +23,15 @@ export const useImportAssets = ({ successToastText }: UseImportAssetsProps) => { } = useKibana().services; const setErrorToast = useErrorToast(); - return useMutation( - () => - // eslint-disable-next-line @typescript-eslint/no-explicit-any - http.post('/internal/osquery/assets/update'), - { - onSuccess: () => { - setErrorToast(); - queryClient.invalidateQueries(PACKS_ID); - queryClient.invalidateQueries(INTEGRATION_ASSETS_STATUS_ID); - toasts.addSuccess(successToastText); - }, - onError: (error) => { - setErrorToast(error); - }, - } - ); + return useMutation(() => http.post('/internal/osquery/assets/update'), { + onSuccess: () => { + setErrorToast(); + queryClient.invalidateQueries(PACKS_ID); + queryClient.invalidateQueries(INTEGRATION_ASSETS_STATUS_ID); + toasts.addSuccess(successToastText); + }, + onError: (error) => { + setErrorToast(error); + }, + }); }; diff --git a/x-pack/plugins/osquery/public/common/helpers.ts b/x-pack/plugins/osquery/public/common/helpers.ts index 42860bfb80edc..6b0a5f2a51c39 100644 --- a/x-pack/plugins/osquery/public/common/helpers.ts +++ b/x-pack/plugins/osquery/public/common/helpers.ts @@ -31,7 +31,6 @@ export const generateTablePaginationOptions = ( return { activePage, cursorStart, - fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5, querySize: limit, }; }; diff --git a/x-pack/plugins/osquery/public/common/hooks/use_logs_data_view.tsx b/x-pack/plugins/osquery/public/common/hooks/use_logs_data_view.tsx new file mode 100644 index 0000000000000..8da13f72a077d --- /dev/null +++ b/x-pack/plugins/osquery/public/common/hooks/use_logs_data_view.tsx @@ -0,0 +1,31 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from 'react-query'; +import type { DataView } from '@kbn/data-plugin/common'; + +import { useKibana } from '../lib/kibana'; + +export interface LogsDataView extends DataView { + id: string; +} + +export const useLogsDataView = () => { + const dataViews = useKibana().services.data.dataViews; + + return useQuery(['logsDataView'], async () => { + let dataView = (await dataViews.find('logs-osquery_manager.result*', 1))[0]; + if (!dataView && dataViews.getCanSaveSync()) { + dataView = await dataViews.createAndSave({ + title: 'logs-osquery_manager.result*', + timeFieldName: '@timestamp', + }); + } + + return dataView as LogsDataView; + }); +}; diff --git a/x-pack/plugins/osquery/public/common/validations.ts b/x-pack/plugins/osquery/public/common/validations.ts index 1dc2ddbf139b8..4a29d274fcc0c 100644 --- a/x-pack/plugins/osquery/public/common/validations.ts +++ b/x-pack/plugins/osquery/public/common/validations.ts @@ -7,12 +7,12 @@ import { i18n } from '@kbn/i18n'; -import type { ValidationFunc } from '../shared_imports'; +import type { FormData, ValidationFunc } from '../shared_imports'; import { fieldValidators } from '../shared_imports'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const queryFieldValidation: ValidationFunc = fieldValidators.emptyField( - i18n.translate('xpack.osquery.pack.queryFlyoutForm.emptyQueryError', { - defaultMessage: 'Query is a required field', - }) -); +export const queryFieldValidation: ValidationFunc = + fieldValidators.emptyField( + i18n.translate('xpack.osquery.pack.queryFlyoutForm.emptyQueryError', { + defaultMessage: 'Query is a required field', + }) + ); diff --git a/x-pack/plugins/osquery/public/components/main_navigation.tsx b/x-pack/plugins/osquery/public/components/main_navigation.tsx index 8757ee0ea576c..8e0c3d7a8d55c 100644 --- a/x-pack/plugins/osquery/public/components/main_navigation.tsx +++ b/x-pack/plugins/osquery/public/components/main_navigation.tsx @@ -27,7 +27,7 @@ export const MainNavigation = () => {