From 3f620428b3a81b2eaaef2a0374dd9e0e4da815a9 Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Wed, 2 Nov 2022 12:39:30 +0200 Subject: [PATCH 01/86] [Lens] Heatmap expression types improvement. (#144174) * Added expression function builder for heatmap * Fixed heatmap preview. * Fixed tests. Co-authored-by: Stratoula Kalafateli --- .../expression_functions/heatmap_legend.ts | 10 +- .../expression_heatmap/common/index.ts | 3 + .../common/types/expression_functions.ts | 14 ++ .../heatmap/visualization.test.ts | 11 +- .../visualizations/heatmap/visualization.tsx | 229 +++++++----------- 5 files changed, 114 insertions(+), 153 deletions(-) diff --git a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_legend.ts b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_legend.ts index 79a356ddad934..9aa489a031eeb 100644 --- a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_legend.ts +++ b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_legend.ts @@ -7,17 +7,11 @@ */ import { Position } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; -import type { ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common'; import { DEFAULT_LEGEND_SIZE, LegendSize } from '@kbn/visualizations-plugin/common/constants'; import { EXPRESSION_HEATMAP_LEGEND_NAME } from '../constants'; -import { HeatmapLegendConfig, HeatmapLegendConfigResult } from '../types'; +import { HeatmapLegendExpressionFunctionDefinition } from '../types'; -export const heatmapLegendConfig: ExpressionFunctionDefinition< - typeof EXPRESSION_HEATMAP_LEGEND_NAME, - null, - HeatmapLegendConfig, - HeatmapLegendConfigResult -> = { +export const heatmapLegendConfig: HeatmapLegendExpressionFunctionDefinition = { name: EXPRESSION_HEATMAP_LEGEND_NAME, aliases: [], type: EXPRESSION_HEATMAP_LEGEND_NAME, diff --git a/src/plugins/chart_expressions/expression_heatmap/common/index.ts b/src/plugins/chart_expressions/expression_heatmap/common/index.ts index 484dee11c300b..1fa096cc6fe8a 100755 --- a/src/plugins/chart_expressions/expression_heatmap/common/index.ts +++ b/src/plugins/chart_expressions/expression_heatmap/common/index.ts @@ -18,6 +18,9 @@ export type { HeatmapLegendConfigResult, HeatmapGridConfigResult, HeatmapArguments, + HeatmapExpressionFunctionDefinition, + HeatmapLegendExpressionFunctionDefinition, + HeatmapGridExpressionFunctionDefinition, } from './types'; export { heatmapFunction, heatmapLegendConfig, heatmapGridConfig } from './expression_functions'; diff --git a/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts index 1bf5fe3bbb36b..2537d4c7f5105 100644 --- a/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts @@ -109,3 +109,17 @@ export type HeatmapExpressionFunctionDefinition = ExpressionFunctionDefinition< HeatmapArguments, ExpressionValueRender >; + +export type HeatmapLegendExpressionFunctionDefinition = ExpressionFunctionDefinition< + typeof EXPRESSION_HEATMAP_LEGEND_NAME, + null, + HeatmapLegendConfig, + HeatmapLegendConfigResult +>; + +export type HeatmapGridExpressionFunctionDefinition = ExpressionFunctionDefinition< + typeof EXPRESSION_HEATMAP_GRID_NAME, + null, + HeatmapGridConfig, + HeatmapGridConfigResult +>; diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts index 8f958b8fa75ce..bbebf37ff4b5c 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts @@ -427,7 +427,6 @@ describe('heatmap', () => { arguments: { isVisible: [true], position: [Position.Right], - legendSize: [], }, }, ], @@ -441,11 +440,6 @@ describe('heatmap', () => { type: 'function', function: HEATMAP_GRID_FUNCTION, arguments: { - // grid - strokeWidth: [], - strokeColor: [], - xTitle: [], - yTitle: [], // cells isCellLabelVisible: [false], // Y-axis @@ -505,6 +499,7 @@ describe('heatmap', () => { ...exampleState(), layerId: 'first', xAccessor: 'x-accessor', + valueAccessor: 'value-accessor', }; expect( @@ -521,7 +516,7 @@ describe('heatmap', () => { arguments: { xAccessor: ['x-accessor'], yAccessor: [''], - valueAccessor: [''], + valueAccessor: ['value-accessor'], palette: [ { type: 'expression', @@ -545,7 +540,7 @@ describe('heatmap', () => { function: LEGEND_FUNCTION, arguments: { isVisible: [false], - position: [], + position: ['right'], }, }, ], diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx index fc8ef976548b4..dfa37ba75f38c 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx @@ -18,6 +18,12 @@ import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { HeatmapConfiguration } from '@kbn/visualizations-plugin/common'; +import { + HeatmapExpressionFunctionDefinition, + HeatmapGridExpressionFunctionDefinition, + HeatmapLegendExpressionFunctionDefinition, +} from '@kbn/expression-heatmap-plugin/common'; +import { buildExpression, buildExpressionFunction } from '@kbn/expressions-plugin/common'; import type { OperationMetadata, Suggestion, Visualization } from '../../types'; import type { HeatmapVisualizationState } from './types'; import { getSuggestions } from './suggestions'; @@ -25,7 +31,6 @@ import { CHART_NAMES, CHART_SHAPES, DEFAULT_PALETTE_NAME, - FUNCTION_NAME, GROUP_ID, HEATMAP_GRID_FUNCTION, LEGEND_FUNCTION, @@ -321,82 +326,53 @@ export const getHeatmapVisualization = ({ return null; } + const legendFn = buildExpressionFunction( + 'heatmap_legend', + { + isVisible: state.legend.isVisible, + position: state.legend.position, + legendSize: state.legend.legendSize, + } + ); + + const gridConfigFn = buildExpressionFunction( + 'heatmap_grid', + { + // grid + strokeWidth: state.gridConfig.strokeWidth, + strokeColor: state.gridConfig.strokeColor, + // cells + isCellLabelVisible: state.gridConfig.isCellLabelVisible, + // Y-axis + isYAxisLabelVisible: state.gridConfig.isYAxisLabelVisible, + isYAxisTitleVisible: state.gridConfig.isYAxisTitleVisible ?? false, + yTitle: state.gridConfig.yTitle, + // X-axis + isXAxisLabelVisible: state.gridConfig.isXAxisLabelVisible, + isXAxisTitleVisible: state.gridConfig.isXAxisTitleVisible ?? false, + xTitle: state.gridConfig.xTitle, + } + ); + + const heatmapFn = buildExpressionFunction('heatmap', { + xAccessor: state.xAccessor ?? '', + yAccessor: state.yAccessor ?? '', + valueAccessor: state.valueAccessor ?? '', + lastRangeIsRightOpen: state.palette?.params?.continuity + ? ['above', 'all'].includes(state.palette.params.continuity) + : true, + palette: state.palette?.params + ? paletteService + .get(CUSTOM_PALETTE) + .toExpression(computePaletteParams(state.palette?.params)) + : paletteService.get(DEFAULT_PALETTE_NAME).toExpression(), + legend: buildExpression([legendFn]), + gridConfig: buildExpression([gridConfigFn]), + }); + return { type: 'expression', - chain: [ - ...(datasourceExpression?.chain ?? []), - { - type: 'function', - function: FUNCTION_NAME, - arguments: { - xAccessor: [state.xAccessor ?? ''], - yAccessor: [state.yAccessor ?? ''], - valueAccessor: [state.valueAccessor ?? ''], - lastRangeIsRightOpen: [ - state.palette?.params?.continuity - ? ['above', 'all'].includes(state.palette.params.continuity) - : true, - ], - palette: state.palette?.params - ? [ - paletteService - .get(CUSTOM_PALETTE) - .toExpression( - computePaletteParams((state.palette?.params || {}) as CustomPaletteParams) - ), - ] - : [paletteService.get(DEFAULT_PALETTE_NAME).toExpression()], - legend: [ - { - type: 'expression', - chain: [ - { - type: 'function', - function: LEGEND_FUNCTION, - arguments: { - isVisible: [state.legend.isVisible], - position: [state.legend.position], - legendSize: state.legend.legendSize ? [state.legend.legendSize] : [], - }, - }, - ], - }, - ], - gridConfig: [ - { - type: 'expression', - chain: [ - { - type: 'function', - function: HEATMAP_GRID_FUNCTION, - arguments: { - // grid - strokeWidth: state.gridConfig.strokeWidth - ? [state.gridConfig.strokeWidth] - : [], - strokeColor: state.gridConfig.strokeColor - ? [state.gridConfig.strokeColor] - : [], - // cells - isCellLabelVisible: [state.gridConfig.isCellLabelVisible], - // Y-axis - isYAxisLabelVisible: [state.gridConfig.isYAxisLabelVisible], - isYAxisTitleVisible: [state.gridConfig.isYAxisTitleVisible ?? false], - yTitle: state.gridConfig.yTitle ? [state.gridConfig.yTitle] : [], - // X-axis - isXAxisLabelVisible: state.gridConfig.isXAxisLabelVisible - ? [state.gridConfig.isXAxisLabelVisible] - : [], - isXAxisTitleVisible: [state.gridConfig.isXAxisTitleVisible ?? false], - xTitle: state.gridConfig.xTitle ? [state.gridConfig.xTitle] : [], - }, - }, - ], - }, - ], - }, - }, - ], + chain: [...(datasourceExpression?.chain ?? []), heatmapFn.toAst()], }; }, @@ -407,73 +383,52 @@ export const getHeatmapVisualization = ({ const originalOrder = datasource?.getTableSpec().map(({ columnId }) => columnId); // When we add a column it could be empty, and therefore have no order - if (!originalOrder) { + if (!originalOrder || !state.valueAccessor) { return null; } + const legendFn = buildExpressionFunction( + 'heatmap_legend', + { + isVisible: false, + position: 'right', + } + ); + + const gridConfigFn = buildExpressionFunction( + 'heatmap_grid', + { + // grid + strokeWidth: 1, + // cells + isCellLabelVisible: false, + // Y-axis + isYAxisLabelVisible: false, + isYAxisTitleVisible: state.gridConfig.isYAxisTitleVisible, + yTitle: state.gridConfig.yTitle ?? '', + // X-axis + isXAxisLabelVisible: false, + isXAxisTitleVisible: state.gridConfig.isXAxisTitleVisible, + xTitle: state.gridConfig.xTitle ?? '', + } + ); + + const heatmapFn = buildExpressionFunction('heatmap', { + xAccessor: state.xAccessor ?? '', + yAccessor: state.yAccessor ?? '', + valueAccessor: state.valueAccessor ?? '', + legend: buildExpression([legendFn]), + gridConfig: buildExpression([gridConfigFn]), + palette: state.palette?.params + ? paletteService + .get(CUSTOM_PALETTE) + .toExpression(computePaletteParams(state.palette?.params)) + : paletteService.get(DEFAULT_PALETTE_NAME).toExpression(), + }); + return { type: 'expression', - chain: [ - ...(datasourceExpression?.chain ?? []), - { - type: 'function', - function: FUNCTION_NAME, - arguments: { - xAccessor: [state.xAccessor ?? ''], - yAccessor: [state.yAccessor ?? ''], - valueAccessor: [state.valueAccessor ?? ''], - legend: [ - { - type: 'expression', - chain: [ - { - type: 'function', - function: LEGEND_FUNCTION, - arguments: { - isVisible: [false], - position: [], - }, - }, - ], - }, - ], - palette: state.palette?.params - ? [ - paletteService - .get(CUSTOM_PALETTE) - .toExpression( - computePaletteParams((state.palette?.params || {}) as CustomPaletteParams) - ), - ] - : [paletteService.get(DEFAULT_PALETTE_NAME).toExpression()], - gridConfig: [ - { - type: 'expression', - chain: [ - { - type: 'function', - function: HEATMAP_GRID_FUNCTION, - arguments: { - // grid - strokeWidth: [1], - // cells - isCellLabelVisible: [false], - // Y-axis - isYAxisLabelVisible: [false], - isYAxisTitleVisible: [state.gridConfig.isYAxisTitleVisible], - yTitle: [state.gridConfig.yTitle ?? ''], - // X-axis - isXAxisLabelVisible: [false], - isXAxisTitleVisible: [state.gridConfig.isXAxisTitleVisible], - xTitle: [state.gridConfig.xTitle ?? ''], - }, - }, - ], - }, - ], - }, - }, - ], + chain: [...(datasourceExpression?.chain ?? []), heatmapFn.toAst()], }; }, From 10dc2c6f7815fa7688402abe3363aa495e6fc38c Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Wed, 2 Nov 2022 12:39:03 +0100 Subject: [PATCH 02/86] Fix Incorrect alerts are displayed in timeline when navigating from Detection & Responses page (#144319) * Fix Incorrect alerts are displayed in the timeline when navigating from Detection & Responses page --- .../host_alerts_table.test.tsx | 48 ++++++++++++++++ .../host_alerts_table/host_alerts_table.tsx | 13 ++++- .../rule_alerts_table.test.tsx | 36 +++++++++++- .../rule_alerts_table/rule_alerts_table.tsx | 12 +++- .../user_alerts_table.test.tsx | 51 ++++++++++++++++- .../user_alerts_table/user_alerts_table.tsx | 13 ++++- .../components/detection_response/utils.tsx | 2 + .../risk_score/index.test.tsx | 55 ++++++++++++++++++- .../entity_analytics/risk_score/index.tsx | 3 +- 9 files changed, 219 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx index e4cabce7780b9..cbd4e0c59b224 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx @@ -13,6 +13,7 @@ import { TestProviders } from '../../../../common/mock'; import { parsedVulnerableHostsAlertsResult } from './mock_data'; import type { UseHostAlertsItems } from './use_host_alerts_items'; import { HostAlertsTable } from './host_alerts_table'; +import { openAlertsFilter } from '../utils'; const mockGetAppUrl = jest.fn(); jest.mock('../../../../common/lib/kibana/hooks', () => { @@ -25,6 +26,15 @@ jest.mock('../../../../common/lib/kibana/hooks', () => { }; }); +const mockOpenTimelineWithFilters = jest.fn(); +jest.mock('../hooks/use_navigate_to_timeline', () => { + return { + useNavigateToTimeline: () => ({ + openTimelineWithFilters: mockOpenTimelineWithFilters, + }), + }; +}); + type UseHostAlertsItemsReturn = ReturnType; const defaultUseHostAlertsItemsReturn: UseHostAlertsItemsReturn = { items: [], @@ -124,4 +134,42 @@ describe('HostAlertsTable', () => { fireEvent.click(page3); expect(mockSetPage).toHaveBeenCalledWith(2); }); + + it('should open timeline with filters when total alerts is clicked', () => { + mockUseHostAlertsItemsReturn({ items: [parsedVulnerableHostsAlertsResult[0]] }); + const { getByTestId } = renderComponent(); + + fireEvent.click(getByTestId('hostSeverityAlertsTable-totalAlertsLink')); + + expect(mockOpenTimelineWithFilters).toHaveBeenCalledWith([ + [ + { + field: 'host.name', + value: 'Host-342m5gl1g2', + }, + openAlertsFilter, + ], + ]); + }); + + it('should open timeline with filters when critical alert count is clicked', () => { + mockUseHostAlertsItemsReturn({ items: [parsedVulnerableHostsAlertsResult[0]] }); + const { getByTestId } = renderComponent(); + + fireEvent.click(getByTestId('hostSeverityAlertsTable-criticalLink')); + + expect(mockOpenTimelineWithFilters).toHaveBeenCalledWith([ + [ + { + field: 'host.name', + value: 'Host-342m5gl1g2', + }, + openAlertsFilter, + { + field: 'kibana.alert.severity', + value: 'critical', + }, + ], + ]); + }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx index 555a2d7be5b4d..c090277fc1786 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx @@ -28,7 +28,7 @@ import { HostDetailsLink } from '../../../../common/components/links'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { useNavigateToTimeline } from '../hooks/use_navigate_to_timeline'; import * as i18n from '../translations'; -import { ITEMS_PER_PAGE, SEVERITY_COLOR } from '../utils'; +import { ITEMS_PER_PAGE, openAlertsFilter, SEVERITY_COLOR } from '../utils'; import type { HostAlertsItem } from './use_host_alerts_items'; import { useHostAlertsItems } from './use_host_alerts_items'; @@ -53,7 +53,9 @@ export const HostAlertsTable = React.memo(({ signalIndexName }: HostAlertsTableP : undefined; openTimelineWithFilters( - severityFilter ? [[hostNameFilter, severityFilter]] : [[hostNameFilter]] + severityFilter + ? [[hostNameFilter, openAlertsFilter, severityFilter]] + : [[hostNameFilter, openAlertsFilter]] ); }, [openTimelineWithFilters] @@ -133,7 +135,11 @@ const getTableColumns: GetTableColumns = (handleClick) => [ name: i18n.ALERTS_TEXT, 'data-test-subj': 'hostSeverityAlertsTable-totalAlerts', render: (totalAlerts: number, { hostName }) => ( - handleClick({ hostName })}> + handleClick({ hostName })} + > ), @@ -144,6 +150,7 @@ const getTableColumns: GetTableColumns = (handleClick) => [ render: (count: number, { hostName }) => ( handleClick({ hostName, severity: 'critical' })} > diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.test.tsx index 0776b69e96b27..fca9b4cd8e479 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.test.tsx @@ -8,13 +8,14 @@ import moment from 'moment'; import React from 'react'; -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import { SecurityPageName } from '../../../../../common/constants'; import { TestProviders } from '../../../../common/mock'; import type { RuleAlertsTableProps } from './rule_alerts_table'; import { RuleAlertsTable } from './rule_alerts_table'; import type { RuleAlertsItem, UseRuleAlertsItems } from './use_rule_alerts_items'; +import { openAlertsFilter } from '../utils'; const mockGetAppUrl = jest.fn(); jest.mock('../../../../common/lib/kibana/hooks', () => { @@ -27,6 +28,15 @@ jest.mock('../../../../common/lib/kibana/hooks', () => { }; }); +const mockOpenTimelineWithFilters = jest.fn(); +jest.mock('../hooks/use_navigate_to_timeline', () => { + return { + useNavigateToTimeline: () => ({ + openTimelineWithFilters: mockOpenTimelineWithFilters, + }), + }; +}); + type UseRuleAlertsItemsReturn = ReturnType; const defaultUseRuleAlertsItemsReturn: UseRuleAlertsItemsReturn = { items: [], @@ -44,10 +54,11 @@ jest.mock('./use_rule_alerts_items', () => ({ const defaultProps: RuleAlertsTableProps = { signalIndexName: '', }; +const ruleName = 'ruleName'; const items: RuleAlertsItem[] = [ { id: 'ruleId', - name: 'ruleName', + name: ruleName, last_alert_at: moment().subtract(1, 'day').format(), alert_count: 10, severity: 'high', @@ -144,4 +155,25 @@ describe('RuleAlertsTable', () => { expect(result.getByTestId('severityRuleAlertsTable-name')).toHaveAttribute('href', linkUrl); }); + + it('should open timeline with filters when total alerts is clicked', () => { + mockUseRuleAlertsItemsReturn({ items }); + const { getByTestId } = render( + + + + ); + + fireEvent.click(getByTestId('severityRuleAlertsTable-alertCountLink')); + + expect(mockOpenTimelineWithFilters).toHaveBeenCalledWith([ + [ + { + field: 'kibana.alert.rule.name', + value: ruleName, + }, + openAlertsFilter, + ], + ]); + }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.tsx index e9ec906070f74..82ec2837e77b7 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.tsx @@ -22,7 +22,7 @@ import { FormattedRelative } from '@kbn/i18n-react'; import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import { HeaderSection } from '../../../../common/components/header_section'; -import { SEVERITY_COLOR } from '../utils'; +import { openAlertsFilter, SEVERITY_COLOR } from '../utils'; import * as i18n from '../translations'; import type { RuleAlertsItem } from './use_rule_alerts_items'; import { useRuleAlertsItems } from './use_rule_alerts_items'; @@ -90,7 +90,11 @@ export const getTableColumns: GetTableColumns = ({ getAppUrl, navigateTo, openRu name: i18n.RULE_ALERTS_COLUMN_ALERT_COUNT, 'data-test-subj': 'severityRuleAlertsTable-alertCount', render: (alertCount: number, { name }) => ( - openRuleInTimeline(name)}> + openRuleInTimeline(name)} + > ), @@ -118,7 +122,9 @@ export const RuleAlertsTable = React.memo(({ signalIndexNa const openRuleInTimeline = useCallback( (ruleName: string) => { - openTimelineWithFilters([[{ field: 'kibana.alert.rule.name', value: ruleName }]]); + openTimelineWithFilters([ + [{ field: 'kibana.alert.rule.name', value: ruleName }, openAlertsFilter], + ]); }, [openTimelineWithFilters] ); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.test.tsx index 9b6ed807e34e6..1d7cb38b864ac 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.test.tsx @@ -13,7 +13,9 @@ import { TestProviders } from '../../../../common/mock'; import { parsedVulnerableUserAlertsResult } from './mock_data'; import type { UseUserAlertsItems } from './use_user_alerts_items'; import { UserAlertsTable } from './user_alerts_table'; +import { openAlertsFilter } from '../utils'; +const userName = 'crffn20qcs'; const mockGetAppUrl = jest.fn(); jest.mock('../../../../common/lib/kibana/hooks', () => { const original = jest.requireActual('../../../../common/lib/kibana/hooks'); @@ -25,6 +27,15 @@ jest.mock('../../../../common/lib/kibana/hooks', () => { }; }); +const mockOpenTimelineWithFilters = jest.fn(); +jest.mock('../hooks/use_navigate_to_timeline', () => { + return { + useNavigateToTimeline: () => ({ + openTimelineWithFilters: mockOpenTimelineWithFilters, + }), + }; +}); + type UseUserAlertsItemsReturn = ReturnType; const defaultUseUserAlertsItemsReturn: UseUserAlertsItemsReturn = { items: [], @@ -98,7 +109,7 @@ describe('UserAlertsTable', () => { mockUseUserAlertsItemsReturn({ items: [parsedVulnerableUserAlertsResult[0]] }); const { queryByTestId } = renderComponent(); - expect(queryByTestId('userSeverityAlertsTable-userName')).toHaveTextContent('crffn20qcs'); + expect(queryByTestId('userSeverityAlertsTable-userName')).toHaveTextContent(userName); expect(queryByTestId('userSeverityAlertsTable-totalAlerts')).toHaveTextContent('4'); expect(queryByTestId('userSeverityAlertsTable-critical')).toHaveTextContent('4'); expect(queryByTestId('userSeverityAlertsTable-high')).toHaveTextContent('1'); @@ -124,4 +135,42 @@ describe('UserAlertsTable', () => { fireEvent.click(page3); expect(mockSetPage).toHaveBeenCalledWith(2); }); + + it('should open timeline with filters when total alerts is clicked', () => { + mockUseUserAlertsItemsReturn({ items: [parsedVulnerableUserAlertsResult[0]] }); + const { getByTestId } = renderComponent(); + + fireEvent.click(getByTestId('userSeverityAlertsTable-totalAlertsLink')); + + expect(mockOpenTimelineWithFilters).toHaveBeenCalledWith([ + [ + { + field: 'user.name', + value: userName, + }, + openAlertsFilter, + ], + ]); + }); + + it('should open timeline with filters when critical alerts link is clicked', () => { + mockUseUserAlertsItemsReturn({ items: [parsedVulnerableUserAlertsResult[0]] }); + const { getByTestId } = renderComponent(); + + fireEvent.click(getByTestId('userSeverityAlertsTable-criticalLink')); + + expect(mockOpenTimelineWithFilters).toHaveBeenCalledWith([ + [ + { + field: 'user.name', + value: userName, + }, + openAlertsFilter, + { + field: 'kibana.alert.severity', + value: 'critical', + }, + ], + ]); + }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx index c1a7ad0791221..e4a2c29caa475 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx @@ -28,7 +28,7 @@ import { UserDetailsLink } from '../../../../common/components/links'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { useNavigateToTimeline } from '../hooks/use_navigate_to_timeline'; import * as i18n from '../translations'; -import { ITEMS_PER_PAGE, SEVERITY_COLOR } from '../utils'; +import { ITEMS_PER_PAGE, openAlertsFilter, SEVERITY_COLOR } from '../utils'; import type { UserAlertsItem } from './use_user_alerts_items'; import { useUserAlertsItems } from './use_user_alerts_items'; @@ -53,7 +53,9 @@ export const UserAlertsTable = React.memo(({ signalIndexName }: UserAlertsTableP : undefined; openTimelineWithFilters( - severityFilter ? [[userNameFilter, severityFilter]] : [[userNameFilter]] + severityFilter + ? [[userNameFilter, openAlertsFilter, severityFilter]] + : [[userNameFilter, openAlertsFilter]] ); }, [openTimelineWithFilters] @@ -132,7 +134,11 @@ const getTableColumns: GetTableColumns = (handleClick) => [ name: i18n.ALERTS_TEXT, 'data-test-subj': 'userSeverityAlertsTable-totalAlerts', render: (totalAlerts: number, { userName }) => ( - handleClick({ userName })}> + handleClick({ userName })} + > ), @@ -143,6 +149,7 @@ const getTableColumns: GetTableColumns = (handleClick) => [ render: (count: number, { userName }) => ( handleClick({ userName, severity: 'critical' })} > diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/utils.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/utils.tsx index 76b690e0fbf0a..b82108c50e0e2 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/utils.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/utils.tsx @@ -21,3 +21,5 @@ const MAX_ALLOWED_RESULTS = 100; * */ export const getPageCount = (count: number = 0) => Math.ceil(Math.min(count || 0, MAX_ALLOWED_RESULTS) / ITEMS_PER_PAGE); + +export const openAlertsFilter = { field: 'kibana.alert.workflow_status', value: 'open' }; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx index 70faf174ae32f..2222fbebf2dfa 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { render } from '@testing-library/react'; +import { render, fireEvent } from '@testing-library/react'; import React from 'react'; import { TestProviders } from '../../../../common/mock'; import { EntityAnalyticsRiskScores } from '.'; @@ -13,6 +13,7 @@ import type { UserRiskScore } from '../../../../../common/search_strategy'; import { RiskScoreEntity, RiskSeverity } from '../../../../../common/search_strategy'; import type { SeverityCount } from '../../../../common/components/severity/types'; import { useRiskScore, useRiskScoreKpi } from '../../../../risk_score/containers'; +import { openAlertsFilter } from '../../detection_response/utils'; const mockSeverityCount: SeverityCount = { [RiskSeverity.low]: 1, @@ -42,6 +43,15 @@ const mockUseRiskScore = useRiskScore as jest.Mock; const mockUseRiskScoreKpi = useRiskScoreKpi as jest.Mock; jest.mock('../../../../risk_score/containers'); +const mockOpenTimelineWithFilters = jest.fn(); +jest.mock('../../detection_response/hooks/use_navigate_to_timeline', () => { + return { + useNavigateToTimeline: () => ({ + openTimelineWithFilters: mockOpenTimelineWithFilters, + }), + }; +}); + describe.each([RiskScoreEntity.host, RiskScoreEntity.user])( 'EntityAnalyticsRiskScores entityType: %s', (riskEntity) => { @@ -150,5 +160,48 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])( expect(queryByTestId('risk-score-alerts')).toHaveTextContent(alertsCount.toString()); }); + + it('navigates to timeline with filters when alerts count is clicked', () => { + mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() }); + mockUseRiskScoreKpi.mockReturnValue({ + severityCount: mockSeverityCount, + loading: false, + }); + const name = 'testName'; + const data = [ + { + '@timestamp': '1234567899', + [riskEntity]: { + name, + risk: { + rule_risks: [], + calculated_level: RiskSeverity.high, + calculated_score_norm: 75, + multipliers: [], + }, + }, + alertsCount: 999, + }, + ]; + mockUseRiskScore.mockReturnValue({ ...defaultProps, data }); + + const { getByTestId } = render( + + + + ); + + fireEvent.click(getByTestId('risk-score-alerts')); + + expect(mockOpenTimelineWithFilters.mock.calls[0][0]).toEqual([ + [ + { + field: riskEntity === RiskScoreEntity.host ? 'host.name' : 'user.name', + value: name, + }, + openAlertsFilter, + ], + ]); + }); } ); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx index 40306e24fb423..f3410e7e7b4b1 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx @@ -42,6 +42,7 @@ import * as commonI18n from '../common/translations'; import { usersActions } from '../../../../users/store'; import { useNavigateToTimeline } from '../../detection_response/hooks/use_navigate_to_timeline'; import type { TimeRange } from '../../../../common/store/inputs/model'; +import { openAlertsFilter } from '../../detection_response/utils'; const HOST_RISK_TABLE_QUERY_ID = 'hostRiskDashboardTable'; const HOST_RISK_KPI_QUERY_ID = 'headerHostRiskScoreKpiQuery'; @@ -110,7 +111,7 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc field: riskEntity === RiskScoreEntity.host ? 'host.name' : 'user.name', value: entityName, }; - openTimelineWithFilters([[filter]], timeRange); + openTimelineWithFilters([[filter, openAlertsFilter]], timeRange); }, [riskEntity, openTimelineWithFilters] ); From e6ae2f2cee360ffab4a5f6abbc8d7acef49f68c1 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Wed, 2 Nov 2022 11:39:48 +0000 Subject: [PATCH 03/86] [Security Solution] Add custom features to lens (#138995) * allows not to show inspector * pass adapters to onLoad * update unit tests * metric alignment * clean up * update style * fix type error * fix types * clean up types * fix unit tests * fix unit tests * reuse textAlignment * update configs * remove unused args * rm unused props * fix types * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * fix unit tests * update FTR configs * update snapshot * rename arg * update lens attributes * revert picture * add comment Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../expression_functions/metric_vis_function.ts | 10 ++++++++++ .../common/types/expression_functions.ts | 3 ++- .../common/types/expression_renderers.ts | 3 +++ .../__snapshots__/with_auto_scale.test.tsx.snap | 1 + .../public/components/metric_component.tsx | 6 ++++++ .../public/components/with_auto_scale.tsx | 12 +++++++++++- x-pack/plugins/lens/common/types.ts | 1 + .../lens/public/embeddable/embeddable_component.tsx | 6 +++++- .../visualizations/legacy_metric/visualization.tsx | 3 +++ .../__snapshots__/kpi_dns_queries.test.ts.snap | 1 + .../__snapshots__/kpi_network_events.test.ts.snap | 1 + .../__snapshots__/kpi_tls_handshakes.test.ts.snap | 1 + .../__snapshots__/kpi_unique_flow_ids.test.ts.snap | 1 + .../lens_attributes/network/kpi_dns_queries.ts | 1 + .../lens_attributes/network/kpi_network_events.ts | 1 + .../lens_attributes/network/kpi_tls_handshakes.ts | 1 + .../lens_attributes/network/kpi_unique_flow_ids.ts | 1 + 17 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.ts b/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.ts index a7d655107fc2d..77f061e7a0cea 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.ts +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.ts @@ -45,6 +45,13 @@ export const metricVisFunction = (): MetricVisExpressionFunctionDefinition => ({ defaultMessage: 'Metric visualization', }), args: { + autoScaleMetricAlignment: { + types: ['string'], + help: i18n.translate('expressionLegacyMetricVis.function.autoScaleMetricAlignment.help', { + defaultMessage: 'Metric alignment after scaled', + }), + required: false, + }, percentageMode: { types: ['boolean'], default: false, @@ -177,6 +184,9 @@ export const metricVisFunction = (): MetricVisExpressionFunctionDefinition => ({ visType, visConfig: { metric: { + ...(args.autoScaleMetricAlignment + ? { autoScaleMetricAlignment: args.autoScaleMetricAlignment } + : {}), palette: args.palette?.params, percentageMode: args.percentageMode, metricColorMode: args.colorMode, diff --git a/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts index f0a63b012dc0a..746915f213271 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts @@ -15,10 +15,11 @@ import { } from '@kbn/expressions-plugin/common'; import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common'; import { ColorMode, CustomPaletteState } from '@kbn/charts-plugin/common'; -import { VisParams, visType, LabelPositionType } from './expression_renderers'; +import { VisParams, visType, LabelPositionType, MetricAlignment } from './expression_renderers'; import { EXPRESSION_METRIC_NAME } from '../constants'; export interface MetricArguments { + autoScaleMetricAlignment?: MetricAlignment; percentageMode: boolean; colorMode: ColorMode; showLabels: boolean; diff --git a/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_renderers.ts index 8c370480a7be9..9341e267575bd 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_renderers.ts +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_renderers.ts @@ -29,7 +29,10 @@ export type LabelPositionType = $Values; export type MetricStyle = Style & Pick; export type LabelsConfig = Labels & { style: Style; position: LabelPositionType }; + +export type MetricAlignment = 'left' | 'center' | 'right'; export interface MetricVisParam { + autoScaleMetricAlignment?: MetricAlignment; percentageMode: boolean; percentageFormatPattern?: string; metricColorMode: ColorMode; diff --git a/src/plugins/chart_expressions/expression_legacy_metric/public/components/__snapshots__/with_auto_scale.test.tsx.snap b/src/plugins/chart_expressions/expression_legacy_metric/public/components/__snapshots__/with_auto_scale.test.tsx.snap index 4acbab635ba47..37b8587d6b6af 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/public/components/__snapshots__/with_auto_scale.test.tsx.snap +++ b/src/plugins/chart_expressions/expression_legacy_metric/public/components/__snapshots__/with_auto_scale.test.tsx.snap @@ -29,3 +29,4 @@ exports[`AutoScale withAutoScale renders 1`] = ` `; + diff --git a/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.tsx b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.tsx index 2bebcda46bbe5..5dd9ef4d277aa 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.tsx +++ b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.tsx @@ -143,6 +143,12 @@ class MetricVisComponent extends Component { minHeight: '100%', minWidth: '100%', }, + ...(this.props.visParams.metric?.autoScaleMetricAlignment + ? { + autoScaleMetricAlignment: + this.props.visParams.metric?.autoScaleMetricAlignment, + } + : {}), } : undefined } diff --git a/src/plugins/chart_expressions/expression_legacy_metric/public/components/with_auto_scale.tsx b/src/plugins/chart_expressions/expression_legacy_metric/public/components/with_auto_scale.tsx index 3343385dff40e..2f67094fdc4ed 100644 --- a/src/plugins/chart_expressions/expression_legacy_metric/public/components/with_auto_scale.tsx +++ b/src/plugins/chart_expressions/expression_legacy_metric/public/components/with_auto_scale.tsx @@ -20,6 +20,7 @@ import { useResizeObserver } from '@elastic/eui'; import { autoScaleWrapperStyle } from './with_auto_scale.styles'; interface AutoScaleParams { + autoScaleMetricAlignment?: 'left' | 'center' | 'right'; minScale?: number; containerStyles: CSSProperties; } @@ -83,7 +84,6 @@ export function withAutoScale(WrappedComponent: ComponentType) { const parentRef = useRef(null); const childrenRef = useRef(null); const parentDimensions = useResizeObserver(parentRef.current); - const scaleFn = useMemo( () => throttle(() => { @@ -120,6 +120,16 @@ export function withAutoScale(WrappedComponent: ComponentType) { ref={childrenRef} style={{ transform: `scale(${scale || 0})`, + ...(parentDimensions.width && + scale && + autoScaleParams?.autoScaleMetricAlignment && + autoScaleParams?.autoScaleMetricAlignment !== 'center' + ? { + position: 'relative', + [autoScaleParams.autoScaleMetricAlignment]: + (1 - scale) * parentDimensions.width * scale * -1, // The difference of width after scaled + } + : {}), }} > diff --git a/x-pack/plugins/lens/common/types.ts b/x-pack/plugins/lens/common/types.ts index 9056e58eef1eb..f1e0e523548e0 100644 --- a/x-pack/plugins/lens/common/types.ts +++ b/x-pack/plugins/lens/common/types.ts @@ -85,6 +85,7 @@ export interface PieVisualizationState { palette?: PaletteOutput; } export interface LegacyMetricState { + autoScaleMetricAlignment?: 'left' | 'right' | 'center'; layerId: string; accessor?: string; layerType: LayerType; diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx index 56939b54299ce..e05f82160b658 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx @@ -62,6 +62,7 @@ export type TypedLensByValueInput = Omit & { export type EmbeddableComponentProps = (TypedLensByValueInput | LensByReferenceInput) & { withDefaultActions?: boolean; extraActions?: Action[]; + showInspector?: boolean; }; interface PluginsStartDependencies { @@ -89,6 +90,7 @@ export function getEmbeddableComponent(core: CoreStart, plugins: PluginsStartDep input={input} theme={theme} extraActions={input.extraActions} + showInspector={input.showInspector} withDefaultActions={input.withDefaultActions} /> ); @@ -119,6 +121,7 @@ interface EmbeddablePanelWrapperProps { input: EmbeddableComponentProps; theme: ThemeServiceStart; extraActions?: Action[]; + showInspector?: boolean; withDefaultActions?: boolean; } @@ -130,6 +133,7 @@ const EmbeddablePanelWrapper: FC = ({ input, theme, extraActions, + showInspector = true, withDefaultActions, }) => { const [embeddable, loading] = useEmbeddableFactory({ factory, input }); @@ -154,7 +158,7 @@ const EmbeddablePanelWrapper: FC = ({ return [...(extraActions ?? []), ...actions]; }} - inspector={inspector} + inspector={showInspector ? inspector : undefined} actionPredicate={actionPredicate} showShadow={false} showBadges={false} diff --git a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx index 37304e09523a7..c4546fc8e7141 100644 --- a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx @@ -105,6 +105,9 @@ const toExpression = ( type: 'function', function: 'legacyMetricVis', arguments: { + ...(state?.autoScaleMetricAlignment + ? { autoScaleMetricAlignment: [state?.autoScaleMetricAlignment] } + : {}), labelPosition: [state?.titlePosition || DEFAULT_TITLE_POSITION], font: [ { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_dns_queries.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_dns_queries.test.ts.snap index 34bc6f13f8004..11d7e4f53ede4 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_dns_queries.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_dns_queries.test.ts.snap @@ -158,6 +158,7 @@ Object { }, "visualization": Object { "accessor": "0374e520-eae0-4ac1-bcfe-37565e7fc9e3", + "autoScaleMetricAlignment": "left", "colorMode": "None", "layerId": "cea37c70-8f91-43bf-b9fe-72d8c049f6a3", "layerType": "data", diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_network_events.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_network_events.test.ts.snap index b29761bbf8a74..f91197d152584 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_network_events.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_network_events.test.ts.snap @@ -163,6 +163,7 @@ Object { }, "visualization": Object { "accessor": "370ebd07-5ce0-4f46-a847-0e363c50d037", + "autoScaleMetricAlignment": "left", "layerId": "eaadfec7-deaa-4aeb-a403-3b4e516416d2", "layerType": "data", }, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_tls_handshakes.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_tls_handshakes.test.ts.snap index b8ed38aa918c4..1ee417328f194 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_tls_handshakes.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_tls_handshakes.test.ts.snap @@ -182,6 +182,7 @@ Object { }, "visualization": Object { "accessor": "21052b6b-5504-4084-a2e2-c17f772345cf", + "autoScaleMetricAlignment": "left", "layerId": "1f48a633-8eee-45ae-9471-861227e9ca03", "layerType": "data", }, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_flow_ids.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_flow_ids.test.ts.snap index 06daf745e0345..d971cfa0cd7ce 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_flow_ids.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_flow_ids.test.ts.snap @@ -146,6 +146,7 @@ Object { }, "visualization": Object { "accessor": "a27f3503-9c73-4fc1-86bb-12461dae4b70", + "autoScaleMetricAlignment": "left", "layerId": "5d46d48f-6ce8-46be-a797-17ad50642564", "layerType": "data", }, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.ts index c4691a4797b5b..4f759160aebb8 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_dns_queries.ts @@ -17,6 +17,7 @@ export const kpiDnsQueriesLensAttributes: LensAttributes = { accessor: '0374e520-eae0-4ac1-bcfe-37565e7fc9e3', layerType: 'data', colorMode: 'None', + autoScaleMetricAlignment: 'left', }, query: { query: '', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.ts index bb88ceb732c66..bdaa099a95dac 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_network_events.ts @@ -16,6 +16,7 @@ export const kpiNetworkEventsLensAttributes: LensAttributes = { layerId: 'eaadfec7-deaa-4aeb-a403-3b4e516416d2', accessor: '370ebd07-5ce0-4f46-a847-0e363c50d037', layerType: 'data', + autoScaleMetricAlignment: 'left', }, query: { query: '', diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.ts index b7b651bf56362..8b4e8c6b5e43e 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_tls_handshakes.ts @@ -15,6 +15,7 @@ export const kpiTlsHandshakesLensAttributes: LensAttributes = { layerId: '1f48a633-8eee-45ae-9471-861227e9ca03', accessor: '21052b6b-5504-4084-a2e2-c17f772345cf', layerType: 'data', + autoScaleMetricAlignment: 'left', }, query: { query: diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.ts index 3660f2ff6ad06..01d59b68ad800 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/kpi_unique_flow_ids.ts @@ -16,6 +16,7 @@ export const kpiUniqueFlowIdsLensAttributes: LensAttributes = { layerId: '5d46d48f-6ce8-46be-a797-17ad50642564', accessor: 'a27f3503-9c73-4fc1-86bb-12461dae4b70', layerType: 'data', + autoScaleMetricAlignment: 'left', }, query: { query: 'source.ip: * or destination.ip: * ', From 8a212ea209095be008915ee02a23ba5e7fdf340e Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Wed, 2 Nov 2022 12:44:37 +0100 Subject: [PATCH 04/86] added settings action to agent activity flyout (#144394) --- .../agent_list_page/components/agent_activity_flyout.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx index dfa6623f4a90a..c3b5605ecd484 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx @@ -254,6 +254,11 @@ const actionNames: { cancelledText: 'update tags', }, CANCEL: { inProgressText: 'Cancelling', completedText: 'cancelled', cancelledText: '' }, + SETTINGS: { + inProgressText: 'Updating settings of', + completedText: 'updated settings', + cancelledText: 'update settings', + }, ACTION: { inProgressText: 'Actioning', completedText: 'actioned', cancelledText: 'action' }, }; From 096d61c1f15ad797b35bcfcf33a0655a7ed68572 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 2 Nov 2022 12:57:32 +0100 Subject: [PATCH 05/86] [Synthetics] Step details add object weight breakdown (#144345) --- .../components/color_palette.tsx | 103 ++++++++++++++++++ .../components/object_weight_list.tsx | 57 ++++++++++ .../hooks/use_object_metrics.ts | 66 +++++++++++ .../step_details_page/step_detail_page.tsx | 22 ++-- 4 files changed, 240 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/color_palette.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/object_weight_list.tsx create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_object_metrics.ts diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/color_palette.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/color_palette.tsx new file mode 100644 index 0000000000000..bafd0de5aa48a --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/color_palette.tsx @@ -0,0 +1,103 @@ +/* + * 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, { useState, useEffect } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingContent } from '@elastic/eui'; +import { useTheme } from '@kbn/observability-plugin/public'; +import styled from 'styled-components'; +import { colourPalette } from './network_waterfall/step_detail/waterfall/data_formatting'; +export const ColorPalette = ({ + label, + mimeType, + percent, + value, + loading, +}: { + label: string; + mimeType: string; + percent: number; + value: string; + loading: boolean; +}) => { + return ( + + + {label} + + + + + + + {value} + + + + ); +}; + +export const ColorPaletteFlexItem = ({ + mimeType, + percent, + loading, +}: { + mimeType: string; + percent: number; + loading: boolean; +}) => { + const { eui } = useTheme(); + + const [value, setVal] = useState(0); + + useEffect(() => { + setTimeout(() => { + if (value < percent) { + setVal(value + 1); + } + }, 10); + }, [percent, value]); + + if (loading) { + return ; + } + + return ( + + + )[mimeType], + height: 20, + width: `${value}%`, + }} + /> + + + ); +}; + +const LoadingLine = styled(EuiLoadingContent)` + &&& { + > span { + height: 20px; + } + } +`; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/object_weight_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/object_weight_list.tsx new file mode 100644 index 0000000000000..6fd10b6545196 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/object_weight_list.tsx @@ -0,0 +1,57 @@ +/* + * 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, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { ColorPalette } from './color_palette'; +import { useObjectMetrics } from '../hooks/use_object_metrics'; + +export const ObjectWeightList = () => { + const objectMetrics = useObjectMetrics(); + + return ( + <> + + + +

{OBJECT_WEIGHT_LABEL}

+
+
+ + + {TOTAL_SIZE_LABEL}:{' '} + {objectMetrics.totalObjectsWeight} + + +
+ +
+ {objectMetrics.items.map(({ label, mimeType, weightPercent, weight }) => ( + <> + + {' '} + + ))} +
+ + ); +}; + +const OBJECT_WEIGHT_LABEL = i18n.translate('xpack.synthetics.stepDetails.objectWeight', { + defaultMessage: 'Object weight', +}); + +const TOTAL_SIZE_LABEL = i18n.translate('xpack.synthetics.stepDetails.totalSize', { + defaultMessage: 'Total size', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_object_metrics.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_object_metrics.ts new file mode 100644 index 0000000000000..c80d57af30766 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_object_metrics.ts @@ -0,0 +1,66 @@ +/* + * 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 { useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import { MIME_FILTERS } from '../components/network_waterfall/step_detail/waterfall/waterfall_filter'; +import { + MimeType, + MimeTypesMap, +} from '../components/network_waterfall/step_detail/waterfall/types'; +import { networkEventsSelector } from '../../../state/network_events/selectors'; + +export const useObjectMetrics = () => { + const { checkGroupId, stepIndex } = useParams<{ checkGroupId: string; stepIndex: string }>(); + + const _networkEvents = useSelector(networkEventsSelector); + const networkEvents = _networkEvents[checkGroupId ?? '']?.[Number(stepIndex)]; + + const objectTypeCounts: Record = {}; + const objectTypeWeights: Record = {}; + + networkEvents?.events.forEach((event) => { + if (event.mimeType) { + objectTypeCounts[MimeTypesMap[event.mimeType] ?? MimeType.Other] = + (objectTypeCounts[MimeTypesMap[event.mimeType] ?? MimeType.Other] ?? 0) + 1; + objectTypeWeights[MimeTypesMap[event.mimeType] ?? MimeType.Other] = + (objectTypeWeights[MimeTypesMap[event.mimeType] ?? MimeType.Other] ?? 0) + + (event.transferSize || 0); + } + }); + + const totalObjects = Object.values(objectTypeCounts).reduce((acc, val) => acc + val, 0); + + const totalObjectsWeight = Object.values(objectTypeWeights).reduce((acc, val) => acc + val, 0); + + return { + loading: networkEvents?.loading ?? true, + totalObjects, + totalObjectsWeight: formatBytes(totalObjectsWeight), + items: MIME_FILTERS.map(({ label, mimeType }) => ({ + label, + count: objectTypeCounts[mimeType] ?? 0, + total: totalObjects, + mimeType, + percent: ((objectTypeCounts[mimeType] ?? 0) / totalObjects) * 100, + weight: formatBytes(objectTypeWeights[mimeType] ?? 0), + weightPercent: ((objectTypeWeights[mimeType] ?? 0) / totalObjectsWeight) * 100, + })), + }; +}; + +const formatBytes = (bytes: number, decimals = 0) => { + if (bytes === 0) return '0 Bytes'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx index 52af4964ee716..f7e2752dcfd9d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx @@ -14,11 +14,14 @@ import { EuiHorizontalRule, EuiPanel, EuiLoadingSpinner, + EuiSpacer, } from '@elastic/eui'; import { WaterfallChartContainer } from './components/network_waterfall/step_detail/waterfall/waterfall_chart_container'; +import { ObjectWeightList } from './components/object_weight_list'; import { StepImage } from './components/step_image'; import { useJourneySteps } from '../monitor_details/hooks/use_journey_steps'; import { MonitorDetailsLinkPortal } from '../monitor_add_edit/monitor_details_portal'; + import { useStepDetailsBreadcrumbs } from './hooks/use_step_details_breadcrumbs'; export const StepDetailPage = () => { @@ -51,9 +54,9 @@ export const StepDetailPage = () => { name={data.details.journey.monitor.name!} /> )} - + - + {data?.details?.journey && currentStep && ( { - + {/* TODO: Add breakdown of network timings donut*/} @@ -77,15 +80,18 @@ export const StepDetailPage = () => { - + + - {/* TODO: Add step metrics*/} + + {/* TODO: Add step metrics*/}{' '} + - + - - {/* TODO: Add breakdown of object list*/} + + {/* TODO: Add breakdown of object weight*/} From ca1c58d3dff2a40236f79150e78d278df0f194ff Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Wed, 2 Nov 2022 13:18:21 +0100 Subject: [PATCH 06/86] [UnifiedFieldList] Migrate field list components from Lens to UnifiedFieldList (#142758) * [UnifiedFieldList] Extract FieldsAccordion component from Lens * [UnifiedFieldList] Extract FieldList component from Lens * [CI] Auto-commit changed files from 'node scripts/build_plugin_list_docs' * [UnifiedFieldList] Rename component * [UnifiedFieldList] Start extracting logic for fetching fields existence info * [UnifiedFieldList] Start extracting logic for fetching fields existence info * [UnifiedFieldList] Fix special and runtime fields * [UnifiedFieldList] Start extracting logic for fetching fields existence info * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * [UnifiedFieldList] Make API stricter * [UnifiedFieldList] Make sure that key is specified for items * [UnifiedFieldList] Fetch data for all active data views * [UnifiedFieldList] Refactor some other occurances * [UnifiedFieldList] Update more tests * [UnifiedFieldList] Fix some checks * [UnifiedFieldList] Some progress on updating tests * [UnifiedFieldList] Update more tests * [UnifiedFieldList] Skip redundant request's results * [UnifiedFieldList] Update more tests * [UnifiedFieldList] Improve tests * [UnifiedFieldList] Improve tests * [UnifiedFieldList] Improve tests * [UnifiedFieldList] Move grouping into a customizable hook * [UnifiedFieldList] Fix after the merge * [UnifiedFieldList] Fix checks * Revert "[UnifiedFieldList] Fix after the merge" This reverts commit 500db7ed89982aa32c2807504d094bccab6b8573. * [UnifiedFieldList] Handle merge better * [UnifiedFieldList] Update the naming * [UnifiedFieldList] Support Selected fields * [UnifiedFieldList] Update tests * [UnifiedFieldList] Fix grouping * [UnifiedFieldList] Update more tests * [UnifiedFieldList] Fix refetch after adding a field * [UnifiedFieldList] Load es query builder in async way * [UnifiedFieldList] Fix a bug in case of renaming a field * [UnifiedFieldList] Small refactoring * [UnifiedFieldList] Refactor text based view * [UnifiedFieldList] Better types support * [UnifiedFieldList] Simplify props * [UnifiedFieldList] Fix types * [UnifiedFieldList] Async loading for FieldListGrouped code * [UnifiedFieldList] Add more tests * [UnifiedFieldList] Add more tests * [UnifiedFieldList] Add more tests * [UnifiedFieldList] Add more tests * [UnifiedFieldList] Add more tests * [UnifiedFieldList] Add more tests * [UnifiedFieldList] Add more tests * [UnifiedFieldList] Add docs * [UnifiedFieldList] Clean up * [UnifiedFieldList] Fix onNoData callback * [UnifiedFieldList] Address PR comments * [UnifiedFieldList] Address PR comments * [UnifiedFieldList] Support a custom data-test-subj * [UnifiedFieldList] Fix concurrency handling logic * [UnifiedFieldList] Remove a generic tooltip message. Lens and Discover will have their own tooltips. Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli --- src/plugins/unified_field_list/README.md | 45 +- .../field_list/field_list_grouped.scss | 4 +- .../field_list/field_list_grouped.test.tsx | 413 +++++++++ .../field_list/field_list_grouped.tsx | 245 ++++++ .../field_list/fields_accordion.scss | 8 + .../field_list/fields_accordion.test.tsx | 62 ++ .../field_list}/fields_accordion.tsx | 124 +-- .../public/components/field_list/index.tsx | 31 + .../field_list}/no_fields_callout.test.tsx | 21 +- .../field_list}/no_fields_callout.tsx | 53 +- .../components/field_stats/field_stats.tsx | 4 +- .../public/hooks/use_existing_fields.test.tsx | 536 ++++++++++++ .../public/hooks/use_existing_fields.ts | 347 ++++++++ .../public/hooks/use_grouped_fields.test.tsx | 272 ++++++ .../public/hooks/use_grouped_fields.ts | 267 ++++++ .../unified_field_list/public/index.ts | 20 + .../public/services/field_existing/index.ts | 7 +- .../field_existing/load_field_existing.ts | 11 +- .../unified_field_list/public/types.ts | 41 + x-pack/plugins/lens/common/types.ts | 5 - .../public/data_views_service/loader.test.ts | 230 +---- .../lens/public/data_views_service/loader.ts | 122 --- .../lens/public/data_views_service/mocks.ts | 14 +- .../lens/public/data_views_service/service.ts | 28 +- .../form_based/__mocks__/loader.ts | 2 - .../datasources/form_based/datapanel.scss | 11 +- .../datasources/form_based/datapanel.test.tsx | 798 ++++++++++-------- .../datasources/form_based/datapanel.tsx | 474 ++++------- .../dimension_panel/dimension_editor.tsx | 4 - .../dimension_panel/dimension_panel.test.tsx | 36 +- .../dimension_panel/field_input.test.tsx | 85 +- .../dimension_panel/field_input.tsx | 2 - .../dimension_panel/field_select.tsx | 12 +- .../dimension_panel/reference_editor.test.tsx | 18 +- .../dimension_panel/reference_editor.tsx | 10 +- .../datasources/form_based/field_list.tsx | 220 ----- .../form_based/fields_accordion.test.tsx | 110 --- .../form_based/layerpanel.test.tsx | 2 - .../definitions/date_histogram.test.tsx | 8 - .../definitions/filters/filters.test.tsx | 8 - .../operations/definitions/index.ts | 5 - .../definitions/last_value.test.tsx | 8 - .../definitions/percentile.test.tsx | 8 - .../definitions/percentile_ranks.test.tsx | 8 - .../definitions/ranges/ranges.test.tsx | 8 - .../definitions/static_value.test.tsx | 8 - .../definitions/terms/field_inputs.tsx | 5 +- .../operations/definitions/terms/index.tsx | 4 - .../definitions/terms/terms.test.tsx | 113 +-- .../datasources/text_based/datapanel.test.tsx | 104 ++- .../datasources/text_based/datapanel.tsx | 121 ++- .../text_based/fields_accordion.tsx | 106 --- .../text_based/layerpanel.test.tsx | 2 - .../editor_frame/config_panel/layer_panel.tsx | 2 - .../plugins/lens/public/mocks/store_mocks.tsx | 2 - .../dataview_picker/helpers.ts | 18 +- .../dataview_picker/index.ts | 2 +- .../lens/public/shared_components/index.ts | 2 +- .../__snapshots__/load_initial.test.tsx.snap | 2 - .../public/state_management/lens_slice.ts | 2 - .../lens/public/state_management/types.ts | 4 - x-pack/plugins/lens/public/types.ts | 2 - x-pack/plugins/lens/public/utils.ts | 5 - .../annotations_panel.tsx | 9 +- .../query_annotation_panel.tsx | 6 +- .../tooltip_annotation_panel.tsx | 10 +- .../translations/translations/fr-FR.json | 17 - .../translations/translations/ja-JP.json | 17 - .../translations/translations/zh-CN.json | 17 - 69 files changed, 3258 insertions(+), 2069 deletions(-) rename x-pack/plugins/lens/public/datasources/form_based/field_list.scss => src/plugins/unified_field_list/public/components/field_list/field_list_grouped.scss (78%) create mode 100644 src/plugins/unified_field_list/public/components/field_list/field_list_grouped.test.tsx create mode 100644 src/plugins/unified_field_list/public/components/field_list/field_list_grouped.tsx create mode 100644 src/plugins/unified_field_list/public/components/field_list/fields_accordion.scss create mode 100644 src/plugins/unified_field_list/public/components/field_list/fields_accordion.test.tsx rename {x-pack/plugins/lens/public/datasources/form_based => src/plugins/unified_field_list/public/components/field_list}/fields_accordion.tsx (50%) create mode 100755 src/plugins/unified_field_list/public/components/field_list/index.tsx rename {x-pack/plugins/lens/public/datasources/form_based => src/plugins/unified_field_list/public/components/field_list}/no_fields_callout.test.tsx (86%) rename {x-pack/plugins/lens/public/datasources/form_based => src/plugins/unified_field_list/public/components/field_list}/no_fields_callout.tsx (52%) create mode 100644 src/plugins/unified_field_list/public/hooks/use_existing_fields.test.tsx create mode 100644 src/plugins/unified_field_list/public/hooks/use_existing_fields.ts create mode 100644 src/plugins/unified_field_list/public/hooks/use_grouped_fields.test.tsx create mode 100644 src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts delete mode 100644 x-pack/plugins/lens/public/datasources/form_based/field_list.tsx delete mode 100644 x-pack/plugins/lens/public/datasources/form_based/fields_accordion.test.tsx delete mode 100644 x-pack/plugins/lens/public/datasources/text_based/fields_accordion.tsx diff --git a/src/plugins/unified_field_list/README.md b/src/plugins/unified_field_list/README.md index 9030a32a3bdca..23edffd5101dc 100755 --- a/src/plugins/unified_field_list/README.md +++ b/src/plugins/unified_field_list/README.md @@ -6,6 +6,8 @@ This Kibana plugin contains components and services for field list UI (as in fie ## Components +* `` - renders a fields list which is split in sections (Selected, Special, Available, Empty, Meta fields). It accepts already grouped fields, please use `useGroupedFields` hook for it. + * `` - loads and renders stats (Top values, Distribution) for a data view field. * `` - renders a button to open this field in Lens. @@ -13,7 +15,7 @@ This Kibana plugin contains components and services for field list UI (as in fie * `` - a popover container component for a field. * `` - this header component included a field name and common actions. -* + * `` - renders Visualize action in the popover footer. These components can be combined and customized as the following: @@ -59,6 +61,47 @@ These components can be combined and customized as the following: * `loadFieldExisting(...)` - returns the loaded existing fields (can also work with Ad-hoc data views) +## Hooks + +* `useExistingFieldsFetcher(...)` - this hook is responsible for fetching fields existence info for specified data views. It can be used higher in components tree than `useExistingFieldsReader` hook. + +* `useExistingFieldsReader(...)` - you can call this hook to read fields existence info which was fetched by `useExistingFieldsFetcher` hook. Using multiple "reader" hooks from different children components is supported. So you would need only one "fetcher" and as many "reader" hooks as necessary. + +* `useGroupedFields(...)` - this hook groups fields list into sections of Selected, Special, Available, Empty, Meta fields. + +An example of using hooks together with ``: + +``` +const { refetchFieldsExistenceInfo, isProcessing } = useExistingFieldsFetcher({ + dataViews, + query, + filters, + fromDate, + toDate, + ... +}); +const fieldsExistenceReader = useExistingFieldsReader() +const { fieldGroups } = useGroupedFields({ + dataViewId: currentDataViewId, + allFields, + fieldsExistenceReader, + ... +}); + +// and now we can render a field list + + +// or check whether a field contains data +const { hasFieldData } = useExistingFieldsReader(); +const hasData = hasFieldData(currentDataViewId, fieldName) // return a boolean +``` + ## Server APIs * `/api/unified_field_list/field_stats` - returns the loaded field stats (except for Ad-hoc data views) diff --git a/x-pack/plugins/lens/public/datasources/form_based/field_list.scss b/src/plugins/unified_field_list/public/components/field_list/field_list_grouped.scss similarity index 78% rename from x-pack/plugins/lens/public/datasources/form_based/field_list.scss rename to src/plugins/unified_field_list/public/components/field_list/field_list_grouped.scss index f28581b835b07..cd4b9ba2f6e22 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/field_list.scss +++ b/src/plugins/unified_field_list/public/components/field_list/field_list_grouped.scss @@ -2,7 +2,7 @@ * 1. Don't cut off the shadow of the field items */ -.lnsIndexPatternFieldList { +.unifiedFieldList__fieldListGrouped { @include euiOverflowShadow; @include euiScrollBar; margin-left: -$euiSize; /* 1 */ @@ -11,7 +11,7 @@ overflow: auto; } -.lnsIndexPatternFieldList__accordionContainer { +.unifiedFieldList__fieldListGrouped__container { padding-top: $euiSizeS; position: absolute; top: 0; diff --git a/src/plugins/unified_field_list/public/components/field_list/field_list_grouped.test.tsx b/src/plugins/unified_field_list/public/components/field_list/field_list_grouped.test.tsx new file mode 100644 index 0000000000000..59cd7e56ff390 --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_list/field_list_grouped.test.tsx @@ -0,0 +1,413 @@ +/* + * 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 React from 'react'; +import { act } from 'react-dom/test-utils'; +import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { EuiText, EuiLoadingSpinner } from '@elastic/eui'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { DataViewField } from '@kbn/data-views-plugin/common'; +import { ReactWrapper } from 'enzyme'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import FieldListGrouped, { type FieldListGroupedProps } from './field_list_grouped'; +import { ExistenceFetchStatus } from '../../types'; +import { FieldsAccordion } from './fields_accordion'; +import { NoFieldsCallout } from './no_fields_callout'; +import { useGroupedFields, type GroupedFieldsParams } from '../../hooks/use_grouped_fields'; + +describe('UnifiedFieldList + useGroupedFields()', () => { + let defaultProps: FieldListGroupedProps; + let mockedServices: GroupedFieldsParams['services']; + const allFields = dataView.fields; + // 5 times more fields. Added fields will be treated as empty as they are not a part of the data view. + const manyFields = [...new Array(5)].flatMap((_, index) => + allFields.map((field) => { + return new DataViewField({ ...field.toSpec(), name: `${field.name}${index || ''}` }); + }) + ); + + beforeEach(() => { + const dataViews = dataViewPluginMocks.createStartContract(); + mockedServices = { + dataViews, + }; + + dataViews.get.mockImplementation(async (id: string) => { + return dataView; + }); + + defaultProps = { + fieldGroups: {}, + fieldsExistenceStatus: ExistenceFetchStatus.succeeded, + fieldsExistInIndex: true, + screenReaderDescriptionForSearchInputId: 'testId', + renderFieldItem: jest.fn(({ field, itemIndex, groupIndex }) => ( + + {field.name} + + )), + }; + }); + + interface WrapperProps { + listProps: Omit, 'fieldGroups'>; + hookParams: Omit, 'services'>; + } + + async function mountGroupedList({ listProps, hookParams }: WrapperProps): Promise { + const Wrapper: React.FC = (props) => { + const { fieldGroups } = useGroupedFields({ + ...props.hookParams, + services: mockedServices, + }); + + return ; + }; + + let wrapper: ReactWrapper; + await act(async () => { + wrapper = await mountWithIntl(); + // wait for lazy modules if any + await new Promise((resolve) => setTimeout(resolve, 0)); + await wrapper.update(); + }); + + return wrapper!; + } + + it('renders correctly in empty state', () => { + const wrapper = mountWithIntl( + + ); + + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe(''); + }); + + it('renders correctly in loading state', async () => { + const wrapper = await mountGroupedList({ + listProps: { + ...defaultProps, + fieldsExistenceStatus: ExistenceFetchStatus.unknown, + }, + hookParams: { + dataViewId: dataView.id!, + allFields, + }, + }); + + expect(wrapper.find(FieldListGrouped).prop('fieldsExistenceStatus')).toBe( + ExistenceFetchStatus.unknown + ); + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe(''); + expect(wrapper.find(FieldsAccordion)).toHaveLength(3); + expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(3); + expect( + wrapper.find(FieldsAccordion).map((accordion) => accordion.prop('hasLoaded')) + ).toStrictEqual([false, false, false]); + expect(wrapper.find(NoFieldsCallout)).toHaveLength(0); + + await act(async () => { + await wrapper.setProps({ + listProps: { + ...defaultProps, + fieldsExistenceStatus: ExistenceFetchStatus.succeeded, + }, + }); + await wrapper.update(); + }); + + expect(wrapper.find(FieldListGrouped).prop('fieldsExistenceStatus')).toBe( + ExistenceFetchStatus.succeeded + ); + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe('25 available fields. 0 empty fields. 3 meta fields.'); + expect(wrapper.find(FieldsAccordion)).toHaveLength(3); + expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); + expect( + wrapper.find(FieldsAccordion).map((accordion) => accordion.prop('hasLoaded')) + ).toStrictEqual([true, true, true]); + expect( + wrapper.find(FieldsAccordion).map((accordion) => accordion.prop('paginatedFields').length) + ).toStrictEqual([25, 0, 0]); + expect(wrapper.find(NoFieldsCallout)).toHaveLength(1); + }); + + it('renders correctly in failed state', async () => { + const wrapper = await mountGroupedList({ + listProps: { + ...defaultProps, + fieldsExistenceStatus: ExistenceFetchStatus.failed, + }, + hookParams: { + dataViewId: dataView.id!, + allFields, + }, + }); + + expect(wrapper.find(FieldListGrouped).prop('fieldsExistenceStatus')).toBe( + ExistenceFetchStatus.failed + ); + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe('25 available fields. 0 empty fields. 3 meta fields.'); + expect(wrapper.find(FieldsAccordion)).toHaveLength(3); + expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); + expect( + wrapper.find(FieldsAccordion).map((accordion) => accordion.prop('hasLoaded')) + ).toStrictEqual([true, true, true]); + expect( + wrapper.find(FieldsAccordion).map((accordion) => accordion.prop('showExistenceFetchError')) + ).toStrictEqual([true, true, true]); + }); + + it('renders correctly in no fields state', async () => { + const wrapper = await mountGroupedList({ + listProps: { + ...defaultProps, + fieldsExistInIndex: false, + fieldsExistenceStatus: ExistenceFetchStatus.failed, + }, + hookParams: { + dataViewId: dataView.id!, + allFields: [], + }, + }); + + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe('0 available fields. 0 empty fields. 0 meta fields.'); + expect(wrapper.find(FieldsAccordion)).toHaveLength(3); + expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); + expect( + wrapper.find(NoFieldsCallout).map((callout) => callout.prop('fieldsExistInIndex')) + ).toStrictEqual([false, false, false]); + }); + + it('renders correctly for text-based queries (no data view)', async () => { + const wrapper = await mountGroupedList({ + listProps: { + ...defaultProps, + fieldsExistenceStatus: ExistenceFetchStatus.succeeded, + }, + hookParams: { + dataViewId: null, + allFields, + onSelectedFieldFilter: (field) => field.name === 'bytes', + }, + }); + + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe('1 selected field. 28 available fields.'); + expect( + wrapper.find(FieldsAccordion).map((accordion) => accordion.prop('paginatedFields').length) + ).toStrictEqual([1, 28]); + }); + + it('renders correctly when Meta gets open', async () => { + const wrapper = await mountGroupedList({ + listProps: { + ...defaultProps, + fieldsExistenceStatus: ExistenceFetchStatus.succeeded, + }, + hookParams: { + dataViewId: dataView.id!, + allFields, + }, + }); + + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe('25 available fields. 0 empty fields. 3 meta fields.'); + expect( + wrapper.find(FieldsAccordion).map((accordion) => accordion.prop('paginatedFields').length) + ).toStrictEqual([25, 0, 0]); + + await act(async () => { + await wrapper + .find('[data-test-subj="fieldListGroupedMetaFields"]') + .find('button') + .first() + .simulate('click'); + await wrapper.update(); + }); + + expect( + wrapper.find(FieldsAccordion).map((accordion) => accordion.prop('paginatedFields').length) + ).toStrictEqual([25, 0, 3]); + }); + + it('renders correctly when paginated', async () => { + const wrapper = await mountGroupedList({ + listProps: { + ...defaultProps, + fieldsExistenceStatus: ExistenceFetchStatus.succeeded, + }, + hookParams: { + dataViewId: dataView.id!, + allFields: manyFields, + }, + }); + + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe('25 available fields. 112 empty fields. 3 meta fields.'); + expect( + wrapper.find(FieldsAccordion).map((accordion) => accordion.prop('paginatedFields').length) + ).toStrictEqual([25, 0, 0]); + + await act(async () => { + await wrapper + .find('[data-test-subj="fieldListGroupedEmptyFields"]') + .find('button') + .first() + .simulate('click'); + await wrapper.update(); + }); + + expect( + wrapper.find(FieldsAccordion).map((accordion) => accordion.prop('paginatedFields').length) + ).toStrictEqual([25, 50, 0]); + + await act(async () => { + await wrapper + .find('[data-test-subj="fieldListGroupedMetaFields"]') + .find('button') + .first() + .simulate('click'); + await wrapper.update(); + }); + + expect( + wrapper.find(FieldsAccordion).map((accordion) => accordion.prop('paginatedFields').length) + ).toStrictEqual([25, 88, 0]); + }); + + it('renders correctly when filtered', async () => { + const hookParams = { + dataViewId: dataView.id!, + allFields: manyFields, + }; + const wrapper = await mountGroupedList({ + listProps: { + ...defaultProps, + fieldsExistenceStatus: ExistenceFetchStatus.succeeded, + }, + hookParams, + }); + + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe('25 available fields. 112 empty fields. 3 meta fields.'); + + await act(async () => { + await wrapper.setProps({ + hookParams: { + ...hookParams, + onFilterField: (field: DataViewField) => field.name.startsWith('@'), + }, + }); + await wrapper.update(); + }); + + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe('2 available fields. 8 empty fields. 0 meta fields.'); + + await act(async () => { + await wrapper.setProps({ + hookParams: { + ...hookParams, + onFilterField: (field: DataViewField) => field.name.startsWith('_'), + }, + }); + await wrapper.update(); + }); + + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe('0 available fields. 12 empty fields. 3 meta fields.'); + }); + + it('renders correctly when non-supported fields are filtered out', async () => { + const hookParams = { + dataViewId: dataView.id!, + allFields: manyFields, + }; + const wrapper = await mountGroupedList({ + listProps: { + ...defaultProps, + fieldsExistenceStatus: ExistenceFetchStatus.succeeded, + }, + hookParams, + }); + + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe('25 available fields. 112 empty fields. 3 meta fields.'); + + await act(async () => { + await wrapper.setProps({ + hookParams: { + ...hookParams, + onSupportedFieldFilter: (field: DataViewField) => field.aggregatable, + }, + }); + await wrapper.update(); + }); + + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe('23 available fields. 104 empty fields. 3 meta fields.'); + }); + + it('renders correctly when selected fields are present', async () => { + const hookParams = { + dataViewId: dataView.id!, + allFields: manyFields, + }; + const wrapper = await mountGroupedList({ + listProps: { + ...defaultProps, + fieldsExistenceStatus: ExistenceFetchStatus.succeeded, + }, + hookParams, + }); + + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe('25 available fields. 112 empty fields. 3 meta fields.'); + + await act(async () => { + await wrapper.setProps({ + hookParams: { + ...hookParams, + onSelectedFieldFilter: (field: DataViewField) => + ['@timestamp', 'bytes'].includes(field.name), + }, + }); + await wrapper.update(); + }); + + expect( + wrapper.find(`#${defaultProps.screenReaderDescriptionForSearchInputId}`).first().text() + ).toBe('2 selected fields. 25 available fields. 112 empty fields. 3 meta fields.'); + }); +}); diff --git a/src/plugins/unified_field_list/public/components/field_list/field_list_grouped.tsx b/src/plugins/unified_field_list/public/components/field_list/field_list_grouped.tsx new file mode 100644 index 0000000000000..5510ddb2b1d43 --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_list/field_list_grouped.tsx @@ -0,0 +1,245 @@ +/* + * 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 { partition, throttle } from 'lodash'; +import React, { useState, Fragment, useCallback, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiScreenReaderOnly, EuiSpacer } from '@elastic/eui'; +import { type DataViewField } from '@kbn/data-views-plugin/common'; +import { NoFieldsCallout } from './no_fields_callout'; +import { FieldsAccordion, type FieldsAccordionProps } from './fields_accordion'; +import type { FieldListGroups, FieldListItem } from '../../types'; +import { ExistenceFetchStatus } from '../../types'; +import './field_list_grouped.scss'; + +const PAGINATION_SIZE = 50; + +function getDisplayedFieldsLength( + fieldGroups: FieldListGroups, + accordionState: Partial> +) { + return Object.entries(fieldGroups) + .filter(([key]) => accordionState[key]) + .reduce((allFieldCount, [, { fields }]) => allFieldCount + fields.length, 0); +} + +export interface FieldListGroupedProps { + fieldGroups: FieldListGroups; + fieldsExistenceStatus: ExistenceFetchStatus; + fieldsExistInIndex: boolean; + renderFieldItem: FieldsAccordionProps['renderFieldItem']; + screenReaderDescriptionForSearchInputId?: string; + 'data-test-subj'?: string; +} + +function InnerFieldListGrouped({ + fieldGroups, + fieldsExistenceStatus, + fieldsExistInIndex, + renderFieldItem, + screenReaderDescriptionForSearchInputId, + 'data-test-subj': dataTestSubject = 'fieldListGrouped', +}: FieldListGroupedProps) { + const hasSyncedExistingFields = + fieldsExistenceStatus && fieldsExistenceStatus !== ExistenceFetchStatus.unknown; + + const [fieldGroupsToShow, fieldGroupsToCollapse] = partition( + Object.entries(fieldGroups), + ([, { showInAccordion }]) => showInAccordion + ); + const [pageSize, setPageSize] = useState(PAGINATION_SIZE); + const [scrollContainer, setScrollContainer] = useState(undefined); + const [accordionState, setAccordionState] = useState>>(() => + Object.fromEntries( + fieldGroupsToShow.map(([key, { isInitiallyOpen }]) => [key, isInitiallyOpen]) + ) + ); + + const lazyScroll = useCallback(() => { + if (scrollContainer) { + const nearBottom = + scrollContainer.scrollTop + scrollContainer.clientHeight > + scrollContainer.scrollHeight * 0.9; + if (nearBottom) { + setPageSize( + Math.max( + PAGINATION_SIZE, + Math.min( + pageSize + PAGINATION_SIZE * 0.5, + getDisplayedFieldsLength(fieldGroups, accordionState) + ) + ) + ); + } + } + }, [scrollContainer, pageSize, setPageSize, fieldGroups, accordionState]); + + const paginatedFields = useMemo(() => { + let remainingItems = pageSize; + return Object.fromEntries( + fieldGroupsToShow.map(([key, fieldGroup]) => { + if (!accordionState[key] || remainingItems <= 0) { + return [key, []]; + } + const slicedFieldList = fieldGroup.fields.slice(0, remainingItems); + remainingItems = remainingItems - slicedFieldList.length; + return [key, slicedFieldList]; + }) + ); + }, [pageSize, fieldGroupsToShow, accordionState]); + + return ( +
{ + if (el && !el.dataset.dynamicScroll) { + el.dataset.dynamicScroll = 'true'; + setScrollContainer(el); + } + }} + onScroll={throttle(lazyScroll, 100)} + > +
+ {Boolean(screenReaderDescriptionForSearchInputId) && ( + +
+ {hasSyncedExistingFields + ? [ + fieldGroups.SelectedFields && + (!fieldGroups.SelectedFields?.hideIfEmpty || + fieldGroups.SelectedFields?.fields?.length > 0) && + i18n.translate( + 'unifiedFieldList.fieldListGrouped.fieldSearchForSelectedFieldsLiveRegion', + { + defaultMessage: + '{selectedFields} selected {selectedFields, plural, one {field} other {fields}}.', + values: { + selectedFields: fieldGroups.SelectedFields?.fields?.length || 0, + }, + } + ), + fieldGroups.AvailableFields?.fields && + i18n.translate( + 'unifiedFieldList.fieldListGrouped.fieldSearchForAvailableFieldsLiveRegion', + { + defaultMessage: + '{availableFields} available {availableFields, plural, one {field} other {fields}}.', + values: { + availableFields: fieldGroups.AvailableFields.fields.length, + }, + } + ), + fieldGroups.EmptyFields && + (!fieldGroups.EmptyFields?.hideIfEmpty || + fieldGroups.EmptyFields?.fields?.length > 0) && + i18n.translate( + 'unifiedFieldList.fieldListGrouped.fieldSearchForEmptyFieldsLiveRegion', + { + defaultMessage: + '{emptyFields} empty {emptyFields, plural, one {field} other {fields}}.', + values: { + emptyFields: fieldGroups.EmptyFields?.fields?.length || 0, + }, + } + ), + fieldGroups.MetaFields && + (!fieldGroups.MetaFields?.hideIfEmpty || + fieldGroups.MetaFields?.fields?.length > 0) && + i18n.translate( + 'unifiedFieldList.fieldListGrouped.fieldSearchForMetaFieldsLiveRegion', + { + defaultMessage: + '{metaFields} meta {metaFields, plural, one {field} other {fields}}.', + values: { + metaFields: fieldGroups.MetaFields?.fields?.length || 0, + }, + } + ), + ] + .filter(Boolean) + .join(' ') + : ''} +
+
+ )} +
    + {fieldGroupsToCollapse.flatMap(([, { fields }]) => + fields.map((field, index) => ( + + {renderFieldItem({ field, itemIndex: index, groupIndex: 0, hideDetails: true })} + + )) + )} +
+ + {fieldGroupsToShow.map(([key, fieldGroup], index) => { + const hidden = Boolean(fieldGroup.hideIfEmpty) && !fieldGroup.fields.length; + if (hidden) { + return null; + } + return ( + + + id={`${dataTestSubject}${key}`} + initialIsOpen={Boolean(accordionState[key])} + label={fieldGroup.title} + helpTooltip={fieldGroup.helpText} + hideDetails={fieldGroup.hideDetails} + hasLoaded={hasSyncedExistingFields} + fieldsCount={fieldGroup.fields.length} + isFiltered={fieldGroup.fieldCount !== fieldGroup.fields.length} + paginatedFields={paginatedFields[key]} + groupIndex={index + 1} + onToggle={(open) => { + setAccordionState((s) => ({ + ...s, + [key]: open, + })); + const displayedFieldLength = getDisplayedFieldsLength(fieldGroups, { + ...accordionState, + [key]: open, + }); + setPageSize( + Math.max( + PAGINATION_SIZE, + Math.min(Math.ceil(pageSize * 1.5), displayedFieldLength) + ) + ); + }} + showExistenceFetchError={fieldsExistenceStatus === ExistenceFetchStatus.failed} + showExistenceFetchTimeout={fieldsExistenceStatus === ExistenceFetchStatus.failed} // TODO: deprecate timeout logic? + renderCallout={() => ( + + )} + renderFieldItem={renderFieldItem} + /> + + + ); + })} +
+
+ ); +} + +export type GenericFieldListGrouped = typeof InnerFieldListGrouped; +const FieldListGrouped = React.memo(InnerFieldListGrouped) as GenericFieldListGrouped; + +// Necessary for React.lazy +// eslint-disable-next-line import/no-default-export +export default FieldListGrouped; diff --git a/src/plugins/unified_field_list/public/components/field_list/fields_accordion.scss b/src/plugins/unified_field_list/public/components/field_list/fields_accordion.scss new file mode 100644 index 0000000000000..501b27969e768 --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_list/fields_accordion.scss @@ -0,0 +1,8 @@ +.unifiedFieldList__fieldsAccordion__titleTooltip { + margin-right: $euiSizeXS; +} + +.unifiedFieldList__fieldsAccordion__fieldItems { + // Quick fix for making sure the shadow and focus rings are visible outside the accordion bounds + padding: $euiSizeXS; +} diff --git a/src/plugins/unified_field_list/public/components/field_list/fields_accordion.test.tsx b/src/plugins/unified_field_list/public/components/field_list/fields_accordion.test.tsx new file mode 100644 index 0000000000000..2804c1bbe5ee1 --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_list/fields_accordion.test.tsx @@ -0,0 +1,62 @@ +/* + * 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 React from 'react'; +import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { EuiLoadingSpinner, EuiNotificationBadge, EuiText } from '@elastic/eui'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { FieldsAccordion, FieldsAccordionProps } from './fields_accordion'; +import { FieldListItem } from '../../types'; + +describe('UnifiedFieldList ', () => { + let defaultProps: FieldsAccordionProps; + const paginatedFields = dataView.fields; + + beforeEach(() => { + defaultProps = { + initialIsOpen: true, + onToggle: jest.fn(), + groupIndex: 0, + id: 'id', + label: 'label-test', + hasLoaded: true, + fieldsCount: paginatedFields.length, + isFiltered: false, + paginatedFields, + renderCallout: () =>
Callout
, + renderFieldItem: ({ field }) => {field.name}, + }; + }); + + it('renders fields correctly', () => { + const wrapper = mountWithIntl(); + expect(wrapper.find(EuiText)).toHaveLength(paginatedFields.length + 1); // + title + expect(wrapper.find(EuiText).first().text()).toBe(defaultProps.label); + expect(wrapper.find(EuiText).at(1).text()).toBe(paginatedFields[0].name); + expect(wrapper.find(EuiText).last().text()).toBe( + paginatedFields[paginatedFields.length - 1].name + ); + }); + + it('renders callout if no fields', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('#lens-test-callout').length).toEqual(1); + }); + + it('renders accented notificationBadge state if isFiltered', () => { + const wrapper = mountWithIntl(); + expect(wrapper.find(EuiNotificationBadge).prop('color')).toEqual('accent'); + }); + + it('renders spinner if has not loaded', () => { + const wrapper = mountWithIntl(); + expect(wrapper.find(EuiLoadingSpinner).length).toEqual(1); + }); +}); diff --git a/x-pack/plugins/lens/public/datasources/form_based/fields_accordion.tsx b/src/plugins/unified_field_list/public/components/field_list/fields_accordion.tsx similarity index 50% rename from x-pack/plugins/lens/public/datasources/form_based/fields_accordion.tsx rename to src/plugins/unified_field_list/public/components/field_list/fields_accordion.tsx index d6b4c73b51082..5222cf1b0e678 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/fields_accordion.tsx +++ b/src/plugins/unified_field_list/public/components/field_list/fields_accordion.tsx @@ -1,12 +1,12 @@ /* * 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. + * 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 './datapanel.scss'; -import React, { memo, useCallback, useMemo } from 'react'; +import React, { useMemo, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiText, @@ -17,26 +17,11 @@ import { EuiIconTip, } from '@elastic/eui'; import classNames from 'classnames'; -import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; -import { Filter } from '@kbn/es-query'; -import type { Query } from '@kbn/es-query'; -import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; -import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { FieldItem } from './field_item'; -import type { DatasourceDataPanelProps, IndexPattern, IndexPatternField } from '../../types'; +import { type DataViewField } from '@kbn/data-views-plugin/common'; +import type { FieldListItem } from '../../types'; +import './fields_accordion.scss'; -export interface FieldItemSharedProps { - core: DatasourceDataPanelProps['core']; - fieldFormats: FieldFormatsStart; - chartsThemeService: ChartsPluginSetup['theme']; - indexPattern: IndexPattern; - highlight?: string; - query: Query; - dateRange: DatasourceDataPanelProps['dateRange']; - filters: Filter[]; -} - -export interface FieldsAccordionProps { +export interface FieldsAccordionProps { initialIsOpen: boolean; onToggle: (open: boolean) => void; id: string; @@ -44,23 +29,22 @@ export interface FieldsAccordionProps { helpTooltip?: string; hasLoaded: boolean; fieldsCount: number; + hideDetails?: boolean; isFiltered: boolean; - paginatedFields: IndexPatternField[]; - fieldProps: FieldItemSharedProps; - renderCallout: JSX.Element; - exists: (field: IndexPatternField) => boolean; + groupIndex: number; + paginatedFields: T[]; + renderFieldItem: (params: { + field: T; + hideDetails?: boolean; + itemIndex: number; + groupIndex: number; + }) => JSX.Element; + renderCallout: () => JSX.Element; showExistenceFetchError?: boolean; showExistenceFetchTimeout?: boolean; - hideDetails?: boolean; - groupIndex: number; - dropOntoWorkspace: DatasourceDataPanelProps['dropOntoWorkspace']; - hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; - editField?: (name: string) => void; - removeField?: (name: string) => void; - uiActions: UiActionsStart; } -export const FieldsAccordion = memo(function InnerFieldsAccordion({ +function InnerFieldsAccordion({ initialIsOpen, onToggle, id, @@ -68,56 +52,21 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({ helpTooltip, hasLoaded, fieldsCount, + hideDetails, isFiltered, + groupIndex, paginatedFields, - fieldProps, + renderFieldItem, renderCallout, - exists, - hideDetails, showExistenceFetchError, showExistenceFetchTimeout, - groupIndex, - dropOntoWorkspace, - hasSuggestionForField, - editField, - removeField, - uiActions, -}: FieldsAccordionProps) { - const renderField = useCallback( - (field: IndexPatternField, index) => ( - - ), - [ - fieldProps, - exists, - hideDetails, - dropOntoWorkspace, - hasSuggestionForField, - groupIndex, - editField, - removeField, - uiActions, - ] - ); - +}: FieldsAccordionProps) { const renderButton = useMemo(() => { const titleClassname = classNames({ // eslint-disable-next-line @typescript-eslint/naming-convention - lnsInnerIndexPatternDataPanel__titleTooltip: !!helpTooltip, + unifiedFieldList__fieldsAccordion__titleTooltip: !!helpTooltip, }); + return ( {label} @@ -142,12 +91,12 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({ if (showExistenceFetchError) { return ( @@ -156,12 +105,12 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({ if (showExistenceFetchTimeout) { return ( @@ -194,12 +143,19 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({ {hasLoaded && (!!fieldsCount ? ( -
    - {paginatedFields && paginatedFields.map(renderField)} +
      + {paginatedFields && + paginatedFields.map((field, index) => ( + + {renderFieldItem({ field, itemIndex: index, groupIndex, hideDetails })} + + ))}
    ) : ( - renderCallout + renderCallout() ))} ); -}); +} + +export const FieldsAccordion = React.memo(InnerFieldsAccordion) as typeof InnerFieldsAccordion; diff --git a/src/plugins/unified_field_list/public/components/field_list/index.tsx b/src/plugins/unified_field_list/public/components/field_list/index.tsx new file mode 100755 index 0000000000000..44302a7e1c42b --- /dev/null +++ b/src/plugins/unified_field_list/public/components/field_list/index.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 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 React, { Fragment } from 'react'; +import { type DataViewField } from '@kbn/data-views-plugin/common'; +import type { FieldListGroupedProps, GenericFieldListGrouped } from './field_list_grouped'; +import { type FieldListItem } from '../../types'; + +const Fallback = () => ; + +const LazyFieldListGrouped = React.lazy( + () => import('./field_list_grouped') +) as GenericFieldListGrouped; + +function WrappedFieldListGrouped( + props: FieldListGroupedProps +) { + return ( + }> + {...props} /> + + ); +} + +export const FieldListGrouped = WrappedFieldListGrouped; +export type { FieldListGroupedProps }; diff --git a/x-pack/plugins/lens/public/datasources/form_based/no_fields_callout.test.tsx b/src/plugins/unified_field_list/public/components/field_list/no_fields_callout.test.tsx similarity index 86% rename from x-pack/plugins/lens/public/datasources/form_based/no_fields_callout.test.tsx rename to src/plugins/unified_field_list/public/components/field_list/no_fields_callout.test.tsx index 635c06691a733..03936a89877ba 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/no_fields_callout.test.tsx +++ b/src/plugins/unified_field_list/public/components/field_list/no_fields_callout.test.tsx @@ -1,17 +1,18 @@ /* * 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. + * 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 React from 'react'; import { shallow } from 'enzyme'; import { NoFieldsCallout } from './no_fields_callout'; -describe('NoFieldCallout', () => { +describe('UnifiedFieldList ', () => { it('renders correctly for index with no fields', () => { - const component = shallow(); + const component = shallow(); expect(component).toMatchInlineSnapshot(` { `); }); it('renders correctly when empty with no filters/timerange reasons', () => { - const component = shallow(); + const component = shallow(); expect(component).toMatchInlineSnapshot(` { }); it('renders correctly with passed defaultNoFieldsMessage', () => { const component = shallow( - + ); expect(component).toMatchInlineSnapshot(` { it('renders properly when affected by field filter', () => { const component = shallow( - + ); expect(component).toMatchInlineSnapshot(` { it('renders correctly when affected by global filters and timerange', () => { const component = shallow( { it('renders correctly when affected by global filters and field filters', () => { const component = shallow( { it('renders correctly when affected by field filters, global filter and timerange', () => { const component = shallow( { - if (!existFieldsInIndex) { + if (!fieldsExistInIndex) { return ( @@ -44,7 +48,7 @@ export const NoFieldsCallout = ({ color="warning" title={ isAffectedByFieldFilter - ? i18n.translate('xpack.lens.indexPatterns.noFilteredFieldsLabel', { + ? i18n.translate('unifiedFieldList.fieldList.noFieldsCallout.noFilteredFieldsLabel', { defaultMessage: 'No fields match the selected filters.', }) : defaultNoFieldsMessage @@ -53,30 +57,39 @@ export const NoFieldsCallout = ({ {(isAffectedByTimerange || isAffectedByFieldFilter || isAffectedByGlobalFilter) && ( <> - {i18n.translate('xpack.lens.indexPatterns.noFields.tryText', { + {i18n.translate('unifiedFieldList.fieldList.noFieldsCallout.noFields.tryText', { defaultMessage: 'Try:', })}
      {isAffectedByTimerange && (
    • - {i18n.translate('xpack.lens.indexPatterns.noFields.extendTimeBullet', { - defaultMessage: 'Extending the time range', - })} + {i18n.translate( + 'unifiedFieldList.fieldList.noFieldsCallout.noFields.extendTimeBullet', + { + defaultMessage: 'Extending the time range', + } + )}
    • )} {isAffectedByFieldFilter && (
    • - {i18n.translate('xpack.lens.indexPatterns.noFields.fieldTypeFilterBullet', { - defaultMessage: 'Using different field filters', - })} + {i18n.translate( + 'unifiedFieldList.fieldList.noFieldsCallout.noFields.fieldTypeFilterBullet', + { + defaultMessage: 'Using different field filters', + } + )}
    • )} {isAffectedByGlobalFilter && (
    • - {i18n.translate('xpack.lens.indexPatterns.noFields.globalFiltersBullet', { - defaultMessage: 'Changing the global filters', - })} + {i18n.translate( + 'unifiedFieldList.fieldList.noFieldsCallout.noFields.globalFiltersBullet', + { + defaultMessage: 'Changing the global filters', + } + )}
    • )}
    diff --git a/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx b/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx index 07d35b78b58a2..b3600dc9f3971 100755 --- a/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx +++ b/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx @@ -8,8 +8,8 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { - DataView, - DataViewField, + type DataView, + type DataViewField, ES_FIELD_TYPES, getEsQueryConfig, KBN_FIELD_TYPES, diff --git a/src/plugins/unified_field_list/public/hooks/use_existing_fields.test.tsx b/src/plugins/unified_field_list/public/hooks/use_existing_fields.test.tsx new file mode 100644 index 0000000000000..7a27a1468213d --- /dev/null +++ b/src/plugins/unified_field_list/public/hooks/use_existing_fields.test.tsx @@ -0,0 +1,536 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { createStubDataView, stubFieldSpecMap } from '@kbn/data-plugin/public/stubs'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import { + useExistingFieldsFetcher, + useExistingFieldsReader, + resetExistingFieldsCache, + type ExistingFieldsFetcherParams, + ExistingFieldsReader, +} from './use_existing_fields'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { coreMock } from '@kbn/core/public/mocks'; +import * as ExistingFieldsServiceApi from '../services/field_existing/load_field_existing'; +import { ExistenceFetchStatus } from '../types'; + +const dslQuery = { bool: { must: [], filter: [], should: [], must_not: [] } }; +const rollupAggsMock = { + date_histogram: { + '@timestamp': { + agg: 'date_histogram', + fixed_interval: '20m', + delay: '10m', + time_zone: 'UTC', + }, + }, +}; + +jest.spyOn(ExistingFieldsServiceApi, 'loadFieldExisting').mockImplementation(async () => ({ + indexPatternTitle: 'test', + existingFieldNames: [], +})); + +describe('UnifiedFieldList useExistingFields', () => { + let mockedServices: ExistingFieldsFetcherParams['services']; + const anotherDataView = createStubDataView({ + spec: { + id: 'another-data-view', + title: 'logstash-0', + fields: stubFieldSpecMap, + }, + }); + const dataViewWithRestrictions = createStubDataView({ + spec: { + id: 'another-data-view-with-restrictions', + title: 'logstash-1', + fields: stubFieldSpecMap, + typeMeta: { + aggs: rollupAggsMock, + }, + }, + }); + jest.spyOn(dataViewWithRestrictions, 'getAggregationRestrictions'); + + beforeEach(() => { + const dataViews = dataViewPluginMocks.createStartContract(); + const core = coreMock.createStart(); + mockedServices = { + dataViews, + data: dataPluginMock.createStartContract(), + core, + }; + + core.uiSettings.get.mockImplementation((key: string) => { + if (key === UI_SETTINGS.META_FIELDS) { + return ['_id']; + } + }); + + dataViews.get.mockImplementation(async (id: string) => { + return [dataView, anotherDataView, dataViewWithRestrictions].find((dw) => dw.id === id)!; + }); + + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockClear(); + (dataViewWithRestrictions.getAggregationRestrictions as jest.Mock).mockClear(); + resetExistingFieldsCache(); + }); + + it('should work correctly based on the specified data view', async () => { + const dataViewId = dataView.id!; + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + return { + existingFieldNames: [dataView.fields[0].name], + }; + }); + + const hookFetcher = renderHook(useExistingFieldsFetcher, { + initialProps: { + dataViews: [dataView], + services: mockedServices, + fromDate: '2019-01-01', + toDate: '2020-01-01', + query: { query: '', language: 'lucene' }, + filters: [], + }, + }); + + const hookReader = renderHook(useExistingFieldsReader); + + await hookFetcher.waitForNextUpdate(); + + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( + expect.objectContaining({ + fromDate: '2019-01-01', + toDate: '2020-01-01', + dslQuery, + dataView, + timeFieldName: dataView.timeFieldName, + }) + ); + + // has existence info for the loaded data view => works more restrictive + expect(hookReader.result.current.isFieldsExistenceInfoUnavailable(dataViewId)).toBe(false); + expect(hookReader.result.current.hasFieldData(dataViewId, dataView.fields[0].name)).toBe(true); + expect(hookReader.result.current.hasFieldData(dataViewId, dataView.fields[1].name)).toBe(false); + expect(hookReader.result.current.getFieldsExistenceStatus(dataViewId)).toBe( + ExistenceFetchStatus.succeeded + ); + + // does not have existence info => works less restrictive + const anotherDataViewId = 'test-id'; + expect(hookReader.result.current.isFieldsExistenceInfoUnavailable(anotherDataViewId)).toBe( + false + ); + expect(hookReader.result.current.hasFieldData(anotherDataViewId, dataView.fields[0].name)).toBe( + true + ); + expect(hookReader.result.current.hasFieldData(anotherDataViewId, dataView.fields[1].name)).toBe( + true + ); + expect(hookReader.result.current.getFieldsExistenceStatus(anotherDataViewId)).toBe( + ExistenceFetchStatus.unknown + ); + }); + + it('should work correctly with multiple readers', async () => { + const dataViewId = dataView.id!; + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + return { + existingFieldNames: [dataView.fields[0].name], + }; + }); + + const hookFetcher = renderHook(useExistingFieldsFetcher, { + initialProps: { + dataViews: [dataView], + services: mockedServices, + fromDate: '2019-01-01', + toDate: '2020-01-01', + query: { query: '', language: 'lucene' }, + filters: [], + }, + }); + + const hookReader1 = renderHook(useExistingFieldsReader); + const hookReader2 = renderHook(useExistingFieldsReader); + + await hookFetcher.waitForNextUpdate(); + + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalled(); + + const checkResults = (currentResult: ExistingFieldsReader) => { + expect(currentResult.isFieldsExistenceInfoUnavailable(dataViewId)).toBe(false); + expect(currentResult.hasFieldData(dataViewId, dataView.fields[0].name)).toBe(true); + expect(currentResult.hasFieldData(dataViewId, dataView.fields[1].name)).toBe(false); + expect(currentResult.getFieldsExistenceStatus(dataViewId)).toBe( + ExistenceFetchStatus.succeeded + ); + }; + + // both readers should get the same results + + checkResults(hookReader1.result.current); + checkResults(hookReader2.result.current); + + // info should be persisted even if the fetcher was unmounted + + hookFetcher.unmount(); + + checkResults(hookReader1.result.current); + checkResults(hookReader2.result.current); + }); + + it('should work correctly if load fails', async () => { + const dataViewId = dataView.id!; + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + throw new Error('test'); + }); + + const hookFetcher = renderHook(useExistingFieldsFetcher, { + initialProps: { + dataViews: [dataView], + services: mockedServices, + fromDate: '2019-01-01', + toDate: '2020-01-01', + query: { query: '', language: 'lucene' }, + filters: [], + }, + }); + + const hookReader = renderHook(useExistingFieldsReader); + + await hookFetcher.waitForNextUpdate(); + + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalled(); + + const currentResult = hookReader.result.current; + expect(currentResult.isFieldsExistenceInfoUnavailable(dataViewId)).toBe(true); + expect(currentResult.hasFieldData(dataViewId, dataView.fields[0].name)).toBe(true); + expect(currentResult.getFieldsExistenceStatus(dataViewId)).toBe(ExistenceFetchStatus.failed); + }); + + it('should work correctly for multiple data views', async () => { + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation( + async ({ dataView: currentDataView }) => { + return { + existingFieldNames: [currentDataView.fields[0].name], + }; + } + ); + + const hookFetcher = renderHook(useExistingFieldsFetcher, { + initialProps: { + dataViews: [dataView, anotherDataView, dataViewWithRestrictions], + services: mockedServices, + fromDate: '2019-01-01', + toDate: '2020-01-01', + query: { query: '', language: 'lucene' }, + filters: [], + }, + }); + + const hookReader = renderHook(useExistingFieldsReader); + await hookFetcher.waitForNextUpdate(); + + const currentResult = hookReader.result.current; + + expect(currentResult.isFieldsExistenceInfoUnavailable(dataView.id!)).toBe(false); + expect(currentResult.isFieldsExistenceInfoUnavailable(anotherDataView.id!)).toBe(false); + expect(currentResult.isFieldsExistenceInfoUnavailable(dataViewWithRestrictions.id!)).toBe(true); + expect(currentResult.isFieldsExistenceInfoUnavailable('test-id')).toBe(false); + + expect(currentResult.hasFieldData(dataView.id!, dataView.fields[0].name)).toBe(true); + expect(currentResult.hasFieldData(dataView.id!, dataView.fields[1].name)).toBe(false); + + expect(currentResult.hasFieldData(anotherDataView.id!, anotherDataView.fields[0].name)).toBe( + true + ); + expect(currentResult.hasFieldData(anotherDataView.id!, anotherDataView.fields[1].name)).toBe( + false + ); + + expect( + currentResult.hasFieldData( + dataViewWithRestrictions.id!, + dataViewWithRestrictions.fields[0].name + ) + ).toBe(true); + expect( + currentResult.hasFieldData( + dataViewWithRestrictions.id!, + dataViewWithRestrictions.fields[1].name + ) + ).toBe(true); + expect(currentResult.hasFieldData('test-id', 'test-field')).toBe(true); + + expect(currentResult.getFieldsExistenceStatus(dataView.id!)).toBe( + ExistenceFetchStatus.succeeded + ); + expect(currentResult.getFieldsExistenceStatus(anotherDataView.id!)).toBe( + ExistenceFetchStatus.succeeded + ); + expect(currentResult.getFieldsExistenceStatus(dataViewWithRestrictions.id!)).toBe( + ExistenceFetchStatus.succeeded + ); + expect(currentResult.getFieldsExistenceStatus('test-id')).toBe(ExistenceFetchStatus.unknown); + + expect(dataViewWithRestrictions.getAggregationRestrictions).toHaveBeenCalledTimes(1); + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledTimes(2); + }); + + it('should work correctly for data views with restrictions', async () => { + const dataViewId = dataViewWithRestrictions.id!; + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + throw new Error('test'); + }); + + const hookFetcher = renderHook(useExistingFieldsFetcher, { + initialProps: { + dataViews: [dataViewWithRestrictions], + services: mockedServices, + fromDate: '2019-01-01', + toDate: '2020-01-01', + query: { query: '', language: 'lucene' }, + filters: [], + }, + }); + + const hookReader = renderHook(useExistingFieldsReader); + await hookFetcher.waitForNextUpdate(); + await hookFetcher.waitFor(() => !hookFetcher.result.current.isProcessing); + + expect(dataViewWithRestrictions.getAggregationRestrictions).toHaveBeenCalled(); + expect(ExistingFieldsServiceApi.loadFieldExisting).not.toHaveBeenCalled(); + + const currentResult = hookReader.result.current; + expect(currentResult.isFieldsExistenceInfoUnavailable(dataViewId)).toBe(true); + expect(currentResult.hasFieldData(dataViewId, dataViewWithRestrictions.fields[0].name)).toBe( + true + ); + expect(currentResult.getFieldsExistenceStatus(dataViewId)).toBe(ExistenceFetchStatus.succeeded); + }); + + it('should work correctly for when data views are changed', async () => { + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation( + async ({ dataView: currentDataView }) => { + return { + existingFieldNames: [currentDataView.fields[0].name], + }; + } + ); + + const params: ExistingFieldsFetcherParams = { + dataViews: [dataView], + services: mockedServices, + fromDate: '2019-01-01', + toDate: '2020-01-01', + query: { query: '', language: 'lucene' }, + filters: [], + }; + const hookFetcher = renderHook(useExistingFieldsFetcher, { + initialProps: params, + }); + + const hookReader = renderHook(useExistingFieldsReader); + await hookFetcher.waitForNextUpdate(); + + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( + expect.objectContaining({ + fromDate: '2019-01-01', + toDate: '2020-01-01', + dslQuery, + dataView, + timeFieldName: dataView.timeFieldName, + }) + ); + + expect(hookReader.result.current.getFieldsExistenceStatus(dataView.id!)).toBe( + ExistenceFetchStatus.succeeded + ); + expect(hookReader.result.current.getFieldsExistenceStatus(anotherDataView.id!)).toBe( + ExistenceFetchStatus.unknown + ); + + hookFetcher.rerender({ + ...params, + dataViews: [dataView, anotherDataView], + }); + + await hookFetcher.waitForNextUpdate(); + + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + fromDate: '2019-01-01', + toDate: '2020-01-01', + dslQuery, + dataView, + timeFieldName: dataView.timeFieldName, + }) + ); + + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + fromDate: '2019-01-01', + toDate: '2020-01-01', + dslQuery, + dataView: anotherDataView, + timeFieldName: anotherDataView.timeFieldName, + }) + ); + + expect(hookReader.result.current.getFieldsExistenceStatus(dataView.id!)).toBe( + ExistenceFetchStatus.succeeded + ); + expect(hookReader.result.current.getFieldsExistenceStatus(anotherDataView.id!)).toBe( + ExistenceFetchStatus.succeeded + ); + }); + + it('should work correctly for when params are changed', async () => { + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation( + async ({ dataView: currentDataView }) => { + return { + existingFieldNames: [currentDataView.fields[0].name], + }; + } + ); + + const params: ExistingFieldsFetcherParams = { + dataViews: [dataView], + services: mockedServices, + fromDate: '2019-01-01', + toDate: '2020-01-01', + query: { query: '', language: 'lucene' }, + filters: [], + }; + const hookFetcher = renderHook(useExistingFieldsFetcher, { + initialProps: params, + }); + + const hookReader = renderHook(useExistingFieldsReader); + await hookFetcher.waitForNextUpdate(); + + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( + expect.objectContaining({ + fromDate: '2019-01-01', + toDate: '2020-01-01', + dslQuery, + dataView, + timeFieldName: dataView.timeFieldName, + }) + ); + + expect(hookReader.result.current.getFieldsExistenceStatus(dataView.id!)).toBe( + ExistenceFetchStatus.succeeded + ); + + hookFetcher.rerender({ + ...params, + fromDate: '2021-01-01', + toDate: '2022-01-01', + query: { query: 'test', language: 'kuery' }, + }); + + await hookFetcher.waitForNextUpdate(); + + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + fromDate: '2021-01-01', + toDate: '2022-01-01', + dslQuery: { + bool: { + filter: [ + { + multi_match: { + lenient: true, + query: 'test', + type: 'best_fields', + }, + }, + ], + must: [], + must_not: [], + should: [], + }, + }, + dataView, + timeFieldName: dataView.timeFieldName, + }) + ); + }); + + it('should call onNoData callback only once', async () => { + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + return { + existingFieldNames: ['_id'], + }; + }); + + const params: ExistingFieldsFetcherParams = { + dataViews: [dataView], + services: mockedServices, + fromDate: '2019-01-01', + toDate: '2020-01-01', + query: { query: '', language: 'lucene' }, + filters: [], + onNoData: jest.fn(), + }; + const hookFetcher = renderHook(useExistingFieldsFetcher, { + initialProps: params, + }); + + const hookReader = renderHook(useExistingFieldsReader); + await hookFetcher.waitForNextUpdate(); + + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( + expect.objectContaining({ + fromDate: '2019-01-01', + toDate: '2020-01-01', + dslQuery, + dataView, + timeFieldName: dataView.timeFieldName, + }) + ); + + expect(hookReader.result.current.getFieldsExistenceStatus(dataView.id!)).toBe( + ExistenceFetchStatus.succeeded + ); + + expect(params.onNoData).toHaveBeenCalledWith(dataView.id); + expect(params.onNoData).toHaveBeenCalledTimes(1); + + hookFetcher.rerender({ + ...params, + fromDate: '2021-01-01', + toDate: '2022-01-01', + }); + + await hookFetcher.waitForNextUpdate(); + + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + fromDate: '2021-01-01', + toDate: '2022-01-01', + dslQuery, + dataView, + timeFieldName: dataView.timeFieldName, + }) + ); + + expect(params.onNoData).toHaveBeenCalledTimes(1); // still 1 time + }); +}); diff --git a/src/plugins/unified_field_list/public/hooks/use_existing_fields.ts b/src/plugins/unified_field_list/public/hooks/use_existing_fields.ts new file mode 100644 index 0000000000000..ebf12d4609500 --- /dev/null +++ b/src/plugins/unified_field_list/public/hooks/use_existing_fields.ts @@ -0,0 +1,347 @@ +/* + * 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 { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { htmlIdGenerator } from '@elastic/eui'; +import { BehaviorSubject } from 'rxjs'; +import { CoreStart } from '@kbn/core/public'; +import type { AggregateQuery, EsQueryConfig, Filter, Query } from '@kbn/es-query'; +import { + DataPublicPluginStart, + DataViewsContract, + getEsQueryConfig, + UI_SETTINGS, +} from '@kbn/data-plugin/public'; +import { type DataView } from '@kbn/data-plugin/common'; +import { loadFieldExisting } from '../services/field_existing'; +import { ExistenceFetchStatus } from '../types'; + +const getBuildEsQueryAsync = async () => (await import('@kbn/es-query')).buildEsQuery; +const generateId = htmlIdGenerator(); + +export interface ExistingFieldsInfo { + fetchStatus: ExistenceFetchStatus; + existingFieldsByFieldNameMap: Record; + numberOfFetches: number; + hasDataViewRestrictions?: boolean; +} + +export interface ExistingFieldsFetcherParams { + dataViews: DataView[]; + fromDate: string; + toDate: string; + query: Query | AggregateQuery; + filters: Filter[]; + services: { + core: Pick; + data: DataPublicPluginStart; + dataViews: DataViewsContract; + }; + onNoData?: (dataViewId: string) => unknown; +} + +type ExistingFieldsByDataViewMap = Record; + +export interface ExistingFieldsFetcher { + refetchFieldsExistenceInfo: (dataViewId?: string) => Promise; + isProcessing: boolean; +} + +export interface ExistingFieldsReader { + hasFieldData: (dataViewId: string, fieldName: string) => boolean; + getFieldsExistenceStatus: (dataViewId: string) => ExistenceFetchStatus; + isFieldsExistenceInfoUnavailable: (dataViewId: string) => boolean; +} + +const initialData: ExistingFieldsByDataViewMap = {}; +const unknownInfo: ExistingFieldsInfo = { + fetchStatus: ExistenceFetchStatus.unknown, + existingFieldsByFieldNameMap: {}, + numberOfFetches: 0, +}; + +const globalMap$ = new BehaviorSubject(initialData); // for syncing between hooks +let lastFetchId: string = ''; // persist last fetch id to skip older requests/responses if any + +export const useExistingFieldsFetcher = ( + params: ExistingFieldsFetcherParams +): ExistingFieldsFetcher => { + const mountedRef = useRef(true); + const [activeRequests, setActiveRequests] = useState(0); + const isProcessing = activeRequests > 0; + + const fetchFieldsExistenceInfo = useCallback( + async ({ + dataViewId, + query, + filters, + fromDate, + toDate, + services: { dataViews, data, core }, + onNoData, + fetchId, + }: ExistingFieldsFetcherParams & { + dataViewId: string | undefined; + fetchId: string; + }): Promise => { + if (!dataViewId) { + return; + } + + const currentInfo = globalMap$.getValue()?.[dataViewId]; + + if (!mountedRef.current) { + return; + } + + const numberOfFetches = (currentInfo?.numberOfFetches ?? 0) + 1; + const dataView = await dataViews.get(dataViewId); + + if (!dataView?.title) { + return; + } + + setActiveRequests((value) => value + 1); + + const hasRestrictions = Boolean(dataView.getAggregationRestrictions?.()); + const info: ExistingFieldsInfo = { + ...unknownInfo, + numberOfFetches, + }; + + if (hasRestrictions) { + info.fetchStatus = ExistenceFetchStatus.succeeded; + info.hasDataViewRestrictions = true; + } else { + try { + const result = await loadFieldExisting({ + dslQuery: await buildSafeEsQuery( + dataView, + query, + filters, + getEsQueryConfig(core.uiSettings) + ), + fromDate, + toDate, + timeFieldName: dataView.timeFieldName, + data, + uiSettingsClient: core.uiSettings, + dataViewsService: dataViews, + dataView, + }); + + const existingFieldNames = result?.existingFieldNames || []; + + const metaFields = core.uiSettings.get(UI_SETTINGS.META_FIELDS) || []; + if ( + !existingFieldNames.filter((fieldName) => !metaFields.includes?.(fieldName)).length && + numberOfFetches === 1 && + onNoData + ) { + onNoData(dataViewId); + } + + info.existingFieldsByFieldNameMap = booleanMap(existingFieldNames); + info.fetchStatus = ExistenceFetchStatus.succeeded; + } catch (error) { + // eslint-disable-next-line no-console + console.error(error); + info.fetchStatus = ExistenceFetchStatus.failed; + } + } + + // skip redundant and older results + if (mountedRef.current && fetchId === lastFetchId) { + globalMap$.next({ + ...globalMap$.getValue(), + [dataViewId]: info, + }); + } + + setActiveRequests((value) => value - 1); + }, + [mountedRef, setActiveRequests] + ); + + const dataViewsHash = getDataViewsHash(params.dataViews); + const refetchFieldsExistenceInfo = useCallback( + async (dataViewId?: string) => { + const fetchId = generateId(); + lastFetchId = fetchId; + // refetch only for the specified data view + if (dataViewId) { + await fetchFieldsExistenceInfo({ + fetchId, + dataViewId, + ...params, + }); + return; + } + // refetch for all mentioned data views + await Promise.all( + params.dataViews.map((dataView) => + fetchFieldsExistenceInfo({ + fetchId, + dataViewId: dataView.id, + ...params, + }) + ) + ); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + fetchFieldsExistenceInfo, + dataViewsHash, + params.query, + params.filters, + params.fromDate, + params.toDate, + ] + ); + + useEffect(() => { + refetchFieldsExistenceInfo(); + }, [refetchFieldsExistenceInfo]); + + useEffect(() => { + return () => { + mountedRef.current = false; + globalMap$.next({}); // reset the cache (readers will continue using their own data slice until they are unmounted too) + }; + }, [mountedRef]); + + return useMemo( + () => ({ + refetchFieldsExistenceInfo, + isProcessing, + }), + [refetchFieldsExistenceInfo, isProcessing] + ); +}; + +export const useExistingFieldsReader: () => ExistingFieldsReader = () => { + const mountedRef = useRef(true); + const [existingFieldsByDataViewMap, setExistingFieldsByDataViewMap] = + useState(globalMap$.getValue()); + + useEffect(() => { + const subscription = globalMap$.subscribe((data) => { + if (mountedRef.current && Object.keys(data).length > 0) { + setExistingFieldsByDataViewMap((savedData) => ({ + ...savedData, + ...data, + })); + } + }); + + return () => { + subscription.unsubscribe(); + }; + }, [setExistingFieldsByDataViewMap, mountedRef]); + + const hasFieldData = useCallback( + (dataViewId: string, fieldName: string) => { + const info = existingFieldsByDataViewMap[dataViewId]; + + if (info?.fetchStatus === ExistenceFetchStatus.succeeded) { + return ( + info?.hasDataViewRestrictions || Boolean(info?.existingFieldsByFieldNameMap[fieldName]) + ); + } + + return true; + }, + [existingFieldsByDataViewMap] + ); + + const getFieldsExistenceInfo = useCallback( + (dataViewId: string) => { + return dataViewId ? existingFieldsByDataViewMap[dataViewId] : unknownInfo; + }, + [existingFieldsByDataViewMap] + ); + + const getFieldsExistenceStatus = useCallback( + (dataViewId: string): ExistenceFetchStatus => { + return getFieldsExistenceInfo(dataViewId)?.fetchStatus || ExistenceFetchStatus.unknown; + }, + [getFieldsExistenceInfo] + ); + + const isFieldsExistenceInfoUnavailable = useCallback( + (dataViewId: string): boolean => { + const info = getFieldsExistenceInfo(dataViewId); + return Boolean( + info?.fetchStatus === ExistenceFetchStatus.failed || info?.hasDataViewRestrictions + ); + }, + [getFieldsExistenceInfo] + ); + + useEffect(() => { + return () => { + mountedRef.current = false; + }; + }, [mountedRef]); + + return useMemo( + () => ({ + hasFieldData, + getFieldsExistenceStatus, + isFieldsExistenceInfoUnavailable, + }), + [hasFieldData, getFieldsExistenceStatus, isFieldsExistenceInfoUnavailable] + ); +}; + +export const resetExistingFieldsCache = () => { + globalMap$.next(initialData); +}; + +function getDataViewsHash(dataViews: DataView[]): string { + return ( + dataViews + // From Lens it's coming as IndexPattern type and not the real DataView type + .map( + (dataView) => + `${dataView.id}:${dataView.title}:${dataView.timeFieldName || 'no-timefield'}:${ + dataView.fields?.length ?? 0 // adding a field will also trigger a refetch of fields existence data + }` + ) + .join(',') + ); +} + +// Wrapper around buildEsQuery, handling errors (e.g. because a query can't be parsed) by +// returning a query dsl object not matching anything +async function buildSafeEsQuery( + dataView: DataView, + query: Query | AggregateQuery, + filters: Filter[], + queryConfig: EsQueryConfig +) { + const buildEsQuery = await getBuildEsQueryAsync(); + try { + return buildEsQuery(dataView, query, filters, queryConfig); + } catch (e) { + return { + bool: { + must_not: { + match_all: {}, + }, + }, + }; + } +} + +function booleanMap(keys: string[]) { + return keys.reduce((acc, key) => { + acc[key] = true; + return acc; + }, {} as Record); +} diff --git a/src/plugins/unified_field_list/public/hooks/use_grouped_fields.test.tsx b/src/plugins/unified_field_list/public/hooks/use_grouped_fields.test.tsx new file mode 100644 index 0000000000000..d4d6d3cdc906f --- /dev/null +++ b/src/plugins/unified_field_list/public/hooks/use_grouped_fields.test.tsx @@ -0,0 +1,272 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { + stubDataViewWithoutTimeField, + stubLogstashDataView as dataView, +} from '@kbn/data-views-plugin/common/data_view.stub'; +import { createStubDataView, stubFieldSpecMap } from '@kbn/data-plugin/public/stubs'; +import { DataViewField } from '@kbn/data-views-plugin/common'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { type GroupedFieldsParams, useGroupedFields } from './use_grouped_fields'; +import { ExistenceFetchStatus, FieldListGroups, FieldsGroupNames } from '../types'; + +describe('UnifiedFieldList useGroupedFields()', () => { + let mockedServices: GroupedFieldsParams['services']; + const allFields = dataView.fields; + const anotherDataView = createStubDataView({ + spec: { + id: 'another-data-view', + title: 'logstash-0', + fields: stubFieldSpecMap, + }, + }); + + beforeEach(() => { + const dataViews = dataViewPluginMocks.createStartContract(); + mockedServices = { + dataViews, + }; + + dataViews.get.mockImplementation(async (id: string) => { + return [dataView, stubDataViewWithoutTimeField].find((dw) => dw.id === id)!; + }); + }); + + it('should work correctly for no data', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGroupedFields({ + dataViewId: dataView.id!, + allFields: [], + services: mockedServices, + }) + ); + + await waitForNextUpdate(); + + const fieldGroups = result.current.fieldGroups; + + expect( + Object.keys(fieldGroups!).map( + (key) => `${key}-${fieldGroups![key as FieldsGroupNames]?.fields.length}` + ) + ).toStrictEqual([ + 'SpecialFields-0', + 'SelectedFields-0', + 'AvailableFields-0', + 'EmptyFields-0', + 'MetaFields-0', + ]); + }); + + it('should work correctly with fields', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGroupedFields({ + dataViewId: dataView.id!, + allFields, + services: mockedServices, + }) + ); + + await waitForNextUpdate(); + + const fieldGroups = result.current.fieldGroups; + + expect( + Object.keys(fieldGroups!).map( + (key) => `${key}-${fieldGroups![key as FieldsGroupNames]?.fields.length}` + ) + ).toStrictEqual([ + 'SpecialFields-0', + 'SelectedFields-0', + 'AvailableFields-25', + 'EmptyFields-0', + 'MetaFields-3', + ]); + }); + + it('should work correctly when filtered', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGroupedFields({ + dataViewId: dataView.id!, + allFields, + services: mockedServices, + onFilterField: (field: DataViewField) => field.name.startsWith('@'), + }) + ); + + await waitForNextUpdate(); + + const fieldGroups = result.current.fieldGroups; + + expect( + Object.keys(fieldGroups!).map( + (key) => `${key}-${fieldGroups![key as FieldsGroupNames]?.fields.length}` + ) + ).toStrictEqual([ + 'SpecialFields-0', + 'SelectedFields-0', + 'AvailableFields-2', + 'EmptyFields-0', + 'MetaFields-0', + ]); + }); + + it('should work correctly when custom unsupported fields are skipped', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGroupedFields({ + dataViewId: dataView.id!, + allFields, + services: mockedServices, + onSupportedFieldFilter: (field: DataViewField) => field.aggregatable, + }) + ); + + await waitForNextUpdate(); + + const fieldGroups = result.current.fieldGroups; + + expect( + Object.keys(fieldGroups!).map( + (key) => `${key}-${fieldGroups![key as FieldsGroupNames]?.fields.length}` + ) + ).toStrictEqual([ + 'SpecialFields-0', + 'SelectedFields-0', + 'AvailableFields-23', + 'EmptyFields-0', + 'MetaFields-3', + ]); + }); + + it('should work correctly when selected fields are present', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGroupedFields({ + dataViewId: dataView.id!, + allFields, + services: mockedServices, + onSelectedFieldFilter: (field: DataViewField) => + ['bytes', 'extension', '_id', '@timestamp'].includes(field.name), + }) + ); + + await waitForNextUpdate(); + + const fieldGroups = result.current.fieldGroups; + + expect( + Object.keys(fieldGroups!).map( + (key) => `${key}-${fieldGroups![key as FieldsGroupNames]?.fields.length}` + ) + ).toStrictEqual([ + 'SpecialFields-0', + 'SelectedFields-4', + 'AvailableFields-25', + 'EmptyFields-0', + 'MetaFields-3', + ]); + }); + + it('should work correctly for text-based queries (no data view)', async () => { + const { result } = renderHook(() => + useGroupedFields({ + dataViewId: null, + allFields, + services: mockedServices, + }) + ); + + const fieldGroups = result.current.fieldGroups; + + expect( + Object.keys(fieldGroups!).map( + (key) => `${key}-${fieldGroups![key as FieldsGroupNames]?.fields.length}` + ) + ).toStrictEqual(['SpecialFields-0', 'SelectedFields-0', 'AvailableFields-28', 'MetaFields-0']); + }); + + it('should work correctly when details are overwritten', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGroupedFields({ + dataViewId: dataView.id!, + allFields, + services: mockedServices, + onOverrideFieldGroupDetails: (groupName) => { + if (groupName === FieldsGroupNames.SelectedFields) { + return { + helpText: 'test', + }; + } + }, + }) + ); + + await waitForNextUpdate(); + + const fieldGroups = result.current.fieldGroups; + + expect(fieldGroups[FieldsGroupNames.SelectedFields]?.helpText).toBe('test'); + expect(fieldGroups[FieldsGroupNames.AvailableFields]?.helpText).not.toBe('test'); + }); + + it('should work correctly when changing a data view and existence info is available only for one of them', async () => { + const knownDataViewId = dataView.id!; + let fieldGroups: FieldListGroups; + const props: GroupedFieldsParams = { + dataViewId: dataView.id!, + allFields, + services: mockedServices, + fieldsExistenceReader: { + hasFieldData: (dataViewId, fieldName) => { + return dataViewId === knownDataViewId && ['bytes', 'extension'].includes(fieldName); + }, + getFieldsExistenceStatus: (dataViewId) => + dataViewId === knownDataViewId + ? ExistenceFetchStatus.succeeded + : ExistenceFetchStatus.unknown, + isFieldsExistenceInfoUnavailable: (dataViewId) => dataViewId !== knownDataViewId, + }, + }; + + const { result, waitForNextUpdate, rerender } = renderHook(useGroupedFields, { + initialProps: props, + }); + await waitForNextUpdate(); + + fieldGroups = result.current.fieldGroups; + + expect( + Object.keys(fieldGroups!).map( + (key) => `${key}-${fieldGroups![key as FieldsGroupNames]?.fields.length}` + ) + ).toStrictEqual([ + 'SpecialFields-0', + 'SelectedFields-0', + 'AvailableFields-2', + 'EmptyFields-23', + 'MetaFields-3', + ]); + + rerender({ + ...props, + dataViewId: anotherDataView.id!, + allFields: anotherDataView.fields, + }); + + await waitForNextUpdate(); + + fieldGroups = result.current.fieldGroups; + + expect( + Object.keys(fieldGroups!).map( + (key) => `${key}-${fieldGroups![key as FieldsGroupNames]?.fields.length}` + ) + ).toStrictEqual(['SpecialFields-0', 'SelectedFields-0', 'AvailableFields-8', 'MetaFields-0']); + }); +}); diff --git a/src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts b/src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts new file mode 100644 index 0000000000000..cfa5407a238cc --- /dev/null +++ b/src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts @@ -0,0 +1,267 @@ +/* + * 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 { groupBy } from 'lodash'; +import { useEffect, useMemo, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { type DataView, type DataViewField } from '@kbn/data-views-plugin/common'; +import { type DataViewsContract } from '@kbn/data-views-plugin/public'; +import { + type FieldListGroups, + type FieldsGroupDetails, + type FieldsGroup, + type FieldListItem, + FieldsGroupNames, +} from '../types'; +import { type ExistingFieldsReader } from './use_existing_fields'; + +export interface GroupedFieldsParams { + dataViewId: string | null; // `null` is for text-based queries + allFields: T[]; + services: { + dataViews: DataViewsContract; + }; + fieldsExistenceReader?: ExistingFieldsReader; + onOverrideFieldGroupDetails?: ( + groupName: FieldsGroupNames + ) => Partial | undefined | null; + onSupportedFieldFilter?: (field: T) => boolean; + onSelectedFieldFilter?: (field: T) => boolean; + onFilterField?: (field: T) => boolean; +} + +export interface GroupedFieldsResult { + fieldGroups: FieldListGroups; +} + +export function useGroupedFields({ + dataViewId, + allFields, + services, + fieldsExistenceReader, + onOverrideFieldGroupDetails, + onSupportedFieldFilter, + onSelectedFieldFilter, + onFilterField, +}: GroupedFieldsParams): GroupedFieldsResult { + const [dataView, setDataView] = useState(null); + const fieldsExistenceInfoUnavailable: boolean = dataViewId + ? fieldsExistenceReader?.isFieldsExistenceInfoUnavailable(dataViewId) ?? false + : true; + const hasFieldDataHandler = + dataViewId && fieldsExistenceReader + ? fieldsExistenceReader.hasFieldData + : hasFieldDataByDefault; + + useEffect(() => { + const getDataView = async () => { + if (dataViewId) { + setDataView(await services.dataViews.get(dataViewId)); + } + }; + getDataView(); + // if field existence information changed, reload the data view too + }, [dataViewId, services.dataViews, setDataView, hasFieldDataHandler]); + + const unfilteredFieldGroups: FieldListGroups = useMemo(() => { + const containsData = (field: T) => { + if (!dataViewId || !dataView) { + return true; + } + const overallField = dataView.getFieldByName?.(field.name); + return Boolean(overallField && hasFieldDataHandler(dataViewId, overallField.name)); + }; + + const fields = allFields || []; + const allSupportedTypesFields = onSupportedFieldFilter + ? fields.filter(onSupportedFieldFilter) + : fields; + const sortedFields = [...allSupportedTypesFields].sort(sortFields); + const groupedFields = { + ...getDefaultFieldGroups(), + ...groupBy(sortedFields, (field) => { + if (field.type === 'document') { + return 'specialFields'; + } else if (dataView?.metaFields?.includes(field.name)) { + return 'metaFields'; + } else if (containsData(field)) { + return 'availableFields'; + } else return 'emptyFields'; + }), + }; + const selectedFields = onSelectedFieldFilter ? sortedFields.filter(onSelectedFieldFilter) : []; + + let fieldGroupDefinitions: FieldListGroups = { + SpecialFields: { + fields: groupedFields.specialFields, + fieldCount: groupedFields.specialFields.length, + isAffectedByGlobalFilter: false, + isAffectedByTimeFilter: false, + isInitiallyOpen: false, + showInAccordion: false, + title: '', + hideDetails: true, + }, + SelectedFields: { + fields: selectedFields, + fieldCount: selectedFields.length, + isInitiallyOpen: true, + showInAccordion: true, + title: i18n.translate('unifiedFieldList.useGroupedFields.selectedFieldsLabel', { + defaultMessage: 'Selected fields', + }), + isAffectedByGlobalFilter: false, + isAffectedByTimeFilter: true, + hideDetails: false, + hideIfEmpty: true, + }, + AvailableFields: { + fields: groupedFields.availableFields, + fieldCount: groupedFields.availableFields.length, + isInitiallyOpen: true, + showInAccordion: true, + title: + dataViewId && fieldsExistenceInfoUnavailable + ? i18n.translate('unifiedFieldList.useGroupedFields.allFieldsLabel', { + defaultMessage: 'All fields', + }) + : i18n.translate('unifiedFieldList.useGroupedFields.availableFieldsLabel', { + defaultMessage: 'Available fields', + }), + isAffectedByGlobalFilter: false, + isAffectedByTimeFilter: true, + // Show details on timeout but not failure + // hideDetails: fieldsExistenceInfoUnavailable && !existenceFetchTimeout, // TODO: is this check still necessary? + hideDetails: fieldsExistenceInfoUnavailable, + defaultNoFieldsMessage: i18n.translate( + 'unifiedFieldList.useGroupedFields.noAvailableDataLabel', + { + defaultMessage: `There are no available fields that contain data.`, + } + ), + }, + EmptyFields: { + fields: groupedFields.emptyFields, + fieldCount: groupedFields.emptyFields.length, + isAffectedByGlobalFilter: false, + isAffectedByTimeFilter: false, + isInitiallyOpen: false, + showInAccordion: true, + hideDetails: false, + hideIfEmpty: !dataViewId, + title: i18n.translate('unifiedFieldList.useGroupedFields.emptyFieldsLabel', { + defaultMessage: 'Empty fields', + }), + defaultNoFieldsMessage: i18n.translate( + 'unifiedFieldList.useGroupedFields.noEmptyDataLabel', + { + defaultMessage: `There are no empty fields.`, + } + ), + helpText: i18n.translate('unifiedFieldList.useGroupedFields.emptyFieldsLabelHelp', { + defaultMessage: 'Empty fields did not contain any values based on your filters.', + }), + }, + MetaFields: { + fields: groupedFields.metaFields, + fieldCount: groupedFields.metaFields.length, + isAffectedByGlobalFilter: false, + isAffectedByTimeFilter: false, + isInitiallyOpen: false, + showInAccordion: true, + hideDetails: false, + hideIfEmpty: !dataViewId, + title: i18n.translate('unifiedFieldList.useGroupedFields.metaFieldsLabel', { + defaultMessage: 'Meta fields', + }), + defaultNoFieldsMessage: i18n.translate( + 'unifiedFieldList.useGroupedFields.noMetaDataLabel', + { + defaultMessage: `There are no meta fields.`, + } + ), + }, + }; + + // do not show empty field accordion if there is no existence information + if (fieldsExistenceInfoUnavailable) { + delete fieldGroupDefinitions.EmptyFields; + } + + if (onOverrideFieldGroupDetails) { + fieldGroupDefinitions = Object.keys(fieldGroupDefinitions).reduce>( + (definitions, name) => { + const groupName = name as FieldsGroupNames; + const group: FieldsGroup | undefined = fieldGroupDefinitions[groupName]; + if (group) { + definitions[groupName] = { + ...group, + ...(onOverrideFieldGroupDetails(groupName) || {}), + }; + } + return definitions; + }, + {} as FieldListGroups + ); + } + + return fieldGroupDefinitions; + }, [ + allFields, + onSupportedFieldFilter, + onSelectedFieldFilter, + onOverrideFieldGroupDetails, + dataView, + dataViewId, + hasFieldDataHandler, + fieldsExistenceInfoUnavailable, + ]); + + const fieldGroups: FieldListGroups = useMemo(() => { + if (!onFilterField) { + return unfilteredFieldGroups; + } + + return Object.fromEntries( + Object.entries(unfilteredFieldGroups).map(([name, group]) => [ + name, + { ...group, fields: group.fields.filter(onFilterField) }, + ]) + ) as FieldListGroups; + }, [unfilteredFieldGroups, onFilterField]); + + return useMemo( + () => ({ + fieldGroups, + }), + [fieldGroups] + ); +} + +function sortFields(fieldA: T, fieldB: T) { + return (fieldA.displayName || fieldA.name).localeCompare( + fieldB.displayName || fieldB.name, + undefined, + { + sensitivity: 'base', + } + ); +} + +function hasFieldDataByDefault(): boolean { + return true; +} + +function getDefaultFieldGroups() { + return { + specialFields: [], + availableFields: [], + emptyFields: [], + metaFields: [], + }; +} diff --git a/src/plugins/unified_field_list/public/index.ts b/src/plugins/unified_field_list/public/index.ts index 2ada1027ee97a..94abf51566463 100755 --- a/src/plugins/unified_field_list/public/index.ts +++ b/src/plugins/unified_field_list/public/index.ts @@ -14,6 +14,7 @@ export type { NumberStatsResult, TopValuesResult, } from '../common/types'; +export { FieldListGrouped, type FieldListGroupedProps } from './components/field_list'; export type { FieldStatsProps, FieldStatsServices } from './components/field_stats'; export { FieldStats } from './components/field_stats'; export { @@ -44,4 +45,23 @@ export type { UnifiedFieldListPluginSetup, UnifiedFieldListPluginStart, AddFieldFilterHandler, + FieldListGroups, + FieldsGroupDetails, } from './types'; +export { ExistenceFetchStatus, FieldsGroupNames } from './types'; + +export { + useExistingFieldsFetcher, + useExistingFieldsReader, + resetExistingFieldsCache, + type ExistingFieldsInfo, + type ExistingFieldsFetcherParams, + type ExistingFieldsFetcher, + type ExistingFieldsReader, +} from './hooks/use_existing_fields'; + +export { + useGroupedFields, + type GroupedFieldsParams, + type GroupedFieldsResult, +} from './hooks/use_grouped_fields'; diff --git a/src/plugins/unified_field_list/public/services/field_existing/index.ts b/src/plugins/unified_field_list/public/services/field_existing/index.ts index 56be726b7c90f..6541afb4673bb 100644 --- a/src/plugins/unified_field_list/public/services/field_existing/index.ts +++ b/src/plugins/unified_field_list/public/services/field_existing/index.ts @@ -6,4 +6,9 @@ * Side Public License, v 1. */ -export { loadFieldExisting } from './load_field_existing'; +import type { LoadFieldExistingHandler } from './load_field_existing'; + +export const loadFieldExisting: LoadFieldExistingHandler = async (params) => { + const { loadFieldExisting: loadFieldExistingHandler } = await import('./load_field_existing'); + return await loadFieldExistingHandler(params); +}; diff --git a/src/plugins/unified_field_list/public/services/field_existing/load_field_existing.ts b/src/plugins/unified_field_list/public/services/field_existing/load_field_existing.ts index 79b2b056c6062..f8e369838c51a 100644 --- a/src/plugins/unified_field_list/public/services/field_existing/load_field_existing.ts +++ b/src/plugins/unified_field_list/public/services/field_existing/load_field_existing.ts @@ -24,7 +24,12 @@ interface FetchFieldExistenceParams { uiSettingsClient: IUiSettingsClient; } -export async function loadFieldExisting({ +export type LoadFieldExistingHandler = (params: FetchFieldExistenceParams) => Promise<{ + existingFieldNames: string[]; + indexPatternTitle: string; +}>; + +export const loadFieldExisting: LoadFieldExistingHandler = async ({ data, dslQuery, fromDate, @@ -33,7 +38,7 @@ export async function loadFieldExisting({ dataViewsService, uiSettingsClient, dataView, -}: FetchFieldExistenceParams) { +}) => { const includeFrozen = uiSettingsClient.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN); const useSampling = uiSettingsClient.get(FIELD_EXISTENCE_SETTING); const metaFields = uiSettingsClient.get(UI_SETTINGS.META_FIELDS); @@ -53,4 +58,4 @@ export async function loadFieldExisting({ return response.rawResponse; }, }); -} +}; diff --git a/src/plugins/unified_field_list/public/types.ts b/src/plugins/unified_field_list/public/types.ts index f7a712534d59d..de96cf6a44cfb 100755 --- a/src/plugins/unified_field_list/public/types.ts +++ b/src/plugins/unified_field_list/public/types.ts @@ -19,3 +19,44 @@ export type AddFieldFilterHandler = ( value: unknown, type: '+' | '-' ) => void; + +export enum ExistenceFetchStatus { + failed = 'failed', + succeeded = 'succeeded', + unknown = 'unknown', +} + +export interface FieldListItem { + name: DataViewField['name']; + type?: DataViewField['type']; + displayName?: DataViewField['displayName']; +} + +export enum FieldsGroupNames { + SpecialFields = 'SpecialFields', + SelectedFields = 'SelectedFields', + AvailableFields = 'AvailableFields', + EmptyFields = 'EmptyFields', + MetaFields = 'MetaFields', +} + +export interface FieldsGroupDetails { + showInAccordion: boolean; + isInitiallyOpen: boolean; + title: string; + helpText?: string; + isAffectedByGlobalFilter: boolean; + isAffectedByTimeFilter: boolean; + hideDetails?: boolean; + defaultNoFieldsMessage?: string; + hideIfEmpty?: boolean; +} + +export interface FieldsGroup extends FieldsGroupDetails { + fields: T[]; + fieldCount: number; +} + +export type FieldListGroups = { + [key in FieldsGroupNames]?: FieldsGroup; +}; diff --git a/x-pack/plugins/lens/common/types.ts b/x-pack/plugins/lens/common/types.ts index f1e0e523548e0..3bf19d4d78b5c 100644 --- a/x-pack/plugins/lens/common/types.ts +++ b/x-pack/plugins/lens/common/types.ts @@ -20,11 +20,6 @@ export type { OriginalColumn } from './expressions/map_to_columns'; export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat; -export interface ExistingFields { - indexPatternTitle: string; - existingFieldNames: string[]; -} - export interface DateRange { fromDate: string; toDate: string; diff --git a/x-pack/plugins/lens/public/data_views_service/loader.test.ts b/x-pack/plugins/lens/public/data_views_service/loader.test.ts index 97ded75233cda..e7e2bab166a70 100644 --- a/x-pack/plugins/lens/public/data_views_service/loader.test.ts +++ b/x-pack/plugins/lens/public/data_views_service/loader.test.ts @@ -5,22 +5,10 @@ * 2.0. */ -import { DataViewsContract, DataViewSpec, FieldSpec } from '@kbn/data-views-plugin/public'; -import { IndexPattern, IndexPatternField } from '../types'; -import { - ensureIndexPattern, - loadIndexPatternRefs, - loadIndexPatterns, - syncExistingFields, -} from './loader'; +import { DataViewsContract } from '@kbn/data-views-plugin/public'; +import { ensureIndexPattern, loadIndexPatternRefs, loadIndexPatterns } from './loader'; import { sampleIndexPatterns, mockDataViewsService } from './mocks'; import { documentField } from '../datasources/form_based/document_field'; -import { coreMock } from '@kbn/core/public/mocks'; -import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { UI_SETTINGS } from '@kbn/data-plugin/public'; -import { createHttpFetchError } from '@kbn/core-http-browser-mocks'; describe('loader', () => { describe('loadIndexPatternRefs', () => { @@ -266,218 +254,4 @@ describe('loader', () => { expect(onError).not.toHaveBeenCalled(); }); }); - - describe('syncExistingFields', () => { - const core = coreMock.createStart(); - const dataViews = dataViewPluginMocks.createStartContract(); - const data = dataPluginMock.createStartContract(); - - const dslQuery = { - bool: { - must: [], - filter: [{ match_all: {} }], - should: [], - must_not: [], - }, - }; - - function getIndexPatternList() { - return [ - { - id: '1', - title: '1', - fields: [{ name: 'ip1_field_1' }, { name: 'ip1_field_2' }], - hasRestrictions: false, - }, - { - id: '2', - title: '2', - fields: [{ name: 'ip2_field_1' }, { name: 'ip2_field_2' }], - hasRestrictions: false, - }, - { - id: '3', - title: '3', - fields: [{ name: 'ip3_field_1' }, { name: 'ip3_field_2' }], - hasRestrictions: false, - }, - ] as unknown as IndexPattern[]; - } - - beforeEach(() => { - core.uiSettings.get.mockImplementation((key: string) => { - if (key === UI_SETTINGS.META_FIELDS) { - return []; - } - }); - dataViews.get.mockImplementation((id: string) => - Promise.resolve( - getIndexPatternList().find( - (indexPattern) => indexPattern.id === id - ) as unknown as DataView - ) - ); - }); - - it('should call once for each index pattern', async () => { - const updateIndexPatterns = jest.fn(); - dataViews.getFieldsForIndexPattern.mockImplementation( - (dataView: DataViewSpec | DataView) => - Promise.resolve(dataView.fields) as Promise - ); - - await syncExistingFields({ - dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, - indexPatternList: getIndexPatternList(), - updateIndexPatterns, - dslQuery, - onNoData: jest.fn(), - currentIndexPatternTitle: 'abc', - isFirstExistenceFetch: false, - existingFields: {}, - core, - data, - dataViews, - }); - - expect(dataViews.get).toHaveBeenCalledTimes(3); - expect(dataViews.getFieldsForIndexPattern).toHaveBeenCalledTimes(3); - expect(updateIndexPatterns).toHaveBeenCalledTimes(1); - - const [newState, options] = updateIndexPatterns.mock.calls[0]; - expect(options).toEqual({ applyImmediately: true }); - - expect(newState).toEqual({ - isFirstExistenceFetch: false, - existingFields: { - '1': { ip1_field_1: true, ip1_field_2: true }, - '2': { ip2_field_1: true, ip2_field_2: true }, - '3': { ip3_field_1: true, ip3_field_2: true }, - }, - }); - }); - - it('should call onNoData callback if current index pattern returns no fields', async () => { - const updateIndexPatterns = jest.fn(); - const onNoData = jest.fn(); - dataViews.getFieldsForIndexPattern.mockImplementation( - async (dataView: DataViewSpec | DataView) => { - return (dataView.title === '1' - ? [{ name: `${dataView.title}_field_1` }, { name: `${dataView.title}_field_2` }] - : []) as unknown as Promise; - } - ); - - const args = { - dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, - indexPatternList: getIndexPatternList(), - updateIndexPatterns, - dslQuery, - onNoData, - currentIndexPatternTitle: 'abc', - isFirstExistenceFetch: false, - existingFields: {}, - core, - data, - dataViews, - }; - - await syncExistingFields(args); - - expect(onNoData).not.toHaveBeenCalled(); - - await syncExistingFields({ ...args, isFirstExistenceFetch: true }); - expect(onNoData).not.toHaveBeenCalled(); - }); - - it('should set all fields to available and existence error flag if the request fails', async () => { - const updateIndexPatterns = jest.fn(); - dataViews.getFieldsForIndexPattern.mockImplementation(() => { - return new Promise((_, reject) => { - reject(new Error()); - }); - }); - - const args = { - dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, - indexPatternList: [ - { - id: '1', - title: '1', - hasRestrictions: false, - fields: [{ name: 'field1' }, { name: 'field2' }] as IndexPatternField[], - }, - ] as IndexPattern[], - updateIndexPatterns, - dslQuery, - onNoData: jest.fn(), - currentIndexPatternTitle: 'abc', - isFirstExistenceFetch: false, - existingFields: {}, - core, - data, - dataViews, - }; - - await syncExistingFields(args); - - const [newState, options] = updateIndexPatterns.mock.calls[0]; - expect(options).toEqual({ applyImmediately: true }); - - expect(newState.existenceFetchFailed).toEqual(true); - expect(newState.existenceFetchTimeout).toEqual(false); - expect(newState.existingFields['1']).toEqual({ - field1: true, - field2: true, - }); - }); - - it('should set all fields to available and existence error flag if the request times out', async () => { - const updateIndexPatterns = jest.fn(); - dataViews.getFieldsForIndexPattern.mockImplementation(() => { - return new Promise((_, reject) => { - const error = createHttpFetchError( - 'timeout', - 'error', - {} as Request, - { status: 408 } as Response - ); - reject(error); - }); - }); - - const args = { - dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, - indexPatternList: [ - { - id: '1', - title: '1', - hasRestrictions: false, - fields: [{ name: 'field1' }, { name: 'field2' }] as IndexPatternField[], - }, - ] as IndexPattern[], - updateIndexPatterns, - dslQuery, - onNoData: jest.fn(), - currentIndexPatternTitle: 'abc', - isFirstExistenceFetch: false, - existingFields: {}, - core, - data, - dataViews, - }; - - await syncExistingFields(args); - - const [newState, options] = updateIndexPatterns.mock.calls[0]; - expect(options).toEqual({ applyImmediately: true }); - - expect(newState.existenceFetchFailed).toEqual(false); - expect(newState.existenceFetchTimeout).toEqual(true); - expect(newState.existingFields['1']).toEqual({ - field1: true, - field2: true, - }); - }); - }); }); diff --git a/x-pack/plugins/lens/public/data_views_service/loader.ts b/x-pack/plugins/lens/public/data_views_service/loader.ts index f0184d0a11d0b..f33ba8f3d37a9 100644 --- a/x-pack/plugins/lens/public/data_views_service/loader.ts +++ b/x-pack/plugins/lens/public/data_views_service/loader.ts @@ -8,13 +8,8 @@ import { isNestedField } from '@kbn/data-views-plugin/common'; import type { DataViewsContract, DataView, DataViewSpec } from '@kbn/data-views-plugin/public'; import { keyBy } from 'lodash'; -import { CoreStart } from '@kbn/core/public'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { loadFieldExisting } from '@kbn/unified-field-list-plugin/public'; import { IndexPattern, IndexPatternField, IndexPatternMap, IndexPatternRef } from '../types'; import { documentField } from '../datasources/form_based/document_field'; -import { DateRange } from '../../common'; -import { DataViewsState } from '../state_management'; type ErrorHandler = (err: Error) => void; type MinimalDataViewsContract = Pick; @@ -247,120 +242,3 @@ export async function ensureIndexPattern({ }; return newIndexPatterns; } - -async function refreshExistingFields({ - dateRange, - indexPatternList, - dslQuery, - core, - data, - dataViews, -}: { - dateRange: DateRange; - indexPatternList: IndexPattern[]; - dslQuery: object; - core: Pick; - data: DataPublicPluginStart; - dataViews: DataViewsContract; -}) { - try { - const emptinessInfo = await Promise.all( - indexPatternList.map(async (pattern) => { - if (pattern.hasRestrictions) { - return { - indexPatternTitle: pattern.title, - existingFieldNames: pattern.fields.map((field) => field.name), - }; - } - - const dataView = await dataViews.get(pattern.id); - return await loadFieldExisting({ - dslQuery, - fromDate: dateRange.fromDate, - toDate: dateRange.toDate, - timeFieldName: pattern.timeFieldName, - data, - uiSettingsClient: core.uiSettings, - dataViewsService: dataViews, - dataView, - }); - }) - ); - return { result: emptinessInfo, status: 200 }; - } catch (e) { - return { result: undefined, status: e.res?.status as number }; - } -} - -type FieldsPropsFromDataViewsState = Pick< - DataViewsState, - 'existingFields' | 'isFirstExistenceFetch' | 'existenceFetchTimeout' | 'existenceFetchFailed' ->; -export async function syncExistingFields({ - updateIndexPatterns, - isFirstExistenceFetch, - currentIndexPatternTitle, - onNoData, - existingFields, - ...requestOptions -}: { - dateRange: DateRange; - indexPatternList: IndexPattern[]; - existingFields: Record>; - updateIndexPatterns: ( - newFieldState: FieldsPropsFromDataViewsState, - options: { applyImmediately: boolean } - ) => void; - isFirstExistenceFetch: boolean; - currentIndexPatternTitle: string; - dslQuery: object; - onNoData?: () => void; - core: Pick; - data: DataPublicPluginStart; - dataViews: DataViewsContract; -}) { - const { indexPatternList } = requestOptions; - const newExistingFields = { ...existingFields }; - - const { result, status } = await refreshExistingFields(requestOptions); - - if (result) { - if (isFirstExistenceFetch) { - const fieldsCurrentIndexPattern = result.find( - (info) => info.indexPatternTitle === currentIndexPatternTitle - ); - if (fieldsCurrentIndexPattern && fieldsCurrentIndexPattern.existingFieldNames.length === 0) { - onNoData?.(); - } - } - - for (const { indexPatternTitle, existingFieldNames } of result) { - newExistingFields[indexPatternTitle] = booleanMap(existingFieldNames); - } - } else { - for (const { title, fields } of indexPatternList) { - newExistingFields[title] = booleanMap(fields.map((field) => field.name)); - } - } - - updateIndexPatterns( - { - existingFields: newExistingFields, - ...(result - ? { isFirstExistenceFetch: status !== 200 } - : { - isFirstExistenceFetch, - existenceFetchFailed: status !== 408, - existenceFetchTimeout: status === 408, - }), - }, - { applyImmediately: true } - ); -} - -function booleanMap(keys: string[]) { - return keys.reduce((acc, key) => { - acc[key] = true; - return acc; - }, {} as Record); -} diff --git a/x-pack/plugins/lens/public/data_views_service/mocks.ts b/x-pack/plugins/lens/public/data_views_service/mocks.ts index ed8d6e86e58a4..b4acacbe98b73 100644 --- a/x-pack/plugins/lens/public/data_views_service/mocks.ts +++ b/x-pack/plugins/lens/public/data_views_service/mocks.ts @@ -12,7 +12,7 @@ import { createMockedRestrictedIndexPattern, } from '../datasources/form_based/mocks'; import { DataViewsState } from '../state_management'; -import { ExistingFieldsMap, IndexPattern } from '../types'; +import { IndexPattern } from '../types'; import { getFieldByNameFactory } from './loader'; /** @@ -22,25 +22,13 @@ import { getFieldByNameFactory } from './loader'; export const createMockDataViewsState = ({ indexPatterns, indexPatternRefs, - isFirstExistenceFetch, - existingFields, }: Partial = {}): DataViewsState => { const refs = indexPatternRefs ?? Object.values(indexPatterns ?? {}).map(({ id, title, name }) => ({ id, title, name })); - const allFields = - existingFields ?? - refs.reduce((acc, { id, title }) => { - if (indexPatterns && id in indexPatterns) { - acc[title] = Object.fromEntries(indexPatterns[id].fields.map((f) => [f.displayName, true])); - } - return acc; - }, {} as ExistingFieldsMap); return { indexPatterns: indexPatterns ?? {}, indexPatternRefs: refs, - isFirstExistenceFetch: Boolean(isFirstExistenceFetch), - existingFields: allFields, }; }; diff --git a/x-pack/plugins/lens/public/data_views_service/service.ts b/x-pack/plugins/lens/public/data_views_service/service.ts index 28a0d82799992..5192de1d2385e 100644 --- a/x-pack/plugins/lens/public/data_views_service/service.ts +++ b/x-pack/plugins/lens/public/data_views_service/service.ts @@ -14,14 +14,8 @@ import { UPDATE_FILTER_REFERENCES_ACTION, UPDATE_FILTER_REFERENCES_TRIGGER, } from '@kbn/unified-search-plugin/public'; -import type { DateRange } from '../../common'; import type { IndexPattern, IndexPatternMap, IndexPatternRef } from '../types'; -import { - ensureIndexPattern, - loadIndexPatternRefs, - loadIndexPatterns, - syncExistingFields, -} from './loader'; +import { ensureIndexPattern, loadIndexPatternRefs, loadIndexPatterns } from './loader'; import type { DataViewsState } from '../state_management'; import { generateId } from '../id_generator'; @@ -71,18 +65,6 @@ export interface IndexPatternServiceAPI { id: string; cache: IndexPatternMap; }) => Promise; - /** - * Loads the existingFields map given the current context - */ - refreshExistingFields: (args: { - dateRange: DateRange; - currentIndexPatternTitle: string; - dslQuery: object; - onNoData?: () => void; - existingFields: Record>; - indexPatternList: IndexPattern[]; - isFirstExistenceFetch: boolean; - }) => Promise; replaceDataViewId: (newDataView: DataView) => Promise; /** @@ -150,14 +132,6 @@ export function createIndexPatternService({ }, ensureIndexPattern: (args) => ensureIndexPattern({ onError: onChangeError, dataViews, ...args }), - refreshExistingFields: (args) => - syncExistingFields({ - updateIndexPatterns, - ...args, - data, - dataViews, - core, - }), loadIndexPatternRefs: async ({ isFullEditor }) => isFullEditor ? loadIndexPatternRefs(dataViews) : [], getDefaultIndex: () => core.uiSettings.get('defaultIndex'), diff --git a/x-pack/plugins/lens/public/datasources/form_based/__mocks__/loader.ts b/x-pack/plugins/lens/public/datasources/form_based/__mocks__/loader.ts index 47af8d816b73f..7ad4172ce3829 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/__mocks__/loader.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/__mocks__/loader.ts @@ -28,11 +28,9 @@ export function loadInitialDataViews() { const restricted = createMockedRestrictedIndexPattern(); return { indexPatternRefs: [], - existingFields: {}, indexPatterns: { [indexPattern.id]: indexPattern, [restricted.id]: restricted, }, - isFirstExistenceFetch: false, }; } diff --git a/x-pack/plugins/lens/public/datasources/form_based/datapanel.scss b/x-pack/plugins/lens/public/datasources/form_based/datapanel.scss index ef68c784100e4..32887d3f9350d 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/datapanel.scss +++ b/x-pack/plugins/lens/public/datasources/form_based/datapanel.scss @@ -14,15 +14,6 @@ margin-bottom: $euiSizeS; } -.lnsInnerIndexPatternDataPanel__titleTooltip { - margin-right: $euiSizeXS; -} - -.lnsInnerIndexPatternDataPanel__fieldItems { - // Quick fix for making sure the shadow and focus rings are visible outside the accordion bounds - padding: $euiSizeXS; -} - .lnsInnerIndexPatternDataPanel__textField { @include euiFormControlLayoutPadding(1, 'right'); @include euiFormControlLayoutPadding(1, 'left'); @@ -60,4 +51,4 @@ .lnsFilterButton .euiFilterButton__textShift { min-width: 0; -} \ No newline at end of file +} diff --git a/x-pack/plugins/lens/public/datasources/form_based/datapanel.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/datapanel.test.tsx index e7b0cd6d457a9..6639484ca6be4 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/datapanel.test.tsx @@ -13,15 +13,16 @@ import { dataViewPluginMocks, Start as DataViewPublicStart, } from '@kbn/data-views-plugin/public/mocks'; -import { InnerFormBasedDataPanel, FormBasedDataPanel, Props } from './datapanel'; -import { FieldList } from './field_list'; +import { InnerFormBasedDataPanel, FormBasedDataPanel } from './datapanel'; +import { FieldListGrouped } from '@kbn/unified-field-list-plugin/public'; +import * as UseExistingFieldsApi from '@kbn/unified-field-list-plugin/public/hooks/use_existing_fields'; +import * as ExistingFieldsServiceApi from '@kbn/unified-field-list-plugin/public/services/field_existing/load_field_existing'; import { FieldItem } from './field_item'; -import { NoFieldsCallout } from './no_fields_callout'; import { act } from 'react-dom/test-utils'; import { coreMock } from '@kbn/core/public/mocks'; import { FormBasedPrivateState } from './types'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; -import { EuiProgress, EuiLoadingSpinner } from '@elastic/eui'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { EuiCallOut, EuiLoadingSpinner, EuiProgress } from '@elastic/eui'; import { documentField } from './document_field'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; @@ -33,10 +34,9 @@ import { DOCUMENT_FIELD_NAME } from '../../../common'; import { createIndexPatternServiceMock } from '../../mocks/data_views_service_mock'; import { createMockFramePublicAPI } from '../../mocks'; import { DataViewsState } from '../../state_management'; -import { ExistingFieldsMap, FramePublicAPI, IndexPattern } from '../../types'; -import { IndexPatternServiceProps } from '../../data_views_service/service'; -import { FieldSpec, DataView } from '@kbn/data-views-plugin/public'; +import { DataView } from '@kbn/data-views-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/public'; +import { ReactWrapper } from 'enzyme'; const fieldsOne = [ { @@ -162,17 +162,12 @@ const fieldsThree = [ documentField, ]; -function getExistingFields(indexPatterns: Record) { - const existingFields: ExistingFieldsMap = {}; - for (const { title, fields } of Object.values(indexPatterns)) { - const fieldsMap: Record = {}; - for (const { displayName, name } of fields) { - fieldsMap[displayName ?? name] = true; - } - existingFields[title] = fieldsMap; - } - return existingFields; -} +jest.spyOn(UseExistingFieldsApi, 'useExistingFieldsFetcher'); +jest.spyOn(UseExistingFieldsApi, 'useExistingFieldsReader'); +jest.spyOn(ExistingFieldsServiceApi, 'loadFieldExisting').mockImplementation(async () => ({ + indexPatternTitle: 'test', + existingFieldNames: [], +})); const initialState: FormBasedPrivateState = { currentIndexPatternId: '1', @@ -234,8 +229,63 @@ const initialState: FormBasedPrivateState = { }, }; -function getFrameAPIMock({ indexPatterns, existingFields, ...rest }: Partial = {}) { +function getFrameAPIMock({ + indexPatterns, + ...rest +}: Partial & { indexPatterns: DataViewsState['indexPatterns'] }) { const frameAPI = createMockFramePublicAPI(); + + return { + ...frameAPI, + dataViews: { + ...frameAPI.dataViews, + indexPatterns, + ...rest, + }, + }; +} + +const dslQuery = { bool: { must: [], filter: [], should: [], must_not: [] } }; + +// @ts-expect-error Portal mocks are notoriously difficult to type +ReactDOM.createPortal = jest.fn((element) => element); + +async function mountAndWaitForLazyModules(component: React.ReactElement): Promise { + let inst: ReactWrapper; + await act(async () => { + inst = await mountWithIntl(component); + // wait for lazy modules + await new Promise((resolve) => setTimeout(resolve, 0)); + await inst.update(); + }); + + return inst!; +} + +describe('FormBased Data Panel', () => { + const indexPatterns = { + a: { + id: 'a', + title: 'aaa', + timeFieldName: 'atime', + fields: fieldsOne, + getFieldByName: getFieldByNameFactory(fieldsOne), + hasRestrictions: false, + isPersisted: true, + spec: {}, + }, + b: { + id: 'b', + title: 'bbb', + timeFieldName: 'btime', + fields: fieldsTwo, + getFieldByName: getFieldByNameFactory(fieldsTwo), + hasRestrictions: false, + isPersisted: true, + spec: {}, + }, + }; + const defaultIndexPatterns = { '1': { id: '1', @@ -268,42 +318,7 @@ function getFrameAPIMock({ indexPatterns, existingFields, ...rest }: Partial element); -describe('FormBased Data Panel', () => { - const indexPatterns = { - a: { - id: 'a', - title: 'aaa', - timeFieldName: 'atime', - fields: [{ name: 'aaa_field_1' }, { name: 'aaa_field_2' }], - getFieldByName: getFieldByNameFactory([]), - hasRestrictions: false, - }, - b: { - id: 'b', - title: 'bbb', - timeFieldName: 'btime', - fields: [{ name: 'bbb_field_1' }, { name: 'bbb_field_2' }], - getFieldByName: getFieldByNameFactory([]), - hasRestrictions: false, - }, - }; let defaultProps: Parameters[0] & { showNoDataPopover: () => void; }; @@ -313,9 +328,10 @@ describe('FormBased Data Panel', () => { beforeEach(() => { core = coreMock.createStart(); dataViews = dataViewPluginMocks.createStartContract(); + const frame = getFrameAPIMock({ indexPatterns: defaultIndexPatterns }); defaultProps = { data: dataPluginMock.createStartContract(), - dataViews: dataViewPluginMocks.createStartContract(), + dataViews, fieldFormats: fieldFormatsServiceMock.createStartContract(), indexPatternFieldEditor: indexPatternFieldEditorPluginMock.createStartContract(), onIndexPatternRefresh: jest.fn(), @@ -334,12 +350,34 @@ describe('FormBased Data Panel', () => { hasSuggestionForField: jest.fn(() => false), uiActions: uiActionsPluginMock.createStartContract(), indexPatternService: createIndexPatternServiceMock({ core, dataViews }), - frame: getFrameAPIMock(), + frame, + activeIndexPatterns: [frame.dataViews.indexPatterns['1']], }; + + core.uiSettings.get.mockImplementation((key: string) => { + if (key === UI_SETTINGS.META_FIELDS) { + return []; + } + }); + dataViews.get.mockImplementation(async (id: string) => { + const dataView = [ + indexPatterns.a, + indexPatterns.b, + defaultIndexPatterns['1'], + defaultIndexPatterns['2'], + defaultIndexPatterns['3'], + ].find((indexPattern) => indexPattern.id === id) as unknown as DataView; + dataView.metaFields = ['_id']; + return dataView; + }); + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockClear(); + (UseExistingFieldsApi.useExistingFieldsReader as jest.Mock).mockClear(); + (UseExistingFieldsApi.useExistingFieldsFetcher as jest.Mock).mockClear(); + UseExistingFieldsApi.resetExistingFieldsCache(); }); - it('should render a warning if there are no index patterns', () => { - const wrapper = shallowWithIntl( + it('should render a warning if there are no index patterns', async () => { + const wrapper = await mountAndWaitForLazyModules( { frame={createMockFramePublicAPI()} /> ); - expect(wrapper.find('[data-test-subj="indexPattern-no-indexpatterns"]')).toHaveLength(1); + expect(wrapper.find('[data-test-subj="indexPattern-no-indexpatterns"]').exists()).toBeTruthy(); }); describe('loading existence data', () => { - function testProps(updateIndexPatterns: IndexPatternServiceProps['updateIndexPatterns']) { - core.uiSettings.get.mockImplementation((key: string) => { - if (key === UI_SETTINGS.META_FIELDS) { - return []; - } - }); - dataViews.getFieldsForIndexPattern.mockImplementation((dataView) => { - return Promise.resolve([ - { name: `${dataView.title}_field_1` }, - { name: `${dataView.title}_field_2` }, - ]) as Promise; - }); - dataViews.get.mockImplementation(async (id: string) => { - return [indexPatterns.a, indexPatterns.b].find( - (indexPattern) => indexPattern.id === id - ) as unknown as DataView; - }); + function testProps({ + currentIndexPatternId, + otherProps, + }: { + currentIndexPatternId: keyof typeof indexPatterns; + otherProps?: object; + }) { return { ...defaultProps, indexPatternService: createIndexPatternServiceMock({ - updateIndexPatterns, + updateIndexPatterns: jest.fn(), core, dataViews, }), @@ -388,290 +416,329 @@ describe('FormBased Data Panel', () => { dragging: { id: '1', humanData: { label: 'Label' } }, }, dateRange: { fromDate: '2019-01-01', toDate: '2020-01-01' }, - frame: { - dataViews: { - indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, - indexPatterns, - }, - } as unknown as FramePublicAPI, + frame: getFrameAPIMock({ + indexPatterns: indexPatterns as unknown as DataViewsState['indexPatterns'], + }), state: { - currentIndexPatternId: 'a', + currentIndexPatternId, layers: { 1: { - indexPatternId: 'a', + indexPatternId: currentIndexPatternId, columnOrder: [], columns: {}, }, }, } as FormBasedPrivateState, + ...(otherProps || {}), }; } - async function testExistenceLoading( - props: Props, - stateChanges?: Partial, - propChanges?: Partial - ) { - const inst = mountWithIntl(); + it('loads existence data', async () => { + const props = testProps({ + currentIndexPatternId: 'a', + }); - await act(async () => { - inst.update(); + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + return { + existingFieldNames: [indexPatterns.a.fields[0].name, indexPatterns.a.fields[1].name], + }; }); - if (stateChanges || propChanges) { - await act(async () => { - inst.setProps({ - ...props, - ...(propChanges || {}), - state: { - ...props.state, - ...(stateChanges || {}), - }, - }); - inst.update(); - }); - } - } + const inst = await mountAndWaitForLazyModules(); - it('loads existence data', async () => { - const updateIndexPatterns = jest.fn(); - await testExistenceLoading(testProps(updateIndexPatterns)); - - expect(updateIndexPatterns).toHaveBeenCalledWith( - { - existingFields: { - aaa: { - aaa_field_1: true, - aaa_field_2: true, - }, - }, - isFirstExistenceFetch: false, - }, - { applyImmediately: true } + expect(UseExistingFieldsApi.useExistingFieldsFetcher).toHaveBeenCalledWith( + expect.objectContaining({ + dataViews: [indexPatterns.a], + query: props.query, + filters: props.filters, + fromDate: props.dateRange.fromDate, + toDate: props.dateRange.toDate, + }) + ); + expect(UseExistingFieldsApi.useExistingFieldsReader).toHaveBeenCalled(); + expect(inst.find('[data-test-subj="lnsIndexPattern__ariaDescription"]').first().text()).toBe( + '2 available fields. 3 empty fields. 0 meta fields.' ); }); it('loads existence data for current index pattern id', async () => { - const updateIndexPatterns = jest.fn(); - await testExistenceLoading(testProps(updateIndexPatterns), { + const props = testProps({ currentIndexPatternId: 'b', }); - expect(updateIndexPatterns).toHaveBeenCalledWith( - { - existingFields: { - aaa: { - aaa_field_1: true, - aaa_field_2: true, - }, - bbb: { - bbb_field_1: true, - bbb_field_2: true, - }, - }, - isFirstExistenceFetch: false, - }, - { applyImmediately: true } + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + return { + existingFieldNames: [indexPatterns.b.fields[0].name], + }; + }); + + const inst = await mountAndWaitForLazyModules(); + + expect(UseExistingFieldsApi.useExistingFieldsFetcher).toHaveBeenCalledWith( + expect.objectContaining({ + dataViews: [indexPatterns.b], + query: props.query, + filters: props.filters, + fromDate: props.dateRange.fromDate, + toDate: props.dateRange.toDate, + }) + ); + expect(UseExistingFieldsApi.useExistingFieldsReader).toHaveBeenCalled(); + expect(inst.find('[data-test-subj="lnsIndexPattern__ariaDescription"]').first().text()).toBe( + '1 available field. 2 empty fields. 0 meta fields.' ); }); it('does not load existence data if date and index pattern ids are unchanged', async () => { - const updateIndexPatterns = jest.fn(); - await testExistenceLoading( - testProps(updateIndexPatterns), - { - currentIndexPatternId: 'a', + const props = testProps({ + currentIndexPatternId: 'a', + otherProps: { + dateRange: { fromDate: '2019-01-01', toDate: '2020-01-01' }, }, - { dateRange: { fromDate: '2019-01-01', toDate: '2020-01-01' } } + }); + + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + return { + existingFieldNames: [indexPatterns.a.fields[0].name, indexPatterns.a.fields[1].name], + }; + }); + + const inst = await mountAndWaitForLazyModules(); + + expect(UseExistingFieldsApi.useExistingFieldsFetcher).toHaveBeenCalledWith( + expect.objectContaining({ + dataViews: [indexPatterns.a], + fromDate: props.dateRange.fromDate, + toDate: props.dateRange.toDate, + }) ); + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledTimes(1); - expect(updateIndexPatterns).toHaveBeenCalledTimes(1); + await act(async () => { + await inst.setProps({ dateRange: { fromDate: '2019-01-01', toDate: '2020-01-01' } }); + await inst.update(); + }); + + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledTimes(1); }); it('loads existence data if date range changes', async () => { - const updateIndexPatterns = jest.fn(); - await testExistenceLoading(testProps(updateIndexPatterns), undefined, { - dateRange: { fromDate: '2019-01-01', toDate: '2020-01-02' }, + const props = testProps({ + currentIndexPatternId: 'a', + otherProps: { + dateRange: { fromDate: '2019-01-01', toDate: '2020-01-01' }, + }, }); - expect(updateIndexPatterns).toHaveBeenCalledTimes(2); - expect(dataViews.getFieldsForIndexPattern).toHaveBeenCalledTimes(2); - expect(dataViews.get).toHaveBeenCalledTimes(2); - - const firstCall = dataViews.getFieldsForIndexPattern.mock.calls[0]; - expect(firstCall[0]).toEqual(indexPatterns.a); - expect(firstCall[1]?.filter?.bool?.filter).toContainEqual(dslQuery); - expect(firstCall[1]?.filter?.bool?.filter).toContainEqual({ - range: { - atime: { - format: 'strict_date_optional_time', - gte: '2019-01-01', - lte: '2020-01-01', - }, - }, + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + return { + existingFieldNames: [indexPatterns.a.fields[0].name, indexPatterns.a.fields[1].name], + }; }); - const secondCall = dataViews.getFieldsForIndexPattern.mock.calls[1]; - expect(secondCall[0]).toEqual(indexPatterns.a); - expect(secondCall[1]?.filter?.bool?.filter).toContainEqual(dslQuery); - expect(secondCall[1]?.filter?.bool?.filter).toContainEqual({ - range: { - atime: { - format: 'strict_date_optional_time', - gte: '2019-01-01', - lte: '2020-01-02', - }, - }, + const inst = await mountAndWaitForLazyModules(); + + expect(UseExistingFieldsApi.useExistingFieldsFetcher).toHaveBeenCalledWith( + expect.objectContaining({ + dataViews: [indexPatterns.a], + fromDate: props.dateRange.fromDate, + toDate: props.dateRange.toDate, + }) + ); + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledTimes(1); + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( + expect.objectContaining({ + fromDate: '2019-01-01', + toDate: '2020-01-01', + dslQuery, + dataView: indexPatterns.a, + timeFieldName: indexPatterns.a.timeFieldName, + }) + ); + + await act(async () => { + await inst.setProps({ dateRange: { fromDate: '2019-01-01', toDate: '2020-01-02' } }); + await inst.update(); }); - expect(updateIndexPatterns).toHaveBeenCalledWith( - { - existingFields: { - aaa: { - aaa_field_1: true, - aaa_field_2: true, - }, - }, - isFirstExistenceFetch: false, - }, - { applyImmediately: true } + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledTimes(2); + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( + expect.objectContaining({ + fromDate: '2019-01-01', + toDate: '2020-01-02', + dslQuery, + dataView: indexPatterns.a, + timeFieldName: indexPatterns.a.timeFieldName, + }) ); }); it('loads existence data if layer index pattern changes', async () => { - const updateIndexPatterns = jest.fn(); - await testExistenceLoading(testProps(updateIndexPatterns), { - layers: { - 1: { - indexPatternId: 'b', - columnOrder: [], - columns: {}, - }, + const props = testProps({ + currentIndexPatternId: 'a', + otherProps: { + dateRange: { fromDate: '2019-01-01', toDate: '2020-01-01' }, }, }); - expect(updateIndexPatterns).toHaveBeenCalledTimes(2); - - const secondCall = dataViews.getFieldsForIndexPattern.mock.calls[1]; - expect(secondCall[0]).toEqual(indexPatterns.a); - expect(secondCall[1]?.filter?.bool?.filter).toContainEqual(dslQuery); - expect(secondCall[1]?.filter?.bool?.filter).toContainEqual({ - range: { - atime: { - format: 'strict_date_optional_time', - gte: '2019-01-01', - lte: '2020-01-01', - }, - }, - }); + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation( + async ({ dataView }) => { + return { + existingFieldNames: + dataView === indexPatterns.a + ? [indexPatterns.a.fields[0].name, indexPatterns.a.fields[1].name] + : [indexPatterns.b.fields[0].name], + }; + } + ); - const thirdCall = dataViews.getFieldsForIndexPattern.mock.calls[2]; - expect(thirdCall[0]).toEqual(indexPatterns.b); - expect(thirdCall[1]?.filter?.bool?.filter).toContainEqual(dslQuery); - expect(thirdCall[1]?.filter?.bool?.filter).toContainEqual({ - range: { - btime: { - format: 'strict_date_optional_time', - gte: '2019-01-01', - lte: '2020-01-01', - }, - }, - }); + const inst = await mountAndWaitForLazyModules(); - expect(updateIndexPatterns).toHaveBeenCalledWith( - { - existingFields: { - aaa: { - aaa_field_1: true, - aaa_field_2: true, - }, - bbb: { - bbb_field_1: true, - bbb_field_2: true, - }, - }, - isFirstExistenceFetch: false, - }, - { applyImmediately: true } + expect(UseExistingFieldsApi.useExistingFieldsFetcher).toHaveBeenCalledWith( + expect.objectContaining({ + dataViews: [indexPatterns.a], + fromDate: props.dateRange.fromDate, + toDate: props.dateRange.toDate, + }) + ); + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledTimes(1); + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( + expect.objectContaining({ + fromDate: '2019-01-01', + toDate: '2020-01-01', + dslQuery, + dataView: indexPatterns.a, + timeFieldName: indexPatterns.a.timeFieldName, + }) ); - }); - it('shows a loading indicator when loading', async () => { - const updateIndexPatterns = jest.fn(); - const load = async () => {}; - const inst = mountWithIntl(); - expect(inst.find(EuiProgress).length).toEqual(1); - await act(load); - inst.update(); - expect(inst.find(EuiProgress).length).toEqual(0); - }); + expect(inst.find('[data-test-subj="lnsIndexPattern__ariaDescription"]').first().text()).toBe( + '2 available fields. 3 empty fields. 0 meta fields.' + ); - it('does not perform multiple queries at once', async () => { - const updateIndexPatterns = jest.fn(); - let queryCount = 0; - let overlapCount = 0; - const props = testProps(updateIndexPatterns); + await act(async () => { + await inst.setProps({ + currentIndexPatternId: 'b', + state: { + currentIndexPatternId: 'b', + layers: { + 1: { + indexPatternId: 'b', + columnOrder: [], + columns: {}, + }, + }, + } as FormBasedPrivateState, + }); + await inst.update(); + }); - dataViews.getFieldsForIndexPattern.mockImplementation((dataView) => { - if (queryCount) { - ++overlapCount; - } - ++queryCount; - const result = Promise.resolve([ - { name: `${dataView.title}_field_1` }, - { name: `${dataView.title}_field_2` }, - ]) as Promise; + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledTimes(2); + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( + expect.objectContaining({ + fromDate: '2019-01-01', + toDate: '2020-01-01', + dslQuery, + dataView: indexPatterns.b, + timeFieldName: indexPatterns.b.timeFieldName, + }) + ); - result.then(() => --queryCount); + expect(inst.find('[data-test-subj="lnsIndexPattern__ariaDescription"]').first().text()).toBe( + '1 available field. 2 empty fields. 0 meta fields.' + ); + }); - return result; + it('shows a loading indicator when loading', async () => { + const props = testProps({ + currentIndexPatternId: 'b', }); - const inst = mountWithIntl(); + let resolveFunction: (arg: unknown) => void; + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockReset(); + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(() => { + return new Promise((resolve) => { + resolveFunction = resolve; + }); + }); + const inst = await mountAndWaitForLazyModules(); - inst.update(); + expect(inst.find(EuiProgress).length).toEqual(1); + expect(inst.find('[data-test-subj="lnsIndexPattern__ariaDescription"]').first().text()).toBe( + '' + ); - act(() => { - (inst.setProps as unknown as (props: unknown) => {})({ - ...props, - dateRange: { fromDate: '2019-01-01', toDate: '2020-01-02' }, + await act(async () => { + resolveFunction!({ + existingFieldNames: [indexPatterns.b.fields[0].name], }); - inst.update(); + await inst.update(); }); await act(async () => { - (inst.setProps as unknown as (props: unknown) => {})({ - ...props, - dateRange: { fromDate: '2019-01-01', toDate: '2020-01-03' }, - }); - inst.update(); + await inst.update(); }); - expect(dataViews.getFieldsForIndexPattern).toHaveBeenCalledTimes(2); - expect(overlapCount).toEqual(0); + expect(inst.find(EuiProgress).length).toEqual(0); + expect(inst.find('[data-test-subj="lnsIndexPattern__ariaDescription"]').first().text()).toBe( + '1 available field. 2 empty fields. 0 meta fields.' + ); }); - it("should default to empty dsl if query can't be parsed", async () => { - const updateIndexPatterns = jest.fn(); - const props = { - ...testProps(updateIndexPatterns), - query: { - language: 'kuery', - query: '@timestamp : NOT *', - }, - }; - await testExistenceLoading(props, undefined, undefined); + it("should trigger showNoDataPopover if fields don't have data", async () => { + const props = testProps({ + currentIndexPatternId: 'a', + }); + + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + return { + existingFieldNames: [], + }; + }); + + const inst = await mountAndWaitForLazyModules(); - const firstCall = dataViews.getFieldsForIndexPattern.mock.calls[0]; - expect(firstCall[1]?.filter?.bool?.filter).toContainEqual({ - bool: { - must_not: { - match_all: {}, + expect(defaultProps.showNoDataPopover).toHaveBeenCalled(); + + expect(inst.find('[data-test-subj="lnsIndexPattern__ariaDescription"]').first().text()).toBe( + '0 available fields. 5 empty fields. 0 meta fields.' + ); + }); + + it("should default to empty dsl if query can't be parsed", async () => { + const props = testProps({ + currentIndexPatternId: 'a', + otherProps: { + query: { + language: 'kuery', + query: '@timestamp : NOT *', }, }, }); + + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + return { + existingFieldNames: [indexPatterns.a.fields[0].name, indexPatterns.a.fields[1].name], + }; + }); + + const inst = await mountAndWaitForLazyModules(); + + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( + expect.objectContaining({ + dslQuery: { + bool: { + must_not: { + match_all: {}, + }, + }, + }, + }) + ); + + expect(inst.find('[data-test-subj="lnsIndexPattern__ariaDescription"]').first().text()).toBe( + '2 available fields. 3 empty fields. 0 meta fields.' + ); }); }); @@ -680,15 +747,13 @@ describe('FormBased Data Panel', () => { beforeEach(() => { props = { ...defaultProps, - frame: getFrameAPIMock({ - existingFields: { - idx1: { - bytes: true, - memory: true, - }, - }, - }), }; + + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + return { + existingFieldNames: ['bytes', 'memory'], + }; + }); }); it('should list all selected fields if exist', async () => { @@ -696,7 +761,9 @@ describe('FormBased Data Panel', () => { ...props, layerFields: ['bytes'], }; - const wrapper = mountWithIntl(); + + const wrapper = await mountAndWaitForLazyModules(); + expect( wrapper .find('[data-test-subj="lnsIndexPatternSelectedFields"]') @@ -706,9 +773,10 @@ describe('FormBased Data Panel', () => { }); it('should not list the selected fields accordion if no fields given', async () => { - const wrapper = mountWithIntl(); + const wrapper = await mountAndWaitForLazyModules(); + expect( - wrapper + wrapper! .find('[data-test-subj="lnsIndexPatternSelectedFields"]') .find(FieldItem) .map((fieldItem) => fieldItem.prop('field').name) @@ -716,14 +784,14 @@ describe('FormBased Data Panel', () => { }); it('should list all supported fields in the pattern sorted alphabetically in groups', async () => { - const wrapper = mountWithIntl(); + const wrapper = await mountAndWaitForLazyModules(); + expect(wrapper.find(FieldItem).first().prop('field').displayName).toEqual('Records'); + const availableAccordion = wrapper.find('[data-test-subj="lnsIndexPatternAvailableFields"]'); expect( - wrapper - .find('[data-test-subj="lnsIndexPatternAvailableFields"]') - .find(FieldItem) - .map((fieldItem) => fieldItem.prop('field').name) + availableAccordion.find(FieldItem).map((fieldItem) => fieldItem.prop('field').name) ).toEqual(['memory', 'bytes']); + expect(availableAccordion.find(FieldItem).at(0).prop('exists')).toEqual(true); wrapper .find('[data-test-subj="lnsIndexPatternEmptyFields"]') .find('button') @@ -736,10 +804,11 @@ describe('FormBased Data Panel', () => { expect( emptyAccordion.find(FieldItem).map((fieldItem) => fieldItem.prop('field').displayName) ).toEqual(['client', 'source', 'timestampLabel']); + expect(emptyAccordion.find(FieldItem).at(1).prop('exists')).toEqual(false); }); it('should show meta fields accordion', async () => { - const wrapper = mountWithIntl( + const wrapper = await mountAndWaitForLazyModules( { })} /> ); + wrapper .find('[data-test-subj="lnsIndexPatternMetaFields"]') .find('button') @@ -777,13 +847,15 @@ describe('FormBased Data Panel', () => { }); it('should display NoFieldsCallout when all fields are empty', async () => { - const wrapper = mountWithIntl( - - ); - expect(wrapper.find(NoFieldsCallout).length).toEqual(2); + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + return { + existingFieldNames: [], + }; + }); + + const wrapper = await mountAndWaitForLazyModules(); + + expect(wrapper.find(EuiCallOut).length).toEqual(2); expect( wrapper .find('[data-test-subj="lnsIndexPatternAvailableFields"]') @@ -804,52 +876,55 @@ describe('FormBased Data Panel', () => { }); it('should display spinner for available fields accordion if existing fields are not loaded yet', async () => { - const wrapper = mountWithIntl( - - ); + let resolveFunction: (arg: unknown) => void; + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockReset(); + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(() => { + return new Promise((resolve) => { + resolveFunction = resolve; + }); + }); + const wrapper = await mountAndWaitForLazyModules(); + expect( wrapper.find('[data-test-subj="lnsIndexPatternAvailableFields"]').find(EuiLoadingSpinner) .length ).toEqual(1); - wrapper.setProps({ frame: getFrameAPIMock({ existingFields: { idx1: {} } }) }); - expect(wrapper.find(NoFieldsCallout).length).toEqual(2); - }); + expect(wrapper.find(EuiCallOut).length).toEqual(0); - it('should not allow field details when error', () => { - const wrapper = mountWithIntl( - - ); + await act(async () => { + resolveFunction!({ + existingFieldNames: [], + }); + }); - expect(wrapper.find(FieldList).prop('fieldGroups')).toEqual( - expect.objectContaining({ - AvailableFields: expect.objectContaining({ hideDetails: true }), - }) - ); + await act(async () => { + await wrapper.update(); + }); + + expect( + wrapper.find('[data-test-subj="lnsIndexPatternAvailableFields"]').find(EuiLoadingSpinner) + .length + ).toEqual(0); + expect(wrapper.find(EuiCallOut).length).toEqual(2); }); - it('should allow field details when timeout', () => { - const wrapper = mountWithIntl( - - ); + it('should not allow field details when error', async () => { + (ExistingFieldsServiceApi.loadFieldExisting as jest.Mock).mockImplementation(async () => { + throw new Error('test'); + }); - expect(wrapper.find(FieldList).prop('fieldGroups')).toEqual( + const wrapper = await mountAndWaitForLazyModules(); + + expect(wrapper.find(FieldListGrouped).prop('fieldGroups')).toEqual( expect.objectContaining({ - AvailableFields: expect.objectContaining({ hideDetails: false }), + AvailableFields: expect.objectContaining({ hideDetails: true }), }) ); }); - it('should filter down by name', () => { - const wrapper = mountWithIntl(); + it('should filter down by name', async () => { + const wrapper = await mountAndWaitForLazyModules(); + act(() => { wrapper.find('[data-test-subj="lnsIndexPatternFieldSearch"]').simulate('change', { target: { value: 'me' }, @@ -867,8 +942,9 @@ describe('FormBased Data Panel', () => { ]); }); - it('should announce filter in live region', () => { - const wrapper = mountWithIntl(); + it('should announce filter in live region', async () => { + const wrapper = await mountAndWaitForLazyModules(); + act(() => { wrapper.find('[data-test-subj="lnsIndexPatternFieldSearch"]').simulate('change', { target: { value: 'me' }, @@ -886,8 +962,8 @@ describe('FormBased Data Panel', () => { ); }); - it('should filter down by type', () => { - const wrapper = mountWithIntl(); + it('should filter down by type', async () => { + const wrapper = await mountAndWaitForLazyModules(); wrapper.find('[data-test-subj="lnsIndexPatternFiltersToggle"]').first().simulate('click'); @@ -898,8 +974,8 @@ describe('FormBased Data Panel', () => { ).toEqual(['amemory', 'bytes']); }); - it('should display no fields in groups when filtered by type Record', () => { - const wrapper = mountWithIntl(); + it('should display no fields in groups when filtered by type Record', async () => { + const wrapper = await mountAndWaitForLazyModules(); wrapper.find('[data-test-subj="lnsIndexPatternFiltersToggle"]').first().simulate('click'); @@ -908,11 +984,12 @@ describe('FormBased Data Panel', () => { expect(wrapper.find(FieldItem).map((fieldItem) => fieldItem.prop('field').name)).toEqual([ DOCUMENT_FIELD_NAME, ]); - expect(wrapper.find(NoFieldsCallout).length).toEqual(3); + expect(wrapper.find(EuiCallOut).length).toEqual(3); }); - it('should toggle type if clicked again', () => { - const wrapper = mountWithIntl(); + it('should toggle type if clicked again', async () => { + const wrapper = await mountAndWaitForLazyModules(); + wrapper.find('[data-test-subj="lnsIndexPatternFiltersToggle"]').first().simulate('click'); wrapper.find('[data-test-subj="typeFilter-number"]').first().simulate('click'); @@ -927,8 +1004,9 @@ describe('FormBased Data Panel', () => { ).toEqual(['Records', 'amemory', 'bytes', 'client', 'source', 'timestampLabel']); }); - it('should filter down by type and by name', () => { - const wrapper = mountWithIntl(); + it('should filter down by type and by name', async () => { + const wrapper = await mountAndWaitForLazyModules(); + act(() => { wrapper.find('[data-test-subj="lnsIndexPatternFieldSearch"]').simulate('change', { target: { value: 'me' }, diff --git a/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx b/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx index 8a7916a01a09a..7da9c57d0123b 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx @@ -6,32 +6,38 @@ */ import './datapanel.scss'; -import { uniq, groupBy } from 'lodash'; -import React, { useState, memo, useCallback, useMemo, useRef, useEffect } from 'react'; +import { uniq } from 'lodash'; +import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { + EuiCallOut, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiFilterButton, EuiFlexGroup, EuiFlexItem, - EuiContextMenuPanel, - EuiContextMenuItem, - EuiPopover, - EuiCallOut, EuiFormControlLayout, - EuiFilterButton, - EuiScreenReaderOnly, EuiIcon, + EuiPopover, + EuiProgress, + htmlIdGenerator, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { EsQueryConfig, Query, Filter } from '@kbn/es-query'; import { FormattedMessage } from '@kbn/i18n-react'; import type { CoreStart } from '@kbn/core/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { type DataView } from '@kbn/data-plugin/common'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; -import { htmlIdGenerator } from '@elastic/eui'; -import { buildEsQuery } from '@kbn/es-query'; -import { getEsQueryConfig } from '@kbn/data-plugin/public'; import { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; import { VISUALIZE_GEO_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { + FieldsGroupNames, + FieldListGrouped, + type FieldListGroupedProps, + useExistingFieldsFetcher, + useGroupedFields, + useExistingFieldsReader, +} from '@kbn/unified-field-list-plugin/public'; import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; import type { DatasourceDataPanelProps, @@ -42,12 +48,11 @@ import type { } from '../../types'; import { ChildDragDropProvider, DragContextState } from '../../drag_drop'; import type { FormBasedPrivateState } from './types'; -import { Loader } from '../../loader'; import { LensFieldIcon } from '../../shared_components/field_picker/lens_field_icon'; import { getFieldType } from './pure_utils'; -import { FieldGroups, FieldList } from './field_list'; -import { fieldContainsData, fieldExists } from '../../shared_components'; +import { fieldContainsData } from '../../shared_components'; import { IndexPatternServiceAPI } from '../../data_views_service/service'; +import { FieldItem } from './field_item'; export type Props = Omit< DatasourceDataPanelProps, @@ -65,10 +70,6 @@ export type Props = Omit< layerFields?: string[]; }; -function sortFields(fieldA: IndexPatternField, fieldB: IndexPatternField) { - return fieldA.displayName.localeCompare(fieldB.displayName, undefined, { sensitivity: 'base' }); -} - const supportedFieldTypes = new Set([ 'string', 'number', @@ -104,25 +105,8 @@ const fieldTypeNames: Record = { murmur3: i18n.translate('xpack.lens.datatypes.murmur3', { defaultMessage: 'murmur3' }), }; -// Wrapper around buildEsQuery, handling errors (e.g. because a query can't be parsed) by -// returning a query dsl object not matching anything -function buildSafeEsQuery( - indexPattern: IndexPattern, - query: Query, - filters: Filter[], - queryConfig: EsQueryConfig -) { - try { - return buildEsQuery(indexPattern, query, filters, queryConfig); - } catch (e) { - return { - bool: { - must_not: { - match_all: {}, - }, - }, - }; - } +function onSupportedFieldFilter(field: IndexPatternField): boolean { + return supportedFieldTypes.has(field.type); } export function FormBasedDataPanel({ @@ -147,51 +131,22 @@ export function FormBasedDataPanel({ usedIndexPatterns, layerFields, }: Props) { - const { indexPatterns, indexPatternRefs, existingFields, isFirstExistenceFetch } = - frame.dataViews; + const { indexPatterns, indexPatternRefs } = frame.dataViews; const { currentIndexPatternId } = state; - const indexPatternList = uniq( - ( - usedIndexPatterns ?? Object.values(state.layers).map(({ indexPatternId }) => indexPatternId) - ).concat(currentIndexPatternId) - ) - .filter((id) => !!indexPatterns[id]) - .sort() - .map((id) => indexPatterns[id]); - - const dslQuery = buildSafeEsQuery( - indexPatterns[currentIndexPatternId], - query, - filters, - getEsQueryConfig(core.uiSettings) - ); + const activeIndexPatterns = useMemo(() => { + return uniq( + ( + usedIndexPatterns ?? Object.values(state.layers).map(({ indexPatternId }) => indexPatternId) + ).concat(currentIndexPatternId) + ) + .filter((id) => !!indexPatterns[id]) + .sort() + .map((id) => indexPatterns[id]); + }, [usedIndexPatterns, indexPatterns, state.layers, currentIndexPatternId]); return ( <> - - indexPatternService.refreshExistingFields({ - dateRange, - currentIndexPatternTitle: indexPatterns[currentIndexPatternId]?.title || '', - onNoData: showNoDataPopover, - dslQuery, - indexPatternList, - isFirstExistenceFetch, - existingFields, - }) - } - loadDeps={[ - query, - filters, - dateRange.fromDate, - dateRange.toDate, - indexPatternList.map((x) => `${x.title}:${x.timeFieldName}`).join(','), - // important here to rerun the fields existence on indexPattern change (i.e. add new fields in place) - frame.dataViews.indexPatterns, - ]} - /> - {Object.keys(indexPatterns).length === 0 && indexPatternRefs.length === 0 ? ( )} @@ -252,18 +209,6 @@ interface DataPanelState { isMetaAccordionOpen: boolean; } -const defaultFieldGroups: { - specialFields: IndexPatternField[]; - availableFields: IndexPatternField[]; - emptyFields: IndexPatternField[]; - metaFields: IndexPatternField[]; -} = { - specialFields: [], - availableFields: [], - emptyFields: [], - metaFields: [], -}; - const htmlId = htmlIdGenerator('datapanel'); const fieldSearchDescriptionId = htmlId(); @@ -286,9 +231,11 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({ frame, onIndexPatternRefresh, layerFields, + showNoDataPopover, + activeIndexPatterns, }: Omit< DatasourceDataPanelProps, - 'state' | 'setState' | 'showNoDataPopover' | 'core' | 'onChangeIndexPattern' | 'usedIndexPatterns' + 'state' | 'setState' | 'core' | 'onChangeIndexPattern' | 'usedIndexPatterns' > & { data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; @@ -301,6 +248,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({ indexPatternFieldEditor: IndexPatternFieldEditorStart; onIndexPatternRefresh: () => void; layerFields?: string[]; + activeIndexPatterns: IndexPattern[]; }) { const [localState, setLocalState] = useState({ nameFilter: '', @@ -310,10 +258,30 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({ isEmptyAccordionOpen: false, isMetaAccordionOpen: false, }); - const { existenceFetchFailed, existenceFetchTimeout, indexPatterns, existingFields } = - frame.dataViews; + const { indexPatterns } = frame.dataViews; const currentIndexPattern = indexPatterns[currentIndexPatternId]; - const existingFieldsForIndexPattern = existingFields[currentIndexPattern?.title]; + + const { refetchFieldsExistenceInfo, isProcessing } = useExistingFieldsFetcher({ + dataViews: activeIndexPatterns as unknown as DataView[], + query, + filters, + fromDate: dateRange.fromDate, + toDate: dateRange.toDate, + services: { + data, + dataViews, + core, + }, + onNoData: (dataViewId) => { + if (dataViewId === currentIndexPatternId) { + showNoDataPopover(); + } + }, + }); + const fieldsExistenceReader = useExistingFieldsReader(); + const fieldsExistenceStatus = + fieldsExistenceReader.getFieldsExistenceStatus(currentIndexPatternId); + const visualizeGeoFieldTrigger = uiActions.getTrigger(VISUALIZE_GEO_FIELD_TRIGGER); const allFields = useMemo(() => { if (!currentIndexPattern) return []; @@ -331,187 +299,74 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({ ...localState.typeFilter, ]); - const fieldInfoUnavailable = - existenceFetchFailed || existenceFetchTimeout || currentIndexPattern?.hasRestrictions; - const editPermission = indexPatternFieldEditor.userPermissions.editIndexPattern() || !currentIndexPattern.isPersisted; - const unfilteredFieldGroups: FieldGroups = useMemo(() => { - const containsData = (field: IndexPatternField) => { - const overallField = currentIndexPattern?.getFieldByName(field.name); - return ( - overallField && - existingFieldsForIndexPattern && - fieldExists(existingFieldsForIndexPattern, overallField.name) - ); - }; - - const allSupportedTypesFields = allFields.filter((field) => - supportedFieldTypes.has(field.type) - ); - const usedByLayersFields = allFields.filter((field) => layerFields?.includes(field.name)); - const sorted = allSupportedTypesFields.sort(sortFields); - const groupedFields = { - ...defaultFieldGroups, - ...groupBy(sorted, (field) => { - if (field.type === 'document') { - return 'specialFields'; - } else if (field.meta) { - return 'metaFields'; - } else if (containsData(field)) { - return 'availableFields'; - } else return 'emptyFields'; - }), - }; - - const isUsingSampling = core.uiSettings.get('lens:useFieldExistenceSampling'); - - const fieldGroupDefinitions: FieldGroups = { - SpecialFields: { - fields: groupedFields.specialFields, - fieldCount: 1, - isAffectedByGlobalFilter: false, - isAffectedByTimeFilter: false, - isInitiallyOpen: false, - showInAccordion: false, - title: '', - hideDetails: true, - }, - SelectedFields: { - fields: usedByLayersFields, - fieldCount: usedByLayersFields.length, - isInitiallyOpen: true, - showInAccordion: true, - title: i18n.translate('xpack.lens.indexPattern.selectedFieldsLabel', { - defaultMessage: 'Selected fields', - }), - isAffectedByGlobalFilter: !!filters.length, - isAffectedByTimeFilter: true, - hideDetails: false, - hideIfEmpty: true, - }, - AvailableFields: { - fields: groupedFields.availableFields, - fieldCount: groupedFields.availableFields.length, - isInitiallyOpen: true, - showInAccordion: true, - title: fieldInfoUnavailable - ? i18n.translate('xpack.lens.indexPattern.allFieldsLabel', { - defaultMessage: 'All fields', - }) - : i18n.translate('xpack.lens.indexPattern.availableFieldsLabel', { - defaultMessage: 'Available fields', - }), - helpText: isUsingSampling - ? i18n.translate('xpack.lens.indexPattern.allFieldsSamplingLabelHelp', { - defaultMessage: - 'Available fields contain the data in the first 500 documents that match your filters. To view all fields, expand Empty fields. You are unable to create visualizations with full text, geographic, flattened, and object fields.', - }) - : i18n.translate('xpack.lens.indexPattern.allFieldsLabelHelp', { - defaultMessage: - 'Drag and drop available fields to the workspace and create visualizations. To change the available fields, select a different data view, edit your queries, or use a different time range. Some field types cannot be visualized in Lens, including full text and geographic fields.', - }), - isAffectedByGlobalFilter: !!filters.length, - isAffectedByTimeFilter: true, - // Show details on timeout but not failure - hideDetails: fieldInfoUnavailable && !existenceFetchTimeout, - defaultNoFieldsMessage: i18n.translate('xpack.lens.indexPatterns.noAvailableDataLabel', { - defaultMessage: `There are no available fields that contain data.`, - }), - }, - EmptyFields: { - fields: groupedFields.emptyFields, - fieldCount: groupedFields.emptyFields.length, - isAffectedByGlobalFilter: false, - isAffectedByTimeFilter: false, - isInitiallyOpen: false, - showInAccordion: true, - hideDetails: false, - title: i18n.translate('xpack.lens.indexPattern.emptyFieldsLabel', { - defaultMessage: 'Empty fields', - }), - defaultNoFieldsMessage: i18n.translate('xpack.lens.indexPatterns.noEmptyDataLabel', { - defaultMessage: `There are no empty fields.`, - }), - helpText: i18n.translate('xpack.lens.indexPattern.emptyFieldsLabelHelp', { - defaultMessage: - 'Empty fields did not contain any values in the first 500 documents based on your filters.', - }), - }, - MetaFields: { - fields: groupedFields.metaFields, - fieldCount: groupedFields.metaFields.length, - isAffectedByGlobalFilter: false, - isAffectedByTimeFilter: false, - isInitiallyOpen: false, - showInAccordion: true, - hideDetails: false, - title: i18n.translate('xpack.lens.indexPattern.metaFieldsLabel', { - defaultMessage: 'Meta fields', - }), - defaultNoFieldsMessage: i18n.translate('xpack.lens.indexPatterns.noMetaDataLabel', { - defaultMessage: `There are no meta fields.`, - }), - }, - }; - - // do not show empty field accordion if there is no existence information - if (fieldInfoUnavailable) { - delete fieldGroupDefinitions.EmptyFields; - } - - return fieldGroupDefinitions; - }, [ - allFields, - core.uiSettings, - fieldInfoUnavailable, - filters.length, - existenceFetchTimeout, - currentIndexPattern, - existingFieldsForIndexPattern, - layerFields, - ]); - - const fieldGroups: FieldGroups = useMemo(() => { - const filterFieldGroup = (fieldGroup: IndexPatternField[]) => - fieldGroup.filter((field) => { - if ( - localState.nameFilter.length && - !field.name.toLowerCase().includes(localState.nameFilter.toLowerCase()) && - !field.displayName.toLowerCase().includes(localState.nameFilter.toLowerCase()) - ) { - return false; - } - if (localState.typeFilter.length > 0) { - return localState.typeFilter.includes(getFieldType(field) as DataType); - } - return true; - }); - return Object.fromEntries( - Object.entries(unfilteredFieldGroups).map(([name, group]) => [ - name, - { ...group, fields: filterFieldGroup(group.fields) }, - ]) - ); - }, [unfilteredFieldGroups, localState.nameFilter, localState.typeFilter]); - - const checkFieldExists = useCallback( - (field: IndexPatternField) => - fieldContainsData(field.name, currentIndexPattern, existingFieldsForIndexPattern), - [currentIndexPattern, existingFieldsForIndexPattern] + const onSelectedFieldFilter = useCallback( + (field: IndexPatternField): boolean => { + return Boolean(layerFields?.includes(field.name)); + }, + [layerFields] ); - const { nameFilter, typeFilter } = localState; + const onFilterField = useCallback( + (field: IndexPatternField) => { + if ( + localState.nameFilter.length && + !field.name.toLowerCase().includes(localState.nameFilter.toLowerCase()) && + !field.displayName.toLowerCase().includes(localState.nameFilter.toLowerCase()) + ) { + return false; + } + if (localState.typeFilter.length > 0) { + return localState.typeFilter.includes(getFieldType(field) as DataType); + } + return true; + }, + [localState] + ); - const filter = useMemo( - () => ({ - nameFilter, - typeFilter, - }), - [nameFilter, typeFilter] + const hasFilters = Boolean(filters.length); + const onOverrideFieldGroupDetails = useCallback( + (groupName) => { + if (groupName === FieldsGroupNames.AvailableFields) { + const isUsingSampling = core.uiSettings.get('lens:useFieldExistenceSampling'); + + return { + helpText: isUsingSampling + ? i18n.translate('xpack.lens.indexPattern.allFieldsSamplingLabelHelp', { + defaultMessage: + 'Available fields contain the data in the first 500 documents that match your filters. To view all fields, expand Empty fields. You are unable to create visualizations with full text, geographic, flattened, and object fields.', + }) + : i18n.translate('xpack.lens.indexPattern.allFieldsLabelHelp', { + defaultMessage: + 'Drag and drop available fields to the workspace and create visualizations. To change the available fields, select a different data view, edit your queries, or use a different time range. Some field types cannot be visualized in Lens, including full text and geographic fields.', + }), + isAffectedByGlobalFilter: hasFilters, + }; + } + if (groupName === FieldsGroupNames.SelectedFields) { + return { + isAffectedByGlobalFilter: hasFilters, + }; + } + }, + [core.uiSettings, hasFilters] ); + const { fieldGroups } = useGroupedFields({ + dataViewId: currentIndexPatternId, + allFields, + services: { + dataViews, + }, + fieldsExistenceReader, + onFilterField, + onSupportedFieldFilter, + onSelectedFieldFilter, + onOverrideFieldGroupDetails, + }); + const closeFieldEditor = useRef<() => void | undefined>(); useEffect(() => { @@ -560,6 +415,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({ onSave: () => { if (indexPatternInstance.isPersisted()) { refreshFieldList(); + refetchFieldsExistenceInfo(indexPatternInstance.id); } else { indexPatternService.replaceDataViewId(indexPatternInstance); } @@ -574,6 +430,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({ indexPatternFieldEditor, refreshFieldList, indexPatternService, + refetchFieldsExistenceInfo, ] ); @@ -590,6 +447,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({ onDelete: () => { if (indexPatternInstance.isPersisted()) { refreshFieldList(); + refetchFieldsExistenceInfo(indexPatternInstance.id); } else { indexPatternService.replaceDataViewId(indexPatternInstance); } @@ -604,24 +462,39 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({ indexPatternFieldEditor, indexPatternService, refreshFieldList, + refetchFieldsExistenceInfo, ] ); - const fieldProps = useMemo( - () => ({ - core, - data, - fieldFormats, - indexPattern: currentIndexPattern, - highlight: localState.nameFilter.toLowerCase(), - dateRange, - query, - filters, - chartsThemeService: charts.theme, - }), + const renderFieldItem: FieldListGroupedProps['renderFieldItem'] = useCallback( + ({ field, itemIndex, groupIndex, hideDetails }) => ( + + ), [ core, - data, fieldFormats, currentIndexPattern, dateRange, @@ -629,6 +502,12 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({ filters, localState.nameFilter, charts.theme, + fieldsExistenceReader.hasFieldData, + dropOntoWorkspace, + hasSuggestionForField, + editField, + removeField, + uiActions, ] ); @@ -640,6 +519,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({ direction="column" responsive={false} > + {isProcessing && } - -
    - {i18n.translate('xpack.lens.indexPatterns.fieldSearchLiveRegion', { - defaultMessage: - '{availableFields} available {availableFields, plural, one {field} other {fields}}. {emptyFields} empty {emptyFields, plural, one {field} other {fields}}. {metaFields} meta {metaFields, plural, one {field} other {fields}}.', - values: { - availableFields: fieldGroups.AvailableFields.fields.length, - // empty fields can be undefined if there is no existence information to be fetched - emptyFields: fieldGroups.EmptyFields?.fields.length || 0, - metaFields: fieldGroups.MetaFields.fields.length, - }, - })} -
    -
    - fieldGroups={fieldGroups} - hasSyncedExistingFields={!!existingFieldsForIndexPattern} - filter={filter} - currentIndexPatternId={currentIndexPatternId} - existenceFetchFailed={existenceFetchFailed} - existenceFetchTimeout={existenceFetchTimeout} - existFieldsInIndex={!!allFields.length} - dropOntoWorkspace={dropOntoWorkspace} - hasSuggestionForField={hasSuggestionForField} - editField={editField} - removeField={removeField} - uiActions={uiActions} + fieldsExistenceStatus={fieldsExistenceStatus} + fieldsExistInIndex={!!allFields.length} + renderFieldItem={renderFieldItem} + screenReaderDescriptionForSearchInputId={fieldSearchDescriptionId} + data-test-subj="lnsIndexPattern" />
    diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx index 330d3285b2951..97dabaca05c03 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx @@ -606,7 +606,6 @@ export function DimensionEditor(props: DimensionEditorProps) { setIsCloseable, paramEditorCustomProps, ReferenceEditor, - existingFields: props.existingFields, ...services, }; @@ -789,7 +788,6 @@ export function DimensionEditor(props: DimensionEditorProps) { }} validation={validation} currentIndexPattern={currentIndexPattern} - existingFields={props.existingFields} selectionStyle={selectedOperationDefinition.selectionStyle} dateRange={dateRange} labelAppend={selectedOperationDefinition?.getHelpMessage?.({ @@ -815,7 +813,6 @@ export function DimensionEditor(props: DimensionEditorProps) { selectedColumn={selectedColumn as FieldBasedIndexPatternColumn} columnId={columnId} indexPattern={currentIndexPattern} - existingFields={props.existingFields} operationSupportMatrix={operationSupportMatrix} updateLayer={(newLayer) => { if (temporaryQuickFunction) { @@ -845,7 +842,6 @@ export function DimensionEditor(props: DimensionEditorProps) { const customParamEditor = ParamEditor ? ( <> { }; }); +jest.mock('@kbn/unified-field-list-plugin/public/hooks/use_existing_fields', () => ({ + useExistingFieldsReader: jest.fn(() => { + return { + hasFieldData: (dataViewId: string, fieldName: string) => { + return ['timestamp', 'bytes', 'memory', 'source'].includes(fieldName); + }, + }; + }), +})); + const fields = [ { name: 'timestamp', @@ -197,14 +208,6 @@ describe('FormBasedDimensionEditor', () => { defaultProps = { indexPatterns: expectedIndexPatterns, - existingFields: { - 'my-fake-index-pattern': { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, state, setState, dateRange: { fromDate: 'now-1d', toDate: 'now' }, @@ -339,16 +342,15 @@ describe('FormBasedDimensionEditor', () => { }); it('should hide fields that have no data', () => { - const props = { - ...defaultProps, - existingFields: { - 'my-fake-index-pattern': { - timestamp: true, - source: true, + (useExistingFieldsReader as jest.Mock).mockImplementationOnce(() => { + return { + hasFieldData: (dataViewId: string, fieldName: string) => { + return ['timestamp', 'source'].includes(fieldName); }, - }, - }; - wrapper = mount(); + }; + }); + + wrapper = mount(); const options = wrapper .find(EuiComboBox) diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/field_input.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/field_input.test.tsx index 877dc18156cdf..a135b08082c9e 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/field_input.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/field_input.test.tsx @@ -112,11 +112,7 @@ function getLayer(col1: GenericIndexPatternColumn = getStringBasedOperationColum }, }; } -function getDefaultOperationSupportMatrix( - layer: FormBasedLayer, - columnId: string, - existingFields: Record> -) { +function getDefaultOperationSupportMatrix(layer: FormBasedLayer, columnId: string) { return getOperationSupportMatrix({ state: { layers: { layer1: layer }, @@ -130,29 +126,36 @@ function getDefaultOperationSupportMatrix( }); } -function getExistingFields() { - const fields: Record = {}; - for (const field of defaultProps.indexPattern.fields) { - fields[field.name] = true; - } - return { - [defaultProps.indexPattern.title]: fields, - }; -} +const mockedReader = { + hasFieldData: (dataViewId: string, fieldName: string) => { + if (defaultProps.indexPattern.id !== dataViewId) { + return false; + } + + const map: Record = {}; + for (const field of defaultProps.indexPattern.fields) { + map[field.name] = true; + } + + return map[fieldName]; + }, +}; + +jest.mock('@kbn/unified-field-list-plugin/public/hooks/use_existing_fields', () => ({ + useExistingFieldsReader: jest.fn(() => mockedReader), +})); describe('FieldInput', () => { it('should render a field select box', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1'); const instance = mount( ); @@ -163,15 +166,13 @@ describe('FieldInput', () => { it('should render an error message when incomplete operation is on', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1'); const instance = mount( { (_, col: ReferenceBasedIndexPatternColumn) => { const updateLayerSpy = jest.fn(); const layer = getLayer(col); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix( - layer, - 'col1', - existingFields - ); + const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1'); const instance = mount( @@ -234,19 +229,13 @@ describe('FieldInput', () => { (_, col: ReferenceBasedIndexPatternColumn) => { const updateLayerSpy = jest.fn(); const layer = getLayer(col); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix( - layer, - 'col1', - existingFields - ); + const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1'); const instance = mount( { it('should render an error message for invalid fields', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1'); const instance = mount( @@ -295,15 +282,13 @@ describe('FieldInput', () => { it('should render a help message when passed and no errors are found', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1'); const instance = mount( @@ -320,15 +305,13 @@ describe('FieldInput', () => { it('should prioritize errors over help messages', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1'); const instance = mount( { it('should update the layer on field selection', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1'); const instance = mount( @@ -372,15 +353,13 @@ describe('FieldInput', () => { it('should not trigger when the same selected field is selected again', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1'); const instance = mount( @@ -398,15 +377,13 @@ describe('FieldInput', () => { it('should prioritize incomplete fields over selected column field to display', () => { const updateLayerSpy = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1'); const instance = mount( { const updateLayerSpy = jest.fn(); const onDeleteColumn = jest.fn(); const layer = getLayer(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix(layer, 'col1'); const instance = mount( diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/field_input.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/field_input.tsx index ec471b70de614..462cd0b546f22 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/field_input.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/field_input.tsx @@ -22,7 +22,6 @@ export function FieldInput({ selectedColumn, columnId, indexPattern, - existingFields, operationSupportMatrix, updateLayer, onDeleteColumn, @@ -62,7 +61,6 @@ export function FieldInput({ void; onDeleteColumn?: () => void; - existingFields: ExistingFieldsMap[string]; fieldIsInvalid: boolean; markAllFieldsCompatible?: boolean; 'data-test-subj'?: string; @@ -47,12 +47,12 @@ export function FieldSelect({ operationByField, onChoose, onDeleteColumn, - existingFields, fieldIsInvalid, markAllFieldsCompatible, ['data-test-subj']: dataTestSub, ...rest }: FieldSelectProps) { + const { hasFieldData } = useExistingFieldsReader(); const memoizedFieldOptions = useMemo(() => { const fields = Object.keys(operationByField).sort(); @@ -67,8 +67,8 @@ export function FieldSelect({ (field) => currentIndexPattern.getFieldByName(field)?.type === 'document' ); - function containsData(field: string) { - return fieldContainsData(field, currentIndexPattern, existingFields); + function containsData(fieldName: string) { + return fieldContainsData(fieldName, currentIndexPattern, hasFieldData); } function fieldNamesToOptions(items: string[]) { @@ -145,7 +145,7 @@ export function FieldSelect({ selectedOperationType, currentIndexPattern, operationByField, - existingFields, + hasFieldData, markAllFieldsCompatible, ]); diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.test.tsx index d46dabf6c12f3..cb50049e3fbec 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.test.tsx @@ -28,6 +28,16 @@ import { import { FieldSelect } from './field_select'; import { FormBasedLayer } from '../types'; +jest.mock('@kbn/unified-field-list-plugin/public/hooks/use_existing_fields', () => ({ + useExistingFieldsReader: jest.fn(() => { + return { + hasFieldData: (dataViewId: string, fieldName: string) => { + return ['timestamp', 'bytes', 'memory', 'source'].includes(fieldName); + }, + }; + }), +})); + jest.mock('../operations'); describe('reference editor', () => { @@ -59,14 +69,6 @@ describe('reference editor', () => { paramEditorUpdater, selectionStyle: 'full' as const, currentIndexPattern: createMockedIndexPattern(), - existingFields: { - 'my-fake-index-pattern': { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, dateRange: { fromDate: 'now-1d', toDate: 'now' }, storage: {} as IStorageWrapper, uiSettings: {} as IUiSettingsClient, diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.tsx index cefee79349087..6b8ecbbfe5246 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/reference_editor.tsx @@ -29,12 +29,7 @@ import { import { FieldChoiceWithOperationType, FieldSelect } from './field_select'; import { hasField } from '../pure_utils'; import type { FormBasedLayer } from '../types'; -import type { - ExistingFieldsMap, - IndexPattern, - IndexPatternField, - ParamEditorCustomProps, -} from '../../../types'; +import type { IndexPattern, IndexPatternField, ParamEditorCustomProps } from '../../../types'; import type { FormBasedDimensionEditorProps } from './dimension_panel'; import { FormRow } from '../operations/definitions/shared_components'; @@ -83,7 +78,6 @@ export interface ReferenceEditorProps { fieldLabel?: string; operationDefinitionMap: Record; isInline?: boolean; - existingFields: ExistingFieldsMap; dateRange: DateRange; labelAppend?: EuiFormRowProps['labelAppend']; isFullscreen: boolean; @@ -114,7 +108,6 @@ export interface ReferenceEditorProps { export const ReferenceEditor = (props: ReferenceEditorProps) => { const { currentIndexPattern, - existingFields, validation, selectionStyle, labelAppend, @@ -307,7 +300,6 @@ export const ReferenceEditor = (props: ReferenceEditorProps) => { ; - -function getDisplayedFieldsLength( - fieldGroups: FieldGroups, - accordionState: Partial> -) { - return Object.entries(fieldGroups) - .filter(([key]) => accordionState[key]) - .reduce((allFieldCount, [, { fields }]) => allFieldCount + fields.length, 0); -} - -export const FieldList = React.memo(function FieldList({ - exists, - fieldGroups, - existenceFetchFailed, - existenceFetchTimeout, - fieldProps, - hasSyncedExistingFields, - filter, - currentIndexPatternId, - existFieldsInIndex, - dropOntoWorkspace, - hasSuggestionForField, - editField, - removeField, - uiActions, -}: { - exists: (field: IndexPatternField) => boolean; - fieldGroups: FieldGroups; - fieldProps: FieldItemSharedProps; - hasSyncedExistingFields: boolean; - existenceFetchFailed?: boolean; - existenceFetchTimeout?: boolean; - filter: { - nameFilter: string; - typeFilter: string[]; - }; - currentIndexPatternId: string; - existFieldsInIndex: boolean; - dropOntoWorkspace: DatasourceDataPanelProps['dropOntoWorkspace']; - hasSuggestionForField: DatasourceDataPanelProps['hasSuggestionForField']; - editField?: (name: string) => void; - removeField?: (name: string) => void; - uiActions: UiActionsStart; -}) { - const [fieldGroupsToShow, fieldFroupsToCollapse] = partition( - Object.entries(fieldGroups), - ([, { showInAccordion }]) => showInAccordion - ); - const [pageSize, setPageSize] = useState(PAGINATION_SIZE); - const [scrollContainer, setScrollContainer] = useState(undefined); - const [accordionState, setAccordionState] = useState>>(() => - Object.fromEntries( - fieldGroupsToShow.map(([key, { isInitiallyOpen }]) => [key, isInitiallyOpen]) - ) - ); - - useEffect(() => { - // Reset the scroll if we have made material changes to the field list - if (scrollContainer) { - scrollContainer.scrollTop = 0; - setPageSize(PAGINATION_SIZE); - } - }, [filter.nameFilter, filter.typeFilter, currentIndexPatternId, scrollContainer]); - - const lazyScroll = useCallback(() => { - if (scrollContainer) { - const nearBottom = - scrollContainer.scrollTop + scrollContainer.clientHeight > - scrollContainer.scrollHeight * 0.9; - if (nearBottom) { - setPageSize( - Math.max( - PAGINATION_SIZE, - Math.min( - pageSize + PAGINATION_SIZE * 0.5, - getDisplayedFieldsLength(fieldGroups, accordionState) - ) - ) - ); - } - } - }, [scrollContainer, pageSize, setPageSize, fieldGroups, accordionState]); - - const paginatedFields = useMemo(() => { - let remainingItems = pageSize; - return Object.fromEntries( - fieldGroupsToShow.map(([key, fieldGroup]) => { - if (!accordionState[key] || remainingItems <= 0) { - return [key, []]; - } - const slicedFieldList = fieldGroup.fields.slice(0, remainingItems); - remainingItems = remainingItems - slicedFieldList.length; - return [key, slicedFieldList]; - }) - ); - }, [pageSize, fieldGroupsToShow, accordionState]); - - return ( -
    { - if (el && !el.dataset.dynamicScroll) { - el.dataset.dynamicScroll = 'true'; - setScrollContainer(el); - } - }} - onScroll={throttle(lazyScroll, 100)} - > -
    -
      - {fieldFroupsToCollapse.flatMap(([, { fields }]) => - fields.map((field, index) => ( - - )) - )} -
    - - {fieldGroupsToShow.map(([key, fieldGroup], index) => { - if (Boolean(fieldGroup.hideIfEmpty) && !fieldGroup.fields.length) return null; - return ( - - { - setAccordionState((s) => ({ - ...s, - [key]: open, - })); - const displayedFieldLength = getDisplayedFieldsLength(fieldGroups, { - ...accordionState, - [key]: open, - }); - setPageSize( - Math.max(PAGINATION_SIZE, Math.min(pageSize * 1.5, displayedFieldLength)) - ); - }} - showExistenceFetchError={existenceFetchFailed} - showExistenceFetchTimeout={existenceFetchTimeout} - renderCallout={ - - } - uiActions={uiActions} - /> - - - ); - })} -
    -
    - ); -}); diff --git a/x-pack/plugins/lens/public/datasources/form_based/fields_accordion.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/fields_accordion.test.tsx deleted file mode 100644 index a471f8e0fa309..0000000000000 --- a/x-pack/plugins/lens/public/datasources/form_based/fields_accordion.test.tsx +++ /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 React from 'react'; -import { EuiLoadingSpinner, EuiNotificationBadge } from '@elastic/eui'; -import { coreMock } from '@kbn/core/public/mocks'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; -import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; -import { IndexPattern } from '../../types'; -import { FieldItem } from './field_item'; -import { FieldsAccordion, FieldsAccordionProps, FieldItemSharedProps } from './fields_accordion'; -import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; -import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; - -describe('Fields Accordion', () => { - let defaultProps: FieldsAccordionProps; - let indexPattern: IndexPattern; - let core: ReturnType; - let fieldProps: FieldItemSharedProps; - - beforeEach(() => { - indexPattern = { - id: '1', - title: 'my-fake-index-pattern', - timeFieldName: 'timestamp', - fields: [ - { - name: 'timestamp', - type: 'date', - aggregatable: true, - searchable: true, - }, - { - name: 'bytes', - type: 'number', - aggregatable: true, - searchable: true, - }, - ], - } as IndexPattern; - core = coreMock.createStart(); - core.http.post.mockClear(); - - fieldProps = { - indexPattern, - fieldFormats: fieldFormatsServiceMock.createStartContract(), - core, - highlight: '', - dateRange: { - fromDate: 'now-7d', - toDate: 'now', - }, - query: { query: '', language: 'lucene' }, - filters: [], - chartsThemeService: chartPluginMock.createSetupContract().theme, - }; - - defaultProps = { - initialIsOpen: true, - onToggle: jest.fn(), - id: 'id', - label: 'label', - hasLoaded: true, - fieldsCount: 2, - isFiltered: false, - paginatedFields: indexPattern.fields, - fieldProps, - renderCallout:
    Callout
    , - exists: () => true, - groupIndex: 0, - dropOntoWorkspace: () => {}, - hasSuggestionForField: () => false, - uiActions: uiActionsPluginMock.createStartContract(), - }; - }); - - it('renders correct number of Field Items', () => { - const wrapper = mountWithIntl( - field.name === 'timestamp'} /> - ); - expect(wrapper.find(FieldItem).at(0).prop('exists')).toEqual(true); - expect(wrapper.find(FieldItem).at(1).prop('exists')).toEqual(false); - }); - - it('passed correct exists flag to each field', () => { - const wrapper = mountWithIntl(); - expect(wrapper.find(FieldItem).length).toEqual(2); - }); - - it('renders callout if no fields', () => { - const wrapper = shallowWithIntl( - - ); - expect(wrapper.find('#lens-test-callout').length).toEqual(1); - }); - - it('renders accented notificationBadge state if isFiltered', () => { - const wrapper = mountWithIntl(); - expect(wrapper.find(EuiNotificationBadge).prop('color')).toEqual('accent'); - }); - - it('renders spinner if has not loaded', () => { - const wrapper = mountWithIntl(); - expect(wrapper.find(EuiLoadingSpinner).length).toEqual(1); - }); -}); diff --git a/x-pack/plugins/lens/public/datasources/form_based/layerpanel.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/layerpanel.test.tsx index defc505f1d9e1..86fd5490f383b 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/layerpanel.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/layerpanel.test.tsx @@ -181,8 +181,6 @@ describe('Layer Data Panel', () => { { id: '2', title: 'my-fake-restricted-pattern' }, { id: '3', title: 'my-compatible-pattern' }, ], - existingFields: {}, - isFirstExistenceFetch: false, indexPatterns: { '1': { id: '1', diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/date_histogram.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/date_histogram.test.tsx index c08f8703c723f..d9510256eb92d 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/date_histogram.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/date_histogram.test.tsx @@ -113,14 +113,6 @@ const defaultOptions = { isFullscreen: false, toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), - existingFields: { - my_index_pattern: { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, }; describe('date_histogram', () => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx index 2264fa8f185fb..e5199a5295ec6 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx @@ -38,14 +38,6 @@ const defaultProps = { toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), layerId: '1', - existingFields: { - my_index_pattern: { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, }; // mocking random id generator function diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/index.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/index.ts index 5253267b286cd..1ed621b19b8bd 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/index.ts @@ -198,7 +198,6 @@ export interface ParamEditorProps< activeData?: FormBasedDimensionEditorProps['activeData']; operationDefinitionMap: Record; paramEditorCustomProps?: ParamEditorCustomProps; - existingFields: Record>; isReferenced?: boolean; } @@ -215,10 +214,6 @@ export interface FieldInputProps { incompleteParams: Omit; dimensionGroups: FormBasedDimensionEditorProps['dimensionGroups']; groupId: FormBasedDimensionEditorProps['groupId']; - /** - * indexPatternId -> fieldName -> boolean - */ - existingFields: Record>; operationSupportMatrix: OperationSupportMatrix; helpMessage?: React.ReactNode; operationDefinitionMap: Record; diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx index 16c6f2727ea50..cf5babde1feb6 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx @@ -44,14 +44,6 @@ const defaultProps = { toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), layerId: '1', - existingFields: { - my_index_pattern: { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, }; describe('last_value', () => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/percentile.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/percentile.test.tsx index e78ac9e9360da..59d8602d7c275 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/percentile.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/percentile.test.tsx @@ -60,14 +60,6 @@ const defaultProps = { toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), layerId: '1', - existingFields: { - my_index_pattern: { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, }; describe('percentile', () => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/percentile_ranks.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/percentile_ranks.test.tsx index adb0d8e491fd7..c29e5ca2c1499 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/percentile_ranks.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/percentile_ranks.test.tsx @@ -53,14 +53,6 @@ const defaultProps = { toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), layerId: '1', - existingFields: { - my_index_pattern: { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, }; describe('percentile ranks', () => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx index d238fd16b8932..e5a870985d2c0 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx @@ -86,14 +86,6 @@ const defaultOptions = { storage: {} as IStorageWrapper, uiSettings: uiSettingsMock, savedObjectsClient: {} as SavedObjectsClientContract, - existingFields: { - my_index_pattern: { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, dateRange: { fromDate: 'now-1y', toDate: 'now', diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx index 0ed6a60677f73..6d79d19f44a53 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx @@ -52,14 +52,6 @@ const defaultProps = { toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), layerId: '1', - existingFields: { - my_index_pattern: { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, }; describe('static_value', () => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/field_inputs.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/field_inputs.tsx index 17b0e5e475ffd..b7f24e7d3d9c1 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/field_inputs.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/field_inputs.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useMemo } from 'react'; import { htmlIdGenerator } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ExistingFieldsMap, IndexPattern } from '../../../../../types'; +import { IndexPattern } from '../../../../../types'; import { DragDropBuckets, FieldsBucketContainer, @@ -27,7 +27,6 @@ export const MAX_MULTI_FIELDS_SIZE = 3; export interface FieldInputsProps { column: TermsIndexPatternColumn; indexPattern: IndexPattern; - existingFields: ExistingFieldsMap; invalidFields?: string[]; operationSupportMatrix: Pick; onChange: (newValues: string[]) => void; @@ -49,7 +48,6 @@ export function FieldInputs({ column, onChange, indexPattern, - existingFields, operationSupportMatrix, invalidFields, }: FieldInputsProps) { @@ -153,7 +151,6 @@ export function FieldInputs({ { throw new Error('Should not be called'); }} diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx index 58f2f479f401a..d7a8770111080 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx @@ -50,6 +50,16 @@ jest.mock('@kbn/unified-field-list-plugin/public/services/field_stats', () => ({ }), })); +jest.mock('@kbn/unified-field-list-plugin/public/hooks/use_existing_fields', () => ({ + useExistingFieldsReader: jest.fn(() => { + return { + hasFieldData: (dataViewId: string, fieldName: string) => { + return ['timestamp', 'bytes', 'memory', 'source'].includes(fieldName); + }, + }; + }), +})); + // mocking random id generator function jest.mock('@elastic/eui', () => { const original = jest.requireActual('@elastic/eui'); @@ -93,14 +103,6 @@ const defaultProps = { setIsCloseable: jest.fn(), layerId: '1', ReferenceEditor, - existingFields: { - 'my-fake-index-pattern': { - timestamp: true, - bytes: true, - memory: true, - source: true, - }, - }, }; describe('terms', () => { @@ -1170,20 +1172,7 @@ describe('terms', () => { >, }; - function getExistingFields() { - const fields: Record = {}; - for (const field of defaultProps.indexPattern.fields) { - fields[field.name] = true; - } - return { - [defaultProps.indexPattern.title]: fields, - }; - } - - function getDefaultOperationSupportMatrix( - columnId: string, - existingFields: Record> - ) { + function getDefaultOperationSupportMatrix(columnId: string) { return getOperationSupportMatrix({ state: { layers: { layer1: layer }, @@ -1199,15 +1188,13 @@ describe('terms', () => { it('should render the default field input for no field (incomplete operation)', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); const instance = mount( @@ -1226,8 +1213,7 @@ describe('terms', () => { it('should show an error message when first field is invalid', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); layer.columns.col1 = { label: 'Top value of unsupported', @@ -1247,7 +1233,6 @@ describe('terms', () => { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} currentFieldIsInvalid /> @@ -1259,8 +1244,7 @@ describe('terms', () => { it('should show an error message when first field is not supported', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); layer.columns.col1 = { label: 'Top value of timestamp', @@ -1280,7 +1264,6 @@ describe('terms', () => { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} incompleteOperation="terms" @@ -1293,8 +1276,7 @@ describe('terms', () => { it('should show an error message when any field but the first is invalid', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); layer.columns.col1 = { label: 'Top value of geo.src + 1 other', @@ -1315,7 +1297,6 @@ describe('terms', () => { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1327,8 +1308,7 @@ describe('terms', () => { it('should show an error message when any field but the first is not supported', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); layer.columns.col1 = { label: 'Top value of geo.src + 1 other', @@ -1349,7 +1329,6 @@ describe('terms', () => { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1361,15 +1340,13 @@ describe('terms', () => { it('should render the an add button for single layer and disabled the remove button', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); const instance = mount( @@ -1392,15 +1369,13 @@ describe('terms', () => { it('should switch to the first supported operation when in single term mode and the picked field is not supported', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); const instance = mount( @@ -1426,8 +1401,7 @@ describe('terms', () => { it('should render the multi terms specific UI', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); (layer.columns.col1 as TermsIndexPatternColumn).params.secondaryFields = ['bytes']; const instance = mount( @@ -1436,7 +1410,6 @@ describe('terms', () => { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1457,8 +1430,7 @@ describe('terms', () => { it('should return to single value UI when removing second item of two', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); (layer.columns.col1 as TermsIndexPatternColumn).params.secondaryFields = ['memory']; const instance = mount( @@ -1467,7 +1439,6 @@ describe('terms', () => { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1489,8 +1460,7 @@ describe('terms', () => { it('should disable remove button and reorder drag when single value and one temporary new field', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); let instance = mount( { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1532,8 +1501,7 @@ describe('terms', () => { it('should accept scripted fields for single value', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); (layer.columns.col1 as TermsIndexPatternColumn).sourceField = 'scripted'; const instance = mount( @@ -1542,7 +1510,6 @@ describe('terms', () => { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1558,8 +1525,7 @@ describe('terms', () => { it('should mark scripted fields for multiple values', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); (layer.columns.col1 as TermsIndexPatternColumn).sourceField = 'scripted'; (layer.columns.col1 as TermsIndexPatternColumn).params.secondaryFields = ['memory']; @@ -1569,7 +1535,6 @@ describe('terms', () => { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1588,8 +1553,7 @@ describe('terms', () => { it('should not filter scripted fields when in single value', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); const instance = mount( { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1618,8 +1581,7 @@ describe('terms', () => { it('should filter scripted fields when in multi terms mode', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); (layer.columns.col1 as TermsIndexPatternColumn).params.secondaryFields = ['memory']; const instance = mount( @@ -1628,7 +1590,6 @@ describe('terms', () => { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1650,8 +1611,7 @@ describe('terms', () => { it('should filter already used fields when displaying fields list', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); (layer.columns.col1 as TermsIndexPatternColumn).params.secondaryFields = ['memory', 'bytes']; let instance = mount( @@ -1660,7 +1620,6 @@ describe('terms', () => { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1690,8 +1649,7 @@ describe('terms', () => { it('should filter fields with unsupported types when in multi terms mode', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); (layer.columns.col1 as TermsIndexPatternColumn).params.secondaryFields = ['memory']; const instance = mount( @@ -1700,7 +1658,6 @@ describe('terms', () => { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1722,8 +1679,7 @@ describe('terms', () => { it('should limit the number of multiple fields', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); (layer.columns.col1 as TermsIndexPatternColumn).params.secondaryFields = [ 'memory', @@ -1736,7 +1692,6 @@ describe('terms', () => { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1757,8 +1712,7 @@ describe('terms', () => { it('should let the user add new empty field up to the limit', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); let instance = mount( { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1793,8 +1746,7 @@ describe('terms', () => { it('should update the parentFormatter on transition between single to multi terms', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); let instance = mount( { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1834,8 +1785,7 @@ describe('terms', () => { it('should preserve custom label when set by the user', () => { const updateLayerSpy = jest.fn(); - const existingFields = getExistingFields(); - const operationSupportMatrix = getDefaultOperationSupportMatrix('col1', existingFields); + const operationSupportMatrix = getDefaultOperationSupportMatrix('col1'); layer.columns.col1 = { label: 'MyCustomLabel', @@ -1857,7 +1807,6 @@ describe('terms', () => { layer={layer} updateLayer={updateLayerSpy} columnId="col1" - existingFields={existingFields} operationSupportMatrix={operationSupportMatrix} selectedColumn={layer.columns.col1 as TermsIndexPatternColumn} /> diff --git a/x-pack/plugins/lens/public/datasources/text_based/datapanel.test.tsx b/x-pack/plugins/lens/public/datasources/text_based/datapanel.test.tsx index b5f158ecd453f..cac4fc380cc99 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/datapanel.test.tsx @@ -8,6 +8,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { act } from 'react-dom/test-utils'; +import { ReactWrapper } from 'enzyme'; import type { Query } from '@kbn/es-query'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; @@ -30,7 +31,6 @@ import { createIndexPatternServiceMock } from '../../mocks/data_views_service_mo import { createMockFramePublicAPI } from '../../mocks'; import { createMockedDragDropContext } from './mocks'; import { DataViewsState } from '../../state_management'; -import { ExistingFieldsMap, IndexPattern } from '../../types'; const fieldsFromQuery = [ { @@ -101,18 +101,6 @@ const fieldsOne = [ }, ]; -function getExistingFields(indexPatterns: Record) { - const existingFields: ExistingFieldsMap = {}; - for (const { title, fields } of Object.values(indexPatterns)) { - const fieldsMap: Record = {}; - for (const { displayName, name } of fields) { - fieldsMap[displayName ?? name] = true; - } - existingFields[title] = fieldsMap; - } - return existingFields; -} - const initialState: TextBasedPrivateState = { layers: { first: { @@ -130,27 +118,16 @@ const initialState: TextBasedPrivateState = { fieldList: fieldsFromQuery, }; -function getFrameAPIMock({ indexPatterns, existingFields, ...rest }: Partial = {}) { +function getFrameAPIMock({ + indexPatterns, + ...rest +}: Partial & { indexPatterns: DataViewsState['indexPatterns'] }) { const frameAPI = createMockFramePublicAPI(); - const defaultIndexPatterns = { - '1': { - id: '1', - title: 'idx1', - timeFieldName: 'timestamp', - hasRestrictions: false, - fields: fieldsOne, - getFieldByName: jest.fn(), - isPersisted: true, - spec: {}, - }, - }; return { ...frameAPI, dataViews: { ...frameAPI.dataViews, - indexPatterns: indexPatterns ?? defaultIndexPatterns, - existingFields: existingFields ?? getExistingFields(indexPatterns ?? defaultIndexPatterns), - isFirstExistenceFetch: false, + indexPatterns, ...rest, }, }; @@ -159,12 +136,39 @@ function getFrameAPIMock({ indexPatterns, existingFields, ...rest }: Partial element); +async function mountAndWaitForLazyModules(component: React.ReactElement): Promise { + let inst: ReactWrapper; + await act(async () => { + inst = await mountWithIntl(component); + // wait for lazy modules + await new Promise((resolve) => setTimeout(resolve, 0)); + inst.update(); + }); + + await inst!.update(); + + return inst!; +} + describe('TextBased Query Languages Data Panel', () => { let core: ReturnType; let dataViews: DataViewPublicStart; + const defaultIndexPatterns = { + '1': { + id: '1', + title: 'idx1', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields: fieldsOne, + getFieldByName: jest.fn(), + isPersisted: true, + spec: {}, + }, + }; let defaultProps: TextBasedDataPanelProps; const dataViewsMock = dataViewPluginMocks.createStartContract(); + beforeEach(() => { core = coreMock.createStart(); dataViews = dataViewPluginMocks.createStartContract(); @@ -194,7 +198,7 @@ describe('TextBased Query Languages Data Panel', () => { hasSuggestionForField: jest.fn(() => false), uiActions: uiActionsPluginMock.createStartContract(), indexPatternService: createIndexPatternServiceMock({ core, dataViews }), - frame: getFrameAPIMock(), + frame: getFrameAPIMock({ indexPatterns: defaultIndexPatterns }), state: initialState, setState: jest.fn(), onChangeIndexPattern: jest.fn(), @@ -202,23 +206,33 @@ describe('TextBased Query Languages Data Panel', () => { }); it('should render a search box', async () => { - const wrapper = mountWithIntl(); - expect(wrapper.find('[data-test-subj="lnsTextBasedLangugesFieldSearch"]').length).toEqual(1); + const wrapper = await mountAndWaitForLazyModules(); + + expect(wrapper.find('[data-test-subj="lnsTextBasedLanguagesFieldSearch"]').length).toEqual(1); }); it('should list all supported fields in the pattern', async () => { - const wrapper = mountWithIntl(); + const wrapper = await mountAndWaitForLazyModules(); + expect( wrapper - .find('[data-test-subj="lnsTextBasedLanguagesPanelFields"]') + .find('[data-test-subj="lnsTextBasedLanguagesAvailableFields"]') .find(FieldButton) .map((fieldItem) => fieldItem.prop('fieldName')) - ).toEqual(['timestamp', 'bytes', 'memory']); + ).toEqual(['bytes', 'memory', 'timestamp']); + + expect(wrapper.find('[data-test-subj="lnsTextBasedLanguagesEmptyFields"]').exists()).toBe( + false + ); + expect(wrapper.find('[data-test-subj="lnsTextBasedLanguagesMetaFields"]').exists()).toBe(false); }); it('should not display the selected fields accordion if there are no fields displayed', async () => { - const wrapper = mountWithIntl(); - expect(wrapper.find('[data-test-subj="lnsSelectedFieldsTextBased"]').length).toEqual(0); + const wrapper = await mountAndWaitForLazyModules(); + + expect(wrapper.find('[data-test-subj="lnsTextBasedLanguagesSelectedFields"]').length).toEqual( + 0 + ); }); it('should display the selected fields accordion if there are fields displayed', async () => { @@ -226,13 +240,17 @@ describe('TextBased Query Languages Data Panel', () => { ...defaultProps, layerFields: ['memory'], }; - const wrapper = mountWithIntl(); - expect(wrapper.find('[data-test-subj="lnsSelectedFieldsTextBased"]').length).not.toEqual(0); + const wrapper = await mountAndWaitForLazyModules(); + + expect( + wrapper.find('[data-test-subj="lnsTextBasedLanguagesSelectedFields"]').length + ).not.toEqual(0); }); it('should list all supported fields in the pattern that match the search input', async () => { - const wrapper = mountWithIntl(); - const searchBox = wrapper.find('[data-test-subj="lnsTextBasedLangugesFieldSearch"]'); + const wrapper = await mountAndWaitForLazyModules(); + + const searchBox = wrapper.find('[data-test-subj="lnsTextBasedLanguagesFieldSearch"]'); act(() => { searchBox.prop('onChange')!({ @@ -240,10 +258,10 @@ describe('TextBased Query Languages Data Panel', () => { } as React.ChangeEvent); }); - wrapper.update(); + await wrapper.update(); expect( wrapper - .find('[data-test-subj="lnsTextBasedLanguagesPanelFields"]') + .find('[data-test-subj="lnsTextBasedLanguagesAvailableFields"]') .find(FieldButton) .map((fieldItem) => fieldItem.prop('fieldName')) ).toEqual(['memory']); diff --git a/x-pack/plugins/lens/public/datasources/text_based/datapanel.tsx b/x-pack/plugins/lens/public/datasources/text_based/datapanel.tsx index 1b0699b2eb930..0416d163670fb 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/datapanel.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/datapanel.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiFormControlLayout, htmlIdGenerator } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import usePrevious from 'react-use/lib/usePrevious'; @@ -14,13 +14,22 @@ import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { isOfAggregateQueryType } from '@kbn/es-query'; -import { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import { DatatableColumn, ExpressionsStart } from '@kbn/expressions-plugin/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { + ExistenceFetchStatus, + FieldListGrouped, + FieldListGroupedProps, + FieldsGroupNames, + useGroupedFields, +} from '@kbn/unified-field-list-plugin/public'; +import { FieldButton } from '@kbn/react-field'; import type { DatasourceDataPanelProps } from '../../types'; import type { TextBasedPrivateState } from './types'; import { getStateFromAggregateQuery } from './utils'; -import { ChildDragDropProvider } from '../../drag_drop'; -import { FieldsAccordion } from './fields_accordion'; +import { ChildDragDropProvider, DragDrop } from '../../drag_drop'; +import { DataType } from '../../types'; +import { LensFieldIcon } from '../../shared_components'; export type TextBasedDataPanelProps = DatasourceDataPanelProps & { data: DataPublicPluginStart; @@ -67,8 +76,16 @@ export function TextBasedDataPanel({ }, [data, dataViews, expressions, prevQuery, query, setState, state]); const { fieldList } = state; - const filteredFields = useMemo(() => { - return fieldList.filter((field) => { + + const onSelectedFieldFilter = useCallback( + (field: DatatableColumn): boolean => { + return Boolean(layerFields?.includes(field.name)); + }, + [layerFields] + ); + + const onFilterField = useCallback( + (field: DatatableColumn) => { if ( localState.nameFilter && !field.name.toLowerCase().includes(localState.nameFilter.toLowerCase()) @@ -76,9 +93,57 @@ export function TextBasedDataPanel({ return false; } return true; - }); - }, [fieldList, localState.nameFilter]); - const usedByLayersFields = fieldList.filter((field) => layerFields?.includes(field.name)); + }, + [localState] + ); + + const onOverrideFieldGroupDetails = useCallback((groupName) => { + if (groupName === FieldsGroupNames.AvailableFields) { + return { + helpText: i18n.translate('xpack.lens.indexPattern.allFieldsForTextBasedLabelHelp', { + defaultMessage: + 'Drag and drop available fields to the workspace and create visualizations. To change the available fields, edit your query.', + }), + }; + } + }, []); + + const { fieldGroups } = useGroupedFields({ + dataViewId: null, + allFields: fieldList, + services: { + dataViews, + }, + onFilterField, + onSelectedFieldFilter, + onOverrideFieldGroupDetails, + }); + + const renderFieldItem: FieldListGroupedProps['renderFieldItem'] = useCallback( + ({ field, itemIndex, groupIndex, hideDetails }) => { + return ( + + {}} + fieldIcon={} + fieldName={field?.name} + /> + + ); + }, + [] + ); return ( -
    -
    - {usedByLayersFields.length > 0 && ( - - )} - -
    -
    + + fieldGroups={fieldGroups} + fieldsExistenceStatus={ + dataHasLoaded ? ExistenceFetchStatus.succeeded : ExistenceFetchStatus.unknown + } + fieldsExistInIndex={Boolean(fieldList.length)} + renderFieldItem={renderFieldItem} + screenReaderDescriptionForSearchInputId={fieldSearchDescriptionId} + data-test-subj="lnsTextBasedLanguages" + />
    diff --git a/x-pack/plugins/lens/public/datasources/text_based/fields_accordion.tsx b/x-pack/plugins/lens/public/datasources/text_based/fields_accordion.tsx deleted file mode 100644 index d02fd98bc9c87..0000000000000 --- a/x-pack/plugins/lens/public/datasources/text_based/fields_accordion.tsx +++ /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 React, { memo, useMemo } from 'react'; -import type { DatatableColumn } from '@kbn/expressions-plugin/public'; - -import { - EuiText, - EuiNotificationBadge, - EuiAccordion, - EuiLoadingSpinner, - EuiSpacer, -} from '@elastic/eui'; -import { FieldButton } from '@kbn/react-field'; -import { DragDrop } from '../../drag_drop'; -import { LensFieldIcon } from '../../shared_components'; -import type { DataType } from '../../types'; - -export interface FieldsAccordionProps { - initialIsOpen: boolean; - hasLoaded: boolean; - isFiltered: boolean; - // forceState: 'open' | 'closed'; - id: string; - label: string; - fields: DatatableColumn[]; -} - -export const FieldsAccordion = memo(function InnerFieldsAccordion({ - initialIsOpen, - hasLoaded, - isFiltered, - id, - label, - fields, -}: FieldsAccordionProps) { - const renderButton = useMemo(() => { - return ( - - {label} - - ); - }, [label]); - - const extraAction = useMemo(() => { - if (hasLoaded) { - return ( - - {fields.length} - - ); - } - - return ; - }, [fields.length, hasLoaded, id, isFiltered]); - - return ( - <> - -
      - {fields.length > 0 && - fields.map((field, index) => ( -
    • - - {}} - fieldIcon={} - fieldName={field?.name} - /> - -
    • - ))} -
    -
    - - - ); -}); diff --git a/x-pack/plugins/lens/public/datasources/text_based/layerpanel.test.tsx b/x-pack/plugins/lens/public/datasources/text_based/layerpanel.test.tsx index f0a9d147ddfd6..bc2d64e8ac55d 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/layerpanel.test.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/layerpanel.test.tsx @@ -68,8 +68,6 @@ describe('Layer Data Panel', () => { { id: '2', title: 'my-fake-restricted-pattern', name: 'my-fake-restricted-pattern' }, { id: '3', title: 'my-compatible-pattern', name: 'my-compatible-pattern' }, ], - existingFields: {}, - isFirstExistenceFetch: false, indexPatterns: {}, } as DataViewsState, }; 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 acbeb79bbe74d..13939fa276b31 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 @@ -568,7 +568,6 @@ export function LayerPanel( invalid: group.invalid, invalidMessage: group.invalidMessage, indexPatterns: dataViews.indexPatterns, - existingFields: dataViews.existingFields, }} /> ) : ( @@ -728,7 +727,6 @@ export function LayerPanel( formatSelectorOptions: activeGroup.formatSelectorOptions, layerType: activeVisualization.getLayerType(layerId, visualizationState), indexPatterns: dataViews.indexPatterns, - existingFields: dataViews.existingFields, activeData: layerVisualizationConfigProps.activeData, }} /> diff --git a/x-pack/plugins/lens/public/mocks/store_mocks.tsx b/x-pack/plugins/lens/public/mocks/store_mocks.tsx index 8320f429e9d5a..d4ba2d042b1ca 100644 --- a/x-pack/plugins/lens/public/mocks/store_mocks.tsx +++ b/x-pack/plugins/lens/public/mocks/store_mocks.tsx @@ -59,8 +59,6 @@ export const defaultState = { dataViews: { indexPatterns: {}, indexPatternRefs: [], - existingFields: {}, - isFirstExistenceFetch: false, }, }; diff --git a/x-pack/plugins/lens/public/shared_components/dataview_picker/helpers.ts b/x-pack/plugins/lens/public/shared_components/dataview_picker/helpers.ts index 4f53930fa4973..febf8b1d7c500 100644 --- a/x-pack/plugins/lens/public/shared_components/dataview_picker/helpers.ts +++ b/x-pack/plugins/lens/public/shared_components/dataview_picker/helpers.ts @@ -5,25 +5,17 @@ * 2.0. */ +import { type ExistingFieldsReader } from '@kbn/unified-field-list-plugin/public'; import { IndexPattern } from '../../types'; /** * Checks if the provided field contains data (works for meta field) */ export function fieldContainsData( - field: string, + fieldName: string, indexPattern: IndexPattern, - existingFields: Record + hasFieldData: ExistingFieldsReader['hasFieldData'] ) { - return ( - indexPattern.getFieldByName(field)?.type === 'document' || fieldExists(existingFields, field) - ); -} - -/** - * Performs an existence check on the existingFields data structure for the provided field. - * Does not work for meta fields. - */ -export function fieldExists(existingFields: Record, fieldName: string) { - return existingFields[fieldName]; + const field = indexPattern.getFieldByName(fieldName); + return field?.type === 'document' || hasFieldData(indexPattern.id, fieldName); } diff --git a/x-pack/plugins/lens/public/shared_components/dataview_picker/index.ts b/x-pack/plugins/lens/public/shared_components/dataview_picker/index.ts index 4de03b2f8b92c..6bf23c9d414db 100644 --- a/x-pack/plugins/lens/public/shared_components/dataview_picker/index.ts +++ b/x-pack/plugins/lens/public/shared_components/dataview_picker/index.ts @@ -6,4 +6,4 @@ */ export { ChangeIndexPattern } from './dataview_picker'; -export { fieldExists, fieldContainsData } from './helpers'; +export { fieldContainsData } from './helpers'; diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index a2fcc9c54882d..e57a18b3ee2ee 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -11,7 +11,7 @@ export { LegendSettingsPopover } from './legend_settings_popover'; export { PalettePicker } from './palette_picker'; export { FieldPicker, LensFieldIcon, TruncatedLabel } from './field_picker'; export type { FieldOption, FieldOptionValue } from './field_picker'; -export { ChangeIndexPattern, fieldExists, fieldContainsData } from './dataview_picker'; +export { ChangeIndexPattern, fieldContainsData } from './dataview_picker'; export { QueryInput, isQueryValid, validateQuery } from './query_input'; export { NewBucketButton, diff --git a/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap b/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap index a6759521f562e..d30a68e5e52b0 100644 --- a/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap +++ b/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap @@ -5,10 +5,8 @@ Object { "lens": Object { "activeDatasourceId": "testDatasource", "dataViews": Object { - "existingFields": Object {}, "indexPatternRefs": Array [], "indexPatterns": Object {}, - "isFirstExistenceFetch": true, }, "datasourceStates": Object { "testDatasource": Object { diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index f21d1e6c4aa1a..e8874fbcda822 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -50,8 +50,6 @@ export const initialState: LensAppState = { dataViews: { indexPatternRefs: [], indexPatterns: {}, - existingFields: {}, - isFirstExistenceFetch: true, }, }; diff --git a/x-pack/plugins/lens/public/state_management/types.ts b/x-pack/plugins/lens/public/state_management/types.ts index 9399506f5fca1..4f7500ec20a5e 100644 --- a/x-pack/plugins/lens/public/state_management/types.ts +++ b/x-pack/plugins/lens/public/state_management/types.ts @@ -30,10 +30,6 @@ export interface VisualizationState { export interface DataViewsState { indexPatternRefs: IndexPatternRef[]; indexPatterns: Record; - existingFields: Record>; - isFirstExistenceFetch: boolean; - existenceFetchFailed?: boolean; - existenceFetchTimeout?: boolean; } export type DatasourceStates = Record; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index a8389c7841712..628afa8d61276 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -108,7 +108,6 @@ export interface EditorFrameProps { export type VisualizationMap = Record; export type DatasourceMap = Record; export type IndexPatternMap = Record; -export type ExistingFieldsMap = Record>; export interface EditorFrameInstance { EditorFrameContainer: (props: EditorFrameProps) => React.ReactElement; @@ -589,7 +588,6 @@ export type DatasourceDimensionProps = SharedDimensionProps & { state: T; activeData?: Record; indexPatterns: IndexPatternMap; - existingFields: Record>; hideTooltip?: boolean; invalid?: boolean; invalidMessage?: string; diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts index efa0d3c226468..619c9f7d71f30 100644 --- a/x-pack/plugins/lens/public/utils.ts +++ b/x-pack/plugins/lens/public/utils.ts @@ -80,8 +80,6 @@ export function getInitialDataViewsObject( return { indexPatterns, indexPatternRefs, - existingFields: {}, - isFirstExistenceFetch: true, }; } @@ -107,9 +105,6 @@ export async function refreshIndexPatternsList({ onIndexPatternRefresh: () => onRefreshCallbacks.forEach((fn) => fn()), }); const indexPattern = newlyMappedIndexPattern[indexPatternId]; - // But what about existingFields here? - // When the indexPatterns cache object gets updated, the data panel will - // notice it and refetch the fields list existence map indexPatternService.updateDataViewsState({ indexPatterns: { ...indexPatternsCache, diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/annotations_panel.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/annotations_panel.tsx index 480c0773f4520..748217469ce63 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/annotations_panel.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/annotations_panel.tsx @@ -23,6 +23,7 @@ import { QueryPointEventAnnotationConfig, } from '@kbn/event-annotation-plugin/common'; import moment from 'moment'; +import { useExistingFieldsReader } from '@kbn/unified-field-list-plugin/public'; import { FieldOption, FieldOptionValue, @@ -31,7 +32,6 @@ import { import { FormatFactory } from '../../../../../common'; import { DimensionEditorSection, - fieldExists, NameInput, useDebouncedValue, } from '../../../../shared_components'; @@ -58,6 +58,7 @@ export const AnnotationsPanel = ( ) => { const { state, setState, layerId, accessor, frame } = props; const isHorizontal = isHorizontalChart(state.layers); + const { hasFieldData } = useExistingFieldsReader(); const { inputValue: localState, handleInputChange: setLocalState } = useDebouncedValue({ value: state, @@ -248,10 +249,7 @@ export const AnnotationsPanel = ( field: field.name, dataType: field.type, }, - exists: fieldExists( - frame.dataViews.existingFields[currentIndexPattern.title], - field.name - ), + exists: hasFieldData(currentIndexPattern.id, field.name), compatible: true, 'data-test-subj': `lnsXY-annotation-fieldOption-${field.name}`, } as FieldOption) @@ -379,7 +377,6 @@ export const AnnotationsPanel = ( currentConfig={currentAnnotation} setConfig={setAnnotations} indexPattern={frame.dataViews.indexPatterns[localLayer.indexPatternId]} - existingFields={frame.dataViews.existingFields} /> diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/query_annotation_panel.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/query_annotation_panel.tsx index 0ee0d1f06d1c8..00f0013c92822 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/query_annotation_panel.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/query_annotation_panel.tsx @@ -10,8 +10,8 @@ import type { Query } from '@kbn/data-plugin/common'; import type { QueryPointEventAnnotationConfig } from '@kbn/event-annotation-plugin/common'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { useExistingFieldsReader } from '@kbn/unified-field-list-plugin/public'; import { - fieldExists, FieldOption, FieldOptionValue, FieldPicker, @@ -41,7 +41,7 @@ export const ConfigPanelQueryAnnotation = ({ queryInputShouldOpen?: boolean; }) => { const currentIndexPattern = frame.dataViews.indexPatterns[layer.indexPatternId]; - const currentExistingFields = frame.dataViews.existingFields[currentIndexPattern.title]; + const { hasFieldData } = useExistingFieldsReader(); // list only date fields const options = currentIndexPattern.fields .filter((field) => field.type === 'date' && field.displayName) @@ -53,7 +53,7 @@ export const ConfigPanelQueryAnnotation = ({ field: field.name, dataType: field.type, }, - exists: fieldExists(currentExistingFields, field.name), + exists: hasFieldData(currentIndexPattern.id, field.name), compatible: true, 'data-test-subj': `lns-fieldOption-${field.name}`, } as FieldOption; diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/tooltip_annotation_panel.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/tooltip_annotation_panel.tsx index 20a99e8458fc0..d3f68686c3bac 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/tooltip_annotation_panel.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/tooltip_annotation_panel.tsx @@ -9,9 +9,9 @@ import { htmlIdGenerator, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useCallback, useMemo } from 'react'; import { QueryPointEventAnnotationConfig } from '@kbn/event-annotation-plugin/common'; -import type { ExistingFieldsMap, IndexPattern } from '../../../../types'; +import { useExistingFieldsReader } from '@kbn/unified-field-list-plugin/public'; +import type { IndexPattern } from '../../../../types'; import { - fieldExists, FieldOption, FieldOptionValue, FieldPicker, @@ -31,7 +31,6 @@ export interface FieldInputsProps { currentConfig: QueryPointEventAnnotationConfig; setConfig: (config: QueryPointEventAnnotationConfig) => void; indexPattern: IndexPattern; - existingFields: ExistingFieldsMap; invalidFields?: string[]; } @@ -51,9 +50,9 @@ export function TooltipSection({ currentConfig, setConfig, indexPattern, - existingFields, invalidFields, }: FieldInputsProps) { + const { hasFieldData } = useExistingFieldsReader(); const onChangeWrapped = useCallback( (values: WrappedValue[]) => { setConfig({ @@ -124,7 +123,6 @@ export function TooltipSection({ ); } - const currentExistingField = existingFields[indexPattern.title]; const options = indexPattern.fields .filter( @@ -140,7 +138,7 @@ export function TooltipSection({ field: field.name, dataType: field.type, }, - exists: fieldExists(currentExistingField, field.name), + exists: hasFieldData(indexPattern.id, field.name), compatible: true, 'data-test-subj': `lnsXY-annotation-tooltip-fieldOption-${field.name}`, } as FieldOption) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 087c56fab47b9..f77bd0fd05796 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -17250,7 +17250,6 @@ "xpack.lens.indexPattern.timeShiftSmallWarning": "{label} utilise un décalage temporel de {columnTimeShift} qui est inférieur à l'intervalle de l'histogramme des dates de {interval}. Pour éviter une non-correspondance des données, utilisez un multiple de {interval} comme décalage.", "xpack.lens.indexPattern.uniqueLabel": "{label} [{num}]", "xpack.lens.indexPattern.valueCountOf": "Nombre de {name}", - "xpack.lens.indexPatterns.fieldSearchLiveRegion": "{availableFields} {availableFields, plural, one {champ} other {champs}} disponible(s). {emptyFields} {emptyFields, plural, one {champ} other {champs}} vide(s). {metaFields} {metaFields, plural, one {champ} other {champs}} méta.", "xpack.lens.indexPatternSuggestion.removeLayerLabel": "Afficher uniquement {indexPatternTitle}", "xpack.lens.indexPatternSuggestion.removeLayerPositionLabel": "Afficher uniquement le calque {layerNumber}", "xpack.lens.modalTitle.title.clear": "Effacer le calque {layerType} ?", @@ -17558,7 +17557,6 @@ "xpack.lens.heatmapVisualization.missingXAccessorLongMessage": "La configuration de l'axe horizontal est manquante.", "xpack.lens.heatmapVisualization.missingXAccessorShortMessage": "Axe horizontal manquant.", "xpack.lens.indexPattern.advancedSettings": "Avancé", - "xpack.lens.indexPattern.allFieldsLabel": "Tous les champs", "xpack.lens.indexPattern.allFieldsLabelHelp": "Glissez-déposez les champs disponibles dans l’espace de travail et créez des visualisations. Pour modifier les champs disponibles, sélectionnez une vue de données différente, modifiez vos requêtes ou utilisez une plage temporelle différente. Certains types de champ ne peuvent pas être visualisés dans Lens, y compris les champ de texte intégral et champs géographiques.", "xpack.lens.indexPattern.allFieldsSamplingLabelHelp": "Les champs disponibles contiennent les données des 500 premiers documents correspondant aux filtres. Pour afficher tous les filtres, développez les champs vides. Vous ne pouvez pas créer de visualisations avec des champs de texte intégral, géographiques, lissés et d’objet.", "xpack.lens.indexPattern.ascendingCountPrecisionErrorWarning.link": "veuillez consulter la documentation", @@ -17605,12 +17603,7 @@ "xpack.lens.indexPattern.differences.signature": "indicateur : nombre", "xpack.lens.indexPattern.emptyDimensionButton": "Dimension vide", "xpack.lens.indexPattern.emptyFieldsLabel": "Champs vides", - "xpack.lens.indexPattern.emptyFieldsLabelHelp": "Les champs vides ne contenaient aucune valeur dans les 500 premiers documents basés sur vos filtres.", "xpack.lens.indexPattern.enableAccuracyMode": "Activer le mode de précision", - "xpack.lens.indexPattern.existenceErrorAriaLabel": "La récupération de l'existence a échoué", - "xpack.lens.indexPattern.existenceErrorLabel": "Impossible de charger les informations de champ", - "xpack.lens.indexPattern.existenceTimeoutAriaLabel": "La récupération de l'existence a expiré", - "xpack.lens.indexPattern.existenceTimeoutLabel": "Les informations de champ ont pris trop de temps", "xpack.lens.indexPattern.fieldItemTooltip": "Effectuez un glisser-déposer pour visualiser.", "xpack.lens.indexPattern.fieldPlaceholder": "Champ", "xpack.lens.indexPattern.fieldStatsButtonEmptyLabel": "Ce champ ne comporte aucune donnée mais vous pouvez toujours effectuer un glisser-déposer pour visualiser.", @@ -17788,16 +17781,6 @@ "xpack.lens.indexPattern.useAsTopLevelAgg": "Regrouper d'abord en fonction de ce champ", "xpack.lens.indexPatterns.clearFiltersLabel": "Effacer le nom et saisissez les filtres", "xpack.lens.indexPatterns.filterByNameLabel": "Rechercher les noms de champs", - "xpack.lens.indexPatterns.noAvailableDataLabel": "Aucun champ disponible ne contient de données.", - "xpack.lens.indexPatterns.noDataLabel": "Aucun champ.", - "xpack.lens.indexPatterns.noEmptyDataLabel": "Aucun champ vide.", - "xpack.lens.indexPatterns.noFields.extendTimeBullet": "Extension de la plage temporelle", - "xpack.lens.indexPatterns.noFields.fieldTypeFilterBullet": "Utilisation de différents filtres de champ", - "xpack.lens.indexPatterns.noFields.globalFiltersBullet": "Modification des filtres globaux", - "xpack.lens.indexPatterns.noFields.tryText": "Essayer :", - "xpack.lens.indexPatterns.noFieldsLabel": "Aucun champ n'existe dans cette vue de données.", - "xpack.lens.indexPatterns.noFilteredFieldsLabel": "Aucun champ ne correspond aux filtres sélectionnés.", - "xpack.lens.indexPatterns.noMetaDataLabel": "Aucun champ méta.", "xpack.lens.label.gauge.labelMajor.header": "Titre", "xpack.lens.label.gauge.labelMinor.header": "Sous-titre", "xpack.lens.label.header": "Étiquette", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f4c281167d8b6..3da599c4ebd22 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17231,7 +17231,6 @@ "xpack.lens.indexPattern.timeShiftSmallWarning": "{label}は{columnTimeShift}の時間シフトを使用しています。これは{interval}の日付ヒストグラム間隔よりも小さいです。不一致のデータを防止するには、時間シフトとして{interval}を使用します。", "xpack.lens.indexPattern.uniqueLabel": "{label} [{num}]", "xpack.lens.indexPattern.valueCountOf": "{name}のカウント", - "xpack.lens.indexPatterns.fieldSearchLiveRegion": "{availableFields}使用可能な{availableFields, plural, other {フィールド}}。{emptyFields}空の{emptyFields, plural, other {フィールド}}。 {metaFields}メタ{metaFields, plural, other {フィールド}}。", "xpack.lens.indexPatternSuggestion.removeLayerLabel": "{indexPatternTitle}のみを表示", "xpack.lens.indexPatternSuggestion.removeLayerPositionLabel": "レイヤー{layerNumber}のみを表示", "xpack.lens.modalTitle.title.clear": "{layerType}レイヤーをクリアしますか?", @@ -17541,7 +17540,6 @@ "xpack.lens.heatmapVisualization.missingXAccessorLongMessage": "横軸の構成がありません。", "xpack.lens.heatmapVisualization.missingXAccessorShortMessage": "横軸がありません。", "xpack.lens.indexPattern.advancedSettings": "高度な設定", - "xpack.lens.indexPattern.allFieldsLabel": "すべてのフィールド", "xpack.lens.indexPattern.allFieldsLabelHelp": "使用可能なフィールドをワークスペースまでドラッグし、ビジュアライゼーションを作成します。使用可能なフィールドを変更するには、別のデータビューを選択するか、クエリを編集するか、別の時間範囲を使用します。一部のフィールドタイプは、完全なテキストおよびグラフィックフィールドを含む Lens では、ビジュアライゼーションできません。", "xpack.lens.indexPattern.allFieldsSamplingLabelHelp": "使用可能なフィールドには、フィルターと一致する最初の 500 件のドキュメントのデータがあります。すべてのフィールドを表示するには、空のフィールドを展開します。全文、地理、フラット化、オブジェクトフィールドでビジュアライゼーションを作成できません。", "xpack.lens.indexPattern.ascendingCountPrecisionErrorWarning.link": "ドキュメントをご覧ください", @@ -17588,12 +17586,7 @@ "xpack.lens.indexPattern.differences.signature": "メトリック:数値", "xpack.lens.indexPattern.emptyDimensionButton": "空のディメンション", "xpack.lens.indexPattern.emptyFieldsLabel": "空のフィールド", - "xpack.lens.indexPattern.emptyFieldsLabelHelp": "空のフィールドには、フィルターに基づく最初の 500 件のドキュメントの値が含まれていませんでした。", "xpack.lens.indexPattern.enableAccuracyMode": "精度モードを有効にする", - "xpack.lens.indexPattern.existenceErrorAriaLabel": "存在の取り込みに失敗しました", - "xpack.lens.indexPattern.existenceErrorLabel": "フィールド情報を読み込めません", - "xpack.lens.indexPattern.existenceTimeoutAriaLabel": "存在の取り込みがタイムアウトしました", - "xpack.lens.indexPattern.existenceTimeoutLabel": "フィールド情報に時間がかかりすぎました", "xpack.lens.indexPattern.fieldItemTooltip": "可視化するには、ドラッグアンドドロップします。", "xpack.lens.indexPattern.fieldPlaceholder": "フィールド", "xpack.lens.indexPattern.fieldStatsButtonEmptyLabel": "このフィールドにはデータがありませんが、ドラッグアンドドロップで可視化できます。", @@ -17771,16 +17764,6 @@ "xpack.lens.indexPattern.useAsTopLevelAgg": "最初にこのフィールドでグループ化", "xpack.lens.indexPatterns.clearFiltersLabel": "名前とタイプフィルターを消去", "xpack.lens.indexPatterns.filterByNameLabel": "検索フィールド名", - "xpack.lens.indexPatterns.noAvailableDataLabel": "データを含むフィールドはありません。", - "xpack.lens.indexPatterns.noDataLabel": "フィールドがありません。", - "xpack.lens.indexPatterns.noEmptyDataLabel": "空のフィールドがありません。", - "xpack.lens.indexPatterns.noFields.extendTimeBullet": "時間範囲を拡張中", - "xpack.lens.indexPatterns.noFields.fieldTypeFilterBullet": "別のフィールドフィルターを使用", - "xpack.lens.indexPatterns.noFields.globalFiltersBullet": "グローバルフィルターを変更", - "xpack.lens.indexPatterns.noFields.tryText": "試行対象:", - "xpack.lens.indexPatterns.noFieldsLabel": "このデータビューにはフィールドがありません。", - "xpack.lens.indexPatterns.noFilteredFieldsLabel": "選択したフィルターと一致するフィールドはありません。", - "xpack.lens.indexPatterns.noMetaDataLabel": "メタフィールドがありません。", "xpack.lens.label.gauge.labelMajor.header": "タイトル", "xpack.lens.label.gauge.labelMinor.header": "サブタイトル", "xpack.lens.label.header": "ラベル", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ce903ab668bef..c595b967b9e54 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17256,7 +17256,6 @@ "xpack.lens.indexPattern.timeShiftSmallWarning": "{label} 使用的时间偏移 {columnTimeShift} 小于 Date Histogram 时间间隔 {interval} 。要防止数据不匹配,请使用 {interval} 的倍数作为时间偏移。", "xpack.lens.indexPattern.uniqueLabel": "{label} [{num}]", "xpack.lens.indexPattern.valueCountOf": "{name} 的计数", - "xpack.lens.indexPatterns.fieldSearchLiveRegion": "{availableFields} 个可用{availableFields, plural, other {字段}}。{emptyFields} 个空{emptyFields, plural, other {字段}}。{metaFields} 个元{metaFields, plural,other {字段}}。", "xpack.lens.indexPatternSuggestion.removeLayerLabel": "仅显示 {indexPatternTitle}", "xpack.lens.indexPatternSuggestion.removeLayerPositionLabel": "仅显示图层 {layerNumber}", "xpack.lens.modalTitle.title.clear": "清除 {layerType} 图层?", @@ -17566,7 +17565,6 @@ "xpack.lens.heatmapVisualization.missingXAccessorLongMessage": "水平轴配置缺失。", "xpack.lens.heatmapVisualization.missingXAccessorShortMessage": "缺失水平轴。", "xpack.lens.indexPattern.advancedSettings": "高级", - "xpack.lens.indexPattern.allFieldsLabel": "所有字段", "xpack.lens.indexPattern.allFieldsLabelHelp": "将可用字段拖放到工作区并创建可视化。要更改可用字段,请选择不同数据视图,编辑您的查询或使用不同时间范围。一些字段类型无法在 Lens 中可视化,包括全文本字段和地理字段。", "xpack.lens.indexPattern.allFieldsSamplingLabelHelp": "可用字段包含与您的筛选匹配的前 500 个文档中的数据。要查看所有字段,请展开空字段。无法使用全文本、地理、扁平和对象字段创建可视化。", "xpack.lens.indexPattern.ascendingCountPrecisionErrorWarning.link": "访问文档", @@ -17613,12 +17611,7 @@ "xpack.lens.indexPattern.differences.signature": "指标:数字", "xpack.lens.indexPattern.emptyDimensionButton": "空维度", "xpack.lens.indexPattern.emptyFieldsLabel": "空字段", - "xpack.lens.indexPattern.emptyFieldsLabelHelp": "空字段在基于您的筛选的前 500 个文档中不包含任何值。", "xpack.lens.indexPattern.enableAccuracyMode": "启用准确性模式", - "xpack.lens.indexPattern.existenceErrorAriaLabel": "现有内容提取失败", - "xpack.lens.indexPattern.existenceErrorLabel": "无法加载字段信息", - "xpack.lens.indexPattern.existenceTimeoutAriaLabel": "现有内容提取超时", - "xpack.lens.indexPattern.existenceTimeoutLabel": "字段信息花费时间过久", "xpack.lens.indexPattern.fieldItemTooltip": "拖放以可视化。", "xpack.lens.indexPattern.fieldPlaceholder": "字段", "xpack.lens.indexPattern.fieldStatsButtonEmptyLabel": "此字段不包含任何数据,但您仍然可以拖放以进行可视化。", @@ -17796,16 +17789,6 @@ "xpack.lens.indexPattern.useAsTopLevelAgg": "先按此字段分组", "xpack.lens.indexPatterns.clearFiltersLabel": "清除名称和类型筛选", "xpack.lens.indexPatterns.filterByNameLabel": "搜索字段名称", - "xpack.lens.indexPatterns.noAvailableDataLabel": "没有包含数据的可用字段。", - "xpack.lens.indexPatterns.noDataLabel": "无字段。", - "xpack.lens.indexPatterns.noEmptyDataLabel": "无空字段。", - "xpack.lens.indexPatterns.noFields.extendTimeBullet": "延伸时间范围", - "xpack.lens.indexPatterns.noFields.fieldTypeFilterBullet": "使用不同的字段筛选", - "xpack.lens.indexPatterns.noFields.globalFiltersBullet": "更改全局筛选", - "xpack.lens.indexPatterns.noFields.tryText": "尝试:", - "xpack.lens.indexPatterns.noFieldsLabel": "在此数据视图中不存在任何字段。", - "xpack.lens.indexPatterns.noFilteredFieldsLabel": "没有字段匹配选定筛选。", - "xpack.lens.indexPatterns.noMetaDataLabel": "无元字段。", "xpack.lens.label.gauge.labelMajor.header": "标题", "xpack.lens.label.gauge.labelMinor.header": "子标题", "xpack.lens.label.header": "标签", From 1fb018bb78559a0ff58fd6c0194fdcfd425b16bb Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 2 Nov 2022 14:53:54 +0200 Subject: [PATCH 07/86] [Lens] Hide the titles of the axis from the preview (#144397) --- .../lens/public/visualizations/heatmap/visualization.test.ts | 4 ++-- .../lens/public/visualizations/heatmap/visualization.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts index bbebf37ff4b5c..fd73a652b53e0 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts @@ -560,10 +560,10 @@ describe('heatmap', () => { isCellLabelVisible: [false], // Y-axis isYAxisLabelVisible: [false], - isYAxisTitleVisible: [true], + isYAxisTitleVisible: [false], // X-axis isXAxisLabelVisible: [false], - isXAxisTitleVisible: [true], + isXAxisTitleVisible: [false], xTitle: [''], yTitle: [''], }, diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx index dfa37ba75f38c..9750cc89c55e7 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx @@ -404,11 +404,11 @@ export const getHeatmapVisualization = ({ isCellLabelVisible: false, // Y-axis isYAxisLabelVisible: false, - isYAxisTitleVisible: state.gridConfig.isYAxisTitleVisible, + isYAxisTitleVisible: false, yTitle: state.gridConfig.yTitle ?? '', // X-axis isXAxisLabelVisible: false, - isXAxisTitleVisible: state.gridConfig.isXAxisTitleVisible, + isXAxisTitleVisible: false, xTitle: state.gridConfig.xTitle ?? '', } ); From 74472192ec7c69676f2ac5da7b2769d8b88334db Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Wed, 2 Nov 2022 14:10:46 +0100 Subject: [PATCH 08/86] added tooltip on last checkin message in agent details (#144386) * added tooltip on last checkin message in agent details * changed to eui tooltip --- .../agent_details/agent_details_overview.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx index 11ddcef4c8aee..0eccf5a8cec33 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx @@ -188,15 +188,19 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ description: (agent.tags ?? []).length > 0 ? : '-', }, ].map(({ title, description }) => { + const tooltip = + typeof description === 'string' && description.length > 20 ? description : ''; return ( {title} - - {description} - + + + {description} + + ); From e8359390b945a6eef876a298e80749948a941c1d Mon Sep 17 00:00:00 2001 From: Casey Zumwalt Date: Wed, 2 Nov 2022 08:15:17 -0500 Subject: [PATCH 09/86] [Ent. Search] Search experiences landing page (#143874) * Scaffold plugin * Layout * Basic landing page layout * Search experiences illustration * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Test fixes * More test fixes * Use a different i18n key for the Search category * Cleanup plugin primitives * i18n * Safe i18n * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Test fixes Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../collectors/application_usage/schema.ts | 1 + src/plugins/telemetry/schema/oss_plugins.json | 131 ++ .../enterprise_search/common/constants.ts | 20 + .../components/layout/index.ts | 8 + .../components/layout/page_template.test.tsx | 77 + .../components/layout/page_template.tsx | 37 + .../search_experiences_guide/index.ts | 8 + .../search_experiences_guide.tsx | 214 +++ .../search_experiences/index.test.tsx | 28 + .../applications/search_experiences/index.tsx | 43 + .../applications/search_experiences/routes.ts | 8 + .../kibana_chrome/generate_breadcrumbs.ts | 7 + .../shared/kibana_chrome/generate_title.ts | 4 + .../shared/kibana_chrome/index.ts | 1 + .../shared/kibana_chrome/set_chrome.tsx | 19 + .../applications/shared/layout/nav.test.tsx | 20 + .../public/applications/shared/layout/nav.tsx | 13 +- .../assets/images/search_experiences.svg | 1393 +++++++++++++++++ .../enterprise_search/public/plugin.ts | 32 + .../enterprise_search/server/plugin.ts | 4 + .../translations/translations/fr-FR.json | 2 +- .../translations/translations/ja-JP.json | 2 +- .../translations/translations/zh-CN.json | 2 +- .../security_and_spaces/tests/catalogue.ts | 2 + .../spaces_only/tests/catalogue.ts | 1 + 25 files changed, 2073 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/page_template.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/page_template.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/search_experiences/components/search_experiences_guide/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/search_experiences/components/search_experiences_guide/search_experiences_guide.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/search_experiences/index.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/search_experiences/index.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/search_experiences/routes.ts create mode 100644 x-pack/plugins/enterprise_search/public/assets/images/search_experiences.svg diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index 4b393a7a1c249..c105b3dd387f0 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -139,6 +139,7 @@ export const applicationUsageSchema = { elasticsearch: commonSchema, appSearch: commonSchema, workplaceSearch: commonSchema, + searchExperiences: commonSchema, graph: commonSchema, logs: commonSchema, metrics: commonSchema, diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 7ad52ecb20d25..ddf1ba3df941a 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -2622,6 +2622,137 @@ } } }, + "searchExperiences": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "Always `main`" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 90 days" + } + }, + "views": { + "type": "array", + "items": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "The application view being tracked" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application sub view since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application sub view is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" + } + } + } + } + } + } + }, "graph": { "properties": { "appId": { diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index 3fe7d29d3801a..b0801ee654d6f 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -97,6 +97,26 @@ export const WORKPLACE_SEARCH_PLUGIN = { SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/workplace-search/', }; +export const SEARCH_EXPERIENCES_PLUGIN = { + ID: 'searchExperiences', + NAME: i18n.translate('xpack.enterpriseSearch.searchExperiences.productName', { + defaultMessage: 'Enterprise Search', + }), + NAV_TITLE: i18n.translate('xpack.enterpriseSearch.searchExperiences.navTitle', { + defaultMessage: 'Search experiences', + }), + DESCRIPTION: i18n.translate('xpack.enterpriseSearch.searchExperiences.productDescription', { + defaultMessage: 'Build an intuitive, engaging search experience without reinventing the wheel.', + }), + URL: '/app/enterprise_search/search_experiences', + SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/', + GITHUB_URL: 'https://github.com/elastic/search-ui/', + DOCUMENTATION_URL: 'https://docs.elastic.co/search-ui/', + ELASTICSEARCH_TUTORIAL_URL: 'https://docs.elastic.co/search-ui/tutorials/elasticsearch', + APP_SEARCH_TUTORIAL_URL: 'https://docs.elastic.co/search-ui/tutorials/app-search', + WORKPLACE_SEARCH_TUTORIAL_URL: 'https://docs.elastic.co/search-ui/tutorials/workplace-search', +}; + export const LICENSED_SUPPORT_URL = 'https://support.elastic.co'; export const JSON_HEADER = { diff --git a/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/index.ts new file mode 100644 index 0000000000000..51fa23df81b01 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { EnterpriseSearchSearchExperiencesPageTemplate } from './page_template'; diff --git a/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/page_template.test.tsx new file mode 100644 index 0000000000000..f1c889c020d7a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/page_template.test.tsx @@ -0,0 +1,77 @@ +/* + * 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. + */ + +jest.mock('../../../shared/layout/nav', () => ({ + useEnterpriseSearchNav: () => [], +})); + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { SetSearchExperiencesChrome } from '../../../shared/kibana_chrome'; +import { EnterpriseSearchPageTemplateWrapper } from '../../../shared/layout'; +import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; + +import { EnterpriseSearchSearchExperiencesPageTemplate } from './page_template'; + +describe('EnterpriseSearchSearchExperiencesPageTemplate', () => { + it('renders', () => { + const wrapper = shallow( + +
    world
    +
    + ); + + expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper); + expect(wrapper.prop('solutionNav')).toEqual({ name: 'Enterprise Search', items: [] }); + expect(wrapper.find('.hello').text()).toEqual('world'); + }); + + describe('page chrome', () => { + it('takes a breadcrumb array & renders a product-specific page chrome', () => { + const wrapper = shallow( + + ); + const setPageChrome = wrapper + .find(EnterpriseSearchPageTemplateWrapper) + .prop('setPageChrome') as any; + + expect(setPageChrome.type).toEqual(SetSearchExperiencesChrome); + expect(setPageChrome.props.trail).toEqual(['Some page']); + }); + }); + + describe('page telemetry', () => { + it('takes a metric & renders product-specific telemetry viewed event', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(SendEnterpriseSearchTelemetry).prop('action')).toEqual('viewed'); + expect(wrapper.find(SendEnterpriseSearchTelemetry).prop('metric')).toEqual('some_page'); + }); + }); + + describe('props', () => { + it('passes down any ...pageTemplateProps that EnterpriseSearchPageTemplateWrapper accepts', () => { + const wrapper = shallow( + } + /> + ); + + expect( + wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('pageHeader')!.pageTitle + ).toEqual('hello world'); + expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('isLoading')).toEqual(false); + expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('emptyState')).toEqual(
    ); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/page_template.tsx new file mode 100644 index 0000000000000..5c4d1958d0c38 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/page_template.tsx @@ -0,0 +1,37 @@ +/* + * 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 { SEARCH_EXPERIENCES_PLUGIN } from '../../../../../common/constants'; +import { SetSearchExperiencesChrome } from '../../../shared/kibana_chrome'; +import { EnterpriseSearchPageTemplateWrapper, PageTemplateProps } from '../../../shared/layout'; +import { useEnterpriseSearchNav } from '../../../shared/layout'; +import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; + +export const EnterpriseSearchSearchExperiencesPageTemplate: React.FC = ({ + children, + pageChrome, + pageViewTelemetry, + ...pageTemplateProps +}) => { + return ( + } + > + {pageViewTelemetry && ( + + )} + {children} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/search_experiences_guide/index.ts b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/search_experiences_guide/index.ts new file mode 100644 index 0000000000000..fd811cfd6a92b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/search_experiences_guide/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { SearchExperiencesGuide } from './search_experiences_guide'; diff --git a/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/search_experiences_guide/search_experiences_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/search_experiences_guide/search_experiences_guide.tsx new file mode 100644 index 0000000000000..3d32130a50e51 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/search_experiences_guide/search_experiences_guide.tsx @@ -0,0 +1,214 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiTitle, + EuiSpacer, + EuiText, + EuiButton, + EuiButtonEmpty, + EuiImage, + EuiHorizontalRule, + EuiCard, + EuiIcon, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import { SEARCH_EXPERIENCES_PLUGIN } from '../../../../../common/constants'; +import searchExperiencesIllustration from '../../../../assets/images/search_experiences.svg'; + +import { SetSearchExperiencesChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { EnterpriseSearchSearchExperiencesPageTemplate } from '../layout'; + +export const SearchExperiencesGuide: React.FC = () => { + return ( + + + + + + + + +

    About Search UI

    +
    + + +

    + +

    +
    +
    + + + + + + + + + + + + + + + + +

    + +

    +
    + + +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    +
    +
    +
    +
    + + + +
    + + +

    + +

    +
    + + + + } + title="Elasticsearch" + description={i18n.translate( + 'xpack.enterpriseSearch.searchExperiences.guide.tutorials.elasticsearch.description', + { + defaultMessage: 'Build a search experience with Elasticsearch and Search UI.', + } + )} + href={SEARCH_EXPERIENCES_PLUGIN.ELASTICSEARCH_TUTORIAL_URL} + target="_blank" + /> + + + } + title="App Search" + description={i18n.translate( + 'xpack.enterpriseSearch.searchExperiences.guide.tutorials.appSearch.description', + { + defaultMessage: 'Build a search experience with App Search and Search UI.', + } + )} + href={SEARCH_EXPERIENCES_PLUGIN.APP_SEARCH_TUTORIAL_URL} + target="_blank" + /> + + + } + title="Workplace Search" + description={i18n.translate( + 'xpack.enterpriseSearch.searchExperiences.guide.tutorials.workplaceSearch.description', + { + defaultMessage: 'Build a search experience with Workplace Search and Search UI.', + } + )} + href={SEARCH_EXPERIENCES_PLUGIN.WORKPLACE_SEARCH_TUTORIAL_URL} + target="_blank" + /> + + +
    +
    + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/search_experiences/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/search_experiences/index.test.tsx new file mode 100644 index 0000000000000..ceaf735802226 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_experiences/index.test.tsx @@ -0,0 +1,28 @@ +/* + * 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 { setMockValues } from '../__mocks__/kea_logic'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { SearchExperiencesGuide } from './components/search_experiences_guide'; + +import { SearchExperiences } from '.'; + +describe('SearchExperiences', () => { + it('renders the Search Experiences guide', () => { + setMockValues({ + errorConnectingMessage: '', + config: { host: 'localhost' }, + }); + const wrapper = shallow(); + + expect(wrapper.find(SearchExperiencesGuide)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/search_experiences/index.tsx b/x-pack/plugins/enterprise_search/public/applications/search_experiences/index.tsx new file mode 100644 index 0000000000000..4416c8309211b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_experiences/index.tsx @@ -0,0 +1,43 @@ +/* + * 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 { Route, Switch } from 'react-router-dom'; + +import { isVersionMismatch } from '../../../common/is_version_mismatch'; +import { InitialAppData } from '../../../common/types'; +import { VersionMismatchPage } from '../shared/version_mismatch'; + +import { SearchExperiencesGuide } from './components/search_experiences_guide'; + +import { ROOT_PATH } from './routes'; + +export const SearchExperiences: React.FC = (props) => { + const { enterpriseSearchVersion, kibanaVersion } = props; + const incompatibleVersions = isVersionMismatch(enterpriseSearchVersion, kibanaVersion); + + const showView = () => { + if (incompatibleVersions) { + return ( + + ); + } + + return ; + }; + + return ( + + + {showView()} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/search_experiences/routes.ts b/x-pack/plugins/enterprise_search/public/applications/search_experiences/routes.ts new file mode 100644 index 0000000000000..d6b0b0a669281 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_experiences/routes.ts @@ -0,0 +1,8 @@ +/* + * 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 const ROOT_PATH = '/'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts index 70fcedb9da260..140ec10df23da 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts @@ -15,6 +15,7 @@ import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN, ENTERPRISE_SEARCH_CONTENT_PLUGIN, + SEARCH_EXPERIENCES_PLUGIN, } from '../../../../common/constants'; import { stripLeadingSlash } from '../../../../common/strip_slashes'; @@ -129,3 +130,9 @@ export const useEnterpriseSearchContentBreadcrumbs = (breadcrumbs: Breadcrumbs = { text: ENTERPRISE_SEARCH_CONTENT_PLUGIN.NAV_TITLE, path: '/' }, ...breadcrumbs, ]); + +export const useSearchExperiencesBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => + useEnterpriseSearchBreadcrumbs([ + { text: SEARCH_EXPERIENCES_PLUGIN.NAV_TITLE, path: '/' }, + ...breadcrumbs, + ]); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts index 29b52953a578f..49279881ef00e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts @@ -10,6 +10,7 @@ import { ANALYTICS_PLUGIN, APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN, + SEARCH_EXPERIENCES_PLUGIN, } from '../../../../common/constants'; /** @@ -43,3 +44,6 @@ export const appSearchTitle = (page: Title = []) => export const workplaceSearchTitle = (page: Title = []) => generateTitle([...page, WORKPLACE_SEARCH_PLUGIN.NAME]); + +export const searchExperiencesTitle = (page: Title = []) => + generateTitle([...page, SEARCH_EXPERIENCES_PLUGIN.NAME]); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts index 28e5833a0faca..1a013bf11df35 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts @@ -12,4 +12,5 @@ export { SetElasticsearchChrome, SetAppSearchChrome, SetWorkplaceSearchChrome, + SetSearchExperiencesChrome, } from './set_chrome'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx index d0327e528376d..72b68c9a5cc7f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx @@ -20,6 +20,7 @@ import { useAppSearchBreadcrumbs, useWorkplaceSearchBreadcrumbs, BreadcrumbTrail, + useSearchExperiencesBreadcrumbs, } from './generate_breadcrumbs'; import { enterpriseSearchTitle, @@ -27,6 +28,7 @@ import { elasticsearchTitle, appSearchTitle, workplaceSearchTitle, + searchExperiencesTitle, } from './generate_title'; /** @@ -150,5 +152,22 @@ export const SetEnterpriseSearchContentChrome: React.FC = ({ tra return null; }; +export const SetSearchExperiencesChrome: React.FC = ({ trail = [] }) => { + const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); + + const title = reverseArray(trail); + const docTitle = searchExperiencesTitle(title); + + const crumbs = useGenerateBreadcrumbs(trail); + const breadcrumbs = useSearchExperiencesBreadcrumbs(crumbs); + + useEffect(() => { + setBreadcrumbs(breadcrumbs); + setDocTitle(docTitle); + }, [trail]); + + return null; +}; + // Small util - performantly reverses an array without mutating the original array const reverseArray = (array: string[]) => array.slice().reverse(); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx index 67fb93ae643f6..bd367e7de8e7d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx @@ -72,6 +72,11 @@ describe('useEnterpriseSearchContentNav', () => { id: 'elasticsearch', name: 'Elasticsearch', }, + { + href: '/app/enterprise_search/search_experiences', + id: 'searchExperiences', + name: 'Search Experiences', + }, { href: '/app/enterprise_search/app_search', id: 'app_search', @@ -108,6 +113,11 @@ describe('useEnterpriseSearchContentNav', () => { id: 'elasticsearch', name: 'Elasticsearch', }, + { + href: '/app/enterprise_search/search_experiences', + id: 'searchExperiences', + name: 'Search Experiences', + }, ], name: 'Search', }); @@ -129,6 +139,11 @@ describe('useEnterpriseSearchContentNav', () => { id: 'elasticsearch', name: 'Elasticsearch', }, + { + href: '/app/enterprise_search/search_experiences', + id: 'searchExperiences', + name: 'Search Experiences', + }, { href: '/app/enterprise_search/workplace_search', id: 'workplace_search', @@ -155,6 +170,11 @@ describe('useEnterpriseSearchContentNav', () => { id: 'elasticsearch', name: 'Elasticsearch', }, + { + href: '/app/enterprise_search/search_experiences', + id: 'searchExperiences', + name: 'Search Experiences', + }, { href: '/app/enterprise_search/app_search', id: 'app_search', diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index 967095d9674ba..471c40668b4a3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -16,6 +16,7 @@ import { ELASTICSEARCH_PLUGIN, ENTERPRISE_SEARCH_CONTENT_PLUGIN, ENTERPRISE_SEARCH_OVERVIEW_PLUGIN, + SEARCH_EXPERIENCES_PLUGIN, WORKPLACE_SEARCH_PLUGIN, } from '../../../../common/constants'; import { enableBehavioralAnalyticsSection } from '../../../../common/ui_settings_keys'; @@ -106,6 +107,16 @@ export const useEnterpriseSearchNav = () => { to: ELASTICSEARCH_PLUGIN.URL, }), }, + { + id: 'searchExperiences', + name: i18n.translate('xpack.enterpriseSearch.nav.searchExperiencesTitle', { + defaultMessage: 'Search Experiences', + }), + ...generateNavLink({ + shouldNotCreateHref: true, + to: SEARCH_EXPERIENCES_PLUGIN.URL, + }), + }, ...(productAccess.hasAppSearchAccess ? [ { @@ -135,7 +146,7 @@ export const useEnterpriseSearchNav = () => { ] : []), ], - name: i18n.translate('xpack.enterpriseSearch.nav.searchExperiencesTitle', { + name: i18n.translate('xpack.enterpriseSearch.nav.searchTitle', { defaultMessage: 'Search', }), }, diff --git a/x-pack/plugins/enterprise_search/public/assets/images/search_experiences.svg b/x-pack/plugins/enterprise_search/public/assets/images/search_experiences.svg new file mode 100644 index 0000000000000..4baea2fca1c7d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/assets/images/search_experiences.svg @@ -0,0 +1,1393 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index e9fc2d81563e6..6e35890bdcaa5 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -30,6 +30,7 @@ import { ENTERPRISE_SEARCH_CONTENT_PLUGIN, ENTERPRISE_SEARCH_OVERVIEW_PLUGIN, WORKPLACE_SEARCH_PLUGIN, + SEARCH_EXPERIENCES_PLUGIN, } from '../common/constants'; import { InitialAppData } from '../common/types'; @@ -215,6 +216,27 @@ export class EnterpriseSearchPlugin implements Plugin { }, }); + core.application.register({ + id: SEARCH_EXPERIENCES_PLUGIN.ID, + title: SEARCH_EXPERIENCES_PLUGIN.NAME, + euiIconType: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.LOGO, + appRoute: SEARCH_EXPERIENCES_PLUGIN.URL, + category: DEFAULT_APP_CATEGORIES.enterpriseSearch, + mount: async (params: AppMountParameters) => { + const kibanaDeps = await this.getKibanaDeps(core, params, cloud); + const { chrome, http } = kibanaDeps.core; + chrome.docTitle.change(SEARCH_EXPERIENCES_PLUGIN.NAME); + + await this.getInitialData(http); + const pluginData = this.getPluginData(); + + const { renderApp } = await import('./applications'); + const { SearchExperiences } = await import('./applications/search_experiences'); + + return renderApp(SearchExperiences, kibanaDeps, pluginData); + }, + }); + if (plugins.home) { plugins.home.featureCatalogue.registerSolution({ id: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.ID, @@ -266,6 +288,16 @@ export class EnterpriseSearchPlugin implements Plugin { category: 'data', showOnHomePage: false, }); + + plugins.home.featureCatalogue.register({ + id: SEARCH_EXPERIENCES_PLUGIN.ID, + title: SEARCH_EXPERIENCES_PLUGIN.NAME, + icon: 'logoEnterpriseSearch', + description: SEARCH_EXPERIENCES_PLUGIN.DESCRIPTION, + path: SEARCH_EXPERIENCES_PLUGIN.URL, + category: 'data', + showOnHomePage: false, + }); } } diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts index 90620af30f6b8..436e412958178 100644 --- a/x-pack/plugins/enterprise_search/server/plugin.ts +++ b/x-pack/plugins/enterprise_search/server/plugin.ts @@ -30,6 +30,7 @@ import { ANALYTICS_PLUGIN, APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN, + SEARCH_EXPERIENCES_PLUGIN, ENTERPRISE_SEARCH_RELEVANCE_LOGS_SOURCE_ID, ENTERPRISE_SEARCH_AUDIT_LOGS_SOURCE_ID, ENTERPRISE_SEARCH_ANALYTICS_LOGS_SOURCE_ID, @@ -110,6 +111,7 @@ export class EnterpriseSearchPlugin implements Plugin { ANALYTICS_PLUGIN.ID, APP_SEARCH_PLUGIN.ID, WORKPLACE_SEARCH_PLUGIN.ID, + SEARCH_EXPERIENCES_PLUGIN.ID, ]; if (customIntegrations) { @@ -158,6 +160,7 @@ export class EnterpriseSearchPlugin implements Plugin { elasticsearch: showEnterpriseSearch, appSearch: hasAppSearchAccess, workplaceSearch: hasWorkplaceSearchAccess, + searchExperiences: showEnterpriseSearch, }, catalogue: { enterpriseSearch: showEnterpriseSearch, @@ -166,6 +169,7 @@ export class EnterpriseSearchPlugin implements Plugin { elasticsearch: showEnterpriseSearch, appSearch: hasAppSearchAccess, workplaceSearch: hasWorkplaceSearchAccess, + searchExperiences: showEnterpriseSearch, }, }; }); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index f77bd0fd05796..faacde782d509 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -11678,7 +11678,7 @@ "xpack.enterpriseSearch.nav.contentTitle": "Contenu", "xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch", "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "Aperçu", - "xpack.enterpriseSearch.nav.searchExperiencesTitle": "Recherche", + "xpack.enterpriseSearch.nav.searchTitle": "Recherche", "xpack.enterpriseSearch.nav.searchIndicesTitle": "Index", "xpack.enterpriseSearch.nav.workplaceSearchTitle": "Workplace Search", "xpack.enterpriseSearch.notFound.action1": "Retour à votre tableau de bord", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3da599c4ebd22..59e637a55f71c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -11664,7 +11664,7 @@ "xpack.enterpriseSearch.nav.contentTitle": "コンテンツ", "xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch", "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "概要", - "xpack.enterpriseSearch.nav.searchExperiencesTitle": "検索", + "xpack.enterpriseSearch.nav.searchTitle": "検索", "xpack.enterpriseSearch.nav.searchIndicesTitle": "インデックス", "xpack.enterpriseSearch.nav.workplaceSearchTitle": "Workplace Search", "xpack.enterpriseSearch.notFound.action1": "ダッシュボードに戻す", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c595b967b9e54..921639d7cdab4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -11683,7 +11683,7 @@ "xpack.enterpriseSearch.nav.contentTitle": "内容", "xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch", "xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "概览", - "xpack.enterpriseSearch.nav.searchExperiencesTitle": "搜索", + "xpack.enterpriseSearch.nav.searchTitle": "搜索", "xpack.enterpriseSearch.nav.searchIndicesTitle": "索引", "xpack.enterpriseSearch.nav.workplaceSearchTitle": "Workplace Search", "xpack.enterpriseSearch.notFound.action1": "返回到您的仪表板", diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts index 3791de8e8d7ef..2844483018e10 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts @@ -68,6 +68,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) { 'elasticsearch', 'appSearch', 'workplaceSearch', + 'searchExperiences', 'spaces', ...esFeatureExceptions, ]; @@ -94,6 +95,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) { 'elasticsearch', 'appSearch', 'workplaceSearch', + 'searchExperiences', 'spaces', ...esFeatureExceptions, ]; diff --git a/x-pack/test/ui_capabilities/spaces_only/tests/catalogue.ts b/x-pack/test/ui_capabilities/spaces_only/tests/catalogue.ts index c2f6087420e26..4b585e4b95c81 100644 --- a/x-pack/test/ui_capabilities/spaces_only/tests/catalogue.ts +++ b/x-pack/test/ui_capabilities/spaces_only/tests/catalogue.ts @@ -32,6 +32,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) { 'elasticsearch', 'appSearch', 'workplaceSearch', + 'searchExperiences', ]; describe('catalogue', () => { From 84d3f3857da0654031652ffca8cdc0d90877eb94 Mon Sep 17 00:00:00 2001 From: Rudolf Meijering Date: Wed, 2 Nov 2022 14:17:02 +0100 Subject: [PATCH 10/86] Background task instances wait for another instance to complete the migration (#143135) * Add WAIT_FOR_MIGRATION_COMPLETION step to migrations to wait for another instance * WIP set waitForMigrationCompletion based on node roles * Fix bazel deps * NodeService tests * Additional tests * Fix tests and types * Fix tests * migrations integration test * Address review feedback * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * In WAIT_FOR_MIGRATION_COMPLETION skip to DONE if migration complete * Fix bug and add more tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../node/core-node-server-internal/index.ts | 2 +- .../src/node_service.test.ts | 58 ++ .../src/node_service.ts | 31 +- .../src/node_service.mock.ts | 27 +- .../src/README.md | 27 +- ...grations_state_action_machine.test.ts.snap | 6 + .../src/initial_state.test.ts | 33 + .../src/initial_state.ts | 3 + .../src/kibana_migrator.test.ts | 1 + .../src/kibana_migrator.ts | 6 +- .../migrations_state_action_machine.test.ts | 1 + .../src/model/helpers.test.ts | 37 + .../src/model/helpers.ts | 22 +- .../src/model/model.test.ts | 766 ++++++++++++++---- .../src/model/model.ts | 105 ++- .../src/next.ts | 3 + .../src/run_resilient_migrator.ts | 3 + .../src/state.ts | 7 + .../BUILD.bazel | 1 + .../src/saved_objects_service.test.ts | 77 ++ .../src/saved_objects_service.ts | 11 +- .../wait_for_migration_completion.test.ts | 154 ++++ src/core/server/server.ts | 1 + .../background_tasks.config.ts | 2 +- .../background_tasks/initializer_context.ts | 2 +- 25 files changed, 1170 insertions(+), 216 deletions(-) create mode 100644 src/core/server/integration_tests/saved_objects/migrations/wait_for_migration_completion.test.ts diff --git a/packages/core/node/core-node-server-internal/index.ts b/packages/core/node/core-node-server-internal/index.ts index 61c0356b6cfd6..4868d9995f209 100644 --- a/packages/core/node/core-node-server-internal/index.ts +++ b/packages/core/node/core-node-server-internal/index.ts @@ -9,4 +9,4 @@ export { nodeConfig } from './src/node_config'; export { NodeService, type PrebootDeps } from './src/node_service'; -export type { InternalNodeServicePreboot } from './src/node_service'; +export type { InternalNodeServicePreboot, InternalNodeServiceStart } from './src/node_service'; diff --git a/packages/core/node/core-node-server-internal/src/node_service.test.ts b/packages/core/node/core-node-server-internal/src/node_service.test.ts index a707e8ec1aed8..f25737ded31bb 100644 --- a/packages/core/node/core-node-server-internal/src/node_service.test.ts +++ b/packages/core/node/core-node-server-internal/src/node_service.test.ts @@ -117,4 +117,62 @@ describe('NodeService', () => { }); }); }); + describe('#start()', () => { + it('returns default roles values when wildcard is provided', async () => { + configService = getMockedConfigService({ roles: ['*'] }); + coreContext = mockCoreContext.create({ logger, configService }); + + service = new NodeService(coreContext); + await service.preboot({ loggingSystem: logger }); + const { roles } = service.start(); + + expect(roles.backgroundTasks).toBe(true); + expect(roles.ui).toBe(true); + }); + + it('returns correct roles when node is configured to `background_tasks`', async () => { + configService = getMockedConfigService({ roles: ['background_tasks'] }); + coreContext = mockCoreContext.create({ logger, configService }); + + service = new NodeService(coreContext); + await service.preboot({ loggingSystem: logger }); + const { roles } = service.start(); + + expect(roles.backgroundTasks).toBe(true); + expect(roles.ui).toBe(false); + }); + + it('returns correct roles when node is configured to `ui`', async () => { + configService = getMockedConfigService({ roles: ['ui'] }); + coreContext = mockCoreContext.create({ logger, configService }); + + service = new NodeService(coreContext); + await service.preboot({ loggingSystem: logger }); + const { roles } = service.start(); + + expect(roles.backgroundTasks).toBe(false); + expect(roles.ui).toBe(true); + }); + + it('returns correct roles when node is configured to both `background_tasks` and `ui`', async () => { + configService = getMockedConfigService({ roles: ['background_tasks', 'ui'] }); + coreContext = mockCoreContext.create({ logger, configService }); + + service = new NodeService(coreContext); + await service.preboot({ loggingSystem: logger }); + const { roles } = service.start(); + + expect(roles.backgroundTasks).toBe(true); + expect(roles.ui).toBe(true); + }); + it('throws if preboot has not been run', () => { + configService = getMockedConfigService({ roles: ['background_tasks', 'ui'] }); + coreContext = mockCoreContext.create({ logger, configService }); + + service = new NodeService(coreContext); + expect(() => service.start()).toThrowErrorMatchingInlineSnapshot( + `"NodeService#start() can only be called after NodeService#preboot()"` + ); + }); + }); }); diff --git a/packages/core/node/core-node-server-internal/src/node_service.ts b/packages/core/node/core-node-server-internal/src/node_service.ts index fb4ee57c41cbe..b5c5c0a8b4c17 100644 --- a/packages/core/node/core-node-server-internal/src/node_service.ts +++ b/packages/core/node/core-node-server-internal/src/node_service.ts @@ -28,7 +28,20 @@ const containsWildcard = (roles: string[]) => roles.includes(NODE_WILDCARD_CHAR) */ export interface InternalNodeServicePreboot { /** - * Retrieve the Kibana instance uuid. + * The Kibana process can take on specialised roles via the `node.roles` config. + * + * The roles can be used by plugins to adjust their behavior based + * on the way the Kibana process has been configured. + */ + roles: NodeRoles; +} + +export interface InternalNodeServiceStart { + /** + * The Kibana process can take on specialised roles via the `node.roles` config. + * + * The roles can be used by plugins to adjust their behavior based + * on the way the Kibana process has been configured. */ roles: NodeRoles; } @@ -41,6 +54,7 @@ export interface PrebootDeps { export class NodeService { private readonly configService: IConfigService; private readonly log: Logger; + private roles?: NodeRoles; constructor(core: CoreContext) { this.configService = core.configService; @@ -52,13 +66,22 @@ export class NodeService { loggingSystem.setGlobalContext({ service: { node: { roles } } }); this.log.info(`Kibana process configured with roles: [${roles.join(', ')}]`); + this.roles = NODE_ACCEPTED_ROLES.reduce((acc, curr) => { + return { ...acc, [camelCase(curr)]: roles.includes(curr) }; + }, {} as NodeRoles); + return { - roles: NODE_ACCEPTED_ROLES.reduce((acc, curr) => { - return { ...acc, [camelCase(curr)]: roles.includes(curr) }; - }, {} as NodeRoles), + roles: this.roles, }; } + public start(): InternalNodeServiceStart { + if (this.roles == null) { + throw new Error('NodeService#start() can only be called after NodeService#preboot()'); + } + return { roles: this.roles }; + } + public stop() { // nothing to do here yet } diff --git a/packages/core/node/core-node-server-mocks/src/node_service.mock.ts b/packages/core/node/core-node-server-mocks/src/node_service.mock.ts index 6ece68ebd1b8f..ff354b663c231 100644 --- a/packages/core/node/core-node-server-mocks/src/node_service.mock.ts +++ b/packages/core/node/core-node-server-mocks/src/node_service.mock.ts @@ -7,7 +7,11 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import type { NodeService, InternalNodeServicePreboot } from '@kbn/core-node-server-internal'; +import type { + NodeService, + InternalNodeServicePreboot, + InternalNodeServiceStart, +} from '@kbn/core-node-server-internal'; const createInternalPrebootContractMock = () => { const prebootContract: jest.Mocked = { @@ -19,17 +23,38 @@ const createInternalPrebootContractMock = () => { return prebootContract; }; +const createInternalStartContractMock = ( + { + ui, + backgroundTasks, + }: { + ui: boolean; + backgroundTasks: boolean; + } = { ui: true, backgroundTasks: true } +) => { + const startContract: jest.Mocked = { + roles: { + backgroundTasks, + ui, + }, + }; + return startContract; +}; + type NodeServiceContract = PublicMethodsOf; const createMock = () => { const mocked: jest.Mocked = { preboot: jest.fn(), + start: jest.fn(), stop: jest.fn(), }; mocked.preboot.mockResolvedValue(createInternalPrebootContractMock()); + mocked.start.mockReturnValue(createInternalStartContractMock()); return mocked; }; export const nodeServiceMock = { create: createMock, createInternalPrebootContract: createInternalPrebootContractMock, + createInternalStartContract: createInternalStartContractMock, }; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md index 12d3b2d490583..52e79edcf8b7d 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md @@ -167,14 +167,19 @@ the same version could have plugins enabled at any time that would introduce new transforms or mappings. → `OUTDATED_DOCUMENTS_SEARCH` -3. If the `.kibana` alias exists we’re migrating from either a v1 or v2 index +3. If `waitForMigrations` was set we're running on a background-tasks node and +we should not participate in the migration but instead wait for the ui node(s) +to complete the migration. + → `WAIT_FOR_MIGRATION_COMPLETION` + +4. If the `.kibana` alias exists we’re migrating from either a v1 or v2 index and the migration source index is the index the `.kibana` alias points to. → `WAIT_FOR_YELLOW_SOURCE` -4. If `.kibana` is a concrete index, we’re migrating from a legacy index +5. If `.kibana` is a concrete index, we’re migrating from a legacy index → `LEGACY_SET_WRITE_BLOCK` -5. If there are no `.kibana` indices, this is a fresh deployment. Initialize a +6. If there are no `.kibana` indices, this is a fresh deployment. Initialize a new saved objects index → `CREATE_NEW_TARGET` @@ -259,6 +264,15 @@ new `.kibana` alias that points to `.kibana_pre6.5.0_001`. `index_not_found_exception` another instance has already completed this step. → `SET_SOURCE_WRITE_BLOCK` +## WAIT_FOR_MIGRATION_COMPLETION +### Next action +`fetchIndices` +### New control state +1. If the ui node finished the migration + → `DONE` +2. Otherwise wait 2s and check again + → WAIT_FOR_MIGRATION_COMPLETION + ## WAIT_FOR_YELLOW_SOURCE ### Next action `waitForIndexStatus` (status='yellow') @@ -417,6 +431,13 @@ update the mappings and then use an update_by_query to ensure that all fields ar ## UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK ### Next action +`waitForPickupUpdatedMappingsTask` + +### New control state + → `MARK_VERSION_INDEX_READY` + +## MARK_VERSION_INDEX_READY +### Next action `updateAliases` Atomically apply the `versionIndexReadyActions` using the _alias actions API. By performing the following actions we guarantee that if multiple versions of Kibana started the upgrade in parallel, only one version will succeed. diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap index 39ff3c4c5700a..4e19c9bf62690 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap @@ -178,6 +178,7 @@ Object { "transformedDocBatches": Array [], "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", + "waitForMigrationCompletion": false, }, }, }, @@ -362,6 +363,7 @@ Object { "transformedDocBatches": Array [], "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", + "waitForMigrationCompletion": false, }, }, }, @@ -550,6 +552,7 @@ Object { "transformedDocBatches": Array [], "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", + "waitForMigrationCompletion": false, }, }, }, @@ -742,6 +745,7 @@ Object { "transformedDocBatches": Array [], "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", + "waitForMigrationCompletion": false, }, }, }, @@ -971,6 +975,7 @@ Object { ], "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", + "waitForMigrationCompletion": false, }, }, }, @@ -1166,6 +1171,7 @@ Object { ], "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", + "waitForMigrationCompletion": false, }, }, }, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts index d1c19aa7d212e..35d22e69f724e 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts @@ -40,6 +40,7 @@ describe('createInitialState', () => { expect( createInitialState({ kibanaVersion: '8.1.0', + waitForMigrationCompletion: true, targetMappings: { dynamic: 'strict', properties: { my_type: { properties: { title: { type: 'text' } } } }, @@ -216,10 +217,32 @@ describe('createInitialState', () => { }, "versionAlias": ".kibana_task_manager_8.1.0", "versionIndex": ".kibana_task_manager_8.1.0_001", + "waitForMigrationCompletion": true, } `); }); + it('creates the initial state for the model with waitForMigrationCompletion false,', () => { + expect( + createInitialState({ + kibanaVersion: '8.1.0', + waitForMigrationCompletion: false, + targetMappings: { + dynamic: 'strict', + properties: { my_type: { properties: { title: { type: 'text' } } } }, + }, + migrationVersionPerType: {}, + indexPrefix: '.kibana_task_manager', + migrationsConfig, + typeRegistry, + docLinks, + logger: mockLogger.get(), + }) + ).toMatchObject({ + waitForMigrationCompletion: false, + }); + }); + it('returns state with the correct `knownTypes`', () => { typeRegistry.registerType({ name: 'foo', @@ -236,6 +259,7 @@ describe('createInitialState', () => { const initialState = createInitialState({ kibanaVersion: '8.1.0', + waitForMigrationCompletion: false, targetMappings: { dynamic: 'strict', properties: { my_type: { properties: { title: { type: 'text' } } } }, @@ -263,6 +287,7 @@ describe('createInitialState', () => { const initialState = createInitialState({ kibanaVersion: '8.1.0', + waitForMigrationCompletion: false, targetMappings: { dynamic: 'strict', properties: { my_type: { properties: { title: { type: 'text' } } } }, @@ -282,6 +307,7 @@ describe('createInitialState', () => { const preMigrationScript = "ctx._id = ctx._source.type + ':' + ctx._id"; const initialState = createInitialState({ kibanaVersion: '8.1.0', + waitForMigrationCompletion: false, targetMappings: { dynamic: 'strict', properties: { my_type: { properties: { title: { type: 'text' } } } }, @@ -305,6 +331,7 @@ describe('createInitialState', () => { Option.isNone( createInitialState({ kibanaVersion: '8.1.0', + waitForMigrationCompletion: false, targetMappings: { dynamic: 'strict', properties: { my_type: { properties: { title: { type: 'text' } } } }, @@ -324,6 +351,7 @@ describe('createInitialState', () => { expect( createInitialState({ kibanaVersion: '8.1.0', + waitForMigrationCompletion: false, targetMappings: { dynamic: 'strict', properties: { my_type: { properties: { title: { type: 'text' } } } }, @@ -378,6 +406,7 @@ describe('createInitialState', () => { const logger = mockLogger.get(); const initialState = createInitialState({ kibanaVersion: '8.1.0', + waitForMigrationCompletion: false, targetMappings: { dynamic: 'strict', properties: { my_type: { properties: { title: { type: 'text' } } } }, @@ -398,6 +427,7 @@ describe('createInitialState', () => { const logger = mockLogger.get(); const initialState = createInitialState({ kibanaVersion: '8.1.0', + waitForMigrationCompletion: false, targetMappings: { dynamic: 'strict', properties: { my_type: { properties: { title: { type: 'text' } } } }, @@ -423,6 +453,7 @@ describe('createInitialState', () => { it('initializes the `discardUnknownObjects` flag to true if the value provided in the config matches the current kibana version', () => { const initialState = createInitialState({ kibanaVersion: '8.1.0', + waitForMigrationCompletion: false, targetMappings: { dynamic: 'strict', properties: { my_type: { properties: { title: { type: 'text' } } } }, @@ -445,6 +476,7 @@ describe('createInitialState', () => { const logger = mockLogger.get(); const initialState = createInitialState({ kibanaVersion: '8.1.0', + waitForMigrationCompletion: false, targetMappings: { dynamic: 'strict', properties: { my_type: { properties: { title: { type: 'text' } } } }, @@ -470,6 +502,7 @@ describe('createInitialState', () => { it('initializes the `discardCorruptObjects` flag to true if the value provided in the config matches the current kibana version', () => { const initialState = createInitialState({ kibanaVersion: '8.1.0', + waitForMigrationCompletion: false, targetMappings: { dynamic: 'strict', properties: { my_type: { properties: { title: { type: 'text' } } } }, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts index 1843227934bcd..72eae8e188094 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts @@ -23,6 +23,7 @@ import { excludeUnusedTypesQuery } from './core'; */ export const createInitialState = ({ kibanaVersion, + waitForMigrationCompletion, targetMappings, preMigrationScript, migrationVersionPerType, @@ -33,6 +34,7 @@ export const createInitialState = ({ logger, }: { kibanaVersion: string; + waitForMigrationCompletion: boolean; targetMappings: IndexMapping; preMigrationScript?: string; migrationVersionPerType: SavedObjectsMigrationVersion; @@ -95,6 +97,7 @@ export const createInitialState = ({ return { controlState: 'INIT', + waitForMigrationCompletion, indexPrefix, legacyIndex: indexPrefix, currentAlias: indexPrefix, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts index 5d1cb32eca6d2..dc5addb1624b8 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts @@ -253,6 +253,7 @@ const mockOptions = () => { const options: MockedOptions = { logger: loggingSystemMock.create().get(), kibanaVersion: '8.2.3', + waitForMigrationCompletion: false, typeRegistry: createRegistry([ { name: 'testtype', diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts index 1d8d766d8ac88..837c2a47bea58 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts @@ -45,6 +45,7 @@ export interface KibanaMigratorOptions { kibanaVersion: string; logger: Logger; docLinks: DocLinksServiceStart; + waitForMigrationCompletion: boolean; } /** @@ -65,7 +66,7 @@ export class KibanaMigrator implements IKibanaMigrator { private readonly activeMappings: IndexMapping; private readonly soMigrationsConfig: SavedObjectsMigrationConfigType; private readonly docLinks: DocLinksServiceStart; - + private readonly waitForMigrationCompletion: boolean; public readonly kibanaVersion: string; /** @@ -79,6 +80,7 @@ export class KibanaMigrator implements IKibanaMigrator { kibanaVersion, logger, docLinks, + waitForMigrationCompletion, }: KibanaMigratorOptions) { this.client = client; this.kibanaIndex = kibanaIndex; @@ -93,6 +95,7 @@ export class KibanaMigrator implements IKibanaMigrator { typeRegistry, log: this.log, }); + this.waitForMigrationCompletion = waitForMigrationCompletion; // Building the active mappings (and associated md5sums) is an expensive // operation so we cache the result this.activeMappings = buildActiveMappings(this.mappingProperties); @@ -148,6 +151,7 @@ export class KibanaMigrator implements IKibanaMigrator { return runResilientMigrator({ client: this.client, kibanaVersion: this.kibanaVersion, + waitForMigrationCompletion: this.waitForMigrationCompletion, targetMappings: buildActiveMappings(indexMap[index].typeMappings), logger: this.log, preMigrationScript: indexMap[index].script, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts index 55b0a53d8f807..255a26275b6e8 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts @@ -39,6 +39,7 @@ describe('migrationsStateActionMachine', () => { const initialState = createInitialState({ kibanaVersion: '7.11.0', + waitForMigrationCompletion: false, targetMappings: { properties: {} }, migrationVersionPerType: {}, indexPrefix: '.my-so-index', diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.test.ts index b9756d46237ed..c364e053c1ff6 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.test.ts @@ -12,6 +12,7 @@ import { addMustClausesToBoolQuery, addMustNotClausesToBoolQuery, getAliases, + versionMigrationCompleted, } from './helpers'; describe('addExcludedTypesToBoolQuery', () => { @@ -230,3 +231,39 @@ describe('getAliases', () => { `); }); }); + +describe('versionMigrationCompleted', () => { + it('returns true if the current and version alias points to the same index', () => { + expect( + versionMigrationCompleted('.current-alias', '.version-alias', { + '.current-alias': 'myindex', + '.version-alias': 'myindex', + }) + ).toBe(true); + }); + it('returns false if the current and version alias does not point to the same index', () => { + expect( + versionMigrationCompleted('.current-alias', '.version-alias', { + '.current-alias': 'myindex', + '.version-alias': 'anotherindex', + }) + ).toBe(false); + }); + it('returns false if the current alias does not exist', () => { + expect( + versionMigrationCompleted('.current-alias', '.version-alias', { + '.version-alias': 'myindex', + }) + ).toBe(false); + }); + it('returns false if the version alias does not exist', () => { + expect( + versionMigrationCompleted('.current-alias', '.version-alias', { + '.current-alias': 'myindex', + }) + ).toBe(false); + }); + it('returns false if neither the version or current alias exists', () => { + expect(versionMigrationCompleted('.current-alias', '.version-alias', {})).toBe(false); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts index 5f84dc01af008..f7377401c16bf 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts @@ -68,6 +68,19 @@ export function mergeMigrationMappingPropertyHashes( }; } +/** + * If `.kibana` and the version specific aliases both exists and + * are pointing to the same index. This version's migration has already + * been completed. + */ +export function versionMigrationCompleted( + currentAlias: string, + versionAlias: string, + aliases: Record +): boolean { + return aliases[currentAlias] != null && aliases[currentAlias] === aliases[versionAlias]; +} + export function indexBelongsToLaterVersion(indexName: string, kibanaVersion: string): boolean { const version = valid(indexVersion(indexName)); return version != null ? gt(version, kibanaVersion) : false; @@ -157,16 +170,17 @@ export function getAliases( indices: FetchIndexResponse ): Either.Either< { type: 'multiple_indices_per_alias'; alias: string; indices: string[] }, - Record + Record > { - const aliases = {} as Record; + const aliases = {} as Record; for (const index of Object.getOwnPropertyNames(indices)) { for (const alias of Object.getOwnPropertyNames(indices[index].aliases || {})) { - if (aliases[alias] != null) { + const secondIndexThisAliasPointsTo = aliases[alias]; + if (secondIndexThisAliasPointsTo != null) { return Either.left({ type: 'multiple_indices_per_alias', alias, - indices: [aliases[alias], index], + indices: [secondIndexThisAliasPointsTo, index], }); } aliases[alias] = index; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts index 67b7e40dc5aff..cb446b952e5ec 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts @@ -102,6 +102,7 @@ describe('migrations v2 model', () => { routingAllocationDisabled: 'routingAllocationDisabled', clusterShardLimitExceeded: 'clusterShardLimitExceeded', }, + waitForMigrationCompletion: false, }; describe('exponential retry delays for retryable_es_client_error', () => { @@ -222,13 +223,14 @@ describe('migrations v2 model', () => { }); describe('INIT', () => { - const initState: State = { + const initBaseState: State = { ...baseState, controlState: 'INIT', currentAlias: '.kibana', versionAlias: '.kibana_7.11.0', versionIndex: '.kibana_7.11.0_001', }; + const mappingsWithUnknownType = { properties: { disabled_saved_object_type: { @@ -244,110 +246,560 @@ describe('migrations v2 model', () => { }, } as const; - test('INIT -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT if .kibana is already pointing to the target index', () => { - const res: ResponseType<'INIT'> = Either.right({ - '.kibana_7.11.0_001': { - aliases: { - '.kibana': {}, - '.kibana_7.11.0': {}, + describe('if waitForMigrationCompletion=true', () => { + const initState = Object.assign({}, initBaseState, { + waitForMigrationCompletion: true, + }); + test('INIT -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT if .kibana is already pointing to the target index', () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_7.11.0_001': { + aliases: { + '.kibana': {}, + '.kibana_7.11.0': {}, + }, + mappings: mappingsWithUnknownType, + settings: {}, }, - mappings: mappingsWithUnknownType, - settings: {}, - }, + }); + const newState = model(initState, res); + + expect(newState.controlState).toEqual('OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT'); + // This snapshot asserts that we merge the + // migrationMappingPropertyHashes of the existing index, but we leave + // the mappings for the disabled_saved_object_type untouched. There + // might be another Kibana instance that knows about this type and + // needs these mappings in place. + expect(newState.targetIndexMappings).toMatchInlineSnapshot(` + Object { + "_meta": Object { + "migrationMappingPropertyHashes": Object { + "disabled_saved_object_type": "7997cf5a56cc02bdc9c93361bde732b0", + "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", + }, + }, + "properties": Object { + "new_saved_object_type": Object { + "properties": Object { + "value": Object { + "type": "text", + }, + }, + }, + }, + } + `); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); }); - const newState = model(initState, res); + test('INIT -> INIT when cluster routing allocation is incompatible', () => { + const res: ResponseType<'INIT'> = Either.left({ + type: 'incompatible_cluster_routing_allocation', + }); + const newState = model(initState, res) as FatalState; - expect(newState.controlState).toEqual('OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT'); - // This snapshot asserts that we merge the - // migrationMappingPropertyHashes of the existing index, but we leave - // the mappings for the disabled_saved_object_type untouched. There - // might be another Kibana instance that knows about this type and - // needs these mappings in place. - expect(newState.targetIndexMappings).toMatchInlineSnapshot(` - Object { - "_meta": Object { - "migrationMappingPropertyHashes": Object { - "disabled_saved_object_type": "7997cf5a56cc02bdc9c93361bde732b0", - "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", + expect(newState.controlState).toEqual('INIT'); + expect(newState.retryCount).toEqual(1); + expect(newState.retryDelay).toEqual(2000); + expect(newState.logs[0]).toMatchInlineSnapshot(` + Object { + "level": "error", + "message": "Action failed with '[incompatible_cluster_routing_allocation] Incompatible Elasticsearch cluster settings detected. Remove the persistent and transient Elasticsearch cluster setting 'cluster.routing.allocation.enable' or set it to a value of 'all' to allow migrations to proceed. Refer to routingAllocationDisabled for more information on how to resolve the issue.'. Retrying attempt 1 in 2 seconds.", + } + `); + }); + test("INIT -> FATAL when .kibana points to newer version's index", () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_7.12.0_001': { + aliases: { + '.kibana': {}, + '.kibana_7.12.0': {}, }, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, }, - "properties": Object { - "new_saved_object_type": Object { - "properties": Object { - "value": Object { - "type": "text", + '.kibana_7.11.0_001': { + aliases: { '.kibana_7.11.0': {} }, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, + }); + const newState = model(initState, res) as FatalState; + + expect(newState.controlState).toEqual('FATAL'); + expect(newState.reason).toMatchInlineSnapshot( + `"The .kibana alias is pointing to a newer version of Kibana: v7.12.0"` + ); + }); + test('INIT -> FATAL when .kibana points to multiple indices', () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_7.12.0_001': { + aliases: { + '.kibana': {}, + '.kibana_7.12.0': {}, + }, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, + '.kibana_7.11.0_001': { + aliases: { '.kibana': {}, '.kibana_7.11.0': {} }, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, + }); + const newState = model(initState, res) as FatalState; + + expect(newState.controlState).toEqual('FATAL'); + expect(newState.reason).toMatchInlineSnapshot( + `"The .kibana alias is pointing to multiple indices: .kibana_7.12.0_001,.kibana_7.11.0_001."` + ); + }); + test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when .kibana points to an index with an invalid version', () => { + // If users tamper with our index version naming scheme we can no + // longer accurately detect a newer version. Older Kibana versions + // will have indices like `.kibana_10` and users might choose an + // invalid name when restoring from a snapshot. So we try to be + // lenient and assume it's an older index and perform a migration. + // If the tampered index belonged to a newer version the migration + // will fail when we start transforming documents. + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_7.invalid.0_001': { + aliases: { + '.kibana': {}, + '.kibana_7.12.0': {}, + }, + mappings: mappingsWithUnknownType, + settings: {}, + }, + '.kibana_7.11.0_001': { + aliases: { '.kibana_7.11.0': {} }, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, + }); + const newState = model(initState, res) as WaitForYellowSourceState; + + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toBe(2000); + }); + test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a v2 migrations index (>= 7.11.0)', () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_7.11.0_001': { + aliases: { '.kibana': {}, '.kibana_7.11.0': {} }, + mappings: mappingsWithUnknownType, + settings: {}, + }, + '.kibana_3': { + aliases: {}, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, + }); + const newState = model( + { + ...initState, + ...{ + kibanaVersion: '7.12.0', + versionAlias: '.kibana_7.12.0', + versionIndex: '.kibana_7.12.0_001', + }, + }, + res + ) as WaitForYellowSourceState; + + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toEqual(2000); + }); + test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a v1 migrations index (>= 6.5 < 7.11.0)', () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_3': { + aliases: { + '.kibana': {}, + }, + mappings: mappingsWithUnknownType, + settings: {}, + }, + }); + const newState = model(initState, res) as WaitForYellowSourceState; + + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toEqual(2000); + }); + test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a legacy index (>= 6.0.0 < 6.5)', () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana': { + aliases: {}, + mappings: mappingsWithUnknownType, + settings: {}, + }, + }); + const newState = model(initState, res); + + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toEqual(2000); + }); + test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a custom kibana.index name (>= 6.5 < 7.11.0)', () => { + const res: ResponseType<'INIT'> = Either.right({ + 'my-saved-objects_3': { + aliases: { + 'my-saved-objects': {}, + }, + mappings: mappingsWithUnknownType, + settings: {}, + }, + }); + const newState = model( + { + ...initState, + controlState: 'INIT', + currentAlias: 'my-saved-objects', + versionAlias: 'my-saved-objects_7.11.0', + versionIndex: 'my-saved-objects_7.11.0_001', + }, + res + ) as WaitForYellowSourceState; + + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toEqual(2000); + }); + test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a custom kibana.index v2 migrations index (>= 7.11.0)', () => { + const res: ResponseType<'INIT'> = Either.right({ + 'my-saved-objects_7.11.0': { + aliases: { + 'my-saved-objects': {}, + }, + mappings: mappingsWithUnknownType, + settings: {}, + }, + }); + const newState = model( + { + ...initState, + controlState: 'INIT', + kibanaVersion: '7.12.0', + currentAlias: 'my-saved-objects', + versionAlias: 'my-saved-objects_7.12.0', + versionIndex: 'my-saved-objects_7.12.0_001', + }, + res + ) as WaitForYellowSourceState; + + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toEqual(2000); + }); + test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when no indices/aliases exist', () => { + const res: ResponseType<'INIT'> = Either.right({}); + const newState = model(initState, res); + + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toEqual(2000); + }); + }); + describe('if waitForMigrationCompletion=false', () => { + const initState = Object.assign({}, initBaseState, { + waitForMigrationCompletion: false, + }); + test('INIT -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT if .kibana is already pointing to the target index', () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_7.11.0_001': { + aliases: { + '.kibana': {}, + '.kibana_7.11.0': {}, + }, + mappings: mappingsWithUnknownType, + settings: {}, + }, + }); + const newState = model(initState, res); + + expect(newState.controlState).toEqual('OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT'); + // This snapshot asserts that we merge the + // migrationMappingPropertyHashes of the existing index, but we leave + // the mappings for the disabled_saved_object_type untouched. There + // might be another Kibana instance that knows about this type and + // needs these mappings in place. + expect(newState.targetIndexMappings).toMatchInlineSnapshot(` + Object { + "_meta": Object { + "migrationMappingPropertyHashes": Object { + "disabled_saved_object_type": "7997cf5a56cc02bdc9c93361bde732b0", + "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", + }, + }, + "properties": Object { + "new_saved_object_type": Object { + "properties": Object { + "value": Object { + "type": "text", + }, }, }, }, + } + `); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); + test('INIT -> INIT when cluster routing allocation is incompatible', () => { + const res: ResponseType<'INIT'> = Either.left({ + type: 'incompatible_cluster_routing_allocation', + }); + const newState = model(initState, res) as FatalState; + + expect(newState.controlState).toEqual('INIT'); + expect(newState.retryCount).toEqual(1); + expect(newState.retryDelay).toEqual(2000); + expect(newState.logs[0]).toMatchInlineSnapshot(` + Object { + "level": "error", + "message": "Action failed with '[incompatible_cluster_routing_allocation] Incompatible Elasticsearch cluster settings detected. Remove the persistent and transient Elasticsearch cluster setting 'cluster.routing.allocation.enable' or set it to a value of 'all' to allow migrations to proceed. Refer to routingAllocationDisabled for more information on how to resolve the issue.'. Retrying attempt 1 in 2 seconds.", + } + `); + }); + test("INIT -> FATAL when .kibana points to newer version's index", () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_7.12.0_001': { + aliases: { + '.kibana': {}, + '.kibana_7.12.0': {}, + }, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, }, - } - `); - expect(newState.retryCount).toEqual(0); - expect(newState.retryDelay).toEqual(0); - }); - test('INIT -> INIT when cluster routing allocation is incompatible', () => { - const res: ResponseType<'INIT'> = Either.left({ - type: 'incompatible_cluster_routing_allocation', + '.kibana_7.11.0_001': { + aliases: { '.kibana_7.11.0': {} }, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, + }); + const newState = model(initState, res) as FatalState; + + expect(newState.controlState).toEqual('FATAL'); + expect(newState.reason).toMatchInlineSnapshot( + `"The .kibana alias is pointing to a newer version of Kibana: v7.12.0"` + ); + }); + test('INIT -> FATAL when .kibana points to multiple indices', () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_7.12.0_001': { + aliases: { + '.kibana': {}, + '.kibana_7.12.0': {}, + }, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, + '.kibana_7.11.0_001': { + aliases: { '.kibana': {}, '.kibana_7.11.0': {} }, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, + }); + const newState = model(initState, res) as FatalState; + + expect(newState.controlState).toEqual('FATAL'); + expect(newState.reason).toMatchInlineSnapshot( + `"The .kibana alias is pointing to multiple indices: .kibana_7.12.0_001,.kibana_7.11.0_001."` + ); + }); + test('INIT -> WAIT_FOR_YELLOW_SOURCE when .kibana points to an index with an invalid version', () => { + // If users tamper with our index version naming scheme we can no + // longer accurately detect a newer version. Older Kibana versions + // will have indices like `.kibana_10` and users might choose an + // invalid name when restoring from a snapshot. So we try to be + // lenient and assume it's an older index and perform a migration. + // If the tampered index belonged to a newer version the migration + // will fail when we start transforming documents. + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_7.invalid.0_001': { + aliases: { + '.kibana': {}, + '.kibana_7.12.0': {}, + }, + mappings: mappingsWithUnknownType, + settings: {}, + }, + '.kibana_7.11.0_001': { + aliases: { '.kibana_7.11.0': {} }, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, + }); + const newState = model(initState, res) as WaitForYellowSourceState; + + expect(newState.controlState).toBe('WAIT_FOR_YELLOW_SOURCE'); + expect(newState.sourceIndex.value).toBe('.kibana_7.invalid.0_001'); }); - const newState = model(initState, res) as FatalState; - expect(newState.controlState).toEqual('INIT'); - expect(newState.retryCount).toEqual(1); - expect(newState.retryDelay).toEqual(2000); - expect(newState.logs[0]).toMatchInlineSnapshot(` - Object { - "level": "error", - "message": "Action failed with '[incompatible_cluster_routing_allocation] Incompatible Elasticsearch cluster settings detected. Remove the persistent and transient Elasticsearch cluster setting 'cluster.routing.allocation.enable' or set it to a value of 'all' to allow migrations to proceed. Refer to routingAllocationDisabled for more information on how to resolve the issue.'. Retrying attempt 1 in 2 seconds.", - } - `); - }); - test("INIT -> FATAL when .kibana points to newer version's index", () => { - const res: ResponseType<'INIT'> = Either.right({ - '.kibana_7.12.0_001': { - aliases: { - '.kibana': {}, - '.kibana_7.12.0': {}, + test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a v2 migrations index (>= 7.11.0)', () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_7.11.0_001': { + aliases: { '.kibana': {}, '.kibana_7.11.0': {} }, + mappings: mappingsWithUnknownType, + settings: {}, }, - mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, - settings: {}, - }, - '.kibana_7.11.0_001': { - aliases: { '.kibana_7.11.0': {} }, - mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, - settings: {}, - }, + '.kibana_3': { + aliases: {}, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, + }); + const newState = model( + { + ...initState, + ...{ + kibanaVersion: '7.12.0', + versionAlias: '.kibana_7.12.0', + versionIndex: '.kibana_7.12.0_001', + }, + }, + res + ) as WaitForYellowSourceState; + + expect(newState.controlState).toBe('WAIT_FOR_YELLOW_SOURCE'); + expect(newState.sourceIndex.value).toBe('.kibana_7.11.0_001'); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); }); - const newState = model(initState, res) as FatalState; - expect(newState.controlState).toEqual('FATAL'); - expect(newState.reason).toMatchInlineSnapshot( - `"The .kibana alias is pointing to a newer version of Kibana: v7.12.0"` - ); - }); - test('INIT -> FATAL when .kibana points to multiple indices', () => { - const res: ResponseType<'INIT'> = Either.right({ - '.kibana_7.12.0_001': { - aliases: { - '.kibana': {}, - '.kibana_7.12.0': {}, + test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a v1 migrations index (>= 6.5 < 7.11.0)', () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_3': { + aliases: { + '.kibana': {}, + }, + mappings: mappingsWithUnknownType, + settings: {}, }, - mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, - settings: {}, - }, - '.kibana_7.11.0_001': { - aliases: { '.kibana': {}, '.kibana_7.11.0': {} }, - mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, - settings: {}, - }, + }); + const newState = model(initState, res) as WaitForYellowSourceState; + + expect(newState.controlState).toBe('WAIT_FOR_YELLOW_SOURCE'); + expect(newState.sourceIndex.value).toBe('.kibana_3'); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); }); - const newState = model(initState, res) as FatalState; + test('INIT -> LEGACY_SET_WRITE_BLOCK when migrating from a legacy index (>= 6.0.0 < 6.5)', () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana': { + aliases: {}, + mappings: mappingsWithUnknownType, + settings: {}, + }, + }); + const newState = model(initState, res); - expect(newState.controlState).toEqual('FATAL'); - expect(newState.reason).toMatchInlineSnapshot( - `"The .kibana alias is pointing to multiple indices: .kibana_7.12.0_001,.kibana_7.11.0_001."` - ); + expect(newState).toMatchObject({ + controlState: 'LEGACY_SET_WRITE_BLOCK', + sourceIndex: Option.some('.kibana_pre6.5.0_001'), + targetIndex: '.kibana_7.11.0_001', + }); + // This snapshot asserts that we disable the unknown saved object + // type. Because it's mappings are disabled, we also don't copy the + // `_meta.migrationMappingPropertyHashes` for the disabled type. + expect(newState.targetIndexMappings).toMatchInlineSnapshot(` + Object { + "_meta": Object { + "migrationMappingPropertyHashes": Object { + "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", + }, + }, + "properties": Object { + "disabled_saved_object_type": Object { + "dynamic": false, + "properties": Object {}, + }, + "new_saved_object_type": Object { + "properties": Object { + "value": Object { + "type": "text", + }, + }, + }, + }, + } + `); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); + test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a custom kibana.index name (>= 6.5 < 7.11.0)', () => { + const res: ResponseType<'INIT'> = Either.right({ + 'my-saved-objects_3': { + aliases: { + 'my-saved-objects': {}, + }, + mappings: mappingsWithUnknownType, + settings: {}, + }, + }); + const newState = model( + { + ...baseState, + controlState: 'INIT', + currentAlias: 'my-saved-objects', + versionAlias: 'my-saved-objects_7.11.0', + versionIndex: 'my-saved-objects_7.11.0_001', + }, + res + ) as WaitForYellowSourceState; + + expect(newState.controlState).toBe('WAIT_FOR_YELLOW_SOURCE'); + expect(newState.sourceIndex.value).toBe('my-saved-objects_3'); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); + test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a custom kibana.index v2 migrations index (>= 7.11.0)', () => { + const res: ResponseType<'INIT'> = Either.right({ + 'my-saved-objects_7.11.0': { + aliases: { + 'my-saved-objects': {}, + }, + mappings: mappingsWithUnknownType, + settings: {}, + }, + }); + const newState = model( + { + ...baseState, + controlState: 'INIT', + kibanaVersion: '7.12.0', + currentAlias: 'my-saved-objects', + versionAlias: 'my-saved-objects_7.12.0', + versionIndex: 'my-saved-objects_7.12.0_001', + }, + res + ) as WaitForYellowSourceState; + + expect(newState.controlState).toBe('WAIT_FOR_YELLOW_SOURCE'); + expect(newState.sourceIndex.value).toBe('my-saved-objects_7.11.0'); + + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); + test('INIT -> CREATE_NEW_TARGET when no indices/aliases exist', () => { + const res: ResponseType<'INIT'> = Either.right({}); + const newState = model(initState, res); + + expect(newState).toMatchObject({ + controlState: 'CREATE_NEW_TARGET', + sourceIndex: Option.none, + targetIndex: '.kibana_7.11.0_001', + }); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); }); - test('INIT -> WAIT_FOR_YELLOW_SOURCE when .kibana points to an index with an invalid version', () => { + }); + + describe('WAIT_FOR_MIGRATION_COMPLETION', () => { + const waitForMState: State = { + ...baseState, + controlState: 'WAIT_FOR_MIGRATION_COMPLETION', + currentAlias: '.kibana', + versionAlias: '.kibana_7.11.0', + versionIndex: '.kibana_7.11.0_001', + }; + + test('WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION when .kibana points to an index with an invalid version', () => { // If users tamper with our index version naming scheme we can no // longer accurately detect a newer version. Older Kibana versions // will have indices like `.kibana_10` and users might choose an @@ -355,13 +807,13 @@ describe('migrations v2 model', () => { // lenient and assume it's an older index and perform a migration. // If the tampered index belonged to a newer version the migration // will fail when we start transforming documents. - const res: ResponseType<'INIT'> = Either.right({ + const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({ '.kibana_7.invalid.0_001': { aliases: { '.kibana': {}, '.kibana_7.12.0': {}, }, - mappings: mappingsWithUnknownType, + mappings: { properties: {} }, settings: {}, }, '.kibana_7.11.0_001': { @@ -370,17 +822,16 @@ describe('migrations v2 model', () => { settings: {}, }, }); - const newState = model(initState, res) as WaitForYellowSourceState; + const newState = model(waitForMState, res) as WaitForYellowSourceState; - expect(newState.controlState).toBe('WAIT_FOR_YELLOW_SOURCE'); - expect(newState.sourceIndex.value).toBe('.kibana_7.invalid.0_001'); + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toBe(2000); }); - - test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a v2 migrations index (>= 7.11.0)', () => { - const res: ResponseType<'INIT'> = Either.right({ + test('WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a v2 migrations index (>= 7.11.0)', () => { + const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({ '.kibana_7.11.0_001': { aliases: { '.kibana': {}, '.kibana_7.11.0': {} }, - mappings: mappingsWithUnknownType, + mappings: { properties: {} }, settings: {}, }, '.kibana_3': { @@ -391,7 +842,7 @@ describe('migrations v2 model', () => { }); const newState = model( { - ...initState, + ...waitForMState, ...{ kibanaVersion: '7.12.0', versionAlias: '.kibana_7.12.0', @@ -401,86 +852,50 @@ describe('migrations v2 model', () => { res ) as WaitForYellowSourceState; - expect(newState.controlState).toBe('WAIT_FOR_YELLOW_SOURCE'); - expect(newState.sourceIndex.value).toBe('.kibana_7.11.0_001'); - expect(newState.retryCount).toEqual(0); - expect(newState.retryDelay).toEqual(0); + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toEqual(2000); }); - - test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a v1 migrations index (>= 6.5 < 7.11.0)', () => { - const res: ResponseType<'INIT'> = Either.right({ + test('WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a v1 migrations index (>= 6.5 < 7.11.0)', () => { + const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({ '.kibana_3': { aliases: { '.kibana': {}, }, - mappings: mappingsWithUnknownType, + mappings: { properties: {} }, settings: {}, }, }); - const newState = model(initState, res) as WaitForYellowSourceState; + const newState = model(waitForMState, res) as WaitForYellowSourceState; - expect(newState.controlState).toBe('WAIT_FOR_YELLOW_SOURCE'); - expect(newState.sourceIndex.value).toBe('.kibana_3'); - expect(newState.retryCount).toEqual(0); - expect(newState.retryDelay).toEqual(0); + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toEqual(2000); }); - test('INIT -> LEGACY_SET_WRITE_BLOCK when migrating from a legacy index (>= 6.0.0 < 6.5)', () => { - const res: ResponseType<'INIT'> = Either.right({ + test('WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a legacy index (>= 6.0.0 < 6.5)', () => { + const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({ '.kibana': { aliases: {}, - mappings: mappingsWithUnknownType, + mappings: { properties: {} }, settings: {}, }, }); - const newState = model(initState, res); + const newState = model(waitForMState, res); - expect(newState).toMatchObject({ - controlState: 'LEGACY_SET_WRITE_BLOCK', - sourceIndex: Option.some('.kibana_pre6.5.0_001'), - targetIndex: '.kibana_7.11.0_001', - }); - // This snapshot asserts that we disable the unknown saved object - // type. Because it's mappings are disabled, we also don't copy the - // `_meta.migrationMappingPropertyHashes` for the disabled type. - expect(newState.targetIndexMappings).toMatchInlineSnapshot(` - Object { - "_meta": Object { - "migrationMappingPropertyHashes": Object { - "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", - }, - }, - "properties": Object { - "disabled_saved_object_type": Object { - "dynamic": false, - "properties": Object {}, - }, - "new_saved_object_type": Object { - "properties": Object { - "value": Object { - "type": "text", - }, - }, - }, - }, - } - `); - expect(newState.retryCount).toEqual(0); - expect(newState.retryDelay).toEqual(0); + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toEqual(2000); }); - test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a custom kibana.index name (>= 6.5 < 7.11.0)', () => { - const res: ResponseType<'INIT'> = Either.right({ + test('WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a custom kibana.index name (>= 6.5 < 7.11.0)', () => { + const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({ 'my-saved-objects_3': { aliases: { 'my-saved-objects': {}, }, - mappings: mappingsWithUnknownType, + mappings: { properties: {} }, settings: {}, }, }); const newState = model( { - ...baseState, - controlState: 'INIT', + ...waitForMState, currentAlias: 'my-saved-objects', versionAlias: 'my-saved-objects_7.11.0', versionIndex: 'my-saved-objects_7.11.0_001', @@ -488,25 +903,22 @@ describe('migrations v2 model', () => { res ) as WaitForYellowSourceState; - expect(newState.controlState).toBe('WAIT_FOR_YELLOW_SOURCE'); - expect(newState.sourceIndex.value).toBe('my-saved-objects_3'); - expect(newState.retryCount).toEqual(0); - expect(newState.retryDelay).toEqual(0); + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toEqual(2000); }); - test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a custom kibana.index v2 migrations index (>= 7.11.0)', () => { - const res: ResponseType<'INIT'> = Either.right({ + test('WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a custom kibana.index v2 migrations index (>= 7.11.0)', () => { + const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({ 'my-saved-objects_7.11.0': { aliases: { 'my-saved-objects': {}, }, - mappings: mappingsWithUnknownType, + mappings: { properties: {} }, settings: {}, }, }); const newState = model( { - ...baseState, - controlState: 'INIT', + ...waitForMState, kibanaVersion: '7.12.0', currentAlias: 'my-saved-objects', versionAlias: 'my-saved-objects_7.12.0', @@ -515,23 +927,31 @@ describe('migrations v2 model', () => { res ) as WaitForYellowSourceState; - expect(newState.controlState).toBe('WAIT_FOR_YELLOW_SOURCE'); - expect(newState.sourceIndex.value).toBe('my-saved-objects_7.11.0'); + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toEqual(2000); + }); + test('WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION when no indices/aliases exist', () => { + const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({}); + const newState = model(waitForMState, res); - expect(newState.retryCount).toEqual(0); - expect(newState.retryDelay).toEqual(0); + expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); + expect(newState.retryDelay).toEqual(2000); }); - test('INIT -> CREATE_NEW_TARGET when no indices/aliases exist', () => { - const res: ResponseType<'INIT'> = Either.right({}); - const newState = model(initState, res); - expect(newState).toMatchObject({ - controlState: 'CREATE_NEW_TARGET', - sourceIndex: Option.none, - targetIndex: '.kibana_7.11.0_001', + it('WAIT_FOR_MIGRATION_COMPLETION -> DONE when another instance finished the migration', () => { + const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({ + '.kibana_7.11.0_001': { + aliases: { + '.kibana': {}, + '.kibana_7.11.0': {}, + }, + mappings: { properties: {} }, + settings: {}, + }, }); - expect(newState.retryCount).toEqual(0); - expect(newState.retryDelay).toEqual(0); + const newState = model(waitForMState, res); + + expect(newState.controlState).toEqual('DONE'); }); }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts index 8eca6d12c4f0a..2ff0a99d6966f 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts @@ -38,6 +38,7 @@ import { mergeMigrationMappingPropertyHashes, throwBadControlState, throwBadResponse, + versionMigrationCompleted, } from './helpers'; import { createBatches } from './create_batches'; import type { MigrationLog } from '../types'; @@ -98,12 +99,8 @@ export const model = (currentState: State, resW: ResponseType): const aliases = aliasesRes.right; if ( - // `.kibana` and the version specific aliases both exists and - // are pointing to the same index. This version's migration has already - // been completed. - aliases[stateP.currentAlias] != null && - aliases[stateP.versionAlias] != null && - aliases[stateP.currentAlias] === aliases[stateP.versionAlias] + // This version's migration has already been completed. + versionMigrationCompleted(stateP.currentAlias, stateP.versionAlias, aliases) ) { return { ...stateP, @@ -117,7 +114,7 @@ export const model = (currentState: State, resW: ResponseType): targetIndex: `${stateP.indexPrefix}_${stateP.kibanaVersion}_001`, targetIndexMappings: mergeMigrationMappingPropertyHashes( stateP.targetIndexMappings, - indices[aliases[stateP.currentAlias]].mappings + indices[aliases[stateP.currentAlias]!].mappings ), versionIndexReadyActions: Option.none, }; @@ -125,7 +122,7 @@ export const model = (currentState: State, resW: ResponseType): // `.kibana` is pointing to an index that belongs to a later // version of Kibana .e.g. a 7.11.0 instance found the `.kibana` alias // pointing to `.kibana_7.12.0_001` - indexBelongsToLaterVersion(aliases[stateP.currentAlias], stateP.kibanaVersion) + indexBelongsToLaterVersion(aliases[stateP.currentAlias]!, stateP.kibanaVersion) ) { return { ...stateP, @@ -136,12 +133,29 @@ export const model = (currentState: State, resW: ResponseType): aliases[stateP.currentAlias] )}`, }; + } else if ( + // Don't actively participate in this migration but wait for another instance to complete it + stateP.waitForMigrationCompletion === true + ) { + return { + ...stateP, + controlState: 'WAIT_FOR_MIGRATION_COMPLETION', + // Wait for 2s before checking again if the migration has completed + retryDelay: 2000, + logs: [ + ...stateP.logs, + { + level: 'info', + message: `Migration required. Waiting until another Kibana instance completes the migration.`, + }, + ], + }; } else if ( // If the `.kibana` alias exists aliases[stateP.currentAlias] != null ) { // The source index is the index the `.kibana` alias points to - const source = aliases[stateP.currentAlias]; + const source = aliases[stateP.currentAlias]!; return { ...stateP, controlState: 'WAIT_FOR_YELLOW_SOURCE', @@ -219,6 +233,47 @@ export const model = (currentState: State, resW: ResponseType): } else { return throwBadResponse(stateP, res); } + } else if (stateP.controlState === 'WAIT_FOR_MIGRATION_COMPLETION') { + const res = resW as ExcludeRetryableEsError>; + const indices = res.right; + const aliasesRes = getAliases(indices); + if ( + // If this version's migration has already been completed we can proceed + Either.isRight(aliasesRes) && + versionMigrationCompleted(stateP.currentAlias, stateP.versionAlias, aliasesRes.right) + ) { + return { + ...stateP, + // Proceed to 'DONE' and start serving traffic. + // Because WAIT_FOR_MIGRATION_COMPLETION can only be used by + // background-task nodes on Cloud, we can be confident that this node + // has exactly the same plugins enabled as the node that finished the + // migration. So we won't need to transform any old documents or update + // the mappings. + controlState: 'DONE', + // Source is a none because we didn't do any migration from a source + // index + sourceIndex: Option.none, + targetIndex: `${stateP.indexPrefix}_${stateP.kibanaVersion}_001`, + versionIndexReadyActions: Option.none, + }; + } else { + // When getAliases returns a left 'multiple_indices_per_alias' error or + // the migration is not yet up to date just continue waiting + return { + ...stateP, + controlState: 'WAIT_FOR_MIGRATION_COMPLETION', + // Wait for 2s before checking again if the migration has completed + retryDelay: 2000, + logs: [ + ...stateP.logs, + { + level: 'info', + message: `Migration required. Waiting until another Kibana instance completes the migration.`, + }, + ], + }; + } } else if (stateP.controlState === 'LEGACY_SET_WRITE_BLOCK') { const res = resW as ExcludeRetryableEsError>; // If the write block is successfully in place @@ -938,16 +993,22 @@ export const model = (currentState: State, resW: ResponseType): throwBadResponse(stateP, res.left); } } - } else if (stateP.controlState === 'UPDATE_TARGET_MAPPINGS') { + } else if (stateP.controlState === 'OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { + const { pitId, hasTransformedDocs, ...state } = stateP; + if (hasTransformedDocs) { + return { + ...state, + controlState: 'OUTDATED_DOCUMENTS_REFRESH', + }; + } return { - ...stateP, - controlState: 'UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK', - updateTargetMappingsTaskId: res.right.taskId, + ...state, + controlState: 'UPDATE_TARGET_MAPPINGS', }; } else { - throwBadResponse(stateP, res as never); + throwBadResponse(stateP, res); } } else if (stateP.controlState === 'OUTDATED_DOCUMENTS_REFRESH') { const res = resW as ExcludeRetryableEsError>; @@ -959,22 +1020,16 @@ export const model = (currentState: State, resW: ResponseType): } else { throwBadResponse(stateP, res); } - } else if (stateP.controlState === 'OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT') { + } else if (stateP.controlState === 'UPDATE_TARGET_MAPPINGS') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { - const { pitId, hasTransformedDocs, ...state } = stateP; - if (hasTransformedDocs) { - return { - ...state, - controlState: 'OUTDATED_DOCUMENTS_REFRESH', - }; - } return { - ...state, - controlState: 'UPDATE_TARGET_MAPPINGS', + ...stateP, + controlState: 'UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK', + updateTargetMappingsTaskId: res.right.taskId, }; } else { - throwBadResponse(stateP, res); + throwBadResponse(stateP, res as never); } } else if (stateP.controlState === 'UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK') { const res = resW as ExcludeRetryableEsError>; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts index 9ac29a3a849ba..a82cf88145996 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts @@ -40,6 +40,7 @@ import type { OutdatedDocumentsRefresh, CheckUnknownDocumentsState, CalculateExcludeFiltersState, + WaitForMigrationCompletionState, } from './state'; import type { TransformRawDocs } from './types'; import * as Actions from './actions'; @@ -60,6 +61,8 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra return { INIT: (state: InitState) => Actions.initAction({ client, indices: [state.currentAlias, state.versionAlias] }), + WAIT_FOR_MIGRATION_COMPLETION: (state: WaitForMigrationCompletionState) => + Actions.fetchIndices({ client, indices: [state.currentAlias, state.versionAlias] }), WAIT_FOR_YELLOW_SOURCE: (state: WaitForYellowSourceState) => Actions.waitForIndexStatus({ client, index: state.sourceIndex.value, status: 'yellow' }), CHECK_UNKNOWN_DOCUMENTS: (state: CheckUnknownDocumentsState) => diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts index 47fe92ad82c54..b94a94a715056 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts @@ -44,6 +44,7 @@ export const MIGRATION_CLIENT_OPTIONS = { maxRetries: 0, requestTimeout: 120_000 export async function runResilientMigrator({ client, kibanaVersion, + waitForMigrationCompletion, targetMappings, logger, preMigrationScript, @@ -56,6 +57,7 @@ export async function runResilientMigrator({ }: { client: ElasticsearchClient; kibanaVersion: string; + waitForMigrationCompletion: boolean; targetMappings: IndexMapping; preMigrationScript?: string; logger: Logger; @@ -68,6 +70,7 @@ export async function runResilientMigrator({ }): Promise { const initialState = createInitialState({ kibanaVersion, + waitForMigrationCompletion, targetMappings, preMigrationScript, migrationVersionPerType, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts index eb8d2e2fbb05b..33ad01be2335b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts @@ -149,12 +149,18 @@ export interface BaseState extends ControlState { * DocLinks for savedObjects. to reference online documentation */ readonly migrationDocLinks: DocLinks['kibanaUpgradeSavedObjects']; + readonly waitForMigrationCompletion: boolean; } export interface InitState extends BaseState { readonly controlState: 'INIT'; } +export interface WaitForMigrationCompletionState extends BaseState { + /** Wait until another instance completes the migration */ + readonly controlState: 'WAIT_FOR_MIGRATION_COMPLETION'; +} + export interface PostInitState extends BaseState { /** * The source index is the index from which the migration reads. If the @@ -430,6 +436,7 @@ export interface LegacyDeleteState extends LegacyBaseState { export type State = Readonly< | FatalState | InitState + | WaitForMigrationCompletionState | DoneState | WaitForYellowSourceState | CheckUnknownDocumentsState diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/BUILD.bazel b/packages/core/saved-objects/core-saved-objects-server-internal/BUILD.bazel index f2fcaa1c68991..7bcee948e25ea 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/BUILD.bazel +++ b/packages/core/saved-objects/core-saved-objects-server-internal/BUILD.bazel @@ -67,6 +67,7 @@ TYPES_DEPS = [ "//packages/core/saved-objects/core-saved-objects-import-export-server-internal:npm_module_types", "//packages/core/usage-data/core-usage-data-base-server-internal:npm_module_types", "//packages/core/deprecations/core-deprecations-server:npm_module_types", + "//packages/core/node/core-node-server:npm_module_types", ] jsts_transpiler( diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts index 83854c3aafdab..61978d3915a1a 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts @@ -23,6 +23,7 @@ import { ByteSizeValue } from '@kbn/config-schema'; import { REPO_ROOT } from '@kbn/utils'; import { getEnvOptions } from '@kbn/config-mocks'; import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; +import { nodeServiceMock } from '@kbn/core-node-server-mocks'; import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { httpServiceMock, httpServerMock } from '@kbn/core-http-server-mocks'; import type { SavedObjectsClientFactoryProvider } from '@kbn/core-saved-objects-server'; @@ -84,6 +85,7 @@ describe('SavedObjectsService', () => { pluginsInitialized, elasticsearch: elasticsearchServiceMock.createInternalStart(), docLinks: docLinksServiceMock.createStartContract(), + node: nodeServiceMock.createInternalStartContract(), }; }; @@ -285,6 +287,81 @@ describe('SavedObjectsService', () => { expect(KibanaMigratorMock).toHaveBeenCalledWith(expect.objectContaining({ kibanaVersion })); }); + it('calls KibanaMigrator with waitForMigrationCompletion=false for the default ui+background tasks role', async () => { + const pkg = loadJsonFile.sync(join(REPO_ROOT, 'package.json')) as RawPackageInfo; + const kibanaVersion = pkg.version; + + const coreContext = createCoreContext({ + env: Env.createDefault(REPO_ROOT, getEnvOptions(), { + ...pkg, + version: `${kibanaVersion}-beta1`, // test behavior when release has a version qualifier + }), + }); + + const soService = new SavedObjectsService(coreContext); + await soService.setup(createSetupDeps()); + const startDeps = createStartDeps(); + startDeps.node = nodeServiceMock.createInternalStartContract({ + ui: true, + backgroundTasks: true, + }); + await soService.start(startDeps); + + expect(KibanaMigratorMock).toHaveBeenCalledWith( + expect.objectContaining({ waitForMigrationCompletion: false }) + ); + }); + + it('calls KibanaMigrator with waitForMigrationCompletion=false for the ui only role', async () => { + const pkg = loadJsonFile.sync(join(REPO_ROOT, 'package.json')) as RawPackageInfo; + const kibanaVersion = pkg.version; + + const coreContext = createCoreContext({ + env: Env.createDefault(REPO_ROOT, getEnvOptions(), { + ...pkg, + version: `${kibanaVersion}-beta1`, // test behavior when release has a version qualifier + }), + }); + + const soService = new SavedObjectsService(coreContext); + await soService.setup(createSetupDeps()); + const startDeps = createStartDeps(); + startDeps.node = nodeServiceMock.createInternalStartContract({ + ui: true, + backgroundTasks: false, + }); + await soService.start(startDeps); + + expect(KibanaMigratorMock).toHaveBeenCalledWith( + expect.objectContaining({ waitForMigrationCompletion: false }) + ); + }); + + it('calls KibanaMigrator with waitForMigrationCompletion=true for the background tasks only role', async () => { + const pkg = loadJsonFile.sync(join(REPO_ROOT, 'package.json')) as RawPackageInfo; + const kibanaVersion = pkg.version; + + const coreContext = createCoreContext({ + env: Env.createDefault(REPO_ROOT, getEnvOptions(), { + ...pkg, + version: `${kibanaVersion}-beta1`, // test behavior when release has a version qualifier + }), + }); + + const soService = new SavedObjectsService(coreContext); + await soService.setup(createSetupDeps()); + const startDeps = createStartDeps(); + startDeps.node = nodeServiceMock.createInternalStartContract({ + ui: false, + backgroundTasks: true, + }); + await soService.start(startDeps); + + expect(KibanaMigratorMock).toHaveBeenCalledWith( + expect.objectContaining({ waitForMigrationCompletion: true }) + ); + }); + it('waits for all es nodes to be compatible before running migrations', async () => { expect.assertions(2); const coreContext = createCoreContext({ skipMigration: false }); diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts index 8036a997d0d51..9f052ff61614c 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts @@ -48,6 +48,7 @@ import { } from '@kbn/core-saved-objects-import-export-server-internal'; import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import type { DeprecationRegistryProvider } from '@kbn/core-deprecations-server'; +import type { NodeInfo } from '@kbn/core-node-server'; import { registerRoutes } from './routes'; import { calculateStatus$ } from './status'; import { registerCoreObjectTypes } from './object_types'; @@ -85,6 +86,7 @@ export interface SavedObjectsStartDeps { elasticsearch: InternalElasticsearchServiceStart; pluginsInitialized?: boolean; docLinks: DocLinksServiceStart; + node: NodeInfo; } export class SavedObjectsService @@ -185,6 +187,7 @@ export class SavedObjectsService elasticsearch, pluginsInitialized = true, docLinks, + node, }: SavedObjectsStartDeps): Promise { if (!this.setupDeps || !this.config) { throw new Error('#setup() needs to be run first'); @@ -194,10 +197,12 @@ export class SavedObjectsService const client = elasticsearch.client; + const waitForMigrationCompletion = node.roles.backgroundTasks && !node.roles.ui; const migrator = this.createMigrator( this.config.migration, elasticsearch.client.asInternalUser, - docLinks + docLinks, + waitForMigrationCompletion ); this.migrator$.next(migrator); @@ -313,7 +318,8 @@ export class SavedObjectsService private createMigrator( soMigrationsConfig: SavedObjectsMigrationConfigType, client: ElasticsearchClient, - docLinks: DocLinksServiceStart + docLinks: DocLinksServiceStart, + waitForMigrationCompletion: boolean ): IKibanaMigrator { return new KibanaMigrator({ typeRegistry: this.typeRegistry, @@ -323,6 +329,7 @@ export class SavedObjectsService kibanaIndex, client, docLinks, + waitForMigrationCompletion, }); } diff --git a/src/core/server/integration_tests/saved_objects/migrations/wait_for_migration_completion.test.ts b/src/core/server/integration_tests/saved_objects/migrations/wait_for_migration_completion.test.ts new file mode 100644 index 0000000000000..4b5197d7f69e8 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/wait_for_migration_completion.test.ts @@ -0,0 +1,154 @@ +/* + * 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 Path from 'path'; +import fs from 'fs/promises'; +import JSON5 from 'json5'; +import { kibanaPackageJson as pkg } from '@kbn/utils'; +import { retryAsync } from '@kbn/core-saved-objects-migration-server-mocks'; +import * as kbnTestServer from '../../../../test_helpers/kbn_server'; +import { Root } from '../../../root'; + +const logFilePath = Path.join(__dirname, 'wait_for_migration_completion.log'); + +async function removeLogFile() { + // ignore errors if it doesn't exist + await fs.unlink(logFilePath).catch(() => void 0); +} + +describe('migration with waitForCompletion=true', () => { + let esServer: kbnTestServer.TestElasticsearchUtils; + let root: Root; + + beforeAll(async () => { + await removeLogFile(); + }); + + afterAll(async () => { + if (root) { + await root.shutdown(); + } + if (esServer) { + await esServer.stop(); + } + + await new Promise((resolve) => setTimeout(resolve, 10000)); + }); + + it('waits for another instance to complete the migration', async () => { + const { startES } = kbnTestServer.createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), + settings: { + es: { + license: 'basic', + }, + }, + }); + + root = createRoot(); + + esServer = await startES(); + await root.preboot(); + await root.setup(); + + root.start(); + const esClient = esServer.es.getClient(); + + await retryAsync( + async () => { + const logFileContent = await fs.readFile(logFilePath, 'utf-8'); + const records = logFileContent + .split('\n') + .filter(Boolean) + .map((str) => JSON5.parse(str)) as any[]; + + expect( + records.find((rec) => + rec.message.startsWith( + `[.kibana] Migration required. Waiting until another Kibana instance completes the migration.` + ) + ) + ).toBeDefined(); + + expect( + records.find((rec) => + rec.message.startsWith(`[.kibana] INIT -> WAIT_FOR_MIGRATION_COMPLETION`) + ) + ).toBeDefined(); + + expect( + records.find((rec) => + rec.message.startsWith( + `[.kibana] WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION` + ) + ) + ).toBeDefined(); + }, + { retryAttempts: 100, retryDelayMs: 200 } + ); + + const aliases: Record = { '.kibana': {} }; + aliases[`.kibana_${pkg.version}`] = {}; + await esClient.indices.create({ index: `.kibana_${pkg.version}_001`, aliases }); + + await retryAsync( + async () => { + const logFileContent = await fs.readFile(logFilePath, 'utf-8'); + const records = logFileContent + .split('\n') + .filter(Boolean) + .map((str) => JSON5.parse(str)) as any[]; + + expect( + records.find((rec) => + rec.message.startsWith(`[.kibana] WAIT_FOR_MIGRATION_COMPLETION -> DONE`) + ) + ).toBeDefined(); + + expect( + records.find((rec) => rec.message.startsWith(`[.kibana] Migration completed`)) + ).toBeDefined(); + }, + { retryAttempts: 100, retryDelayMs: 200 } + ); + }); +}); + +function createRoot() { + return kbnTestServer.createRootWithCorePlugins( + { + migrations: { + skip: false, + }, + node: { + roles: ['background_tasks'], + }, + logging: { + appenders: { + file: { + type: 'file', + fileName: logFilePath, + layout: { + type: 'json', + }, + }, + }, + loggers: [ + { + name: 'root', + level: 'info', + appenders: ['file'], + }, + ], + }, + }, + { + oss: true, + } + ); +} diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 1f60c3242215a..7bddb8da80da1 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -384,6 +384,7 @@ export class Server { elasticsearch: elasticsearchStart, pluginsInitialized: this.#pluginsInitialized, docLinks: docLinkStart, + node: await this.node.start(), }); await this.resolveSavedObjectsStartPromise!(savedObjectsStart); diff --git a/test/node_roles_functional/background_tasks.config.ts b/test/node_roles_functional/background_tasks.config.ts index a2840cbb230ca..dc7a63b353dcc 100644 --- a/test/node_roles_functional/background_tasks.config.ts +++ b/test/node_roles_functional/background_tasks.config.ts @@ -40,7 +40,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { '--usageCollection.usageCounters.bufferDuration=0', `--plugin-path=${path.resolve(__dirname, 'plugins', 'core_plugin_initializer_context')}`, - '--node.roles=["background_tasks"]', + '--node.roles=["ui","background_tasks"]', ], }, }; diff --git a/test/node_roles_functional/test_suites/background_tasks/initializer_context.ts b/test/node_roles_functional/test_suites/background_tasks/initializer_context.ts index dfe814779ff1a..e616ec8243c9d 100644 --- a/test/node_roles_functional/test_suites/background_tasks/initializer_context.ts +++ b/test/node_roles_functional/test_suites/background_tasks/initializer_context.ts @@ -16,7 +16,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide it('passes node roles to server PluginInitializerContext', async () => { await supertest.get('/core_plugin_initializer_context/node/roles').expect(200, { backgroundTasks: true, - ui: false, + ui: true, }); }); }); From 592dad5b55397a3db2d364a786c8ce48ba5e01c1 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 2 Nov 2022 09:23:31 -0400 Subject: [PATCH 11/86] [Fleet] Move default_pipeline declaration to component template (#144329) --- .../services/epm/elasticsearch/template/install.ts | 13 +++++++++++-- .../services/epm/elasticsearch/template/template.ts | 5 ----- .../apis/epm/install_overrides.ts | 2 ++ .../fleet_api_integration/apis/epm/update_assets.ts | 1 + 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index 527b93a66d130..14a773cfd94bf 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -228,9 +228,17 @@ export function buildComponentTemplates(params: { templateName: string; registryElasticsearch: RegistryElasticsearch | undefined; packageName: string; + pipelineName?: string; defaultSettings: IndexTemplate['template']['settings']; }) { - const { templateName, registryElasticsearch, packageName, defaultSettings, mappings } = params; + const { + templateName, + registryElasticsearch, + packageName, + defaultSettings, + mappings, + pipelineName, + } = params; const packageTemplateName = `${templateName}${PACKAGE_TEMPLATE_SUFFIX}`; const userSettingsTemplateName = `${templateName}${USER_SETTINGS_TEMPLATE_SUFFIX}`; @@ -256,6 +264,7 @@ export function buildComponentTemplates(params: { ...templateSettings, index: { ...templateSettings.index, + ...(pipelineName ? { default_pipeline: pipelineName } : {}), mapping: { ...templateSettings?.mapping, total_fields: { @@ -392,12 +401,12 @@ export function prepareTemplate({ mappings, packageName, templateName, + pipelineName, registryElasticsearch: dataStream.elasticsearch, }); const template = getTemplate({ templateIndexPattern, - pipelineName, packageName, composedOfTemplates: Object.keys(componentTemplates), templatePriority, diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index 5605125bd4ff3..c0187e1446258 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -58,14 +58,12 @@ const META_PROP_KEYS = ['metric_type', 'unit']; */ export function getTemplate({ templateIndexPattern, - pipelineName, packageName, composedOfTemplates, templatePriority, hidden, }: { templateIndexPattern: string; - pipelineName?: string | undefined; packageName: string; composedOfTemplates: string[]; templatePriority: number; @@ -78,9 +76,6 @@ export function getTemplate({ templatePriority, hidden ); - if (pipelineName) { - template.template.settings.index.default_pipeline = pipelineName; - } if (template.template.settings.index.final_pipeline) { throw new Error(`Error template for ${templateIndexPattern} contains a final_pipeline`); } diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_overrides.ts b/x-pack/test/fleet_api_integration/apis/epm/install_overrides.ts index b89c036e84a9d..7b691ca10b5a6 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_overrides.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_overrides.ts @@ -123,6 +123,7 @@ export default function (providerContext: FtrProviderContext) { }, { meta: true } )); + // omit routings delete body.template.settings.index.routing; @@ -131,6 +132,7 @@ export default function (providerContext: FtrProviderContext) { settings: { index: { codec: 'best_compression', + default_pipeline: 'logs-overrides.test-0.1.0', lifecycle: { name: 'overridden by user', }, diff --git a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts index 9d01797db5b91..c8bde9dd076f8 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts @@ -217,6 +217,7 @@ export default function (providerContext: FtrProviderContext) { expect(resPackage.body.component_templates[0].component_template.template.settings).eql({ index: { codec: 'best_compression', + default_pipeline: 'logs-all_assets.test_logs-0.2.0', lifecycle: { name: 'reference2', }, From 4ec767dfb8f6142c9d7ce1e0227b6c7778f56fcf Mon Sep 17 00:00:00 2001 From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> Date: Wed, 2 Nov 2022 09:28:05 -0400 Subject: [PATCH 12/86] [ResponseOps][Cases] Add more fields to getCasesByAlertId API (#144199) * Retrieving new fields, need tests * Adding tests * Fixing spelling * Fixing test errors --- x-pack/plugins/cases/common/api/cases/case.ts | 33 +++--- .../plugins/cases/server/client/cases/get.ts | 16 ++- .../server/services/attachments/index.ts | 12 +- .../cases/server/services/cases/index.ts | 14 ++- .../routes/actions/response_actions.test.ts | 8 ++ .../common/lib/validation.ts | 47 ++++++-- .../tests/common/alerts/get_cases.ts | 109 +++++++++++++++++- .../tests/common/alerts/get_cases.ts | 20 +++- 8 files changed, 212 insertions(+), 47 deletions(-) diff --git a/x-pack/plugins/cases/common/api/cases/case.ts b/x-pack/plugins/cases/common/api/cases/case.ts index a6b81dc42af74..ecda555f49553 100644 --- a/x-pack/plugins/cases/common/api/cases/case.ts +++ b/x-pack/plugins/cases/common/api/cases/case.ts @@ -14,27 +14,21 @@ import { CasesStatusResponseRt, CaseStatusRt } from './status'; import { CaseConnectorRt } from '../connectors'; import { CaseAssigneesRt } from './assignee'; -const BucketsAggs = rt.array( - rt.type({ - key: rt.string, - }) -); +export const AttachmentTotalsRt = rt.type({ + alerts: rt.number, + userComments: rt.number, +}); -export const GetCaseIdsByAlertIdAggsRt = rt.type({ - references: rt.type({ - doc_count: rt.number, - caseIds: rt.type({ - buckets: BucketsAggs, - }), - }), +export const RelatedCaseInfoRt = rt.type({ + id: rt.string, + title: rt.string, + description: rt.string, + status: CaseStatusRt, + createdAt: rt.string, + totals: AttachmentTotalsRt, }); -export const CasesByAlertIdRt = rt.array( - rt.type({ - id: rt.string, - title: rt.string, - }) -); +export const CasesByAlertIdRt = rt.array(RelatedCaseInfoRt); export const SettingsRt = rt.type({ syncAlerts: rt.boolean, @@ -350,5 +344,6 @@ export type CaseExternalServiceBasic = rt.TypeOf; export type AllReportersFindRequest = AllTagsFindRequest; -export type GetCaseIdsByAlertIdAggs = rt.TypeOf; +export type AttachmentTotals = rt.TypeOf; +export type RelatedCaseInfo = rt.TypeOf; export type CasesByAlertId = rt.TypeOf; diff --git a/x-pack/plugins/cases/server/client/cases/get.ts b/x-pack/plugins/cases/server/client/cases/get.ts index 27d098702f4c8..4f219db2c28f7 100644 --- a/x-pack/plugins/cases/server/client/cases/get.ts +++ b/x-pack/plugins/cases/server/client/cases/get.ts @@ -19,6 +19,7 @@ import type { CasesByAlertIDRequest, CasesByAlertId, CaseAttributes, + AttachmentTotals, } from '../../../common/api'; import { CaseResponseRt, @@ -62,9 +63,10 @@ export const getCasesByAlertID = async ( clientArgs: CasesClientArgs ): Promise => { const { - services: { caseService }, + services: { caseService, attachmentService }, logger, authorization, + unsecuredSavedObjectsClient, } = clientArgs; try { @@ -104,6 +106,11 @@ export const getCasesByAlertID = async ( return []; } + const commentStats = await attachmentService.getCaseCommentStats({ + unsecuredSavedObjectsClient, + caseIds, + }); + const casesInfo = await caseService.getCases({ caseIds, }); @@ -125,6 +132,10 @@ export const getCasesByAlertID = async ( validCasesInfo.map((caseInfo) => ({ id: caseInfo.id, title: caseInfo.attributes.title, + description: caseInfo.attributes.description, + status: caseInfo.attributes.status, + createdAt: caseInfo.attributes.created_at, + totals: getAttachmentTotalsForCaseId(caseInfo.id, commentStats), })) ); } catch (error) { @@ -138,6 +149,9 @@ export const getCasesByAlertID = async ( } }; +const getAttachmentTotalsForCaseId = (id: string, stats: Map) => + stats.get(id) ?? { alerts: 0, userComments: 0 }; + /** * The parameters for retrieving a case */ diff --git a/x-pack/plugins/cases/server/services/attachments/index.ts b/x-pack/plugins/cases/server/services/attachments/index.ts index e12ff71a20cf9..325038b016c45 100644 --- a/x-pack/plugins/cases/server/services/attachments/index.ts +++ b/x-pack/plugins/cases/server/services/attachments/index.ts @@ -20,6 +20,7 @@ import type { import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { KueryNode } from '@kbn/es-query'; import type { + AttachmentTotals, AttributesTypeAlerts, CommentAttributes as AttachmentAttributes, CommentAttributesWithoutRefs as AttachmentAttributesWithoutRefs, @@ -93,11 +94,6 @@ interface BulkUpdateAttachmentArgs extends ClientArgs, IndexRefresh { comments: UpdateArgs[]; } -interface CommentStats { - nonAlerts: number; - alerts: number; -} - export class AttachmentService { constructor( private readonly log: Logger, @@ -463,7 +459,7 @@ export class AttachmentService { }: { unsecuredSavedObjectsClient: SavedObjectsClientContract; caseIds: string[]; - }): Promise> { + }): Promise> { if (caseIds.length <= 0) { return new Map(); } @@ -498,11 +494,11 @@ export class AttachmentService { return ( res.aggregations?.references.caseIds.buckets.reduce((acc, idBucket) => { acc.set(idBucket.key, { - nonAlerts: idBucket.reverse.comments.doc_count, + userComments: idBucket.reverse.comments.doc_count, alerts: idBucket.reverse.alerts.value, }); return acc; - }, new Map()) ?? new Map() + }, new Map()) ?? new Map() ); } diff --git a/x-pack/plugins/cases/server/services/cases/index.ts b/x-pack/plugins/cases/server/services/cases/index.ts index 8b0a5e70f925e..8359675a0ce0b 100644 --- a/x-pack/plugins/cases/server/services/cases/index.ts +++ b/x-pack/plugins/cases/server/services/cases/index.ts @@ -28,7 +28,6 @@ import { MAX_DOCS_PER_PAGE, } from '../../../common/constants'; import type { - GetCaseIdsByAlertIdAggs, CaseResponse, CasesFindRequest, CommentAttributes, @@ -122,6 +121,15 @@ interface GetReportersArgs { filter?: KueryNode; } +interface GetCaseIdsByAlertIdAggs { + references: { + doc_count: number; + caseIds: { + buckets: Array<{ key: string }>; + }; + }; +} + export class CasesService { private readonly log: Logger; private readonly unsecuredSavedObjectsClient: SavedObjectsClientContract; @@ -222,13 +230,13 @@ export class CasesService { const casesWithComments = new Map(); for (const [id, caseInfo] of casesMap.entries()) { - const { alerts, nonAlerts } = commentTotals.get(id) ?? { alerts: 0, nonAlerts: 0 }; + const { alerts, userComments } = commentTotals.get(id) ?? { alerts: 0, userComments: 0 }; casesWithComments.set( id, flattenCaseSavedObject({ savedObject: caseInfo, - totalComment: nonAlerts, + totalComment: userComments, totalAlerts: alerts, }) ); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts index 8624bc375cd9f..545c4f31c3650 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts @@ -66,6 +66,7 @@ import { import { legacyMetadataSearchResponseMock } from '../metadata/support/test_support'; import { registerResponseActionRoutes } from './response_actions'; import * as ActionDetailsService from '../../services/actions/action_details_by_id'; +import { CaseStatuses } from '@kbn/cases-components'; interface CallRouteInterface { body?: ResponseActionRequestBody; @@ -694,6 +695,13 @@ describe('Response actions', () => { { id: `case-${counter++}`, title: 'case', + createdAt: '2022-10-31T11:49:48.806Z', + description: 'a description', + status: CaseStatuses.open, + totals: { + userComments: 1, + alerts: 1, + }, }, ]; }); diff --git a/x-pack/test/cases_api_integration/common/lib/validation.ts b/x-pack/test/cases_api_integration/common/lib/validation.ts index a84c733586ec0..e022beb5a597a 100644 --- a/x-pack/test/cases_api_integration/common/lib/validation.ts +++ b/x-pack/test/cases_api_integration/common/lib/validation.ts @@ -6,26 +6,57 @@ */ import expect from '@kbn/expect'; -import { CaseResponse, CasesByAlertId } from '@kbn/cases-plugin/common/api'; +import { + AttachmentTotals, + CaseResponse, + CasesByAlertId, + RelatedCaseInfo, +} from '@kbn/cases-plugin/common/api'; import { xorWith, isEqual } from 'lodash'; +type AttachmentTotalsKeys = keyof AttachmentTotals; + +export interface TestCaseWithTotals { + caseInfo: CaseResponse; + totals?: Partial; +} + /** * Ensure that the result of the alerts API request matches with the cases created for the test. */ export function validateCasesFromAlertIDResponse( casesFromAPIResponse: CasesByAlertId, - createdCasesForTest: CaseResponse[] + createdCasesForTest: TestCaseWithTotals[] ) { - const idToTitle = new Map( - createdCasesForTest.map((caseInfo) => [caseInfo.id, caseInfo.title]) + const idToResponse = new Map( + casesFromAPIResponse.map((response) => [response.id, response]) ); - for (const apiResCase of casesFromAPIResponse) { - // check that the title in the api response matches the title in the map from the created cases - expect(apiResCase.title).to.be(idToTitle.get(apiResCase.id)); + expect(idToResponse.size).to.be(createdCasesForTest.length); + + // only iterate over the test cases not the api response values + for (const expectedTestInfo of createdCasesForTest) { + expect(idToResponse.get(expectedTestInfo.caseInfo.id)?.title).to.be( + expectedTestInfo.caseInfo.title + ); + expect(idToResponse.get(expectedTestInfo.caseInfo.id)?.description).to.be( + expectedTestInfo.caseInfo.description + ); + expect(idToResponse.get(expectedTestInfo.caseInfo.id)?.status).to.be( + expectedTestInfo.caseInfo.status + ); + expect(idToResponse.get(expectedTestInfo.caseInfo.id)?.createdAt).to.be( + expectedTestInfo.caseInfo.created_at + ); + + // only check the totals that are defined in the test case + for (const totalKey of Object.keys(expectedTestInfo.totals ?? {}) as AttachmentTotalsKeys[]) { + expect(idToResponse.get(expectedTestInfo.caseInfo.id)?.totals[totalKey]).to.be( + expectedTestInfo.totals?.[totalKey] + ); + } } } - /** * Compares two arrays to determine if they are sort of equal. This function returns true if the arrays contain the same * elements but the ordering does not matter. diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/alerts/get_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/alerts/get_cases.ts index 4d6c20192442a..d0a5f45dae851 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/alerts/get_cases.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/alerts/get_cases.ts @@ -10,7 +10,11 @@ import { CASES_URL } from '@kbn/cases-plugin/common/constants'; import { CaseResponse } from '@kbn/cases-plugin/common/api'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { getPostCaseRequest, postCommentAlertReq } from '../../../../common/lib/mock'; +import { + getPostCaseRequest, + postCommentAlertReq, + postCommentUserReq, +} from '../../../../common/lib/mock'; import { createCase, createComment, @@ -56,7 +60,11 @@ export default ({ getService }: FtrProviderContext): void => { const caseIDsWithAlert = await getCasesByAlert({ supertest, alertID: 'test-id' }); expect(caseIDsWithAlert.length).to.eql(3); - validateCasesFromAlertIDResponse(caseIDsWithAlert, [case1, case2, case3]); + validateCasesFromAlertIDResponse(caseIDsWithAlert, [ + { caseInfo: case1, totals: { alerts: 1, userComments: 0 } }, + { caseInfo: case2, totals: { alerts: 1, userComments: 0 } }, + { caseInfo: case3, totals: { alerts: 1, userComments: 0 } }, + ]); }); it('should return all cases with the same alert ID when more than 100 cases', async () => { @@ -83,7 +91,15 @@ export default ({ getService }: FtrProviderContext): void => { expect(caseIDsWithAlert.length).to.eql(numCases); - validateCasesFromAlertIDResponse(caseIDsWithAlert, cases); + const testResults = cases.map((caseInfo) => ({ + caseInfo, + totals: { + alerts: 1, + userComments: 0, + }, + })); + + validateCasesFromAlertIDResponse(caseIDsWithAlert, testResults); }); it('should return no cases when the alert ID is not found', async () => { @@ -131,6 +147,55 @@ export default ({ getService }: FtrProviderContext): void => { await supertest.get(`${CASES_URL}/alerts/`).expect(302); }); + describe('attachment stats', () => { + it('should only count unique alert ids in the alert totals', async () => { + const [case1, case2] = await Promise.all([ + createCase(supertest, getPostCaseRequest({ title: 'a' })), + createCase(supertest, getPostCaseRequest({ title: 'b' })), + ]); + + await Promise.all([ + createComment({ supertest, caseId: case1.id, params: postCommentAlertReq }), + createComment({ supertest, caseId: case2.id, params: postCommentAlertReq }), + ]); + + // two alerts with same alert id attached to case1 + await createComment({ supertest, caseId: case1.id, params: postCommentAlertReq }); + + const caseIDsWithAlert = await getCasesByAlert({ supertest, alertID: 'test-id' }); + + expect(caseIDsWithAlert.length).to.eql(2); + validateCasesFromAlertIDResponse(caseIDsWithAlert, [ + { caseInfo: case1, totals: { alerts: 1, userComments: 0 } }, + { caseInfo: case2, totals: { alerts: 1, userComments: 0 } }, + ]); + }); + + it('should get the total number of user comments attached to a case', async () => { + const [case1, case2] = await Promise.all([ + createCase(supertest, getPostCaseRequest({ title: 'a' })), + createCase(supertest, getPostCaseRequest({ title: 'b' })), + ]); + + await Promise.all([ + createComment({ supertest, caseId: case1.id, params: postCommentAlertReq }), + createComment({ supertest, caseId: case2.id, params: postCommentAlertReq }), + ]); + + // two user comments attached to case2 + await createComment({ supertest, caseId: case2.id, params: postCommentUserReq }); + await createComment({ supertest, caseId: case2.id, params: postCommentUserReq }); + + const caseIDsWithAlert = await getCasesByAlert({ supertest, alertID: 'test-id' }); + + expect(caseIDsWithAlert.length).to.eql(2); + validateCasesFromAlertIDResponse(caseIDsWithAlert, [ + { caseInfo: case1, totals: { alerts: 1, userComments: 0 } }, + { caseInfo: case2, totals: { alerts: 1, userComments: 2 } }, + ]); + }); + }); + describe('rbac', () => { const supertestWithoutAuth = getService('supertestWithoutAuth'); @@ -197,7 +262,15 @@ export default ({ getService }: FtrProviderContext): void => { }); expect(res.length).to.eql(scenario.cases.length); - validateCasesFromAlertIDResponse(res, scenario.cases); + const testResults = scenario.cases.map((caseInfo) => ({ + caseInfo, + totals: { + alerts: 1, + userComments: 0, + }, + })); + + validateCasesFromAlertIDResponse(res, testResults); } }); @@ -263,7 +336,19 @@ export default ({ getService }: FtrProviderContext): void => { query: { owner: 'securitySolutionFixture' }, }); - expect(res).to.eql([{ id: case1.id, title: case1.title }]); + expect(res).to.eql([ + { + id: case1.id, + title: case1.title, + description: case1.description, + status: case1.status, + createdAt: case1.created_at, + totals: { + userComments: 0, + alerts: 1, + }, + }, + ]); }); it('should return the correct cases info when the owner query parameter contains unprivileged values', async () => { @@ -301,7 +386,19 @@ export default ({ getService }: FtrProviderContext): void => { query: { owner: ['securitySolutionFixture', 'observabilityFixture'] }, }); - expect(res).to.eql([{ id: case1.id, title: case1.title }]); + expect(res).to.eql([ + { + id: case1.id, + title: case1.title, + description: case1.description, + status: case1.status, + createdAt: case1.created_at, + totals: { + userComments: 0, + alerts: 1, + }, + }, + ]); }); }); }); diff --git a/x-pack/test/cases_api_integration/spaces_only/tests/common/alerts/get_cases.ts b/x-pack/test/cases_api_integration/spaces_only/tests/common/alerts/get_cases.ts index 691d132aa1ede..be0a698a6b525 100644 --- a/x-pack/test/cases_api_integration/spaces_only/tests/common/alerts/get_cases.ts +++ b/x-pack/test/cases_api_integration/spaces_only/tests/common/alerts/get_cases.ts @@ -65,7 +65,11 @@ export default ({ getService }: FtrProviderContext): void => { }); expect(cases.length).to.eql(3); - validateCasesFromAlertIDResponse(cases, [case1, case2, case3]); + validateCasesFromAlertIDResponse(cases, [ + { caseInfo: case1, totals: { alerts: 1, userComments: 0 } }, + { caseInfo: case2, totals: { alerts: 1, userComments: 0 } }, + { caseInfo: case3, totals: { alerts: 1, userComments: 0 } }, + ]); }); it('should return 1 case in space2 when 2 cases were created in space1 and 1 in space2', async () => { @@ -103,7 +107,19 @@ export default ({ getService }: FtrProviderContext): void => { }); expect(casesByAlert.length).to.eql(1); - expect(casesByAlert).to.eql([{ id: case3.id, title: case3.title }]); + expect(casesByAlert).to.eql([ + { + id: case3.id, + title: case3.title, + description: case3.description, + status: case3.status, + createdAt: case3.created_at, + totals: { + userComments: 0, + alerts: 1, + }, + }, + ]); }); }); }; From 14ce15e6e80c8ebfb342d33ca0df6d9951c8fb22 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Wed, 2 Nov 2022 15:40:33 +0200 Subject: [PATCH 13/86] Fix discover embeddable ready state for tests (#144243) * notify search embeddable when data is loaded set the embeddable as rendered only when data is loaded * typo * remove event * isLoading * fix journeys * revert grid change * dispatcherror * added no data and AGGREGATED_LEVEL tests * error * restore deleted test * deb --- .../saved_search_embeddable.test.ts | 132 ++++++++++++++++-- .../embeddable/saved_search_embeddable.tsx | 26 +++- .../journeys/ecommerce_dashboard.ts | 2 +- .../performance/journeys/flight_dashboard.ts | 2 +- 4 files changed, 140 insertions(+), 22 deletions(-) diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts b/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts index 28a4bb185ea76..10be9bb3e3c66 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts @@ -16,8 +16,11 @@ import { discoverServiceMock } from '../__mocks__/services'; import { SavedSearchEmbeddable, SearchEmbeddableConfig } from './saved_search_embeddable'; import { render } from 'react-dom'; import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks'; -import { throwError } from 'rxjs'; +import { of, throwError } from 'rxjs'; import { ReactWrapper } from 'enzyme'; +import { SHOW_FIELD_STATISTICS } from '../../common'; +import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import { VIEW_MODE } from '../components/view_mode_toggle'; import { SavedSearchEmbeddableComponent } from './saved_search_embeddable_component'; let discoverComponent: ReactWrapper; @@ -38,13 +41,17 @@ describe('saved search embeddable', () => { let mountpoint: HTMLDivElement; let filterManagerMock: jest.Mocked; let servicesMock: jest.Mocked; + let executeTriggerActions: jest.Mock; + let showFieldStatisticsMockValue: boolean = false; + let viewModeMockValue: VIEW_MODE = VIEW_MODE.DOCUMENT_LEVEL; const createEmbeddable = (searchMock?: jest.Mock) => { const savedSearchMock = { id: 'mock-id', sort: [['message', 'asc']] as Array<[string, string]>, searchSource: createSearchSourceMock({ index: dataViewMock }, undefined, searchMock), + viewMode: viewModeMockValue, }; const url = getSavedSearchUrl(savedSearchMock.id); @@ -87,28 +94,27 @@ describe('saved search embeddable', () => { beforeEach(() => { mountpoint = document.createElement('div'); filterManagerMock = createFilterManagerMock(); + + showFieldStatisticsMockValue = false; + viewModeMockValue = VIEW_MODE.DOCUMENT_LEVEL; + servicesMock = discoverServiceMock as unknown as jest.Mocked; + + (servicesMock.uiSettings as unknown as jest.Mocked).get.mockImplementation( + (key: string) => { + if (key === SHOW_FIELD_STATISTICS) return showFieldStatisticsMockValue; + } + ); }); afterEach(() => { mountpoint.remove(); - }); - - it('should render saved search embeddable two times initially', async () => { - const { embeddable } = createEmbeddable(); - embeddable.updateOutput = jest.fn(); - - embeddable.render(mountpoint); - expect(render).toHaveBeenCalledTimes(1); - - // wait for data fetching - await waitOneTick(); - expect(render).toHaveBeenCalledTimes(2); + jest.resetAllMocks(); }); it('should update input correctly', async () => { const { embeddable } = createEmbeddable(); - embeddable.updateOutput = jest.fn(); + jest.spyOn(embeddable, 'updateOutput'); embeddable.render(mountpoint); await waitOneTick(); @@ -146,10 +152,101 @@ describe('saved search embeddable', () => { expect(executeTriggerActions).toHaveBeenCalled(); }); + it('should render saved search embeddable when successfully loading data', async () => { + // mock return data + const search = jest.fn().mockReturnValue( + of({ + rawResponse: { hits: { hits: [{ id: 1 }], total: 1 } }, + isPartial: false, + isRunning: false, + }) + ); + const { embeddable } = createEmbeddable(search); + jest.spyOn(embeddable, 'updateOutput'); + + // check that loading state + const loadingOutput = embeddable.getOutput(); + expect(loadingOutput.loading).toBe(true); + expect(loadingOutput.rendered).toBe(false); + expect(loadingOutput.error).toBe(undefined); + + embeddable.render(mountpoint); + expect(render).toHaveBeenCalledTimes(1); + + // wait for data fetching + await waitOneTick(); + expect(render).toHaveBeenCalledTimes(2); + + // check that loading state + const loadedOutput = embeddable.getOutput(); + expect(loadedOutput.loading).toBe(false); + expect(loadedOutput.rendered).toBe(true); + expect(loadedOutput.error).toBe(undefined); + }); + + it('should render saved search embeddable when empty data is returned', async () => { + // mock return data + const search = jest.fn().mockReturnValue( + of({ + rawResponse: { hits: { hits: [], total: 0 } }, + isPartial: false, + isRunning: false, + }) + ); + const { embeddable } = createEmbeddable(search); + jest.spyOn(embeddable, 'updateOutput'); + + // check that loading state + const loadingOutput = embeddable.getOutput(); + expect(loadingOutput.loading).toBe(true); + expect(loadingOutput.rendered).toBe(false); + expect(loadingOutput.error).toBe(undefined); + + embeddable.render(mountpoint); + expect(render).toHaveBeenCalledTimes(1); + + // wait for data fetching + await waitOneTick(); + expect(render).toHaveBeenCalledTimes(2); + + // check that loading state + const loadedOutput = embeddable.getOutput(); + expect(loadedOutput.loading).toBe(false); + expect(loadedOutput.rendered).toBe(true); + expect(loadedOutput.error).toBe(undefined); + }); + + it('should render in AGGREGATED_LEVEL view mode', async () => { + showFieldStatisticsMockValue = true; + viewModeMockValue = VIEW_MODE.AGGREGATED_LEVEL; + + const { embeddable } = createEmbeddable(); + jest.spyOn(embeddable, 'updateOutput'); + + // check that loading state + const loadingOutput = embeddable.getOutput(); + expect(loadingOutput.loading).toBe(true); + expect(loadingOutput.rendered).toBe(false); + expect(loadingOutput.error).toBe(undefined); + + embeddable.render(mountpoint); + expect(render).toHaveBeenCalledTimes(1); + + // wait for data fetching + await waitOneTick(); + expect(render).toHaveBeenCalledTimes(2); + + // check that loading state + const loadedOutput = embeddable.getOutput(); + expect(loadedOutput.loading).toBe(false); + expect(loadedOutput.rendered).toBe(true); + expect(loadedOutput.error).toBe(undefined); + }); + it('should emit error output in case of fetch error', async () => { const search = jest.fn().mockReturnValue(throwError(new Error('Fetch error'))); const { embeddable } = createEmbeddable(search); - embeddable.updateOutput = jest.fn(); + jest.spyOn(embeddable, 'updateOutput'); embeddable.render(mountpoint); // wait for data fetching @@ -158,5 +255,10 @@ describe('saved search embeddable', () => { expect((embeddable.updateOutput as jest.Mock).mock.calls[1][0].error.message).toBe( 'Fetch error' ); + // check that loading state + const loadedOutput = embeddable.getOutput(); + expect(loadedOutput.loading).toBe(false); + expect(loadedOutput.rendered).toBe(true); + expect(loadedOutput.error).not.toBe(undefined); }); }); diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx index ffdead82d1dae..440fe6a8084a9 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx @@ -479,6 +479,7 @@ export class SavedSearchEmbeddable if (!this.searchProps) { throw new Error('Search props not defined'); } + super.render(domNode as HTMLElement); this.node = domNode; @@ -515,6 +516,10 @@ export class SavedSearchEmbeddable , domNode ); + this.updateOutput({ + ...this.getOutput(), + rendered: true, + }); return; } const useLegacyTable = this.services.uiSettings.get(DOC_TABLE_LEGACY); @@ -534,12 +539,23 @@ export class SavedSearchEmbeddable , domNode ); - } - this.updateOutput({ - ...this.getOutput(), - rendered: true, - }); + const hasError = this.getOutput().error !== undefined; + + if (this.searchProps!.isLoading === false && props.searchProps.rows !== undefined) { + this.renderComplete.dispatchComplete(); + this.updateOutput({ + ...this.getOutput(), + rendered: true, + }); + } else if (hasError) { + this.renderComplete.dispatchError(); + this.updateOutput({ + ...this.getOutput(), + rendered: true, + }); + } + } } private async load(searchProps: SearchProps, forceFetch = false) { diff --git a/x-pack/performance/journeys/ecommerce_dashboard.ts b/x-pack/performance/journeys/ecommerce_dashboard.ts index 51b9512f4ba32..b9c107cd12cbd 100644 --- a/x-pack/performance/journeys/ecommerce_dashboard.ts +++ b/x-pack/performance/journeys/ecommerce_dashboard.ts @@ -21,5 +21,5 @@ export const journey = new Journey({ .step('Go to Ecommerce Dashboard', async ({ page }) => { await page.click(subj('dashboardListingTitleLink-[eCommerce]-Revenue-Dashboard')); - await waitForVisualizations(page, 12); + await waitForVisualizations(page, 13); }); diff --git a/x-pack/performance/journeys/flight_dashboard.ts b/x-pack/performance/journeys/flight_dashboard.ts index a9ac7408be49f..46030dd47d2fb 100644 --- a/x-pack/performance/journeys/flight_dashboard.ts +++ b/x-pack/performance/journeys/flight_dashboard.ts @@ -21,5 +21,5 @@ export const journey = new Journey({ .step('Go to Flights Dashboard', async ({ page }) => { await page.click(subj('dashboardListingTitleLink-[Flights]-Global-Flight-Dashboard')); - await waitForVisualizations(page, 13); + await waitForVisualizations(page, 14); }); From 65f0c385d61e5290d663ca10fbdf4905cc75dbed Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Wed, 2 Nov 2022 14:46:05 +0100 Subject: [PATCH 14/86] [APM] Aggregated critical path (#144092) --- ...get_aggregated_critical_path_root_nodes.ts | 92 ++++ .../common/critical_path/get_critical_path.ts | 5 +- x-pack/plugins/apm/common/index.ts | 9 + .../error_group_details/detail_view/index.tsx | 2 +- .../app/service_map/popover/edge_contents.tsx | 2 +- .../app/top_traces_overview/index.tsx | 20 +- .../components/app/trace_explorer/index.tsx | 223 ++++----- ...race_explorer_aggregated_critical_path.tsx | 37 ++ .../trace_explorer_waterfall.tsx | 93 ++++ .../components/app/trace_overview/index.tsx | 112 +++-- .../aggregated_critical_path_tab.tsx | 70 +++ .../transaction_details_tabs.tsx | 12 +- .../waterfall_with_summary/index.tsx | 139 +++--- .../public/components/routing/home/index.tsx | 59 ++- .../routing/templates/apm_main_template.tsx | 14 +- .../critical_path_flamegraph_tooltip.tsx | 120 +++++ .../critical_path_to_flamegraph.ts | 144 ++++++ .../shared/critical_path_flamegraph/index.tsx | 165 +++++++ .../shared/technical_preview_badge.tsx | 8 +- .../hooks/create_shared_use_fetcher.tsx | 72 +++ .../hooks/use_trace_explorer_samples.ts | 29 ++ .../traces/get_aggregated_critical_path.ts | 406 +++++++++++++++++ .../plugins/apm/server/routes/traces/route.ts | 44 ++ .../public/hooks/use_chart_theme.tsx | 42 +- .../tests/traces/critical_path.spec.ts | 426 ++++++++++++++++++ .../tests/traces/find_traces.spec.ts | 6 - 26 files changed, 2054 insertions(+), 297 deletions(-) create mode 100644 x-pack/plugins/apm/common/critical_path/get_aggregated_critical_path_root_nodes.ts create mode 100644 x-pack/plugins/apm/common/index.ts create mode 100644 x-pack/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx create mode 100644 x-pack/plugins/apm/public/components/app/trace_explorer/trace_explorer_waterfall.tsx create mode 100644 x-pack/plugins/apm/public/components/app/transaction_details/aggregated_critical_path_tab.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/critical_path_flamegraph/critical_path_flamegraph_tooltip.tsx create mode 100644 x-pack/plugins/apm/public/components/shared/critical_path_flamegraph/critical_path_to_flamegraph.ts create mode 100644 x-pack/plugins/apm/public/components/shared/critical_path_flamegraph/index.tsx create mode 100644 x-pack/plugins/apm/public/hooks/create_shared_use_fetcher.tsx create mode 100644 x-pack/plugins/apm/public/hooks/use_trace_explorer_samples.ts create mode 100644 x-pack/plugins/apm/server/routes/traces/get_aggregated_critical_path.ts create mode 100644 x-pack/test/apm_api_integration/tests/traces/critical_path.spec.ts diff --git a/x-pack/plugins/apm/common/critical_path/get_aggregated_critical_path_root_nodes.ts b/x-pack/plugins/apm/common/critical_path/get_aggregated_critical_path_root_nodes.ts new file mode 100644 index 0000000000000..5d00db3977b07 --- /dev/null +++ b/x-pack/plugins/apm/common/critical_path/get_aggregated_critical_path_root_nodes.ts @@ -0,0 +1,92 @@ +/* + * 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 { sumBy } from 'lodash'; +import type { CriticalPathResponse } from '../../server/routes/traces/get_aggregated_critical_path'; + +export interface CriticalPathTreeNode { + nodeId: string; + children: CriticalPathTreeNode[]; + countInclusive: number; + countExclusive: number; +} + +export function getAggregatedCriticalPathRootNodes(params: { + criticalPath: CriticalPathResponse; +}): { + rootNodes: CriticalPathTreeNode[]; + maxDepth: number; + numNodes: number; +} { + let maxDepth = 20; // min max depth + + const { criticalPath } = params; + + let numNodes = 0; + + function mergeNodesWithSameOperationId( + nodes: CriticalPathTreeNode[] + ): CriticalPathTreeNode[] { + const nodesByOperationId: Record = {}; + const mergedNodes = nodes.reduce( + (prev, node, index, array) => { + const nodeId = node.nodeId; + const operationId = criticalPath.operationIdByNodeId[nodeId]; + if (nodesByOperationId[operationId]) { + const prevNode = nodesByOperationId[operationId]; + prevNode.children.push(...node.children); + prevNode.countExclusive += node.countExclusive; + prevNode.countInclusive += node.countInclusive; + return prev; + } + + nodesByOperationId[operationId] = node; + + prev.push(node); + return prev; + }, + [] + ); + + numNodes += mergedNodes.length; + + mergedNodes.forEach((node) => { + node.children = mergeNodesWithSameOperationId(node.children); + }); + + return mergedNodes; + } + + function getNode(nodeId: string, depth: number): CriticalPathTreeNode { + maxDepth = Math.max(maxDepth, depth); + + const children = criticalPath.nodes[nodeId].map((childNodeId) => + getNode(childNodeId, depth + 1) + ); + + const nodeCountExclusive = criticalPath.timeByNodeId[nodeId] || 0; + const nodeCountInclusive = + sumBy(children, (child) => child.countInclusive) + nodeCountExclusive; + + return { + nodeId, + children, + countInclusive: nodeCountInclusive, + countExclusive: nodeCountExclusive, + }; + } + + const rootNodes = mergeNodesWithSameOperationId( + criticalPath.rootNodes.map((nodeId) => getNode(nodeId, 1)) + ); + + return { + rootNodes, + maxDepth, + numNodes, + }; +} diff --git a/x-pack/plugins/apm/common/critical_path/get_critical_path.ts b/x-pack/plugins/apm/common/critical_path/get_critical_path.ts index c517548bf3d1f..ad4e166962ccb 100644 --- a/x-pack/plugins/apm/common/critical_path/get_critical_path.ts +++ b/x-pack/plugins/apm/common/critical_path/get_critical_path.ts @@ -37,7 +37,10 @@ export function getCriticalPath(waterfall: IWaterfall): CriticalPath { const orderedChildren = directChildren.concat().sort((a, b) => { const endTimeA = a.offset + a.skew + a.duration; const endTimeB = b.offset + b.skew + b.duration; - return endTimeB - endTimeA; + if (endTimeA === endTimeB) { + return 0; + } + return endTimeB > endTimeA ? 1 : -1; }); // For each point in time, determine what child is on the critical path. diff --git a/x-pack/plugins/apm/common/index.ts b/x-pack/plugins/apm/common/index.ts new file mode 100644 index 0000000000000..d08ff963b0f1f --- /dev/null +++ b/x-pack/plugins/apm/common/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +// for API tests +export { getAggregatedCriticalPathRootNodes } from './critical_path/get_aggregated_critical_path_root_nodes'; diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx index 4b41099240f54..21124629a4063 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx @@ -96,7 +96,7 @@ export function DetailView({ errorGroup, urlParams, kuery }: Props) { const method = error.http?.request?.method; const status = error.http?.response?.status_code; - const traceExplorerLink = router.link('/traces/explorer', { + const traceExplorerLink = router.link('/traces/explorer/waterfall', { query: { ...query, showCriticalPath: false, diff --git a/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx index add6c48fef8e5..8f5b7e9acce39 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx @@ -49,7 +49,7 @@ export function EdgeContents({ elementData }: ContentsProps) { ` [ span where service.name == "${sourceService}" and span.destination.service.resource == "${edgeData.targetData[SPAN_DESTINATION_SERVICE_RESOURCE]}" ]`; } - const url = apmRouter.link('/traces/explorer', { + const url = apmRouter.link('/traces/explorer/waterfall', { query: { ...query, type: TraceSearchType.eql, diff --git a/x-pack/plugins/apm/public/components/app/top_traces_overview/index.tsx b/x-pack/plugins/apm/public/components/app/top_traces_overview/index.tsx index 37eaf27bfb562..685074155c380 100644 --- a/x-pack/plugins/apm/public/components/app/top_traces_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/top_traces_overview/index.tsx @@ -44,18 +44,20 @@ export function TopTracesOverview() { ); return ( - <> - + + + + {fallbackToTransactions && ( - - - - - + + + )} - - + + + + ); } diff --git a/x-pack/plugins/apm/public/components/app/trace_explorer/index.tsx b/x-pack/plugins/apm/public/components/app/trace_explorer/index.tsx index 1b6c35adffc04..74b38ea153558 100644 --- a/x-pack/plugins/apm/public/components/app/trace_explorer/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_explorer/index.tsx @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiTab, EuiTabs } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React, { useEffect, useMemo, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { @@ -12,39 +13,38 @@ import { TraceSearchType, } from '../../../../common/trace_explorer'; import { useApmParams } from '../../../hooks/use_apm_params'; -import { useFetcher } from '../../../hooks/use_fetcher'; +import { useApmRouter } from '../../../hooks/use_apm_router'; +import { useApmRoutePath } from '../../../hooks/use_apm_route_path'; import { useTimeRange } from '../../../hooks/use_time_range'; +import { TraceExplorerSamplesFetcherContextProvider } from '../../../hooks/use_trace_explorer_samples'; +import { APIClientRequestParamsOf } from '../../../services/rest/create_call_apm_api'; import { ApmDatePicker } from '../../shared/date_picker/apm_date_picker'; -import { fromQuery, toQuery, push } from '../../shared/links/url_helpers'; -import { useWaterfallFetcher } from '../transaction_details/use_waterfall_fetcher'; -import { WaterfallWithSummary } from '../transaction_details/waterfall_with_summary'; +import { push } from '../../shared/links/url_helpers'; +import { TechnicalPreviewBadge } from '../../shared/technical_preview_badge'; +import { TransactionTab } from '../transaction_details/waterfall_with_summary/transaction_tabs'; import { TraceSearchBox } from './trace_search_box'; -export function TraceExplorer() { - const [query, setQuery] = useState({ +export function TraceExplorer({ children }: { children: React.ReactElement }) { + const [searchQuery, setSearchQuery] = useState({ query: '', type: TraceSearchType.kql, }); const { + query, query: { rangeFrom, rangeTo, environment, query: queryFromUrlParams, type: typeFromUrlParams, - traceId, - transactionId, - waterfallItemId, - detailTab, - showCriticalPath, }, } = useApmParams('/traces/explorer'); const history = useHistory(); useEffect(() => { - setQuery({ + setSearchQuery({ query: queryFromUrlParams, type: typeFromUrlParams, }); @@ -55,120 +55,95 @@ export function TraceExplorer() { rangeTo, }); - const { data, status, error } = useFetcher( - (callApmApi) => { - return callApmApi('GET /internal/apm/traces/find', { - params: { - query: { - start, - end, - environment, - query: queryFromUrlParams, - type: typeFromUrlParams, - }, - }, - }); - }, - [start, end, environment, queryFromUrlParams, typeFromUrlParams] - ); + const params = useMemo< + APIClientRequestParamsOf<'GET /internal/apm/traces/find'>['params'] + >(() => { + return { + query: { + start, + end, + environment, + query: queryFromUrlParams, + type: typeFromUrlParams, + }, + }; + }, [start, end, environment, queryFromUrlParams, typeFromUrlParams]); - useEffect(() => { - const nextSample = data?.traceSamples[0]; - const nextWaterfallItemId = ''; - history.replace({ - ...history.location, - search: fromQuery({ - ...toQuery(history.location.search), - traceId: nextSample?.traceId ?? '', - transactionId: nextSample?.transactionId, - waterfallItemId: nextWaterfallItemId, - }), - }); - }, [data, history]); + const router = useApmRouter(); - const waterfallFetchResult = useWaterfallFetcher({ - traceId, - transactionId, - start, - end, - }); - - const traceSamplesFetchResult = useMemo( - () => ({ - data, - status, - error, - }), - [data, status, error] - ); + const routePath = useApmRoutePath(); return ( - - - - - { - history.push({ - ...history.location, - search: fromQuery({ - ...toQuery(history.location.search), - query: query.query, - type: query.type, - }), - }); - }} - onQueryChange={(nextQuery) => { - setQuery(nextQuery); - }} - /> - - - - - - - - { - push(history, { - query: { - traceId: sample.traceId, - transactionId: sample.transactionId, - waterfallItemId: '', - }, - }); - }} - onTabClick={(nextDetailTab) => { - push(history, { - query: { - detailTab: nextDetailTab, - }, - }); - }} - detailTab={detailTab} - waterfallItemId={waterfallItemId} - serviceName={ - waterfallFetchResult.waterfall.entryWaterfallTransaction?.doc - .service.name - } - showCriticalPath={showCriticalPath} - onShowCriticalPathChange={(nextShowCriticalPath) => { - push(history, { - query: { - showCriticalPath: nextShowCriticalPath ? 'true' : 'false', - }, - }); - }} - /> - - + + + + + + { + push(history, { + query: { + query: searchQuery.query, + type: searchQuery.type, + }, + }); + }} + onQueryChange={(nextQuery) => { + setSearchQuery(nextQuery); + }} + /> + + + + + + + + + + {i18n.translate('xpack.apm.traceExplorer.waterfallTab', { + defaultMessage: 'Waterfall', + })} + + + + + {i18n.translate('xpack.apm.traceExplorer.criticalPathTab', { + defaultMessage: 'Aggregated critical path', + })} + + + + + + + + + {children} + + ); } diff --git a/x-pack/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx new file mode 100644 index 0000000000000..6ff12fac2351a --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_explorer_aggregated_critical_path.tsx @@ -0,0 +1,37 @@ +/* + * 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, { useMemo } from 'react'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useTimeRange } from '../../../hooks/use_time_range'; +import { useTraceExplorerSamples } from '../../../hooks/use_trace_explorer_samples'; +import { CriticalPathFlamegraph } from '../../shared/critical_path_flamegraph'; + +export function TraceExplorerAggregatedCriticalPath() { + const { + query: { rangeFrom, rangeTo }, + } = useApmParams('/traces/explorer/critical_path'); + + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + + const { + data: { traceSamples }, + status: samplesFetchStatus, + } = useTraceExplorerSamples(); + + const traceIds = useMemo(() => { + return traceSamples.map((sample) => sample.traceId); + }, [traceSamples]); + + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/trace_explorer/trace_explorer_waterfall.tsx b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_explorer_waterfall.tsx new file mode 100644 index 0000000000000..5c25421869da4 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_explorer_waterfall.tsx @@ -0,0 +1,93 @@ +/* + * 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, { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useTimeRange } from '../../../hooks/use_time_range'; +import { useTraceExplorerSamples } from '../../../hooks/use_trace_explorer_samples'; +import { push, replace } from '../../shared/links/url_helpers'; +import { useWaterfallFetcher } from '../transaction_details/use_waterfall_fetcher'; +import { WaterfallWithSummary } from '../transaction_details/waterfall_with_summary'; + +export function TraceExplorerWaterfall() { + const history = useHistory(); + + const traceSamplesFetchResult = useTraceExplorerSamples(); + + const { + query: { + traceId, + transactionId, + waterfallItemId, + rangeFrom, + rangeTo, + environment, + showCriticalPath, + detailTab, + }, + } = useApmParams('/traces/explorer/waterfall'); + + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + + useEffect(() => { + const nextSample = traceSamplesFetchResult.data?.traceSamples[0]; + const nextWaterfallItemId = ''; + replace(history, { + query: { + traceId: nextSample?.traceId ?? '', + transactionId: nextSample?.transactionId ?? '', + waterfallItemId: nextWaterfallItemId, + }, + }); + }, [traceSamplesFetchResult.data, history]); + + const waterfallFetchResult = useWaterfallFetcher({ + traceId, + transactionId, + start, + end, + }); + + return ( + { + push(history, { + query: { + traceId: sample.traceId, + transactionId: sample.transactionId, + waterfallItemId: '', + }, + }); + }} + onTabClick={(nextDetailTab) => { + push(history, { + query: { + detailTab: nextDetailTab, + }, + }); + }} + detailTab={detailTab} + waterfallItemId={waterfallItemId} + serviceName={ + waterfallFetchResult.waterfall.entryWaterfallTransaction?.doc.service + .name + } + showCriticalPath={showCriticalPath} + onShowCriticalPathChange={(nextShowCriticalPath) => { + push(history, { + query: { + showCriticalPath: nextShowCriticalPath ? 'true' : 'false', + }, + }); + }} + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx index 4c176527d49f6..7e48b1bb1a60b 100644 --- a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx @@ -4,16 +4,22 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiTab, EuiTabs } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useApmRouter } from '../../../hooks/use_apm_router'; +import React from 'react'; +import { TraceSearchType } from '../../../../common/trace_explorer'; import { useApmParams } from '../../../hooks/use_apm_params'; +import { useApmRouter } from '../../../hooks/use_apm_router'; import { useApmRoutePath } from '../../../hooks/use_apm_route_path'; -import { TraceSearchType } from '../../../../common/trace_explorer'; -import { TransactionTab } from '../transaction_details/waterfall_with_summary/transaction_tabs'; import { useTraceExplorerEnabledSetting } from '../../../hooks/use_trace_explorer_enabled_setting'; +import { ApmMainTemplate } from '../../routing/templates/apm_main_template'; import { TechnicalPreviewBadge } from '../../shared/technical_preview_badge'; +import { Breadcrumb } from '../breadcrumb'; +import { TransactionTab } from '../transaction_details/waterfall_with_summary/transaction_tabs'; + +type Tab = Required< + Required>['pageHeader'] +>['tabs'][number]; export function TraceOverview({ children }: { children: React.ReactElement }) { const isTraceExplorerEnabled = useTraceExplorerEnabledSetting(); @@ -24,11 +30,7 @@ export function TraceOverview({ children }: { children: React.ReactElement }) { const routePath = useApmRoutePath(); - if (!isTraceExplorerEnabled) { - return children; - } - - const explorerLink = router.link('/traces/explorer', { + const topTracesLink = router.link('/traces', { query: { comparisonEnabled: query.comparisonEnabled, environment: query.environment, @@ -38,17 +40,14 @@ export function TraceOverview({ children }: { children: React.ReactElement }) { offset: query.offset, refreshInterval: query.refreshInterval, refreshPaused: query.refreshPaused, - query: '', - type: TraceSearchType.kql, - waterfallItemId: '', - traceId: '', - transactionId: '', - detailTab: TransactionTab.timeline, - showCriticalPath: false, }, }); - const topTracesLink = router.link('/traces', { + const title = i18n.translate('xpack.apm.views.traceOverview.title', { + defaultMessage: 'Traces', + }); + + const explorerLink = router.link('/traces/explorer/waterfall', { query: { comparisonEnabled: query.comparisonEnabled, environment: query.environment, @@ -58,30 +57,65 @@ export function TraceOverview({ children }: { children: React.ReactElement }) { offset: query.offset, refreshInterval: query.refreshInterval, refreshPaused: query.refreshPaused, + query: '', + type: TraceSearchType.kql, + waterfallItemId: '', + traceId: '', + transactionId: '', + detailTab: TransactionTab.timeline, + showCriticalPath: false, }, }); + const tabs: Tab[] = isTraceExplorerEnabled + ? [ + { + href: topTracesLink, + label: i18n.translate('xpack.apm.traceOverview.topTracesTab', { + defaultMessage: 'Top traces', + }), + isSelected: routePath === '/traces', + }, + { + href: explorerLink, + label: ( + + + {i18n.translate('xpack.apm.traceOverview.traceExplorerTab', { + defaultMessage: 'Explorer', + })} + + + + + + ), + isSelected: routePath.startsWith('/traces/explorer'), + }, + ] + : []; + return ( - - - - - {i18n.translate('xpack.apm.traceOverview.topTracesTab', { - defaultMessage: 'Top traces', - })} - - } - isSelected={routePath === '/traces/explorer'} - > - {i18n.translate('xpack.apm.traceOverview.traceExplorerTab', { - defaultMessage: 'Explorer', - })} - - - - {children} - + + + {children} + + ); } diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/aggregated_critical_path_tab.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/aggregated_critical_path_tab.tsx new file mode 100644 index 0000000000000..3a6f479a61254 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/transaction_details/aggregated_critical_path_tab.tsx @@ -0,0 +1,70 @@ +/* + * 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 } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useMemo } from 'react'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useTimeRange } from '../../../hooks/use_time_range'; +import { CriticalPathFlamegraph } from '../../shared/critical_path_flamegraph'; +import { TechnicalPreviewBadge } from '../../shared/technical_preview_badge'; +import { TabContentProps } from './transaction_details_tabs'; + +function TransactionDetailAggregatedCriticalPath({ + traceSamplesFetchResult, +}: TabContentProps) { + const { + path: { serviceName }, + query: { rangeFrom, rangeTo, transactionName }, + } = useApmParams('/services/{serviceName}/transactions/view'); + + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + + const traceIds = useMemo(() => { + return ( + traceSamplesFetchResult.data?.traceSamples.map( + (sample) => sample.traceId + ) ?? [] + ); + }, [traceSamplesFetchResult.data]); + + return ( + + ); +} + +export const aggregatedCriticalPathTab = { + dataTestSubj: 'apmAggregatedCriticalPathTabButton', + key: 'aggregatedCriticalPath', + label: ( + + + {i18n.translate( + 'xpack.apm.transactionDetails.tabs.aggregatedCriticalPathLabel', + { + defaultMessage: 'Aggregated critical path', + } + )} + + + + + + ), + component: TransactionDetailAggregatedCriticalPath, +}; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx index c99703d4f90f8..866c94702fb00 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/transaction_details_tabs.tsx @@ -27,6 +27,8 @@ import { latencyCorrelationsTab } from './latency_correlations_tab'; import { traceSamplesTab } from './trace_samples_tab'; import { useSampleChartSelection } from '../../../hooks/use_sample_chart_selection'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { useCriticalPathFeatureEnabledSetting } from '../../../hooks/use_critical_path_feature_enabled_setting'; +import { aggregatedCriticalPathTab } from './aggregated_critical_path_tab'; export interface TabContentProps { clearChartSelection: () => void; @@ -46,12 +48,18 @@ const tabs = [ export function TransactionDetailsTabs() { const { query } = useApmParams('/services/{serviceName}/transactions/view'); + const isCriticalPathFeatureEnabled = useCriticalPathFeatureEnabledSetting(); + + const availableTabs = isCriticalPathFeatureEnabled + ? tabs.concat(aggregatedCriticalPathTab) + : tabs; + const { urlParams } = useLegacyUrlParams(); const history = useHistory(); const [currentTab, setCurrentTab] = useState(traceSamplesTab.key); const { component: TabContent } = - tabs.find((tab) => tab.key === currentTab) ?? traceSamplesTab; + availableTabs.find((tab) => tab.key === currentTab) ?? traceSamplesTab; const { environment, kuery, transactionName } = query; @@ -107,7 +115,7 @@ export function TransactionDetailsTabs() { return ( <> - {tabs.map(({ dataTestSubj, key, label }) => ( + {availableTabs.map(({ dataTestSubj, key, label }) => ( ({ const entryTransaction = entryWaterfallTransaction?.doc; return ( - <> - - - -
    - {i18n.translate('xpack.apm.transactionDetails.traceSampleTitle', { - defaultMessage: 'Trace sample', - })} -
    -
    -
    - - {!!traceSamples?.length && ( - - )} - - - - - - - - + + + + +
    + {i18n.translate( + 'xpack.apm.transactionDetails.traceSampleTitle', + { + defaultMessage: 'Trace sample', + } + )} +
    +
    +
    + + {!!traceSamples?.length && ( + - -
    -
    -
    - - + )} +
    + + + + + + + + + + +
    + {isLoading || !entryTransaction ? ( - <> + - + ) : ( - + + + )} - - - - + + + + ); } diff --git a/x-pack/plugins/apm/public/components/routing/home/index.tsx b/x-pack/plugins/apm/public/components/routing/home/index.tsx index 555d42ddb6f98..b908f87832b95 100644 --- a/x-pack/plugins/apm/public/components/routing/home/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/home/index.tsx @@ -19,6 +19,8 @@ import { ServiceInventory } from '../../app/service_inventory'; import { ServiceMapHome } from '../../app/service_map'; import { TopTracesOverview } from '../../app/top_traces_overview'; import { TraceExplorer } from '../../app/trace_explorer'; +import { TraceExplorerAggregatedCriticalPath } from '../../app/trace_explorer/trace_explorer_aggregated_critical_path'; +import { TraceExplorerWaterfall } from '../../app/trace_explorer/trace_explorer_waterfall'; import { TraceOverview } from '../../app/trace_overview'; import { TransactionTab } from '../../app/transaction_details/waterfall_with_summary/transaction_tabs'; import { RedirectTo } from '../redirect_to'; @@ -184,11 +186,7 @@ export const home = { element: , serviceGroupContextTab: 'service-map', }), - ...page({ - path: '/traces', - title: i18n.translate('xpack.apm.views.traceOverview.title', { - defaultMessage: 'Traces', - }), + '/traces': { element: ( @@ -196,7 +194,42 @@ export const home = { ), children: { '/traces/explorer': { - element: , + element: ( + + + + ), + children: { + '/traces/explorer/waterfall': { + element: , + params: t.type({ + query: t.type({ + traceId: t.string, + transactionId: t.string, + waterfallItemId: t.string, + detailTab: t.union([ + t.literal(TransactionTab.timeline), + t.literal(TransactionTab.metadata), + t.literal(TransactionTab.logs), + ]), + }), + }), + defaults: { + query: { + waterfallItemId: '', + traceId: '', + transactionId: '', + detailTab: TransactionTab.timeline, + }, + }, + }, + '/traces/explorer/critical_path': { + element: , + }, + '/traces/explorer': { + element: , + }, + }, params: t.type({ query: t.type({ query: t.string, @@ -204,14 +237,6 @@ export const home = { t.literal(TraceSearchType.kql), t.literal(TraceSearchType.eql), ]), - waterfallItemId: t.string, - traceId: t.string, - transactionId: t.string, - detailTab: t.union([ - t.literal(TransactionTab.timeline), - t.literal(TransactionTab.metadata), - t.literal(TransactionTab.logs), - ]), showCriticalPath: toBooleanRt, }), }), @@ -219,10 +244,6 @@ export const home = { query: { query: '', type: TraceSearchType.kql, - waterfallItemId: '', - traceId: '', - transactionId: '', - detailTab: TransactionTab.timeline, showCriticalPath: '', }, }, @@ -231,7 +252,7 @@ export const home = { element: , }, }, - }), + }, ...dependencies, ...legacyBackends, ...storageExplorer, diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx index 60f06ddaef8fc..c4bcc4e5fc612 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_main_template.tsx @@ -6,17 +6,18 @@ */ import { EuiPageHeaderProps } from '@elastic/eui'; -import React from 'react'; -import { useLocation } from 'react-router-dom'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ObservabilityPageTemplateProps } from '@kbn/observability-plugin/public/components/shared/page_template/page_template'; import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template'; +import React from 'react'; +import { useLocation } from 'react-router-dom'; import { EnvironmentsContextProvider } from '../../../context/environments_context/environments_context'; -import { useFetcher, FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { ApmPluginStartDeps } from '../../../plugin'; -import { ApmEnvironmentFilter } from '../../shared/environment_filter'; -import { getNoDataConfig } from './no_data_config'; import { ServiceGroupSaveButton } from '../../app/service_groups'; import { ServiceGroupsButtonGroup } from '../../app/service_groups/service_groups_button_group'; +import { ApmEnvironmentFilter } from '../../shared/environment_filter'; +import { getNoDataConfig } from './no_data_config'; // Paths that must skip the no data screen const bypassNoDataScreenPaths = ['/settings']; @@ -48,7 +49,8 @@ export function ApmMainTemplate({ showServiceGroupSaveButton?: boolean; showServiceGroupsNav?: boolean; selectedNavButton?: 'serviceGroups' | 'allServices'; -} & KibanaPageTemplateProps) { +} & KibanaPageTemplateProps & + Pick) { const location = useLocation(); const { services } = useKibana(); diff --git a/x-pack/plugins/apm/public/components/shared/critical_path_flamegraph/critical_path_flamegraph_tooltip.tsx b/x-pack/plugins/apm/public/components/shared/critical_path_flamegraph/critical_path_flamegraph_tooltip.tsx new file mode 100644 index 0000000000000..882b974abd6fe --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/critical_path_flamegraph/critical_path_flamegraph_tooltip.tsx @@ -0,0 +1,120 @@ +/* + * 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 { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiPanel, +} from '@elastic/eui'; +import React from 'react'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { i18n } from '@kbn/i18n'; +import type { CriticalPathResponse } from '../../../../server/routes/traces/get_aggregated_critical_path'; +import { + AGENT_NAME, + SERVICE_NAME, + SPAN_NAME, + SPAN_SUBTYPE, + SPAN_TYPE, + TRANSACTION_NAME, + TRANSACTION_TYPE, +} from '../../../../common/elasticsearch_fieldnames'; +import { SpanIcon } from '../span_icon'; +import { AgentIcon } from '../agent_icon'; +import { asPercent } from '../../../../common/utils/formatters'; + +export function CriticalPathFlamegraphTooltip({ + metadata, + countInclusive, + countExclusive, + totalCount, +}: { + metadata?: CriticalPathResponse['metadata'][string]; + countInclusive: number; + countExclusive: number; + totalCount: number; +}) { + if (!metadata) { + return <>; + } + + return ( + + + {metadata['processor.event'] === ProcessorEvent.transaction ? ( + + + + {metadata[TRANSACTION_NAME]} + + + {metadata[TRANSACTION_TYPE]} + + + + ) : ( + + + + + + {metadata[SPAN_NAME]} + + + )} + + + + + + + + + {metadata[SERVICE_NAME]} + + + + + + + + + {i18n.translate('xpack.apm.criticalPathFlameGraph.selfTime', { + defaultMessage: 'Self time: {value}', + values: { + value: asPercent(countExclusive / totalCount, 1), + }, + })} + + + {i18n.translate('xpack.apm.criticalPathFlameGraph.totalTime', { + defaultMessage: 'Total time: {value}', + values: { + value: asPercent(countInclusive / totalCount, 1), + }, + })} + + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/critical_path_flamegraph/critical_path_to_flamegraph.ts b/x-pack/plugins/apm/public/components/shared/critical_path_flamegraph/critical_path_to_flamegraph.ts new file mode 100644 index 0000000000000..4bc5e222fa8c7 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/critical_path_flamegraph/critical_path_to_flamegraph.ts @@ -0,0 +1,144 @@ +/* + * 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 { ColumnarViewModel } from '@elastic/charts'; +import { memoize, sumBy } from 'lodash'; +import { lighten, parseToRgb } from 'polished'; +import seedrandom from 'seedrandom'; +import type { CriticalPathResponse } from '../../../../server/routes/traces/get_aggregated_critical_path'; +import { + CriticalPathTreeNode, + getAggregatedCriticalPathRootNodes, +} from '../../../../common/critical_path/get_aggregated_critical_path_root_nodes'; + +const lightenColor = lighten(0.2); + +export function criticalPathToFlamegraph( + params: { + criticalPath: CriticalPathResponse; + colors: string[]; + } & ({ serviceName: string; transactionName: string } | {}) +): { + viewModel: ColumnarViewModel; + operationId: string[]; + countExclusive: Float64Array; + sum: number; +} { + let sum = 0; + + const { criticalPath, colors } = params; + + const { rootNodes, maxDepth, numNodes } = + getAggregatedCriticalPathRootNodes(params); + + // include the root node + const totalSize = numNodes + 1; + + const operationId = new Array(totalSize); + const countInclusive = new Float64Array(totalSize); + const countExclusive = new Float64Array(totalSize); + const label = new Array(totalSize); + const position = new Float32Array(totalSize * 2); + const size = new Float32Array(totalSize); + const color = new Float32Array(totalSize * 4); + + // eslint-disable-next-line guard-for-in + for (const nodeId in criticalPath.timeByNodeId) { + const count = criticalPath.timeByNodeId[nodeId]; + sum += count; + } + + let maxValue = 0; + + let index = 0; + + const availableColors: Array<[number, number, number, number]> = colors.map( + (vizColor) => { + const rgb = parseToRgb(lightenColor(vizColor)); + + return [rgb.red / 255, rgb.green / 255, rgb.blue / 255, 1]; + } + ); + + const pickColor = memoize((identifier: string) => { + const idx = + Math.abs(seedrandom(identifier).int32()) % availableColors.length; + return availableColors[idx]; + }); + + function addNodeToFlamegraph( + node: CriticalPathTreeNode, + x: number, + y: number + ) { + let nodeOperationId: string; + let nodeLabel: string; + let operationMetadata: CriticalPathResponse['metadata'][string] | undefined; + if (node.nodeId === 'root') { + nodeOperationId = ''; + nodeLabel = 'root'; + } else { + nodeOperationId = criticalPath.operationIdByNodeId[node.nodeId]; + operationMetadata = criticalPath.metadata[nodeOperationId]; + nodeLabel = + operationMetadata['processor.event'] === 'transaction' + ? operationMetadata['transaction.name'] + : operationMetadata['span.name']; + } + + operationId[index] = nodeOperationId; + countInclusive[index] = node.countInclusive; + countExclusive[index] = node.countExclusive; + label[index] = nodeLabel; + position[index * 2] = x / maxValue; + position[index * 2 + 1] = 1 - (y + 1) / (maxDepth + 1); + size[index] = node.countInclusive / maxValue; + + const identifier = + operationMetadata?.['processor.event'] === 'transaction' + ? operationMetadata['transaction.type'] + : operationMetadata?.['span.subtype'] || + operationMetadata?.['span.type'] || + ''; + + color.set(pickColor(identifier), index * 4); + + index++; + + let childX = x; + node.children.forEach((child) => { + addNodeToFlamegraph(child, childX, y + 1); + childX += child.countInclusive; + }); + } + + const root: CriticalPathTreeNode = { + children: rootNodes, + nodeId: 'root', + countExclusive: 0, + countInclusive: sumBy(rootNodes, 'countInclusive'), + }; + + maxValue = root.countInclusive; + + addNodeToFlamegraph(root, 0, 0); + + return { + viewModel: { + value: countInclusive, + label, + color, + position0: position, + position1: position, + size0: size, + size1: size, + }, + operationId, + countExclusive, + sum, + }; +} diff --git a/x-pack/plugins/apm/public/components/shared/critical_path_flamegraph/index.tsx b/x-pack/plugins/apm/public/components/shared/critical_path_flamegraph/index.tsx new file mode 100644 index 0000000000000..f99b82a112c78 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/critical_path_flamegraph/index.tsx @@ -0,0 +1,165 @@ +/* + * 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 { Chart, Datum, Flame, Settings } from '@elastic/charts'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + euiPaletteColorBlind, +} from '@elastic/eui'; +import { css } from '@emotion/css'; +import { useChartTheme } from '@kbn/observability-plugin/public'; +import { uniqueId } from 'lodash'; +import React, { useMemo, useRef } from 'react'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { CriticalPathFlamegraphTooltip } from './critical_path_flamegraph_tooltip'; +import { criticalPathToFlamegraph } from './critical_path_to_flamegraph'; + +const chartClassName = css` + flex-grow: 1; +`; + +export function CriticalPathFlamegraph( + props: { + start: string; + end: string; + traceIds: string[]; + traceIdsFetchStatus: FETCH_STATUS; + } & ({ serviceName: string; transactionName: string } | {}) +) { + const { start, end, traceIds, traceIdsFetchStatus } = props; + + const serviceName = 'serviceName' in props ? props.serviceName : null; + const transactionName = + 'transactionName' in props ? props.transactionName : null; + + // Use a reference to time range, to not invalidate the API fetch + // we only care for traceIds, start/end are there to limit the search + // request to a certain time range. It shouldn't affect the actual results + // of the search. + const timerange = useRef({ start, end }); + timerange.current = { start, end }; + + const { + data: { criticalPath } = { criticalPath: null }, + status: criticalPathFetchStatus, + } = useFetcher( + (callApmApi) => { + if (!traceIds.length) { + return Promise.resolve({ criticalPath: null }); + } + + return callApmApi('POST /internal/apm/traces/aggregated_critical_path', { + params: { + body: { + start: timerange.current.start, + end: timerange.current.end, + traceIds, + serviceName, + transactionName, + }, + }, + }); + }, + [timerange, traceIds, serviceName, transactionName] + ); + + const chartTheme = useChartTheme(); + + const isLoading = + traceIdsFetchStatus === FETCH_STATUS.NOT_INITIATED || + traceIdsFetchStatus === FETCH_STATUS.LOADING || + criticalPathFetchStatus === FETCH_STATUS.NOT_INITIATED || + criticalPathFetchStatus === FETCH_STATUS.LOADING; + + const flameGraph = useMemo(() => { + if (!criticalPath) { + return undefined; + } + + const colors = euiPaletteColorBlind({}); + + const flamegraph = criticalPathToFlamegraph({ + criticalPath, + colors, + }); + + return { + ...flamegraph, + // make sure Flame re-renders when data changes, workaround for https://github.com/elastic/elastic-charts/issues/1766 + key: uniqueId(), + }; + }, [criticalPath]); + + const themeOverrides = { + chartMargins: { top: 0, left: 0, bottom: 0, right: 0 }, + chartPaddings: { left: 0, right: 0, top: 0, bottom: 0 }, + }; + + return ( + + {isLoading ? ( + + + + ) : ( + flameGraph && ( + + + { + const valueIndex = tooltipProps.values[0] + .valueAccessor as number; + const operationId = flameGraph.operationId[valueIndex]; + const operationMetadata = + criticalPath?.metadata[operationId]; + const countInclusive = + flameGraph.viewModel.value[valueIndex]; + const countExclusive = + flameGraph.countExclusive[valueIndex]; + + return ( + + ); + }, + }} + onElementClick={(elements) => {}} + /> + d.value as number} + valueFormatter={(value) => `${value}`} + animation={{ duration: 100 }} + controlProviderCallback={{}} + /> + + + ) + )} + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/technical_preview_badge.tsx b/x-pack/plugins/apm/public/components/shared/technical_preview_badge.tsx index 64002312bdc45..b00d0c256f3f5 100644 --- a/x-pack/plugins/apm/public/components/shared/technical_preview_badge.tsx +++ b/x-pack/plugins/apm/public/components/shared/technical_preview_badge.tsx @@ -9,11 +9,11 @@ import { EuiBetaBadge, IconType } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -interface Props { +type Props = { icon?: IconType; -} +} & Pick, 'size' | 'style'>; -export function TechnicalPreviewBadge({ icon }: Props) { +export function TechnicalPreviewBadge({ icon, size, style }: Props) { return ( ); } diff --git a/x-pack/plugins/apm/public/hooks/create_shared_use_fetcher.tsx b/x-pack/plugins/apm/public/hooks/create_shared_use_fetcher.tsx new file mode 100644 index 0000000000000..ba608fcd451c8 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/create_shared_use_fetcher.tsx @@ -0,0 +1,72 @@ +/* + * 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, { createContext, useContext, useMemo } from 'react'; +import type { APIEndpoint } from '../../server'; +import type { + APIClientRequestParamsOf, + APIReturnType, +} from '../services/rest/create_call_apm_api'; +import { useFetcher, FetcherResult } from './use_fetcher'; + +interface SharedUseFetcher { + useFetcherResult: () => FetcherResult> & { + refetch: () => void; + }; + Provider: React.FunctionComponent< + { + children: React.ReactElement; + params: {}; + } & APIClientRequestParamsOf + >; +} + +export function createSharedUseFetcher( + endpoint: TEndpoint +): SharedUseFetcher { + const Context = createContext< + APIClientRequestParamsOf | undefined + >(undefined); + + const returnValue: SharedUseFetcher = { + useFetcherResult: () => { + const context = useContext(Context); + + if (!context) { + throw new Error('Context was not found'); + } + + const params = context.params; + + const result = useFetcher( + (callApmApi) => { + return callApmApi( + ...([endpoint, { params }] as Parameters) + ); + }, + [params] + ); + + return result as ReturnType< + SharedUseFetcher['useFetcherResult'] + >; + }, + Provider: (props) => { + const { children } = props; + + const params = props.params; + + const memoizedParams = useMemo(() => { + return { params }; + }, [params]); + return ( + {children} + ); + }, + }; + + return returnValue; +} diff --git a/x-pack/plugins/apm/public/hooks/use_trace_explorer_samples.ts b/x-pack/plugins/apm/public/hooks/use_trace_explorer_samples.ts new file mode 100644 index 0000000000000..17b71ac1cc28a --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_trace_explorer_samples.ts @@ -0,0 +1,29 @@ +/* + * 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 { useMemo } from 'react'; +import { createSharedUseFetcher } from './create_shared_use_fetcher'; + +const sharedUseFetcher = createSharedUseFetcher( + 'GET /internal/apm/traces/find' +); + +const useTraceExplorerSamples = () => { + const result = sharedUseFetcher.useFetcherResult(); + + return useMemo(() => { + return { + ...result, + data: result.data || { + traceSamples: [], + }, + }; + }, [result]); +}; +const TraceExplorerSamplesFetcherContextProvider = sharedUseFetcher.Provider; + +export { useTraceExplorerSamples, TraceExplorerSamplesFetcherContextProvider }; diff --git a/x-pack/plugins/apm/server/routes/traces/get_aggregated_critical_path.ts b/x-pack/plugins/apm/server/routes/traces/get_aggregated_critical_path.ts new file mode 100644 index 0000000000000..8acf458f46e22 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/traces/get_aggregated_critical_path.ts @@ -0,0 +1,406 @@ +/* + * 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 { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { rangeQuery, termsQuery } from '@kbn/observability-plugin/server'; +import { Logger } from '@kbn/logging'; +import { + AGENT_NAME, + PROCESSOR_EVENT, + SERVICE_NAME, + SPAN_NAME, + SPAN_SUBTYPE, + SPAN_TYPE, + TRACE_ID, + TRANSACTION_NAME, + TRANSACTION_TYPE, +} from '../../../common/elasticsearch_fieldnames'; +import { AgentName } from '../../../typings/es_schemas/ui/fields/agent'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; + +type OperationMetadata = { + [SERVICE_NAME]: string; + [AGENT_NAME]: AgentName; +} & ( + | { + [PROCESSOR_EVENT]: ProcessorEvent.transaction; + [TRANSACTION_TYPE]: string; + [TRANSACTION_NAME]: string; + } + | { + [PROCESSOR_EVENT]: ProcessorEvent.span; + [SPAN_NAME]: string; + [SPAN_TYPE]: string; + [SPAN_SUBTYPE]: string; + } +); + +type OperationId = string; + +type NodeId = string; + +export interface CriticalPathResponse { + metadata: Record; + timeByNodeId: Record; + nodes: Record; + rootNodes: NodeId[]; + operationIdByNodeId: Record; +} + +const TWO_DAYS_MS = 2 * 24 * 60 * 60 * 1000; + +export async function getAggregatedCriticalPath({ + traceIds, + start, + end, + apmEventClient, + serviceName, + transactionName, + logger, +}: { + traceIds: string[]; + start: number; + end: number; + apmEventClient: APMEventClient; + serviceName: string | null; + transactionName: string | null; + logger: Logger; +}): Promise<{ criticalPath: CriticalPathResponse | null }> { + const now = Date.now(); + + const response = await apmEventClient.search('get_aggregated_critical_path', { + apm: { + events: [ProcessorEvent.span, ProcessorEvent.transaction], + }, + body: { + size: 0, + track_total_hits: false, + query: { + bool: { + filter: [ + ...termsQuery(TRACE_ID, ...traceIds), + // we need a range query to allow ES to skip shards based on the time range, + // but we need enough padding to make sure we get the full trace + ...rangeQuery(start - TWO_DAYS_MS, end + TWO_DAYS_MS), + ], + }, + }, + aggs: { + critical_path: { + scripted_metric: { + params: { + // can't send null parameters to ES. undefined will be removed during JSON serialisation + serviceName: serviceName || undefined, + transactionName: transactionName || undefined, + }, + init_script: { + source: ` + state.eventsById = [:]; + state.metadataByOperationId = [:]; + `, + }, + map_script: { + source: ` + String toHash (def item) { + return item.toString(); + } + + def id; + double duration; + + def operationMetadata = [ + "service.name": doc['service.name'].value, + "processor.event": doc['processor.event'].value, + "agent.name": doc['agent.name'].value + ]; + + def isSpan = !doc['span.id'].empty; + + if (isSpan) { + id = doc['span.id'].value; + operationMetadata.put('span.name', doc['span.name'].value); + if (!doc['span.type'].empty) { + operationMetadata.put('span.type', doc['span.type'].value); + } + if (!doc['span.subtype'].empty) { + operationMetadata.put('span.subtype', doc['span.subtype'].value); + } + duration = doc['span.duration.us'].value; + } else { + id = doc['transaction.id'].value; + operationMetadata.put('transaction.name', doc['transaction.name'].value); + operationMetadata.put('transaction.type', doc['transaction.type'].value); + duration = doc['transaction.duration.us'].value; + } + + String operationId = toHash(operationMetadata); + + def map = [ + "traceId": doc['trace.id'].value, + "id": id, + "parentId": doc['parent.id'].empty ? null : doc['parent.id'].value, + "operationId": operationId, + "timestamp": doc['timestamp.us'].value, + "duration": duration + ]; + + if (state.metadataByOperationId[operationId] == null) { + state.metadataByOperationId.put(operationId, operationMetadata); + } + state.eventsById.put(id, map); + `, + }, + combine_script: { + source: 'return state;', + }, + reduce_script: { + source: ` + String toHash (def item) { + return item.toString(); + } + + def processEvent (def context, def event) { + if (context.processedEvents[event.id] != null) { + return context.processedEvents[event.id]; + } + + def processedEvent = [ + "children": [] + ]; + + if(event.parentId != null) { + def parent = context.events[event.parentId]; + if (parent == null) { + return null; + } + def processedParent = processEvent(context, parent); + if (processedParent == null) { + return null; + } + processedParent.children.add(processedEvent); + } + + context.processedEvents.put(event.id, processedEvent); + + processedEvent.putAll(event); + + if (context.params.serviceName != null && context.params.transactionName != null) { + + def metadata = context.metadata[event.operationId]; + + if (metadata != null + && context.params.serviceName == metadata['service.name'] + && metadata['transaction.name'] != null + && context.params.transactionName == metadata['transaction.name'] + ) { + context.entryTransactions.add(processedEvent); + } + + } else if (event.parentId == null) { + context.entryTransactions.add(processedEvent); + } + + return processedEvent; + } + + double getClockSkew (def context, def item, def parent ) { + if (parent == null) { + return 0; + } + + def processorEvent = context.metadata[item.operationId]['processor.event']; + + def isTransaction = processorEvent == 'transaction'; + + if (!isTransaction) { + return parent.skew; + } + + double parentStart = parent.timestamp + parent.skew; + double offsetStart = parentStart - item.timestamp; + if (offsetStart > 0) { + double latency = Math.round(Math.max(parent.duration - item.duration, 0) / 2); + return offsetStart + latency; + } + + return 0; + } + + void setOffsetAndSkew ( def context, def event, def parent, def startOfTrace ) { + event.skew = getClockSkew(context, event, parent); + event.offset = event.timestamp - startOfTrace; + for(child in event.children) { + setOffsetAndSkew(context, child, event, startOfTrace); + } + event.end = event.offset + event.skew + event.duration; + } + + void count ( def context, def nodeId, def duration ) { + context.timeByNodeId[nodeId] = (context.timeByNodeId[nodeId] ?: 0) + duration; + } + + void scan ( def context, def item, def start, def end, def path ) { + + def nodeId = toHash(path); + + def childNodes = context.nodes[nodeId] != null ? context.nodes[nodeId] : []; + + context.nodes[nodeId] = childNodes; + + context.operationIdByNodeId[nodeId] = item.operationId; + + if (item.children.size() == 0) { + count(context, nodeId, end - start); + return; + } + + item.children.sort((a, b) -> { + if (b.end === a.end) { + return 0; + } + if (b.end > a.end) { + return 1; + } + return -1; + }); + + def scanTime = end; + + for(child in item.children) { + double normalizedChildStart = Math.max(child.offset + child.skew, start); + double childEnd = child.offset + child.skew + child.duration; + + double normalizedChildEnd = Math.min(childEnd, scanTime); + + def isOnCriticalPath = !( + normalizedChildStart >= scanTime || + normalizedChildEnd < start || + childEnd > scanTime + ); + + if (!isOnCriticalPath) { + continue; + } + + def childPath = path.clone(); + + childPath.add(child.operationId); + + def childId = toHash(childPath); + + if(!childNodes.contains(childId)) { + childNodes.add(childId); + } + + if (normalizedChildEnd < (scanTime - 1000)) { + count(context, nodeId, scanTime - normalizedChildEnd); + } + + scan(context, child, normalizedChildStart, childEnd, childPath); + + scanTime = normalizedChildStart; + } + + if (scanTime > start) { + count(context, nodeId, scanTime - start); + } + + } + + def events = [:]; + def metadata = [:]; + def processedEvents = [:]; + def entryTransactions = []; + def timeByNodeId = [:]; + def nodes = [:]; + def rootNodes = []; + def operationIdByNodeId = [:]; + + + def context = [ + "events": events, + "metadata": metadata, + "processedEvents": processedEvents, + "entryTransactions": entryTransactions, + "timeByNodeId": timeByNodeId, + "nodes": nodes, + "operationIdByNodeId": operationIdByNodeId, + "params": params + ]; + + for(state in states) { + if (state.eventsById != null) { + events.putAll(state.eventsById); + } + if (state.metadataByOperationId != null) { + metadata.putAll(state.metadataByOperationId); + } + } + + + for(def event: events.values()) { + processEvent(context, event); + } + + for(transaction in context.entryTransactions) { + transaction.skew = 0; + transaction.offset = 0; + setOffsetAndSkew(context, transaction, null, transaction.timestamp); + + def path = []; + def parent = transaction; + while (parent != null) { + path.add(parent.operationId); + if (parent.parentId == null) { + break; + } + parent = context.processedEvents[parent.parentId]; + } + + Collections.reverse(path); + + def nodeId = toHash(path); + + scan(context, transaction, 0, transaction.duration, path); + + if (!rootNodes.contains(nodeId)) { + rootNodes.add(nodeId); + } + + } + + return [ + "timeByNodeId": timeByNodeId, + "metadata": metadata, + "nodes": nodes, + "rootNodes": rootNodes, + "operationIdByNodeId": operationIdByNodeId + ];`, + }, + }, + }, + }, + }, + }); + + logger.debug( + `Retrieved critical path in ${Date.now() - now}ms, took: ${response.took}ms` + ); + + if (!response.aggregations) { + return { + criticalPath: null, + }; + } + + const criticalPath = response.aggregations?.critical_path + .value as CriticalPathResponse; + + return { + criticalPath, + }; +} diff --git a/x-pack/plugins/apm/server/routes/traces/route.ts b/x-pack/plugins/apm/server/routes/traces/route.ts index dc1408f9bddef..5c72a91c4b013 100644 --- a/x-pack/plugins/apm/server/routes/traces/route.ts +++ b/x-pack/plugins/apm/server/routes/traces/route.ts @@ -6,6 +6,7 @@ */ import * as t from 'io-ts'; +import { nonEmptyStringRt } from '@kbn/io-ts-utils'; import { TraceSearchType } from '../../../common/trace_explorer'; import { setupRequest } from '../../lib/helpers/setup_request'; import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; @@ -23,6 +24,10 @@ import { getTraceItems } from './get_trace_items'; import { getTraceSamplesByQuery } from './get_trace_samples_by_query'; import { getRandomSampler } from '../../lib/helpers/get_random_sampler'; import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; +import { + CriticalPathResponse, + getAggregatedCriticalPath, +} from './get_aggregated_critical_path'; const tracesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/traces', @@ -194,10 +199,49 @@ const findTracesRoute = createApmServerRoute({ }, }); +const aggregatedCriticalPathRoute = createApmServerRoute({ + endpoint: 'POST /internal/apm/traces/aggregated_critical_path', + params: t.type({ + body: t.intersection([ + t.type({ + traceIds: t.array(t.string), + serviceName: t.union([nonEmptyStringRt, t.null]), + transactionName: t.union([nonEmptyStringRt, t.null]), + }), + rangeRt, + ]), + }), + options: { + tags: ['access:apm'], + }, + handler: async ( + resources + ): Promise<{ criticalPath: CriticalPathResponse | null }> => { + const { + params: { + body: { traceIds, start, end, serviceName, transactionName }, + }, + } = resources; + + const apmEventClient = await getApmEventClient(resources); + + return getAggregatedCriticalPath({ + traceIds, + start, + end, + apmEventClient, + serviceName, + transactionName, + logger: resources.logger, + }); + }, +}); + export const traceRouteRepository = { ...tracesByIdRoute, ...tracesRoute, ...rootTransactionByTraceIdRoute, ...transactionByIdRoute, ...findTracesRoute, + ...aggregatedCriticalPathRoute, }; diff --git a/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx b/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx index 42ff021679ce0..6b11566b6e5a7 100644 --- a/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx +++ b/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx @@ -7,6 +7,7 @@ import { PartialTheme } from '@elastic/charts'; import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; +import { useMemo } from 'react'; import { useTheme } from './use_theme'; export function useChartTheme(): PartialTheme[] { @@ -15,24 +16,27 @@ export function useChartTheme(): PartialTheme[] { ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme; - return [ - { - chartMargins: { - left: 10, - right: 10, - top: 10, - bottom: 10, + return useMemo( + () => [ + { + chartMargins: { + left: 10, + right: 10, + top: 10, + bottom: 10, + }, + background: { + color: 'transparent', + }, + lineSeriesStyle: { + point: { visible: false }, + }, + areaSeriesStyle: { + point: { visible: false }, + }, }, - background: { - color: 'transparent', - }, - lineSeriesStyle: { - point: { visible: false }, - }, - areaSeriesStyle: { - point: { visible: false }, - }, - }, - baseChartTheme, - ]; + baseChartTheme, + ], + [baseChartTheme] + ); } diff --git a/x-pack/test/apm_api_integration/tests/traces/critical_path.spec.ts b/x-pack/test/apm_api_integration/tests/traces/critical_path.spec.ts new file mode 100644 index 0000000000000..ca2a1f8d72f89 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/traces/critical_path.spec.ts @@ -0,0 +1,426 @@ +/* + * 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 { getAggregatedCriticalPathRootNodes } from '@kbn/apm-plugin/common'; +import { apm, EntityArrayIterable, EntityIterable, timerange } from '@kbn/apm-synthtrace'; +import expect from '@kbn/expect'; +import { Assign } from '@kbn/utility-types'; +import { invert, sortBy, uniq } from 'lodash'; +import { SupertestReturnType } from '../../common/apm_api_supertest'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2022-01-01T00:00:00.000Z').getTime(); + const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1; + + type Node = ReturnType['rootNodes'][0]; + type Metadata = NonNullable< + SupertestReturnType<'POST /internal/apm/traces/aggregated_critical_path'>['body']['criticalPath'] + >['metadata'][string]; + type HydratedNode = Assign; + + interface FormattedNode { + name: string; + value: number; + children: FormattedNode[]; + } + + // format tree in somewhat concise format for easier testing + function formatTree(nodes: HydratedNode[]): FormattedNode[] { + return sortBy( + nodes.map((node) => { + const name = + node.metadata?.['processor.event'] === 'transaction' + ? node.metadata['transaction.name'] + : node.metadata?.['span.name'] || 'root'; + return { name, value: node.countExclusive, children: formatTree(node.children) }; + }), + (node) => node.name + ); + } + + async function fetchAndBuildCriticalPathTree( + options: { fn: () => EntityIterable } & ({ serviceName: string; transactionName: string } | {}) + ) { + const { fn } = options; + + const generator = fn(); + + const events = generator.toArray(); + + const traceIds = uniq(events.map((event) => event['trace.id']!)); + + await synthtraceEsClient.index(new EntityArrayIterable(events)); + + return apmApiClient + .readUser({ + endpoint: 'POST /internal/apm/traces/aggregated_critical_path', + params: { + body: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + traceIds, + serviceName: 'serviceName' in options ? options.serviceName : null, + transactionName: 'transactionName' in options ? options.transactionName : null, + }, + }, + }) + .then((response) => { + const criticalPath = response.body.criticalPath!; + + const nodeIdByOperationId = invert(criticalPath.operationIdByNodeId); + + const { rootNodes, maxDepth } = getAggregatedCriticalPathRootNodes({ + criticalPath, + }); + + function hydrateNode(node: Node): HydratedNode { + return { + ...node, + metadata: criticalPath.metadata[criticalPath.operationIdByNodeId[node.nodeId]], + children: node.children.map(hydrateNode), + }; + } + + return { + rootNodes: rootNodes.map(hydrateNode), + maxDepth, + criticalPath, + nodeIdByOperationId, + }; + }); + } + + registry.when('Aggregated critical path', { config: 'basic', archives: [] }, () => { + it('builds up the correct tree for a single transaction', async () => { + const java = apm + .service({ name: 'java', environment: 'production', agentName: 'java' }) + .instance('java'); + + const duration = 1000; + const rate = 10; + + const { rootNodes } = await fetchAndBuildCriticalPathTree({ + fn: () => + timerange(start, end) + .interval('15m') + .rate(rate) + .generator((timestamp) => { + return java.transaction('GET /api').timestamp(timestamp).duration(duration); + }), + }); + + expect(rootNodes.length).to.be(1); + + expect(rootNodes[0].countInclusive).to.eql(duration * rate * 1000); + expect(rootNodes[0].countExclusive).to.eql(duration * rate * 1000); + + expect(rootNodes[0].metadata).to.eql({ + 'processor.event': 'transaction', + 'transaction.type': 'request', + 'service.name': 'java', + 'agent.name': 'java', + 'transaction.name': 'GET /api', + }); + }); + + it('builds up the correct tree for a complicated trace', async () => { + const java = apm + .service({ name: 'java', environment: 'production', agentName: 'java' }) + .instance('java'); + + const rate = 10; + + const { rootNodes } = await fetchAndBuildCriticalPathTree({ + fn: () => + timerange(start, end) + .interval('15m') + .rate(rate) + .generator((timestamp) => { + return java + .transaction('GET /api') + .timestamp(timestamp) + .duration(1000) + .children( + java + .span('GET /_search', 'db', 'elasticsearch') + .timestamp(timestamp) + .duration(400), + java + .span('get index stats', 'custom') + .timestamp(timestamp) + .duration(500) + .children( + java + .span('GET /*/_stats', 'db', 'elasticsearch') + .timestamp(timestamp + 50) + .duration(450) + ) + ); + }), + }); + + expect(rootNodes.length).to.be(1); + + expect(rootNodes[0].countInclusive).to.eql(1000 * rate * 1000); + + expect(rootNodes[0].children.length).to.eql(1); + + expect(formatTree(rootNodes)).to.eql([ + { + name: 'GET /api', + value: 500 * 1000 * rate, + children: [ + { + name: 'get index stats', + value: 50 * 1000 * rate, + children: [{ name: 'GET /*/_stats', value: 450 * 1000 * rate, children: [] }], + }, + ], + }, + ]); + }); + + it('slices traces and merges root nodes if service name and transaction name are set', async () => { + // this test also fails when hashCode() is used in the scripted metric aggregation, + // due to collisions. + + const upstreamA = apm + .service({ name: 'upstreamA', environment: 'production', agentName: 'java' }) + .instance('upstreamA'); + + const upstreamB = apm + .service({ name: 'upstreamB', environment: 'production', agentName: 'java' }) + .instance('upstreamB'); + + const downstream = apm + .service({ name: 'downstream', environment: 'production', agentName: 'java' }) + .instance('downstream'); + + const rate = 10; + + function generateTrace() { + return timerange(start, end) + .interval('15m') + .rate(rate) + .generator((timestamp) => { + return [ + upstreamA + .transaction('GET /upstreamA') + .timestamp(timestamp) + .duration(500) + .children( + upstreamA + .span('GET /downstream', 'external', 'http') + .timestamp(timestamp) + .duration(500) + .children( + downstream + .transaction('downstream') + .timestamp(timestamp + 50) + .duration(400) + .children( + downstream + .span('from upstreamA', 'custom') + .timestamp(timestamp + 100) + .duration(300) + ) + ) + ), + upstreamB + .transaction('GET /upstreamB') + .timestamp(timestamp) + .duration(500) + .children( + upstreamB + .span('GET /downstream', 'external', 'http') + .timestamp(timestamp) + .duration(500) + .children( + downstream + .transaction('downstream') + .timestamp(timestamp + 50) + .duration(400) + .children( + downstream + .span('from upstreamB', 'custom') + .timestamp(timestamp + 100) + .duration(300) + ) + ) + ), + ]; + }); + } + + const { rootNodes: unfilteredRootNodes } = await fetchAndBuildCriticalPathTree({ + fn: () => generateTrace(), + }); + + await synthtraceEsClient.clean(); + + const { rootNodes: filteredRootNodes } = await fetchAndBuildCriticalPathTree({ + fn: () => generateTrace(), + serviceName: 'downstream', + transactionName: 'downstream', + }); + + expect(formatTree(unfilteredRootNodes)).eql([ + { + name: 'GET /upstreamA', + value: 0, + children: [ + { + name: 'GET /downstream', + value: 100 * 1000 * rate, + children: [ + { + name: 'downstream', + value: 100 * 1000 * rate, + children: [ + { + name: 'from upstreamA', + value: 300 * 1000 * rate, + children: [], + }, + ], + }, + ], + }, + ], + }, + { + name: 'GET /upstreamB', + value: 0, + children: [ + { + name: 'GET /downstream', + value: 100 * 1000 * rate, + children: [ + { + name: 'downstream', + value: 100 * 1000 * rate, + children: [ + { + name: 'from upstreamB', + value: 300 * 1000 * rate, + children: [], + }, + ], + }, + ], + }, + ], + }, + ]); + + expect(formatTree(filteredRootNodes)).eql([ + { + name: 'downstream', + value: 2 * 100 * 1000 * rate, + children: [ + { + name: 'from upstreamA', + value: 300 * 1000 * rate, + children: [], + }, + { + name: 'from upstreamB', + value: 300 * 1000 * rate, + children: [], + }, + ], + }, + ]); + }); + + it('calculates the critical path for a specific transaction if its not part of the critical path of the entire trace', async () => { + const upstream = apm + .service({ name: 'upstream', environment: 'production', agentName: 'java' }) + .instance('upstream'); + + const downstreamA = apm + .service({ name: 'downstreamA', environment: 'production', agentName: 'java' }) + .instance('downstreamB'); + + const downstreamB = apm + .service({ name: 'downstreamB', environment: 'production', agentName: 'java' }) + .instance('downstreamB'); + + const rate = 10; + + function generateTrace() { + return timerange(start, end) + .interval('15m') + .rate(rate) + .generator((timestamp) => { + return [ + upstream + .transaction('GET /upstream') + .timestamp(timestamp) + .duration(500) + .children( + upstream + .span('GET /downstreamA', 'external', 'http') + .timestamp(timestamp) + .duration(500) + .children( + downstreamA + .transaction('downstreamA') + .timestamp(timestamp + 50) + .duration(400) + ), + upstream + .span('GET /downstreamB', 'external', 'http') + .timestamp(timestamp) + .duration(400) + .children( + downstreamB + .transaction('downstreamB') + .timestamp(timestamp + 50) + .duration(400) + ) + ), + ]; + }); + } + + const { rootNodes: unfilteredRootNodes } = await fetchAndBuildCriticalPathTree({ + fn: () => generateTrace(), + }); + + expect(formatTree(unfilteredRootNodes)[0].children[0].children).to.eql([ + { + name: 'downstreamA', + value: 400 * rate * 1000, + children: [], + }, + ]); + + await synthtraceEsClient.clean(); + + const { rootNodes: filteredRootNodes } = await fetchAndBuildCriticalPathTree({ + fn: () => generateTrace(), + serviceName: 'downstreamB', + transactionName: 'downstreamB', + }); + + expect(formatTree(filteredRootNodes)).to.eql([ + { + name: 'downstreamB', + value: 400 * rate * 1000, + children: [], + }, + ]); + }); + + after(() => synthtraceEsClient.clean()); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts b/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts index 470a68aeacc19..93416c7243f2d 100644 --- a/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts +++ b/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts @@ -1,9 +1,3 @@ -/* - * 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. - */ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License From e39127639dff990515dbd81dc8250830eb34b953 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 2 Nov 2022 15:46:43 +0200 Subject: [PATCH 15/86] [Security Solution] Simplify bulk action execution (#144091) **Relates to:** https://github.com/elastic/kibana/issues/142748 ## Summary Improves typing and simplifies usage of bulk editing hooks. ## Details This patch includes the following changes - improved typing allows to strictly control the input - processing rules loading state for the rules table handling logic is moved inside the hooks - hooks are covered by unit tests ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../rules/bulk_actions/request_schema.mock.ts | 8 +- .../rules/bulk_actions/request_schema.test.ts | 120 +++++------ .../api/rules/bulk_actions/request_schema.ts | 16 +- .../cypress/tasks/rule_details.ts | 2 +- .../rule_management/api/api.test.ts | 98 ++++++++- .../rule_management/api/api.ts | 86 ++++---- .../api/hooks/use_bulk_action_mutation.ts | 29 +-- .../api/hooks/use_bulk_export_mutation.ts | 8 +- .../bulk_actions/show_bulk_error_toast.ts | 28 +++ .../bulk_actions/show_bulk_success_toast.ts | 22 ++ .../logic/bulk_actions/translations.ts | 162 ++++++++------- .../bulk_actions/use_bulk_export.test.ts | 124 +++++++++++ .../logic/bulk_actions/use_bulk_export.ts | 52 ++--- .../use_execute_bulk_action.test.ts | 195 ++++++++++++++++++ .../bulk_actions/use_execute_bulk_action.ts | 119 +++++------ .../use_guess_rule_ids_for_bulk_action.ts | 29 +++ .../bulk_action_dry_run_confirmation.tsx | 10 +- .../bulk_action_rule_errors_list.test.tsx | 8 +- .../bulk_action_rule_errors_list.tsx | 6 +- .../rules_table/bulk_actions/types.ts | 4 +- .../bulk_actions/use_bulk_actions.tsx | 61 +++--- .../bulk_actions/use_bulk_actions_dry_run.ts | 31 +-- ...s => compute_dry_run_edit_payload.test.ts} | 25 +-- ...oad.ts => compute_dry_run_edit_payload.ts} | 23 +-- .../utils/prepare_search_params.test.ts | 4 +- .../utils/prepare_search_params.ts | 6 +- .../rules_table/use_rules_table_actions.tsx | 22 +- .../rule_actions_overflow/index.test.tsx | 12 +- .../rules/rule_actions_overflow/index.tsx | 19 +- .../components/rules/rule_switch/index.tsx | 14 +- .../api/rules/bulk_actions/route.ts | 18 +- .../group10/perform_bulk_action.ts | 184 ++++++++--------- .../group10/perform_bulk_action_dry_run.ts | 30 +-- 33 files changed, 995 insertions(+), 580 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_error_toast.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_success_toast.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.test.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.test.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_guess_rule_ids_for_bulk_action.ts rename x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/{compute_dry_run_payload.test.ts => compute_dry_run_edit_payload.test.ts} (52%) rename x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/{compute_dry_run_payload.ts => compute_dry_run_edit_payload.ts} (82%) diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.mock.ts index 548d8b571660e..fd210ad99680f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.mock.ts @@ -5,18 +5,18 @@ * 2.0. */ -import { BulkAction, BulkActionEditType } from './request_schema'; +import { BulkActionType, BulkActionEditType } from './request_schema'; import type { PerformBulkActionRequestBody } from './request_schema'; export const getPerformBulkActionSchemaMock = (): PerformBulkActionRequestBody => ({ query: '', ids: undefined, - action: BulkAction.disable, + action: BulkActionType.disable, }); export const getPerformBulkActionEditSchemaMock = (): PerformBulkActionRequestBody => ({ query: '', ids: undefined, - action: BulkAction.edit, - [BulkAction.edit]: [{ type: BulkActionEditType.add_tags, value: ['tag1'] }], + action: BulkActionType.edit, + [BulkActionType.edit]: [{ type: BulkActionEditType.add_tags, value: ['tag1'] }], }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts index 63de1d45ec3cb..99f5413e6688b 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts @@ -7,7 +7,7 @@ import { left } from 'fp-ts/lib/Either'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { PerformBulkActionRequestBody, BulkAction, BulkActionEditType } from './request_schema'; +import { PerformBulkActionRequestBody, BulkActionType, BulkActionEditType } from './request_schema'; const retrieveValidationMessage = (payload: unknown) => { const decoded = PerformBulkActionRequestBody.decode(payload); @@ -21,7 +21,7 @@ describe('Perform bulk action request schema', () => { test('valid request: missing query', () => { const payload: PerformBulkActionRequestBody = { query: undefined, - action: BulkAction.enable, + action: BulkActionType.enable, }; const message = retrieveValidationMessage(payload); @@ -59,7 +59,7 @@ describe('Perform bulk action request schema', () => { test('invalid request: unknown property', () => { const payload = { query: 'name: test', - action: BulkAction.enable, + action: BulkActionType.enable, mock: ['id'], }; const message = retrieveValidationMessage(payload); @@ -71,7 +71,7 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong type for ids', () => { const payload = { ids: 'mock', - action: BulkAction.enable, + action: BulkActionType.enable, }; const message = retrieveValidationMessage(payload); @@ -84,7 +84,7 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkAction.enable, + action: BulkActionType.enable, }; const message = retrieveValidationMessage(payload); expect(getPaths(left(message.errors))).toEqual([]); @@ -96,7 +96,7 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkAction.disable, + action: BulkActionType.disable, }; const message = retrieveValidationMessage(payload); expect(getPaths(left(message.errors))).toEqual([]); @@ -108,7 +108,7 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkAction.export, + action: BulkActionType.export, }; const message = retrieveValidationMessage(payload); expect(getPaths(left(message.errors))).toEqual([]); @@ -120,7 +120,7 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkAction.delete, + action: BulkActionType.delete, }; const message = retrieveValidationMessage(payload); expect(getPaths(left(message.errors))).toEqual([]); @@ -132,7 +132,7 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkAction.duplicate, + action: BulkActionType.duplicate, }; const message = retrieveValidationMessage(payload); expect(getPaths(left(message.errors))).toEqual([]); @@ -145,7 +145,7 @@ describe('Perform bulk action request schema', () => { test('invalid request: missing edit payload', () => { const payload = { query: 'name: test', - action: BulkAction.edit, + action: BulkActionType.edit, }; const message = retrieveValidationMessage(payload); @@ -160,8 +160,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: specified edit payload for another action', () => { const payload = { query: 'name: test', - action: BulkAction.enable, - [BulkAction.edit]: [{ type: BulkActionEditType.set_tags, value: ['test-tag'] }], + action: BulkActionType.enable, + [BulkActionType.edit]: [{ type: BulkActionEditType.set_tags, value: ['test-tag'] }], }; const message = retrieveValidationMessage(payload); @@ -175,8 +175,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong type for edit payload', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: { type: BulkActionEditType.set_tags, value: ['test-tag'] }, + action: BulkActionType.edit, + [BulkActionType.edit]: { type: BulkActionEditType.set_tags, value: ['test-tag'] }, }; const message = retrieveValidationMessage(payload); @@ -193,8 +193,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong tags type', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [{ type: BulkActionEditType.set_tags, value: 'test-tag' }], + action: BulkActionType.edit, + [BulkActionType.edit]: [{ type: BulkActionEditType.set_tags, value: 'test-tag' }], }; const message = retrieveValidationMessage(payload); @@ -210,8 +210,8 @@ describe('Perform bulk action request schema', () => { test('valid request: add_tags edit action', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [{ type: BulkActionEditType.add_tags, value: ['test-tag'] }], + action: BulkActionType.edit, + [BulkActionType.edit]: [{ type: BulkActionEditType.add_tags, value: ['test-tag'] }], }; const message = retrieveValidationMessage(payload); @@ -223,8 +223,8 @@ describe('Perform bulk action request schema', () => { test('valid request: set_tags edit action', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [{ type: BulkActionEditType.set_tags, value: ['test-tag'] }], + action: BulkActionType.edit, + [BulkActionType.edit]: [{ type: BulkActionEditType.set_tags, value: ['test-tag'] }], }; const message = retrieveValidationMessage(payload); @@ -236,8 +236,8 @@ describe('Perform bulk action request schema', () => { test('valid request: delete_tags edit action', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [{ type: BulkActionEditType.delete_tags, value: ['test-tag'] }], + action: BulkActionType.edit, + [BulkActionType.edit]: [{ type: BulkActionEditType.delete_tags, value: ['test-tag'] }], }; const message = retrieveValidationMessage(payload); @@ -251,8 +251,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong index_patterns type', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [{ type: BulkActionEditType.set_tags, value: 'logs-*' }], + action: BulkActionType.edit, + [BulkActionType.edit]: [{ type: BulkActionEditType.set_tags, value: 'logs-*' }], }; const message = retrieveValidationMessage(payload); @@ -268,8 +268,10 @@ describe('Perform bulk action request schema', () => { test('valid request: set_index_patterns edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [{ type: BulkActionEditType.set_index_patterns, value: ['logs-*'] }], + action: BulkActionType.edit, + [BulkActionType.edit]: [ + { type: BulkActionEditType.set_index_patterns, value: ['logs-*'] }, + ], }; const message = retrieveValidationMessage(payload); @@ -281,8 +283,10 @@ describe('Perform bulk action request schema', () => { test('valid request: add_index_patterns edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [{ type: BulkActionEditType.add_index_patterns, value: ['logs-*'] }], + action: BulkActionType.edit, + [BulkActionType.edit]: [ + { type: BulkActionEditType.add_index_patterns, value: ['logs-*'] }, + ], }; const message = retrieveValidationMessage(payload); @@ -294,8 +298,8 @@ describe('Perform bulk action request schema', () => { test('valid request: delete_index_patterns edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.delete_index_patterns, value: ['logs-*'] }, ], }; @@ -311,8 +315,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong timeline payload type', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [{ type: BulkActionEditType.set_timeline, value: [] }], + action: BulkActionType.edit, + [BulkActionType.edit]: [{ type: BulkActionEditType.set_timeline, value: [] }], }; const message = retrieveValidationMessage(payload); @@ -328,8 +332,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: missing timeline_id', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_timeline, value: { @@ -353,8 +357,8 @@ describe('Perform bulk action request schema', () => { test('valid request: set_timeline edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_timeline, value: { @@ -376,8 +380,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong schedules payload type', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [{ type: BulkActionEditType.set_schedule, value: [] }], + action: BulkActionType.edit, + [BulkActionType.edit]: [{ type: BulkActionEditType.set_schedule, value: [] }], }; const message = retrieveValidationMessage(payload); @@ -393,8 +397,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong type of payload data', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_schedule, value: { @@ -420,8 +424,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: missing interval', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_schedule, value: { @@ -446,8 +450,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: missing lookback', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_schedule, value: { @@ -472,8 +476,8 @@ describe('Perform bulk action request schema', () => { test('valid request: set_schedule edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_schedule, value: { @@ -495,8 +499,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: invalid rule actions payload', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [{ type: BulkActionEditType.add_rule_actions, value: [] }], + action: BulkActionType.edit, + [BulkActionType.edit]: [{ type: BulkActionEditType.add_rule_actions, value: [] }], }; const message = retrieveValidationMessage(payload); @@ -510,8 +514,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: missing throttle in payload', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_rule_actions, value: { @@ -532,8 +536,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: missing actions in payload', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_rule_actions, value: { @@ -554,8 +558,8 @@ describe('Perform bulk action request schema', () => { test('invalid request: invalid action_type_id property in actions array', () => { const payload = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_rule_actions, value: { @@ -587,8 +591,8 @@ describe('Perform bulk action request schema', () => { test('valid request: add_rule_actions edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_rule_actions, value: { @@ -618,8 +622,8 @@ describe('Perform bulk action request schema', () => { test('valid request: set_rule_actions edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_rule_actions, value: { diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts index 6e5248075b7dc..c09a2c27ea576 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts @@ -22,7 +22,7 @@ import { TimelineTemplateTitle, } from '../../../../rule_schema'; -export enum BulkAction { +export enum BulkActionType { 'enable' = 'enable', 'disable' = 'disable', 'export' = 'export', @@ -162,18 +162,18 @@ export const PerformBulkActionRequestBody = t.intersection([ t.exact( t.type({ action: t.union([ - t.literal(BulkAction.delete), - t.literal(BulkAction.disable), - t.literal(BulkAction.duplicate), - t.literal(BulkAction.enable), - t.literal(BulkAction.export), + t.literal(BulkActionType.delete), + t.literal(BulkActionType.disable), + t.literal(BulkActionType.duplicate), + t.literal(BulkActionType.enable), + t.literal(BulkActionType.export), ]), }) ), t.exact( t.type({ - action: t.literal(BulkAction.edit), - [BulkAction.edit]: NonEmptyArray(BulkActionEditPayload), + action: t.literal(BulkActionType.edit), + [BulkActionType.edit]: NonEmptyArray(BulkActionEditPayload), }) ), ]), diff --git a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts index 3d4f25d248e6a..339dd8f9e46c1 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts @@ -40,7 +40,7 @@ import { addsFields, closeFieldsBrowser, filterFieldsBrowser } from './fields_br export const enablesRule = () => { // Rules get enabled via _bulk_action endpoint - cy.intercept('POST', '/api/detection_engine/rules/_bulk_action').as('bulk_action'); + cy.intercept('POST', '/api/detection_engine/rules/_bulk_action?dry_run=false').as('bulk_action'); cy.get(RULE_SWITCH).should('be.visible'); cy.get(RULE_SWITCH).click(); cy.wait('@bulk_action').then(({ response }) => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts index ca84045e06c65..265cf9d42e9d2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts @@ -15,7 +15,10 @@ import { getUpdateRulesSchemaMock, getRulesSchemaMock, } from '../../../../common/detection_engine/rule_schema/mocks'; - +import { + BulkActionType, + BulkActionEditType, +} from '../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { rulesMock } from '../logic/mock'; import type { FindRulesReferencedByExceptionsListProp } from '../logic/types'; @@ -32,6 +35,7 @@ import { getPrePackagedRulesStatus, previewRule, findRuleExceptionReferences, + performBulkAction, } from './api'; const abortCtrl = new AbortController(); @@ -701,4 +705,96 @@ describe('Detections Rules API', () => { }); }); }); + + describe('performBulkAction', () => { + const fetchMockResult = {}; + + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(fetchMockResult); + }); + + test('passes a query', async () => { + await performBulkAction({ bulkAction: { type: BulkActionType.enable, query: 'some query' } }); + + expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules/_bulk_action', { + method: 'POST', + body: JSON.stringify({ + action: 'enable', + query: 'some query', + }), + query: { + dry_run: false, + }, + }); + }); + + test('passes ids', async () => { + await performBulkAction({ + bulkAction: { type: BulkActionType.disable, ids: ['ruleId1', 'ruleId2'] }, + }); + + expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules/_bulk_action', { + method: 'POST', + body: JSON.stringify({ + action: 'disable', + ids: ['ruleId1', 'ruleId2'], + }), + query: { + dry_run: false, + }, + }); + }); + + test('passes edit payload', async () => { + await performBulkAction({ + bulkAction: { + type: BulkActionType.edit, + ids: ['ruleId1'], + editPayload: [ + { type: BulkActionEditType.add_index_patterns, value: ['some-index-pattern'] }, + ], + }, + }); + + expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules/_bulk_action', { + method: 'POST', + body: JSON.stringify({ + action: 'edit', + ids: ['ruleId1'], + edit: [{ type: 'add_index_patterns', value: ['some-index-pattern'] }], + }), + query: { + dry_run: false, + }, + }); + }); + + test('executes dry run', async () => { + await performBulkAction({ + bulkAction: { type: BulkActionType.disable, query: 'some query' }, + dryRun: true, + }); + + expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules/_bulk_action', { + method: 'POST', + body: JSON.stringify({ + action: 'disable', + query: 'some query', + }), + query: { dry_run: true }, + }); + }); + + test('returns result', async () => { + const result = await performBulkAction({ + bulkAction: { + type: BulkActionType.disable, + query: 'some query', + }, + }); + + expect(result).toBe(fetchMockResult); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts index 3fd5fd65bb639..40a00178c31b8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts @@ -29,7 +29,7 @@ import type { RulesReferencedByExceptionListsSchema } from '../../../../common/d import { DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL } from '../../../../common/detection_engine/rule_exceptions'; import type { BulkActionEditPayload } from '../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; -import { BulkAction } from '../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionType } from '../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import type { RuleResponse, @@ -213,48 +213,45 @@ export interface BulkActionResponse { }; } -export interface BulkActionProps { - action: Exclude; - query?: string; - ids?: string[]; - edit?: BulkActionEditPayload[]; - isDryRun?: boolean; +export type QueryOrIds = { query: string; ids?: undefined } | { query?: undefined; ids: string[] }; +type PlainBulkAction = { + type: Exclude; +} & QueryOrIds; +type EditBulkAction = { + type: BulkActionType.edit; + editPayload: BulkActionEditPayload[]; +} & QueryOrIds; +export type BulkAction = PlainBulkAction | EditBulkAction; + +export interface PerformBulkActionProps { + bulkAction: BulkAction; + dryRun?: boolean; } /** * Perform bulk action with rules selected by a filter query * - * @param query filter query to select rules to perform bulk action with - * @param ids string[] rule ids to select rules to perform bulk action with - * @param edit BulkEditActionPayload edit action payload - * @param action bulk action to perform - * @param isDryRun enables dry run mode for bulk actions + * @param bulkAction bulk action which contains type, query or ids and edit fields + * @param dryRun enables dry run mode for bulk actions * * @throws An error if response is not OK */ -export const performBulkAction = async ({ - action, - query, - edit, - ids, - isDryRun, -}: BulkActionProps): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_BULK_ACTION, { +export async function performBulkAction({ + bulkAction, + dryRun = false, +}: PerformBulkActionProps): Promise { + const params = { + action: bulkAction.type, + query: bulkAction.query, + ids: bulkAction.ids, + edit: bulkAction.type === BulkActionType.edit ? bulkAction.editPayload : undefined, + }; + + return KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_BULK_ACTION, { method: 'POST', - body: JSON.stringify({ - action, - ...(edit ? { edit } : {}), - ...(ids ? { ids } : {}), - ...(query !== undefined ? { query } : {}), - }), - query: { - ...(isDryRun ? { dry_run: isDryRun } : {}), - }, + body: JSON.stringify(params), + query: { dry_run: dryRun }, }); - -export interface BulkExportProps { - query?: string; - ids?: string[]; } export type BulkExportResponse = Blob; @@ -262,23 +259,22 @@ export type BulkExportResponse = Blob; /** * Bulk export rules selected by a filter query * - * @param query filter query to select rules to perform bulk action with - * @param ids string[] rule ids to select rules to perform bulk action with + * @param queryOrIds filter query to select rules to perform bulk action with or rule ids to select rules to perform bulk action with * * @throws An error if response is not OK */ -export const bulkExportRules = async ({ - query, - ids, -}: BulkExportProps): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_BULK_ACTION, { +export async function bulkExportRules(queryOrIds: QueryOrIds): Promise { + const params = { + action: BulkActionType.export, + query: queryOrIds.query, + ids: queryOrIds.ids, + }; + + return KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_BULK_ACTION, { method: 'POST', - body: JSON.stringify({ - action: BulkAction.export, - ...(ids ? { ids } : {}), - ...(query !== undefined ? { query } : {}), - }), + body: JSON.stringify(params), }); +} export interface CreatePrepackagedRulesResponse { rules_installed: number; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts index 647230982c834..866ce74e3c323 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts @@ -6,8 +6,8 @@ */ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; -import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; -import type { BulkActionProps, BulkActionResponse } from '../api'; +import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import type { BulkActionResponse, PerformBulkActionProps } from '../api'; import { performBulkAction } from '../api'; import { useInvalidateFetchPrebuiltRulesStatusQuery } from './use_fetch_prebuilt_rules_status_query'; import { useInvalidateFindRulesQuery, useUpdateRulesCache } from './use_find_rules_query'; @@ -18,7 +18,7 @@ import { DETECTION_ENGINE_RULES_BULK_ACTION } from '../../../../../common/consta export const BULK_ACTION_MUTATION_KEY = ['POST', DETECTION_ENGINE_RULES_BULK_ACTION]; export const useBulkActionMutation = ( - options?: UseMutationOptions + options?: UseMutationOptions ) => { const invalidateFindRulesQuery = useInvalidateFindRulesQuery(); const invalidateFetchRuleByIdQuery = useInvalidateFetchRuleByIdQuery(); @@ -26,32 +26,37 @@ export const useBulkActionMutation = ( const invalidateFetchPrebuiltRulesStatusQuery = useInvalidateFetchPrebuiltRulesStatusQuery(); const updateRulesCache = useUpdateRulesCache(); - return useMutation( - (action: BulkActionProps) => performBulkAction(action), + return useMutation( + (bulkActionProps: PerformBulkActionProps) => performBulkAction(bulkActionProps), { ...options, mutationKey: BULK_ACTION_MUTATION_KEY, onSuccess: (...args) => { - const [res, { action }] = args; - switch (action) { - case BulkAction.enable: - case BulkAction.disable: { + const [ + res, + { + bulkAction: { type: actionType }, + }, + ] = args; + switch (actionType) { + case BulkActionType.enable: + case BulkActionType.disable: { invalidateFetchRuleByIdQuery(); // This action doesn't affect rule content, no need for invalidation updateRulesCache(res?.attributes?.results?.updated ?? []); break; } - case BulkAction.delete: + case BulkActionType.delete: invalidateFindRulesQuery(); invalidateFetchRuleByIdQuery(); invalidateFetchTagsQuery(); invalidateFetchPrebuiltRulesStatusQuery(); break; - case BulkAction.duplicate: + case BulkActionType.duplicate: invalidateFindRulesQuery(); invalidateFetchPrebuiltRulesStatusQuery(); break; - case BulkAction.edit: + case BulkActionType.edit: updateRulesCache(res?.attributes?.results?.updated ?? []); invalidateFetchRuleByIdQuery(); invalidateFetchTagsQuery(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_export_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_export_mutation.ts index 623db44af6098..dbd3a0b9fac3c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_export_mutation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_export_mutation.ts @@ -7,16 +7,16 @@ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import { DETECTION_ENGINE_RULES_BULK_ACTION } from '../../../../../common/constants'; -import type { BulkExportProps, BulkExportResponse } from '../api'; +import type { BulkExportResponse, QueryOrIds } from '../api'; import { bulkExportRules } from '../api'; export const BULK_ACTION_MUTATION_KEY = ['POST', DETECTION_ENGINE_RULES_BULK_ACTION]; export const useBulkExportMutation = ( - options?: UseMutationOptions + options?: UseMutationOptions ) => { - return useMutation( - (action: BulkExportProps) => bulkExportRules(action), + return useMutation( + (action: QueryOrIds) => bulkExportRules(action), { ...options, mutationKey: BULK_ACTION_MUTATION_KEY, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_error_toast.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_error_toast.ts new file mode 100644 index 0000000000000..1c88db4b28ab1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_error_toast.ts @@ -0,0 +1,28 @@ +/* + * 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 { HTTPError } from '../../../../../common/detection_engine/types'; +import type { UseAppToasts } from '../../../../common/hooks/use_app_toasts'; +import type { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { explainBulkError, summarizeBulkError } from './translations'; + +export function showBulkErrorToast( + toasts: UseAppToasts, + action: BulkActionType, + error: HTTPError +): void { + toasts.addError(populateErrorStack(error), { + title: summarizeBulkError(action), + toastMessage: explainBulkError(action, error), + }); +} + +function populateErrorStack(error: HTTPError): HTTPError { + error.stack = JSON.stringify(error.body, null, 2); + + return error; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_success_toast.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_success_toast.ts new file mode 100644 index 0000000000000..7992747ba2f06 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_success_toast.ts @@ -0,0 +1,22 @@ +/* + * 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 { BulkActionSummary } from '..'; +import type { UseAppToasts } from '../../../../common/hooks/use_app_toasts'; +import type { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { explainBulkSuccess, summarizeBulkSuccess } from './translations'; + +export function showBulkSuccessToast( + toasts: UseAppToasts, + action: BulkActionType, + summary: BulkActionSummary +): void { + toasts.addSuccess({ + title: summarizeBulkSuccess(action), + text: explainBulkSuccess(action, summary), + }); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts index 314811d0b142d..76e0b702c5e94 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts @@ -5,92 +5,104 @@ * 2.0. */ -import type { ErrorToastOptions } from '@kbn/core-notifications-browser'; -import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import type { HTTPError } from '../../../../../common/detection_engine/types'; +import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; -import type { BulkActionSummary } from '../../api/api'; +import type { BulkActionResponse, BulkActionSummary } from '../../api/api'; -export function getErrorToastContent( - action: BulkAction, - summary: BulkActionSummary -): ErrorToastOptions { - let title: string; - let toastMessage: string | undefined; +export function summarizeBulkSuccess(action: BulkActionType): string { + switch (action) { + case BulkActionType.export: + return i18n.RULES_BULK_EXPORT_SUCCESS; + + case BulkActionType.duplicate: + return i18n.RULES_BULK_DUPLICATE_SUCCESS; + + case BulkActionType.delete: + return i18n.RULES_BULK_DELETE_SUCCESS; + + case BulkActionType.enable: + return i18n.RULES_BULK_ENABLE_SUCCESS; + + case BulkActionType.disable: + return i18n.RULES_BULK_DISABLE_SUCCESS; + + case BulkActionType.edit: + return i18n.RULES_BULK_EDIT_SUCCESS; + } +} +export function explainBulkSuccess(action: BulkActionType, summary: BulkActionSummary): string { switch (action) { - case BulkAction.export: - title = i18n.RULES_BULK_EXPORT_FAILURE; - if (summary) { - toastMessage = i18n.RULES_BULK_EXPORT_FAILURE_DESCRIPTION(summary.failed); - } - break; - case BulkAction.duplicate: - title = i18n.RULES_BULK_DUPLICATE_FAILURE; - if (summary) { - toastMessage = i18n.RULES_BULK_DUPLICATE_FAILURE_DESCRIPTION(summary.failed); - } - break; - case BulkAction.delete: - title = i18n.RULES_BULK_DELETE_FAILURE; - if (summary) { - toastMessage = i18n.RULES_BULK_DELETE_FAILURE_DESCRIPTION(summary.failed); - } - break; - case BulkAction.enable: - title = i18n.RULES_BULK_ENABLE_FAILURE; - if (summary) { - toastMessage = i18n.RULES_BULK_ENABLE_FAILURE_DESCRIPTION(summary.failed); - } - break; - case BulkAction.disable: - title = i18n.RULES_BULK_DISABLE_FAILURE; - if (summary) { - toastMessage = i18n.RULES_BULK_DISABLE_FAILURE_DESCRIPTION(summary.failed); - } - break; - case BulkAction.edit: - title = i18n.RULES_BULK_EDIT_FAILURE; - if (summary) { - toastMessage = i18n.RULES_BULK_EDIT_FAILURE_DESCRIPTION(summary.failed); - } - break; + case BulkActionType.export: + return getExportSuccessToastMessage(summary.succeeded, summary.total); + + case BulkActionType.duplicate: + return i18n.RULES_BULK_DUPLICATE_SUCCESS_DESCRIPTION(summary.succeeded); + + case BulkActionType.delete: + return i18n.RULES_BULK_DELETE_SUCCESS_DESCRIPTION(summary.succeeded); + + case BulkActionType.enable: + return i18n.RULES_BULK_ENABLE_SUCCESS_DESCRIPTION(summary.succeeded); + + case BulkActionType.disable: + return i18n.RULES_BULK_DISABLE_SUCCESS_DESCRIPTION(summary.succeeded); + + case BulkActionType.edit: + return i18n.RULES_BULK_EDIT_SUCCESS_DESCRIPTION(summary.succeeded); } +} - return { title, toastMessage }; +export function summarizeBulkError(action: BulkActionType): string { + switch (action) { + case BulkActionType.export: + return i18n.RULES_BULK_EXPORT_FAILURE; + + case BulkActionType.duplicate: + return i18n.RULES_BULK_DUPLICATE_FAILURE; + + case BulkActionType.delete: + return i18n.RULES_BULK_DELETE_FAILURE; + + case BulkActionType.enable: + return i18n.RULES_BULK_ENABLE_FAILURE; + + case BulkActionType.disable: + return i18n.RULES_BULK_DISABLE_FAILURE; + + case BulkActionType.edit: + return i18n.RULES_BULK_EDIT_FAILURE; + } } -export function getSuccessToastContent(action: BulkAction, summary: BulkActionSummary) { - let title: string; - let text: string | undefined; +export function explainBulkError(action: BulkActionType, error: HTTPError): string { + // if response doesn't have number of failed rules, it means the whole bulk action failed + const summary = (error.body as BulkActionResponse)?.attributes?.summary; - switch (action) { - case BulkAction.export: - title = i18n.RULES_BULK_EXPORT_SUCCESS; - text = getExportSuccessToastMessage(summary.succeeded, summary.total); - break; - case BulkAction.duplicate: - title = i18n.RULES_BULK_DUPLICATE_SUCCESS; - text = i18n.RULES_BULK_DUPLICATE_SUCCESS_DESCRIPTION(summary.succeeded); - break; - case BulkAction.delete: - title = i18n.RULES_BULK_DELETE_SUCCESS; - text = i18n.RULES_BULK_DELETE_SUCCESS_DESCRIPTION(summary.succeeded); - break; - case BulkAction.enable: - title = i18n.RULES_BULK_ENABLE_SUCCESS; - text = i18n.RULES_BULK_ENABLE_SUCCESS_DESCRIPTION(summary.succeeded); - break; - case BulkAction.disable: - title = i18n.RULES_BULK_DISABLE_SUCCESS; - text = i18n.RULES_BULK_DISABLE_SUCCESS_DESCRIPTION(summary.succeeded); - break; - case BulkAction.edit: - title = i18n.RULES_BULK_EDIT_SUCCESS; - text = i18n.RULES_BULK_EDIT_SUCCESS_DESCRIPTION(summary.succeeded); - break; + if (!summary) { + return ''; } - return { title, text }; + switch (action) { + case BulkActionType.export: + return i18n.RULES_BULK_EXPORT_FAILURE_DESCRIPTION(summary.failed); + + case BulkActionType.duplicate: + return i18n.RULES_BULK_DUPLICATE_FAILURE_DESCRIPTION(summary.failed); + + case BulkActionType.delete: + return i18n.RULES_BULK_DELETE_FAILURE_DESCRIPTION(summary.failed); + + case BulkActionType.enable: + return i18n.RULES_BULK_ENABLE_FAILURE_DESCRIPTION(summary.failed); + + case BulkActionType.disable: + return i18n.RULES_BULK_DISABLE_FAILURE_DESCRIPTION(summary.failed); + + case BulkActionType.edit: + return i18n.RULES_BULK_EDIT_FAILURE_DESCRIPTION(summary.failed); + } } const getExportSuccessToastMessage = (succeeded: number, total: number) => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.test.ts new file mode 100644 index 0000000000000..001a2815fa02e --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.test.ts @@ -0,0 +1,124 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; +import { useBulkExportMutation } from '../../api/hooks/use_bulk_export_mutation'; +import type { QueryOrIds } from '../../api/api'; +import { useBulkExport } from './use_bulk_export'; + +jest.mock('../../../../common/hooks/use_app_toasts'); +jest.mock('../../api/hooks/use_bulk_export_mutation'); +jest.mock('../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'); + +async function bulkExport(queryOrIds: QueryOrIds): Promise { + const { + result: { + current: { bulkExport: bulkExportFn }, + }, + } = renderHook(() => useBulkExport()); + + await bulkExportFn(queryOrIds); +} + +describe('useBulkExport', () => { + let mutateAsync: jest.Mock; + let toasts: Record; + + beforeEach(() => { + jest.clearAllMocks(); + + mutateAsync = jest.fn().mockResolvedValue({ + attributes: { + results: { + updated: [{ immutable: true }, { immutable: false }], + }, + summary: { + total: 2, + succeeded: 2, + }, + }, + }); + (useBulkExportMutation as jest.Mock).mockReturnValue({ mutateAsync }); + + toasts = { + addSuccess: jest.fn(), + addError: jest.fn(), + }; + (useAppToasts as jest.Mock).mockReturnValue(toasts); + }); + + it('executes bulk export action', async () => { + await bulkExport({ query: 'some query' }); + + expect(mutateAsync).toHaveBeenCalledWith({ query: 'some query' }); + }); + + describe('state handlers', () => { + it('shows error toast upon failure', async () => { + (useBulkExportMutation as jest.Mock).mockReturnValue({ + mutateAsync: jest.fn().mockRejectedValue(new Error()), + }); + + await bulkExport({ ids: ['ruleId1'] }); + + expect(toasts.addError).toHaveBeenCalled(); + }); + }); + + describe('when rules table context is available', () => { + let setLoadingRules: jest.Mock; + + beforeEach(() => { + setLoadingRules = jest.fn(); + (useRulesTableContextOptional as jest.Mock).mockReturnValue({ + actions: { + setLoadingRules, + }, + state: { + isAllSelected: false, + }, + }); + }); + + it('sets the loading state before execution', async () => { + await bulkExport({ ids: ['ruleId1', 'ruleId2'] }); + + expect(setLoadingRules).toHaveBeenCalledWith({ + ids: ['ruleId1', 'ruleId2'], + action: BulkActionType.export, + }); + }); + + it('sets the empty loading state before execution when query is set', async () => { + await bulkExport({ query: 'some query' }); + + expect(setLoadingRules).toHaveBeenCalledWith({ + ids: [], + action: BulkActionType.export, + }); + }); + + it('clears loading state for the processing rules after execution', async () => { + await bulkExport({ ids: ['ruleId1', 'ruleId2'] }); + + expect(setLoadingRules).toHaveBeenCalledWith({ ids: [], action: null }); + }); + + it('clears loading state for the processing rules after execution failure', async () => { + (useBulkExportMutation as jest.Mock).mockReturnValue({ + mutateAsync: jest.fn().mockRejectedValue(new Error()), + }); + + await bulkExport({ ids: ['ruleId1', 'ruleId2'] }); + + expect(setLoadingRules).toHaveBeenCalledWith({ ids: [], action: null }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts index 793580e5d3be0..59c38208d8b10 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts @@ -6,44 +6,45 @@ */ import { useCallback } from 'react'; -import type { BulkActionResponse, BulkActionSummary } from '..'; -import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; -import type { HTTPError } from '../../../../../common/detection_engine/types'; +import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import type { UseAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { downloadBlob } from '../../../../common/utils/download_blob'; import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; +import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; import { getExportedRulesCounts } from '../../../rule_management_ui/components/rules_table/helpers'; -import type { RulesTableActions } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; import { useBulkExportMutation } from '../../api/hooks/use_bulk_export_mutation'; -import { getErrorToastContent, getSuccessToastContent } from './translations'; +import { showBulkErrorToast } from './show_bulk_error_toast'; +import { showBulkSuccessToast } from './show_bulk_success_toast'; +import type { QueryOrIds } from '../../api/api'; +import { useGuessRuleIdsForBulkAction } from './use_guess_rule_ids_for_bulk_action'; -interface RulesBulkActionArgs { - visibleRuleIds?: string[]; - search: { query: string } | { ids: string[] }; - setLoadingRules?: RulesTableActions['setLoadingRules']; -} - -export const useBulkExport = () => { +export function useBulkExport() { const toasts = useAppToasts(); const { mutateAsync } = useBulkExportMutation(); + const guessRuleIdsForBulkAction = useGuessRuleIdsForBulkAction(); + const rulesTableContext = useRulesTableContextOptional(); + const setLoadingRules = rulesTableContext?.actions.setLoadingRules; const bulkExport = useCallback( - async ({ visibleRuleIds = [], setLoadingRules, search }: RulesBulkActionArgs) => { + async (queryOrIds: QueryOrIds) => { try { - setLoadingRules?.({ ids: visibleRuleIds, action: BulkAction.export }); - return await mutateAsync(search); + setLoadingRules?.({ + ids: queryOrIds.ids ?? guessRuleIdsForBulkAction(BulkActionType.export), + action: BulkActionType.export, + }); + return await mutateAsync(queryOrIds); } catch (error) { - defaultErrorHandler(toasts, error); + showBulkErrorToast(toasts, BulkActionType.export, error); } finally { setLoadingRules?.({ ids: [], action: null }); } }, - [mutateAsync, toasts] + [guessRuleIdsForBulkAction, setLoadingRules, mutateAsync, toasts] ); return { bulkExport }; -}; +} /** * downloads exported rules, received from export action @@ -61,19 +62,8 @@ export async function downloadExportedRules({ }) { try { downloadBlob(response, `${i18n.EXPORT_FILENAME}.ndjson`); - defaultSuccessHandler(toasts, await getExportedRulesCounts(response)); + showBulkSuccessToast(toasts, BulkActionType.export, await getExportedRulesCounts(response)); } catch (error) { - defaultErrorHandler(toasts, error); + showBulkErrorToast(toasts, BulkActionType.export, error); } } - -function defaultErrorHandler(toasts: UseAppToasts, error: HTTPError): void { - const summary = (error?.body as BulkActionResponse)?.attributes?.summary; - error.stack = JSON.stringify(error.body, null, 2); - - toasts.addError(error, getErrorToastContent(BulkAction.export, summary)); -} - -function defaultSuccessHandler(toasts: UseAppToasts, summary: BulkActionSummary): void { - toasts.addSuccess(getSuccessToastContent(BulkAction.export, summary)); -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.test.ts new file mode 100644 index 0000000000000..aa2648bf633e1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.test.ts @@ -0,0 +1,195 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../../common/lib/telemetry'; +import { useBulkActionMutation } from '../../api/hooks/use_bulk_action_mutation'; +import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; +import { useExecuteBulkAction } from './use_execute_bulk_action'; +import type { BulkAction } from '../../api/api'; + +jest.mock('../../../../common/hooks/use_app_toasts'); +jest.mock('../../../../common/lib/telemetry'); +jest.mock('../../api/hooks/use_bulk_action_mutation'); +jest.mock('../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'); + +async function executeBulkAction( + bulkAction: BulkAction, + options?: Parameters[0] +): Promise { + const { + result: { + current: { executeBulkAction: executeBulkActionFn }, + }, + } = renderHook(() => useExecuteBulkAction(options)); + + await executeBulkActionFn(bulkAction); +} + +describe('useExecuteBulkAction', () => { + let mutateAsync: jest.Mock; + let toasts: Record; + + beforeEach(() => { + jest.clearAllMocks(); + + mutateAsync = jest.fn().mockResolvedValue({ + attributes: { + results: { + updated: [{ immutable: true }, { immutable: false }], + }, + summary: { + total: 2, + succeeded: 2, + }, + }, + }); + (useBulkActionMutation as jest.Mock).mockReturnValue({ mutateAsync }); + + toasts = { + addSuccess: jest.fn(), + addError: jest.fn(), + }; + (useAppToasts as jest.Mock).mockReturnValue(toasts); + }); + + it('executes bulk action', async () => { + const bulkAction = { + type: BulkActionType.enable, + query: 'some query', + } as const; + + await executeBulkAction(bulkAction); + + expect(mutateAsync).toHaveBeenCalledWith({ bulkAction }); + }); + + describe('state handlers', () => { + it('shows success toast upon completion', async () => { + await executeBulkAction({ + type: BulkActionType.enable, + ids: ['ruleId1'], + }); + + expect(toasts.addSuccess).toHaveBeenCalled(); + expect(toasts.addError).not.toHaveBeenCalled(); + }); + + it('does not shows success toast upon completion if suppressed', async () => { + await executeBulkAction( + { + type: BulkActionType.enable, + ids: ['ruleId1'], + }, + { suppressSuccessToast: true } + ); + + expect(toasts.addSuccess).not.toHaveBeenCalled(); + expect(toasts.addError).not.toHaveBeenCalled(); + }); + + it('shows error toast upon failure', async () => { + (useBulkActionMutation as jest.Mock).mockReturnValue({ + mutateAsync: jest.fn().mockRejectedValue(new Error()), + }); + + await executeBulkAction({ + type: BulkActionType.enable, + ids: ['ruleId1'], + }); + + expect(toasts.addError).toHaveBeenCalled(); + expect(toasts.addSuccess).not.toHaveBeenCalled(); + }); + }); + + describe('when rules table context is available', () => { + let setLoadingRules: jest.Mock; + + beforeEach(() => { + setLoadingRules = jest.fn(); + (useRulesTableContextOptional as jest.Mock).mockReturnValue({ + actions: { + setLoadingRules, + }, + state: { + isAllSelected: false, + }, + }); + }); + + it('sets the loading state before execution', async () => { + await executeBulkAction({ + type: BulkActionType.enable, + ids: ['ruleId1', 'ruleId2'], + }); + + expect(setLoadingRules).toHaveBeenCalledWith({ + ids: ['ruleId1', 'ruleId2'], + action: BulkActionType.enable, + }); + }); + + it('sets the empty loading state before execution when query is set', async () => { + await executeBulkAction({ + type: BulkActionType.enable, + query: 'some query', + }); + + expect(setLoadingRules).toHaveBeenCalledWith({ + ids: [], + action: BulkActionType.enable, + }); + }); + + it('clears loading state for the processing rules after execution', async () => { + await executeBulkAction({ + type: BulkActionType.enable, + ids: ['ruleId1', 'ruleId2'], + }); + + expect(setLoadingRules).toHaveBeenCalledWith({ ids: [], action: null }); + }); + + it('clears loading state for the processing rules after execution failure', async () => { + (useBulkActionMutation as jest.Mock).mockReturnValue({ + mutateAsync: jest.fn().mockRejectedValue(new Error()), + }); + + await executeBulkAction({ + type: BulkActionType.enable, + ids: ['ruleId1', 'ruleId2'], + }); + + expect(setLoadingRules).toHaveBeenCalledWith({ ids: [], action: null }); + }); + }); + + describe('telemetry', () => { + it('sends for enable action', async () => { + await executeBulkAction({ + type: BulkActionType.enable, + query: 'some query', + }); + + expect(track).toHaveBeenCalledWith(METRIC_TYPE.COUNT, TELEMETRY_EVENT.SIEM_RULE_ENABLED); + expect(track).toHaveBeenCalledWith(METRIC_TYPE.COUNT, TELEMETRY_EVENT.CUSTOM_RULE_ENABLED); + }); + + it('sends for disable action', async () => { + await executeBulkAction({ + type: BulkActionType.disable, + query: 'some query', + }); + + expect(track).toHaveBeenCalledWith(METRIC_TYPE.COUNT, TELEMETRY_EVENT.SIEM_RULE_DISABLED); + expect(track).toHaveBeenCalledWith(METRIC_TYPE.COUNT, TELEMETRY_EVENT.CUSTOM_RULE_DISABLED); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts index 3ba9e353f3fcd..80bf92c8e3583 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts @@ -7,19 +7,19 @@ import type { NavigateToAppOptions } from '@kbn/core/public'; import { useCallback } from 'react'; -import type { BulkActionResponse, BulkActionSummary } from '..'; +import type { BulkActionResponse } from '..'; import { APP_UI_ID } from '../../../../../common/constants'; -import type { BulkActionEditPayload } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; -import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; -import type { HTTPError } from '../../../../../common/detection_engine/types'; +import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { SecurityPageName } from '../../../../app/types'; import { getEditRuleUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; -import type { UseAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../../common/lib/telemetry'; -import type { RulesTableActions } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; +import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; +import type { BulkAction } from '../../api/api'; import { useBulkActionMutation } from '../../api/hooks/use_bulk_action_mutation'; -import { getErrorToastContent, getSuccessToastContent } from './translations'; +import { showBulkErrorToast } from './show_bulk_error_toast'; +import { showBulkSuccessToast } from './show_bulk_success_toast'; +import { useGuessRuleIdsForBulkAction } from './use_guess_rule_ids_for_bulk_action'; export const goToRuleEditPage = ( ruleId: string, @@ -31,92 +31,65 @@ export const goToRuleEditPage = ( }); }; -type OnActionSuccessCallback = ( - toasts: UseAppToasts, - action: BulkAction, - summary: BulkActionSummary -) => void; - -type OnActionErrorCallback = (toasts: UseAppToasts, action: BulkAction, error: HTTPError) => void; - -interface RulesBulkActionArgs { - action: Exclude; - visibleRuleIds?: string[]; - search: { query: string } | { ids: string[] }; - payload?: { edit?: BulkActionEditPayload[] }; - onError?: OnActionErrorCallback; - onFinish?: () => void; - onSuccess?: OnActionSuccessCallback; - setLoadingRules?: RulesTableActions['setLoadingRules']; +interface UseExecuteBulkActionOptions { + suppressSuccessToast?: boolean; } -export const useExecuteBulkAction = () => { +export const useExecuteBulkAction = (options?: UseExecuteBulkActionOptions) => { const toasts = useAppToasts(); const { mutateAsync } = useBulkActionMutation(); + const guessRuleIdsForBulkAction = useGuessRuleIdsForBulkAction(); + const rulesTableContext = useRulesTableContextOptional(); + const setLoadingRules = rulesTableContext?.actions.setLoadingRules; const executeBulkAction = useCallback( - async ({ - visibleRuleIds = [], - action, - setLoadingRules, - search, - payload, - onSuccess = defaultSuccessHandler, - onError = defaultErrorHandler, - onFinish, - }: RulesBulkActionArgs) => { + async (bulkAction: BulkAction) => { try { - setLoadingRules?.({ ids: visibleRuleIds, action }); - const response = await mutateAsync({ ...search, action, edit: payload?.edit }); - sendTelemetry(action, response); - onSuccess(toasts, action, response.attributes.summary); + setLoadingRules?.({ + ids: bulkAction.ids ?? guessRuleIdsForBulkAction(bulkAction.type), + action: bulkAction.type, + }); + + const response = await mutateAsync({ bulkAction }); + sendTelemetry(bulkAction.type, response); + + if (!options?.suppressSuccessToast) { + showBulkSuccessToast(toasts, bulkAction.type, response.attributes.summary); + } return response; } catch (error) { - onError(toasts, action, error); + showBulkErrorToast(toasts, bulkAction.type, error); } finally { setLoadingRules?.({ ids: [], action: null }); - onFinish?.(); } }, - [mutateAsync, toasts] + [options?.suppressSuccessToast, guessRuleIdsForBulkAction, setLoadingRules, mutateAsync, toasts] ); return { executeBulkAction }; }; -function defaultErrorHandler(toasts: UseAppToasts, action: BulkAction, error: HTTPError) { - const summary = (error?.body as BulkActionResponse)?.attributes?.summary; - error.stack = JSON.stringify(error.body, null, 2); - - toasts.addError(error, getErrorToastContent(action, summary)); -} +function sendTelemetry(action: BulkActionType, response: BulkActionResponse): void { + if (action !== BulkActionType.disable && action !== BulkActionType.enable) { + return; + } -async function defaultSuccessHandler( - toasts: UseAppToasts, - action: BulkAction, - summary: BulkActionSummary -) { - toasts.addSuccess(getSuccessToastContent(action, summary)); -} + if (response.attributes.results.updated.some((rule) => rule.immutable)) { + track( + METRIC_TYPE.COUNT, + action === BulkActionType.enable + ? TELEMETRY_EVENT.SIEM_RULE_ENABLED + : TELEMETRY_EVENT.SIEM_RULE_DISABLED + ); + } -function sendTelemetry(action: BulkAction, response: BulkActionResponse) { - if (action === BulkAction.disable || action === BulkAction.enable) { - if (response.attributes.results.updated.some((rule) => rule.immutable)) { - track( - METRIC_TYPE.COUNT, - action === BulkAction.enable - ? TELEMETRY_EVENT.SIEM_RULE_ENABLED - : TELEMETRY_EVENT.SIEM_RULE_DISABLED - ); - } - if (response.attributes.results.updated.some((rule) => !rule.immutable)) { - track( - METRIC_TYPE.COUNT, - action === BulkAction.disable - ? TELEMETRY_EVENT.CUSTOM_RULE_ENABLED - : TELEMETRY_EVENT.CUSTOM_RULE_DISABLED - ); - } + if (response.attributes.results.updated.some((rule) => !rule.immutable)) { + track( + METRIC_TYPE.COUNT, + action === BulkActionType.disable + ? TELEMETRY_EVENT.CUSTOM_RULE_DISABLED + : TELEMETRY_EVENT.CUSTOM_RULE_ENABLED + ); } } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_guess_rule_ids_for_bulk_action.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_guess_rule_ids_for_bulk_action.ts new file mode 100644 index 0000000000000..af2e1515d6fe1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_guess_rule_ids_for_bulk_action.ts @@ -0,0 +1,29 @@ +/* + * 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 { useCallback } from 'react'; +import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; + +export function useGuessRuleIdsForBulkAction(): (bulkActionType: BulkActionType) => string[] { + const rulesTableContext = useRulesTableContextOptional(); + + return useCallback( + (bulkActionType: BulkActionType) => { + const allRules = rulesTableContext?.state.isAllSelected ? rulesTableContext.state.rules : []; + const processingRules = + bulkActionType === BulkActionType.enable + ? allRules.filter((x) => !x.enabled) + : bulkActionType === BulkActionType.disable + ? allRules.filter((x) => x.enabled) + : allRules; + + return processingRules.map((r) => r.id); + }, + [rulesTableContext] + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx index 941339be4fa31..26ec4db023ba4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx @@ -10,7 +10,7 @@ import { EuiConfirmModal } from '@elastic/eui'; import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; import { BulkActionRuleErrorsList } from './bulk_action_rule_errors_list'; -import { BulkAction } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { assertUnreachable } from '../../../../../../common/utility_types'; import type { BulkActionForConfirmation, DryRunResult } from './types'; @@ -20,9 +20,9 @@ const getActionRejectedTitle = ( failedRulesCount: number ) => { switch (bulkAction) { - case BulkAction.edit: + case BulkActionType.edit: return i18n.BULK_EDIT_CONFIRMATION_REJECTED_TITLE(failedRulesCount); - case BulkAction.export: + case BulkActionType.export: return i18n.BULK_EXPORT_CONFIRMATION_REJECTED_TITLE(failedRulesCount); default: assertUnreachable(bulkAction); @@ -34,9 +34,9 @@ const getActionConfirmLabel = ( succeededRulesCount: number ) => { switch (bulkAction) { - case BulkAction.edit: + case BulkActionType.edit: return i18n.BULK_EDIT_CONFIRMATION_CONFIRM(succeededRulesCount); - case BulkAction.export: + case BulkActionType.export: return i18n.BULK_EXPORT_CONFIRMATION_CONFIRM(succeededRulesCount); default: assertUnreachable(bulkAction); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx index 758ccf0893e36..fd19e01bcde65 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx @@ -13,7 +13,7 @@ import { render, screen } from '@testing-library/react'; import { BulkActionRuleErrorsList } from './bulk_action_rule_errors_list'; import { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; import type { DryRunResult } from './types'; -import { BulkAction } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; const Wrapper: FC = ({ children }) => { return ( @@ -26,7 +26,7 @@ const Wrapper: FC = ({ children }) => { describe('Component BulkEditRuleErrorsList', () => { test('should not render component if no errors present', () => { const { container } = render( - , + , { wrapper: Wrapper, } @@ -46,7 +46,7 @@ describe('Component BulkEditRuleErrorsList', () => { ruleIds: ['rule:1'], }, ]; - render(, { + render(, { wrapper: Wrapper, }); @@ -76,7 +76,7 @@ describe('Component BulkEditRuleErrorsList', () => { ruleIds: ['rule:1', 'rule:2'], }, ]; - render(, { + render(, { wrapper: Wrapper, }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx index 9789377f13ef3..8b908e207daee 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx @@ -10,7 +10,7 @@ import { EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; -import { BulkAction } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import type { DryRunResult, BulkActionForConfirmation } from './types'; @@ -122,7 +122,7 @@ const BulkActionRuleErrorsListComponent = ({ {ruleErrors.map(({ message, errorCode, ruleIds }) => { const rulesCount = ruleIds.length; switch (bulkAction) { - case BulkAction.edit: + case BulkActionType.edit: return ( ); - case BulkAction.export: + case BulkActionType.export: return ( id); await executeBulkAction({ - visibleRuleIds: ruleIds, - action: BulkAction.enable, - setLoadingRules, - search: isAllSelected ? { query: filterQuery } : { ids: ruleIds }, + type: BulkActionType.enable, + ...(isAllSelected ? { query: filterQuery } : { ids: ruleIds }), }); }; @@ -118,10 +117,8 @@ export const useBulkActions = ({ const enabledIds = selectedRules.filter(({ enabled }) => enabled).map(({ id }) => id); await executeBulkAction({ - visibleRuleIds: enabledIds, - action: BulkAction.disable, - setLoadingRules, - search: isAllSelected ? { query: filterQuery } : { ids: enabledIds }, + type: BulkActionType.disable, + ...(isAllSelected ? { query: filterQuery } : { ids: enabledIds }), }); }; @@ -130,10 +127,8 @@ export const useBulkActions = ({ closePopover(); await executeBulkAction({ - visibleRuleIds: selectedRuleIds, - action: BulkAction.duplicate, - setLoadingRules, - search: isAllSelected ? { query: filterQuery } : { ids: selectedRuleIds }, + type: BulkActionType.duplicate, + ...(isAllSelected ? { query: filterQuery } : { ids: selectedRuleIds }), }); clearRulesSelection(); }; @@ -151,10 +146,8 @@ export const useBulkActions = ({ startTransaction({ name: BULK_RULE_ACTIONS.DELETE }); await executeBulkAction({ - visibleRuleIds: selectedRuleIds, - action: BulkAction.delete, - setLoadingRules, - search: isAllSelected ? { query: filterQuery } : { ids: selectedRuleIds }, + type: BulkActionType.delete, + ...(isAllSelected ? { query: filterQuery } : { ids: selectedRuleIds }), }); }; @@ -162,11 +155,9 @@ export const useBulkActions = ({ closePopover(); startTransaction({ name: BULK_RULE_ACTIONS.EXPORT }); - const response = await bulkExport({ - visibleRuleIds: selectedRuleIds, - setLoadingRules, - search: isAllSelected ? { query: filterQuery } : { ids: selectedRuleIds }, - }); + const response = await bulkExport( + isAllSelected ? { query: filterQuery } : { ids: selectedRuleIds } + ); // if response null, likely network error happened and export rules haven't been received if (!response) { @@ -179,7 +170,7 @@ export const useBulkActions = ({ // they can either cancel action or proceed with export of succeeded rules const hasActionBeenConfirmed = await showBulkActionConfirmation( transformExportDetailsToDryRunResult(details), - BulkAction.export + BulkActionType.export ); if (hasActionBeenConfirmed === false) { return; @@ -195,17 +186,17 @@ export const useBulkActions = ({ closePopover(); const dryRunResult = await executeBulkActionsDryRun({ - action: BulkAction.edit, - editAction: bulkEditActionType, - searchParams: isAllSelected + type: BulkActionType.edit, + ...(isAllSelected ? { query: convertRulesFilterToKQL(filterOptions) } - : { ids: selectedRuleIds }, + : { ids: selectedRuleIds }), + editPayload: computeDryRunEditPayload(bulkEditActionType), }); // User has cancelled edit action or there are no custom rules to proceed const hasActionBeenConfirmed = await showBulkActionConfirmation( dryRunResult, - BulkAction.edit + BulkActionType.edit ); if (hasActionBeenConfirmed === false) { return; @@ -256,17 +247,16 @@ export const useBulkActions = ({ }, 5 * 1000); await executeBulkAction({ - visibleRuleIds: selectedRuleIds, - action: BulkAction.edit, - setLoadingRules, - payload: { edit: [editPayload] }, - onFinish: () => hideWarningToast(), - search: prepareSearchParams({ + type: BulkActionType.edit, + ...prepareSearchParams({ ...(isAllSelected ? { filterOptions } : { selectedRuleIds }), dryRunResult, }), + editPayload: [editPayload], }); + hideWarningToast(); + isBulkEditFinished = true; }; @@ -462,7 +452,6 @@ export const useBulkActions = ({ startTransaction, hasMlPermissions, executeBulkAction, - setLoadingRules, filterQuery, toasts, clearRulesSelection, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_dry_run.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_dry_run.ts index d11a2d6c167b8..00e6ef3ad7814 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_dry_run.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_dry_run.ts @@ -8,13 +8,8 @@ import type { UseMutateAsyncFunction } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; -import type { - BulkAction, - BulkActionEditType, -} from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; -import type { BulkActionResponse } from '../../../../rule_management/logic'; +import type { BulkAction, BulkActionResponse } from '../../../../rule_management/logic'; import { performBulkAction } from '../../../../rule_management/logic'; -import { computeDryRunPayload } from './utils/compute_dry_run_payload'; import { processDryRunResult } from './utils/dry_run_result'; import type { DryRunResult } from './types'; @@ -24,7 +19,7 @@ const BULK_ACTIONS_DRY_RUN_QUERY_KEY = 'bulkActionsDryRun'; export type ExecuteBulkActionsDryRun = UseMutateAsyncFunction< DryRunResult | undefined, unknown, - BulkActionsDryRunVariables + BulkAction >; export type UseBulkActionsDryRun = () => { @@ -33,30 +28,16 @@ export type UseBulkActionsDryRun = () => { executeBulkActionsDryRun: ExecuteBulkActionsDryRun; }; -interface BulkActionsDryRunVariables { - action?: Exclude; - editAction?: BulkActionEditType; - searchParams: { query?: string } | { ids?: string[] }; -} - export const useBulkActionsDryRun: UseBulkActionsDryRun = () => { const { data, mutateAsync, isLoading } = useMutation< DryRunResult | undefined, unknown, - BulkActionsDryRunVariables - >([BULK_ACTIONS_DRY_RUN_QUERY_KEY], async ({ searchParams, action, editAction }) => { - if (!action) { - return undefined; - } - + BulkAction + >([BULK_ACTIONS_DRY_RUN_QUERY_KEY], async (bulkAction) => { let result: BulkActionResponse; + try { - result = await performBulkAction({ - ...searchParams, - action, - edit: computeDryRunPayload(action, editAction), - isDryRun: true, - }); + result = await performBulkAction({ bulkAction, dryRun: true }); } catch (err) { // if body doesn't have summary data, action failed altogether and no data available for dry run if ((err.body as BulkActionResponse)?.attributes?.summary?.total === undefined) { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_payload.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.test.ts similarity index 52% rename from x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_payload.test.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.test.ts index b3fe47dd214a0..a1344dcc53ad5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_payload.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.test.ts @@ -5,28 +5,11 @@ * 2.0. */ -import { - BulkAction, - BulkActionEditType, -} from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionEditType } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; -import { computeDryRunPayload } from './compute_dry_run_payload'; - -describe('computeDryRunPayload', () => { - test.each([ - [BulkAction.export], - [BulkAction.duplicate], - [BulkAction.delete], - [BulkAction.enable], - [BulkAction.disable], - ])('should return payload undefined if action is %s', (action) => { - expect(computeDryRunPayload(action)).toBeUndefined(); - }); - - test('should return payload undefined if bulkEdit action is not defined', () => { - expect(computeDryRunPayload(BulkAction.edit)).toBeUndefined(); - }); +import { computeDryRunEditPayload } from './compute_dry_run_edit_payload'; +describe('computeDryRunEditPayload', () => { test.each([ [BulkActionEditType.set_index_patterns, []], [BulkActionEditType.delete_index_patterns, []], @@ -36,7 +19,7 @@ describe('computeDryRunPayload', () => { [BulkActionEditType.set_tags, []], [BulkActionEditType.set_timeline, { timeline_id: '', timeline_title: '' }], ])('should return correct payload for bulk edit action %s', (editAction, value) => { - const payload = computeDryRunPayload(BulkAction.edit, editAction); + const payload = computeDryRunEditPayload(editAction); expect(payload).toHaveLength(1); expect(payload?.[0].type).toEqual(editAction); expect(payload?.[0].value).toEqual(value); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_payload.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts similarity index 82% rename from x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_payload.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts index 36ebb0d5a644d..c8f49ebe4a6c6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_payload.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts @@ -6,10 +6,7 @@ */ import type { BulkActionEditPayload } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; -import { - BulkAction, - BulkActionEditType, -} from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionEditType } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { assertUnreachable } from '../../../../../../../common/utility_types'; /** @@ -18,14 +15,7 @@ import { assertUnreachable } from '../../../../../../../common/utility_types'; * @param {BulkActionEditType | undefined} editAction * @returns {BulkActionEditPayload[] | undefined} */ -export const computeDryRunPayload = ( - action: BulkAction, - editAction?: BulkActionEditType -): BulkActionEditPayload[] | undefined => { - if (action !== BulkAction.edit || !editAction) { - return undefined; - } - +export function computeDryRunEditPayload(editAction: BulkActionEditType): BulkActionEditPayload[] { switch (editAction) { case BulkActionEditType.add_index_patterns: case BulkActionEditType.delete_index_patterns: @@ -74,4 +64,11 @@ export const computeDryRunPayload = ( default: assertUnreachable(editAction); } -}; + + return [ + { + type: editAction, + value: [], + }, + ]; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.test.ts index 3c5dc13ffab66..4a88d19e53970 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.test.ts @@ -39,7 +39,7 @@ describe('prepareSearchParams', () => { dryRunResult, }); - expect(result.ids).toEqual(['rule:1']); + expect(result).toEqual({ ids: ['rule:1'] }); }); test.each([ @@ -105,7 +105,7 @@ describe('prepareSearchParams', () => { }); expect(mockConvertRulesFilterToKQL).toHaveBeenCalledWith(value); - expect(result.query).toEqual(expect.any(String)); + expect(result).toEqual({ query: expect.any(String) }); } ); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.ts index c761e56f4bfef..9b0986d1d68d3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { QueryOrIds } from '../../../../../rule_management/logic'; import type { DryRunResult } from '../types'; import type { FilterOptions } from '../../../../../rule_management/logic/types'; @@ -23,7 +24,10 @@ type PrepareSearchFilterProps = * @param filterOptions {@link FilterOptions} find filter * @returns either list of ids or KQL search query */ -export const prepareSearchParams = ({ dryRunResult, ...props }: PrepareSearchFilterProps) => { +export const prepareSearchParams = ({ + dryRunResult, + ...props +}: PrepareSearchFilterProps): QueryOrIds => { // if selectedRuleIds present, filter out rules that failed during dry run if ('selectedRuleIds' in props) { const failedRuleIdsSet = new Set(dryRunResult?.ruleErrors.flatMap(({ ruleIds }) => ruleIds)); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx index 6e0e2bb761007..6ee7acd438c6e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx @@ -8,7 +8,7 @@ import type { DefaultItemAction } from '@elastic/eui'; import { EuiToolTip } from '@elastic/eui'; import React from 'react'; -import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; @@ -24,14 +24,12 @@ import { goToRuleEditPage, useExecuteBulkAction, } from '../../../rule_management/logic/bulk_actions/use_execute_bulk_action'; -import { useRulesTableContext } from './rules_table/rules_table_context'; import { useHasActionsPrivileges } from './use_has_actions_privileges'; export const useRulesTableActions = (): Array> => { const { navigateToApp } = useKibana().services.application; const hasActionsPrivileges = useHasActionsPrivileges(); const toasts = useAppToasts(); - const { setLoadingRules } = useRulesTableContext().actions; const { startTransaction } = useStartTransaction(); const { executeBulkAction } = useExecuteBulkAction(); const { bulkExport } = useBulkExport(); @@ -69,10 +67,8 @@ export const useRulesTableActions = (): Array> => { onClick: async (rule: Rule) => { startTransaction({ name: SINGLE_RULE_ACTIONS.DUPLICATE }); const result = await executeBulkAction({ - action: BulkAction.duplicate, - setLoadingRules, - visibleRuleIds: [rule.id], - search: { ids: [rule.id] }, + type: BulkActionType.duplicate, + ids: [rule.id], }); const createdRules = result?.attributes.results.created; if (createdRules?.length) { @@ -88,11 +84,7 @@ export const useRulesTableActions = (): Array> => { name: i18n.EXPORT_RULE, onClick: async (rule: Rule) => { startTransaction({ name: SINGLE_RULE_ACTIONS.EXPORT }); - const response = await bulkExport({ - setLoadingRules, - visibleRuleIds: [rule.id], - search: { ids: [rule.id] }, - }); + const response = await bulkExport({ ids: [rule.id] }); if (response) { await downloadExportedRules({ response, @@ -111,10 +103,8 @@ export const useRulesTableActions = (): Array> => { onClick: async (rule: Rule) => { startTransaction({ name: SINGLE_RULE_ACTIONS.DELETE }); await executeBulkAction({ - action: BulkAction.delete, - setLoadingRules, - visibleRuleIds: [rule.id], - search: { ids: [rule.id] }, + type: BulkActionType.delete, + ids: [rule.id], }); }, }, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx index ce265ffb8382c..816c2963779f9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx @@ -120,7 +120,7 @@ describe('RuleActionsOverflow', () => { fireEvent.click(getByTestId('rules-details-duplicate-rule')); expect(executeBulkAction).toHaveBeenCalledWith( - expect.objectContaining({ action: 'duplicate' }) + expect.objectContaining({ type: 'duplicate' }) ); }); @@ -139,9 +139,7 @@ describe('RuleActionsOverflow', () => { fireEvent.click(getByTestId('rules-details-popover-button-icon')); fireEvent.click(getByTestId('rules-details-duplicate-rule')); - expect(executeBulkAction).toHaveBeenCalledWith( - expect.objectContaining({ action: 'duplicate', search: { ids: ['id'] } }) - ); + expect(executeBulkAction).toHaveBeenCalledWith({ type: 'duplicate', ids: ['id'] }); }); }); @@ -213,7 +211,7 @@ describe('RuleActionsOverflow', () => { fireEvent.click(getByTestId('rules-details-popover-button-icon')); fireEvent.click(getByTestId('rules-details-delete-rule')); - expect(executeBulkAction).toHaveBeenCalledWith(expect.objectContaining({ action: 'delete' })); + expect(executeBulkAction).toHaveBeenCalledWith(expect.objectContaining({ type: 'delete' })); }); test('it calls deleteRulesAction with the rule.id when rules-details-delete-rule is clicked', () => { @@ -228,9 +226,7 @@ describe('RuleActionsOverflow', () => { fireEvent.click(getByTestId('rules-details-popover-button-icon')); fireEvent.click(getByTestId('rules-details-delete-rule')); - expect(executeBulkAction).toHaveBeenCalledWith( - expect.objectContaining({ action: 'delete', search: { ids: ['id'] } }) - ); + expect(executeBulkAction).toHaveBeenCalledWith({ type: 'delete', ids: ['id'] }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx index c2fc45d53e1d6..898666623c495 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx @@ -12,11 +12,10 @@ import { EuiPopover, EuiToolTip, } from '@elastic/eui'; -import { noop } from 'lodash'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { APP_UI_ID, SecurityPageName } from '../../../../../common/constants'; -import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { getRulesUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useBoolState } from '../../../../common/hooks/use_bool_state'; @@ -65,7 +64,7 @@ const RuleActionsOverflowComponent = ({ const { navigateToApp } = useKibana().services.application; const toasts = useAppToasts(); const { startTransaction } = useStartTransaction(); - const { executeBulkAction } = useExecuteBulkAction(); + const { executeBulkAction } = useExecuteBulkAction({ suppressSuccessToast: true }); const { bulkExport } = useBulkExport(); const onRuleDeletedCallback = useCallback(() => { @@ -88,9 +87,8 @@ const RuleActionsOverflowComponent = ({ startTransaction({ name: SINGLE_RULE_ACTIONS.DUPLICATE }); closePopover(); const result = await executeBulkAction({ - action: BulkAction.duplicate, - onSuccess: noop, - search: { ids: [rule.id] }, + type: BulkActionType.duplicate, + ids: [rule.id], }); const createdRules = result?.attributes.results.created; if (createdRules?.length) { @@ -117,7 +115,7 @@ const RuleActionsOverflowComponent = ({ onClick={async () => { startTransaction({ name: SINGLE_RULE_ACTIONS.EXPORT }); closePopover(); - const response = await bulkExport({ search: { ids: [rule.id] } }); + const response = await bulkExport({ ids: [rule.id] }); if (response) { await downloadExportedRules({ response, @@ -137,10 +135,11 @@ const RuleActionsOverflowComponent = ({ startTransaction({ name: SINGLE_RULE_ACTIONS.DELETE }); closePopover(); await executeBulkAction({ - action: BulkAction.delete, - onSuccess: onRuleDeletedCallback, - search: { ids: [rule.id] }, + type: BulkActionType.delete, + ids: [rule.id], }); + + onRuleDeletedCallback(); }} > {i18nActions.DELETE_RULE} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx index bcd0e74d6ef9f..32db7db2d5093 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx @@ -7,10 +7,9 @@ import type { EuiSwitchEvent } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSwitch } from '@elastic/eui'; -import { noop } from 'lodash'; import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; -import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; import { useExecuteBulkAction } from '../../../../detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action'; @@ -46,7 +45,7 @@ export const RuleSwitchComponent = ({ const [myIsLoading, setMyIsLoading] = useState(false); const rulesTableContext = useRulesTableContextOptional(); const { startTransaction } = useStartTransaction(); - const { executeBulkAction } = useExecuteBulkAction(); + const { executeBulkAction } = useExecuteBulkAction({ suppressSuccessToast: !rulesTableContext }); const onRuleStateChange = useCallback( async (event: EuiSwitchEvent) => { @@ -55,11 +54,8 @@ export const RuleSwitchComponent = ({ name: enabled ? SINGLE_RULE_ACTIONS.DISABLE : SINGLE_RULE_ACTIONS.ENABLE, }); const bulkActionResponse = await executeBulkAction({ - setLoadingRules: rulesTableContext?.actions.setLoadingRules, - onSuccess: rulesTableContext ? undefined : noop, - action: event.target.checked ? BulkAction.enable : BulkAction.disable, - search: { ids: [id] }, - visibleRuleIds: [], + type: event.target.checked ? BulkActionType.enable : BulkActionType.disable, + ids: [id], }); if (bulkActionResponse?.attributes.results.updated.length) { // The rule was successfully updated @@ -67,7 +63,7 @@ export const RuleSwitchComponent = ({ } setMyIsLoading(false); }, - [enabled, executeBulkAction, id, onChange, rulesTableContext, startTransaction] + [enabled, executeBulkAction, id, onChange, startTransaction] ); const showLoader = useMemo((): boolean => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts index 67a9a233eb807..c3bd79322c318 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts @@ -22,7 +22,7 @@ import { RULES_TABLE_MAX_PAGE_SIZE, } from '../../../../../../../common/constants'; import { - BulkAction, + BulkActionType, PerformBulkActionRequestBody, PerformBulkActionRequestQuery, } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; @@ -298,7 +298,7 @@ export const performBulkActionRoute = ( const isDryRun = request.query.dry_run === 'true'; // dry run is not supported for export, as it doesn't change ES state and has different response format(exported JSON file) - if (isDryRun && body.action === BulkAction.export) { + if (isDryRun && body.action === BulkActionType.export) { return siemResponse.error({ body: `Export action doesn't support dry_run mode`, statusCode: 400, @@ -335,7 +335,7 @@ export const performBulkActionRoute = ( // handling this action before switch statement as bulkEditRules fetch rules within // rulesClient method, hence there is no need to use fetchRulesByQueryOrIds utility - if (body.action === BulkAction.edit && !isDryRun) { + if (body.action === BulkActionType.edit && !isDryRun) { const { rules, errors } = await bulkEditRules({ rulesClient, filter: query, @@ -385,7 +385,7 @@ export const performBulkActionRoute = ( let deleted: RuleAlertType[] = []; switch (body.action) { - case BulkAction.enable: + case BulkActionType.enable: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, @@ -418,7 +418,7 @@ export const performBulkActionRoute = ( .map(({ result }) => result) .filter((rule): rule is RuleAlertType => rule !== null); break; - case BulkAction.disable: + case BulkActionType.disable: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, @@ -452,7 +452,7 @@ export const performBulkActionRoute = ( .filter((rule): rule is RuleAlertType => rule !== null); break; - case BulkAction.delete: + case BulkActionType.delete: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, @@ -483,7 +483,7 @@ export const performBulkActionRoute = ( .filter((rule): rule is RuleAlertType => rule !== null); break; - case BulkAction.duplicate: + case BulkActionType.duplicate: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, @@ -514,7 +514,7 @@ export const performBulkActionRoute = ( .filter((rule): rule is RuleAlertType => rule !== null); break; - case BulkAction.export: + case BulkActionType.export: const exported = await getExportByObjectIds( rulesClient, exceptionsClient, @@ -535,7 +535,7 @@ export const performBulkActionRoute = ( // will be processed only when isDryRun === true // during dry run only validation is getting performed and rule is not saved in ES - case BulkAction.edit: + case BulkActionType.edit: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts index 30b378d2a7eea..82cb42c0039c3 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts @@ -14,7 +14,7 @@ import { } from '@kbn/security-solution-plugin/common/constants'; import type { RuleResponse } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { - BulkAction, + BulkActionType, BulkActionEditType, } from '@kbn/security-solution-plugin/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -83,7 +83,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule()); const { body } = await postBulkAction() - .send({ query: '', action: BulkAction.export }) + .send({ query: '', action: BulkActionType.export }) .expect(200) .expect('Content-Type', 'application/ndjson') .expect('Content-Disposition', 'attachment; filename="rules_export.ndjson"') @@ -115,7 +115,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, testRule); const { body } = await postBulkAction() - .send({ query: '', action: BulkAction.delete }) + .send({ query: '', action: BulkActionType.delete }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); @@ -150,7 +150,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); const { body } = await postBulkAction() - .send({ query: '', action: BulkAction.delete }) + .send({ query: '', action: BulkActionType.delete }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); @@ -171,7 +171,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule(ruleId)); const { body } = await postBulkAction() - .send({ query: '', action: BulkAction.enable }) + .send({ query: '', action: BulkActionType.enable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); @@ -207,7 +207,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); const { body } = await postBulkAction() - .send({ query: '', action: BulkAction.enable }) + .send({ query: '', action: BulkActionType.enable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); @@ -240,7 +240,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule(ruleId, true)); const { body } = await postBulkAction() - .send({ query: '', action: BulkAction.disable }) + .send({ query: '', action: BulkActionType.disable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); @@ -276,7 +276,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); const { body } = await postBulkAction() - .send({ query: '', action: BulkAction.disable }) + .send({ query: '', action: BulkActionType.disable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); @@ -310,7 +310,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, ruleToDuplicate); const { body } = await postBulkAction() - .send({ query: '', action: BulkAction.duplicate }) + .send({ query: '', action: BulkActionType.duplicate }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); @@ -352,7 +352,7 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await postBulkAction() - .send({ query: '', action: BulkAction.duplicate }) + .send({ query: '', action: BulkActionType.duplicate }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); @@ -432,8 +432,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_tags, value: tagsToOverwrite, @@ -506,8 +506,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.delete_tags, value: tagsToDelete, @@ -573,8 +573,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_tags, value: addedTags, @@ -608,8 +608,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_index_patterns, value: ['initial-index-*'], @@ -638,8 +638,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_index_patterns, value: ['index3-*'], @@ -670,8 +670,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.delete_index_patterns, value: ['index2-*'], @@ -699,8 +699,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [mlRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_index_patterns, value: ['index-*'], @@ -732,8 +732,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.delete_index_patterns, value: ['simple-index-*'], @@ -765,8 +765,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_index_patterns, value: [], @@ -820,8 +820,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setTagsBody } = await postBulkAction().send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_tags, value: ['reset-tag'], @@ -862,8 +862,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_timeline, value: { @@ -905,8 +905,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_timeline, value: { @@ -937,8 +937,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [mlRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_index_patterns, value: ['index-*'], @@ -970,8 +970,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.delete_index_patterns, value: ['simple-index-*'], @@ -999,8 +999,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_tags, value: ['test'], @@ -1060,8 +1060,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [prebuiltRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type, value, @@ -1104,8 +1104,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_rule_actions, value: { @@ -1160,8 +1160,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_rule_actions, value: { @@ -1218,8 +1218,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_rule_actions, value: { @@ -1252,8 +1252,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_rule_actions, value: { @@ -1310,8 +1310,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_rule_actions, value: { @@ -1377,8 +1377,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_rule_actions, value: { @@ -1436,8 +1436,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_rule_actions, value: { @@ -1481,8 +1481,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_rule_actions, value: { @@ -1522,8 +1522,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [prebuiltRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type, value: { @@ -1576,8 +1576,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [prebuiltRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_rule_actions, value: { @@ -1641,8 +1641,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_rule_actions, value: { @@ -1697,8 +1697,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_rule_actions, value: { @@ -1749,8 +1749,8 @@ export default ({ getService }: FtrProviderContext): void => { await postBulkAction() .send({ ids: [createdRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_rule_actions, value: { @@ -1783,8 +1783,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_schedule, value: { @@ -1813,8 +1813,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_schedule, value: { @@ -1851,8 +1851,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setIndexBody } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_index_patterns, value: ['initial-index-*'], @@ -1887,8 +1887,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setIndexBody } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.add_index_patterns, value: ['initial-index-*'], @@ -1924,8 +1924,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setIndexBody } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_index_patterns, value: ['initial-index-*'], @@ -1960,8 +1960,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_index_patterns, value: [], @@ -1997,8 +1997,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setIndexBody } = await postBulkAction() .send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_index_patterns, value: ['initial-index-*'], @@ -2035,8 +2035,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.delete_index_patterns, value: ['simple-index-*'], @@ -2071,8 +2071,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.delete_index_patterns, value: ['simple-index-*'], @@ -2107,8 +2107,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.delete_index_patterns, value: ['simple-index-*'], @@ -2142,8 +2142,8 @@ export default ({ getService }: FtrProviderContext): void => { Array.from({ length: 10 }).map(() => postBulkAction().send({ query: '', - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_timeline, value: { @@ -2171,8 +2171,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_timeline, value: { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts index b893f38f20310..741839e707ea9 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts @@ -10,7 +10,7 @@ import { } from '@kbn/security-solution-plugin/common/constants'; import expect from 'expect'; import { - BulkAction, + BulkActionType, BulkActionEditType, } from '@kbn/security-solution-plugin/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -54,7 +54,9 @@ export default ({ getService }: FtrProviderContext): void => { it('should not support export action', async () => { await createRule(supertest, log, getSimpleRule()); - const { body } = await postDryRunBulkAction().send({ action: BulkAction.export }).expect(400); + const { body } = await postDryRunBulkAction() + .send({ action: BulkActionType.export }) + .expect(400); expect(body).toEqual({ message: "Export action doesn't support dry_run mode", @@ -67,7 +69,9 @@ export default ({ getService }: FtrProviderContext): void => { const testRule = getSimpleRule(ruleId); await createRule(supertest, log, testRule); - const { body } = await postDryRunBulkAction().send({ action: BulkAction.delete }).expect(200); + const { body } = await postDryRunBulkAction() + .send({ action: BulkActionType.delete }) + .expect(200); expect(body.attributes.summary).toEqual({ failed: 0, succeeded: 1, total: 1 }); // dry_run mode shouldn't return any rules in results @@ -81,7 +85,9 @@ export default ({ getService }: FtrProviderContext): void => { const ruleId = 'ruleId'; await createRule(supertest, log, getSimpleRule(ruleId)); - const { body } = await postDryRunBulkAction().send({ action: BulkAction.enable }).expect(200); + const { body } = await postDryRunBulkAction() + .send({ action: BulkActionType.enable }) + .expect(200); expect(body.attributes.summary).toEqual({ failed: 0, succeeded: 1, total: 1 }); // dry_run mode shouldn't return any rules in results @@ -97,7 +103,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule(ruleId, true)); const { body } = await postDryRunBulkAction() - .send({ action: BulkAction.disable }) + .send({ action: BulkActionType.disable }) .expect(200); expect(body.attributes.summary).toEqual({ failed: 0, succeeded: 1, total: 1 }); @@ -115,7 +121,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, ruleToDuplicate); const { body } = await postDryRunBulkAction() - .send({ action: BulkAction.disable }) + .send({ action: BulkActionType.disable }) .expect(200); expect(body.attributes.summary).toEqual({ failed: 0, succeeded: 1, total: 1 }); @@ -136,8 +142,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postDryRunBulkAction() .send({ - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_tags, value: ['reset-tag'], @@ -167,8 +173,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postDryRunBulkAction() .send({ ids: [immutableRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: BulkActionEditType.set_tags, value: ['reset-tag'], @@ -208,8 +214,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postDryRunBulkAction() .send({ ids: [mlRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ + action: BulkActionType.edit, + [BulkActionType.edit]: [ { type: editAction, value: [], From 3e707ec9f0fae50b5d19556e65f23e984c157767 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Wed, 2 Nov 2022 09:59:29 -0400 Subject: [PATCH 16/86] [Security Solution][Endpoint] Unit tests for `` and kill/suspend process error messages (#144273) * Tests for `PageOverlay` component * additional tests for kill process * Additional test for suspend-process * Fix import paths --- .../kill_process_action.test.tsx | 34 ++++ .../suspend_process_action.test.tsx | 34 ++++ .../page_overlay/page_overlay.test.tsx | 156 ++++++++++++++++-- .../components/page_overlay/page_overlay.tsx | 11 +- 4 files changed, 219 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/kill_process_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/kill_process_action.test.tsx index b6259e788224d..e65a8d8e751e4 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/kill_process_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/kill_process_action.test.tsx @@ -19,6 +19,11 @@ import { responseActionsHttpMocks } from '../../../../mocks/response_actions_htt import { getEndpointAuthzInitialState } from '../../../../../../common/endpoint/service/authz'; import type { EndpointCapabilities } from '../../../../../../common/endpoint/service/response_actions/constants'; import { ENDPOINT_CAPABILITIES } from '../../../../../../common/endpoint/service/response_actions/constants'; +import type { + ActionDetailsApiResponse, + KillProcessActionOutputContent, +} from '../../../../../../common/endpoint/types'; +import { endpointActionResponseCodes } from '../../lib/endpoint_action_response_codes'; describe('When using the kill-process action from response actions console', () => { let render: ( @@ -233,6 +238,35 @@ describe('When using the kill-process action from response actions console', () }); }); + it.each([['ra_kill-process_error_not-found'], ['ra_kill-process_error_not-permitted']])( + 'should show detailed error if kill-process failure returned code: %s', + async (outputCode) => { + const pendingDetailResponse = apiMocks.responseProvider.actionDetails({ + path: '/api/endpoint/action/a.b.c', + }) as ActionDetailsApiResponse; + pendingDetailResponse.data.agents = ['a.b.c']; + pendingDetailResponse.data.wasSuccessful = false; + pendingDetailResponse.data.errors = ['not found']; + pendingDetailResponse.data.outputs = { + 'a.b.c': { + type: 'json', + content: { + code: outputCode, + }, + }, + }; + apiMocks.responseProvider.actionDetails.mockReturnValue(pendingDetailResponse); + await render(); + enterConsoleCommand(renderResult, 'kill-process --pid 123'); + + await waitFor(() => { + expect(renderResult.getByTestId('killProcess-actionFailure').textContent).toMatch( + new RegExp(endpointActionResponseCodes[outputCode]) + ); + }); + } + ); + it('should show error if kill-process API fails', async () => { apiMocks.responseProvider.killProcess.mockRejectedValueOnce({ status: 500, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/suspend_process_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/suspend_process_action.test.tsx index f234ec1e22800..e02bbcb689c57 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/suspend_process_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/suspend_process_action.test.tsx @@ -19,6 +19,11 @@ import { responseActionsHttpMocks } from '../../../../mocks/response_actions_htt import { getEndpointAuthzInitialState } from '../../../../../../common/endpoint/service/authz'; import type { EndpointCapabilities } from '../../../../../../common/endpoint/service/response_actions/constants'; import { ENDPOINT_CAPABILITIES } from '../../../../../../common/endpoint/service/response_actions/constants'; +import type { + ActionDetailsApiResponse, + KillProcessActionOutputContent, +} from '../../../../../../common/endpoint/types'; +import { endpointActionResponseCodes } from '../../lib/endpoint_action_response_codes'; describe('When using the suspend-process action from response actions console', () => { let render: ( @@ -239,6 +244,35 @@ describe('When using the suspend-process action from response actions console', }); }); + it.each([['ra_suspend-process_error_not-found'], ['ra_suspend-process_error_not-permitted']])( + 'should show detailed error if suspend-process failure returned code: %s', + async (outputCode) => { + const pendingDetailResponse = apiMocks.responseProvider.actionDetails({ + path: '/api/endpoint/action/a.b.c', + }) as ActionDetailsApiResponse; + pendingDetailResponse.data.agents = ['a.b.c']; + pendingDetailResponse.data.wasSuccessful = false; + pendingDetailResponse.data.errors = ['not found']; + pendingDetailResponse.data.outputs = { + 'a.b.c': { + type: 'json', + content: { + code: outputCode, + }, + }, + }; + apiMocks.responseProvider.actionDetails.mockReturnValue(pendingDetailResponse); + await render(); + enterConsoleCommand(renderResult, 'suspend-process --pid 123'); + + await waitFor(() => { + expect(renderResult.getByTestId('suspendProcess-actionFailure').textContent).toMatch( + new RegExp(endpointActionResponseCodes[outputCode]) + ); + }); + } + ); + describe('and when console is closed (not terminated) and then reopened', () => { beforeEach(() => { const _render = render; diff --git a/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.test.tsx b/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.test.tsx index d508fa0cf7504..3898582466106 100644 --- a/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.test.tsx @@ -5,24 +5,158 @@ * 2.0. */ +import React from 'react'; +import type { AppContextTestRender } from '../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; +import type { PageOverlayProps } from './page_overlay'; +import { + PAGE_OVERLAY_DOCUMENT_BODY_FULLSCREEN_CLASSNAME, + PAGE_OVERLAY_DOCUMENT_BODY_IS_VISIBLE_CLASSNAME, + PAGE_OVERLAY_DOCUMENT_BODY_LOCK_CLASSNAME, + PageOverlay, +} from './page_overlay'; +import { act, waitFor } from '@testing-library/react'; + describe('When using PageOverlay component', () => { - it.todo('should display the overlay using minimal props'); + let render: () => ReturnType; + let renderResult: ReturnType; + let reRender: () => ReturnType; + let renderProps: jest.Mocked; + let historyMock: AppContextTestRender['history']; + + beforeEach(() => { + const appTestContext = createAppRootMockRenderer(); + + historyMock = appTestContext.history; + + renderProps = { + children:
    {'page content here'}
    , + onHide: jest.fn(), + 'data-test-subj': 'test', + }; + + render = () => { + renderResult = appTestContext.render(); + return renderResult; + }; + + reRender = () => { + renderResult.rerender(); + return renderResult; + }; + + historyMock.push('/foo'); + }); + + it('should display the overlay using minimal props', () => { + render(); + + const overlay = renderResult.getByTestId('test'); + + expect(overlay.textContent).toEqual('page content here'); + expect(overlay.classList.contains('eui-scrollBar')).toBe(true); + expect(overlay.classList.contains('scrolling')).toBe(true); + expect(overlay.classList.contains('hidden')).toBe(false); + }); + + it('should set classname on `` when visible', () => { + render(); + + const bodyClasslist = window.document.body.classList; + + expect(bodyClasslist.contains(PAGE_OVERLAY_DOCUMENT_BODY_IS_VISIBLE_CLASSNAME)).toBe(true); + expect(bodyClasslist.contains(PAGE_OVERLAY_DOCUMENT_BODY_LOCK_CLASSNAME)).toBe(true); + expect(bodyClasslist.contains(PAGE_OVERLAY_DOCUMENT_BODY_FULLSCREEN_CLASSNAME)).toBe(false); + }); + + it('should all browser window scrolling when `lockDocumentBody` is `false`', () => { + renderProps.lockDocumentBody = false; + render(); + + expect(window.document.body.classList.contains(PAGE_OVERLAY_DOCUMENT_BODY_LOCK_CLASSNAME)).toBe( + false + ); + }); + + it('should remove all classnames from `` when hidden/unmounted', () => { + renderProps.isHidden = true; + render(); + + const bodyClasslist = window.document.body.classList; + + expect(bodyClasslist.contains(PAGE_OVERLAY_DOCUMENT_BODY_IS_VISIBLE_CLASSNAME)).toBe(false); + expect(bodyClasslist.contains(PAGE_OVERLAY_DOCUMENT_BODY_LOCK_CLASSNAME)).toBe(false); + }); + + it('should move the overlay to be the last child of `` if `appendAsBodyLastNode` prop is `true`', async () => { + renderProps.isHidden = true; + render(); + + expect(renderResult.getByTestId('test')).not.toBeVisible(); + + const myDiv = document.createElement('div'); + myDiv.classList.add('my-div'); + document.body.appendChild(myDiv); + + expect(document.body.querySelector('[data-euiportal]')!.nextElementSibling).toBe(myDiv); + + renderProps.isHidden = false; + reRender(); + + await waitFor(() => { + const portalEle = document.body.querySelector('[data-euiportal]')!; + + expect(portalEle.nextElementSibling).toBe(null); + expect(portalEle.previousElementSibling).toBe(myDiv); + }); + }); + + it('should call `onHide` when `hideOnUrlPathnameChange` is `true` and url changes', () => { + render(); + + expect(renderResult.getByTestId('test')).toBeVisible(); + + act(() => { + historyMock.push('/bar'); + }); + + expect(renderProps.onHide).toHaveBeenCalled(); + }); + + it('should NOT call `onHide` when `hideOnUrlPathnameChange` is `false` and url changes', () => { + renderProps.hideOnUrlPathnameChange = false; + render(); - it.todo('should call `onHide` callback when done button is clicked'); + expect(renderResult.getByTestId('test')).toBeVisible(); - it.todo('should set classname on `` when visible'); + act(() => { + historyMock.push('/bar'); + }); - it.todo('should prevent browser window scrolling when `lockDocumentBody` is `true`'); + expect(renderProps.onHide).not.toHaveBeenCalled(); + }); - it.todo('should remove all classnames from `` when hidden/unmounted'); + it('should disable content scrolling inside of overlay', () => { + renderProps.enableScrolling = false; + render(); - it.todo( - 'should move the overlay to be the last child of `` if `appendAsBodyLastNode` prop is `true`' - ); + const overlay = renderResult.getByTestId('test'); - it.todo('should call `onHide` when `hideOnUrlPathnameChange` is `true` and url changes'); + expect(overlay.classList.contains('eui-scrollBar')).toBe(false); + expect(overlay.classList.contains('scrolling')).toBe(false); + }); - it.todo('should NOT call `onHide` when `hideOnUrlPathnameChange` is `false` and url changes'); + it.each` + size | className + ${'xs'} | ${'padding-xs'} + ${'s'} | ${'padding-s'} + ${'m'} | ${'padding-m'} + ${'l'} | ${'padding-l'} + ${'xl'} | ${'padding-xl'} + `('should add padding class names when `paddingSize` of $size is used', ({ size, className }) => { + renderProps.paddingSize = size; + render(); - it.todo('should add padding class names when `paddingSize` prop is defined'); + expect(renderResult.getByTestId('test')).toHaveClass(className); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx b/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx index 2d3a9c9cb7f07..59e5286167bc5 100644 --- a/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx +++ b/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx @@ -77,9 +77,9 @@ const OverlayRootContainer = styled.div` `; const PAGE_OVERLAY_CSS_CLASSNAME = 'securitySolution-pageOverlay'; -const PAGE_OVERLAY_DOCUMENT_BODY_IS_VISIBLE_CLASSNAME = `${PAGE_OVERLAY_CSS_CLASSNAME}-isVisible`; -const PAGE_OVERLAY_DOCUMENT_BODY_LOCK_CLASSNAME = `${PAGE_OVERLAY_CSS_CLASSNAME}-lock`; -const PAGE_OVERLAY_DOCUMENT_BODY_FULLSCREEN_CLASSNAME = `${PAGE_OVERLAY_CSS_CLASSNAME}-fullScreen`; +export const PAGE_OVERLAY_DOCUMENT_BODY_IS_VISIBLE_CLASSNAME = `${PAGE_OVERLAY_CSS_CLASSNAME}-isVisible`; +export const PAGE_OVERLAY_DOCUMENT_BODY_LOCK_CLASSNAME = `${PAGE_OVERLAY_CSS_CLASSNAME}-lock`; +export const PAGE_OVERLAY_DOCUMENT_BODY_FULLSCREEN_CLASSNAME = `${PAGE_OVERLAY_CSS_CLASSNAME}-fullScreen`; const PageOverlayGlobalStyles = createGlobalStyle<{ theme: EuiTheme }>` body.${PAGE_OVERLAY_DOCUMENT_BODY_LOCK_CLASSNAME} { @@ -154,7 +154,7 @@ export interface PageOverlayProps { isHidden?: boolean; /** - * Setting this to `true` (defualt) will enable scrolling inside of the overlay + * Setting this to `true` (default) will enable scrolling inside of the overlay */ enableScrolling?: boolean; @@ -194,7 +194,8 @@ export interface PageOverlayProps { /** * A generic component for taking over the entire Kibana UI main content area (everything below the - * top header that includes the breadcrumbs). + * top header that includes the breadcrumbs). This component adds nothing more than a blank page - its up + * to the `children` pass to actually display any type of intractable UI for the user. */ export const PageOverlay = memo( ({ From c795facbe9a7d23e911de97daa9d9d4721d6eac1 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Wed, 2 Nov 2022 10:01:11 -0400 Subject: [PATCH 17/86] [Security Solution][Endpoint] Add Endpoint error codes for `get-file` response action (#144331) * Endpoint get-file error codes * add tests to get-file for validating error codes are used * Add `get-file` error codes to endpoint emulator --- .../endpoint_action_generator.ts | 15 +++++ .../get_file_action.test.tsx | 47 +++++++++++++- .../lib/endpoint_action_response_codes.ts | 63 +++++++++++++++++++ .../services/endpoint_response_actions.ts | 6 ++ 4 files changed, 129 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts index f8af9d8005893..538efd053611a 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts @@ -193,6 +193,21 @@ export class EndpointActionGenerator extends BaseDataGenerator { return details as unknown as ActionDetails; } + randomGetFileFailureCode(): string { + return this.randomChoice([ + 'ra_get-file_error_not-found', + 'ra_get-file_error_is-directory', + 'ra_get-file_error_invalid-input', + 'ra_get-file_error_not-permitted', + 'ra_get-file_error_too-big', + 'ra_get-file_error_disk-quota', + 'ra_get-file_error_processing', + 'ra_get-file_error_upload-api-unreachable', + 'ra_get-file_error_upload-timeout', + 'ra_get-file_error_queue-timeout', + ]); + } + generateActivityLogAction( overrides: DeepPartial ): EndpointActivityLogAction { diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_file_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_file_action.test.tsx index c2ad573616e3f..d6cb46de63698 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_file_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_file_action.test.tsx @@ -14,15 +14,20 @@ import { ConsoleManagerTestComponent, getConsoleManagerMockRenderResultQueriesAndActions, } from '../../../console/components/console_manager/mocks'; -import { getEndpointConsoleCommands } from '../..'; +import { getEndpointConsoleCommands } from '../../lib/console_commands_definition'; import React from 'react'; import { enterConsoleCommand } from '../../../console/mocks'; import { waitFor } from '@testing-library/react'; import { GET_FILE_ROUTE } from '../../../../../../common/endpoint/constants'; import { getEndpointAuthzInitialStateMock } from '../../../../../../common/endpoint/service/authz/mocks'; -import type { EndpointPrivileges } from '../../../../../../common/endpoint/types'; +import type { + ActionDetailsApiResponse, + EndpointPrivileges, + ResponseActionGetFileOutputContent, +} from '../../../../../../common/endpoint/types'; import { INSUFFICIENT_PRIVILEGES_FOR_COMMAND } from '../../../../../common/translations'; import type { HttpFetchOptionsWithPath } from '@kbn/core-http-browser'; +import { endpointActionResponseCodes } from '../../lib/endpoint_action_response_codes'; jest.mock('../../../../../common/components/user_privileges'); @@ -155,4 +160,42 @@ describe('When using get-file action from response actions console', () => { ); }); }); + + it.each([ + 'ra_get-file_error_not-found', + 'ra_get-file_error_is-directory', + 'ra_get-file_error_invalid-input', + 'ra_get-file_error_not-permitted', + 'ra_get-file_error_too-big', + 'ra_get-file_error_disk-quota', + 'ra_get-file_error_processing', + 'ra_get-file_error_upload-api-unreachable', + 'ra_get-file_error_upload-timeout', + 'ra_get-file_error_queue-timeout', + ])('should show detailed error if get-file failure returned code: %s', async (outputCode) => { + const pendingDetailResponse = apiMocks.responseProvider.actionDetails({ + path: '/api/endpoint/action/a.b.c', + }) as ActionDetailsApiResponse; + pendingDetailResponse.data.agents = ['a.b.c']; + pendingDetailResponse.data.wasSuccessful = false; + pendingDetailResponse.data.errors = ['not found']; + pendingDetailResponse.data.outputs = { + 'a.b.c': { + type: 'json', + content: { + code: outputCode, + } as unknown as ResponseActionGetFileOutputContent, + }, + }; + apiMocks.responseProvider.actionDetails.mockReturnValue(pendingDetailResponse); + await render(); + enterConsoleCommand(renderResult, 'get-file --path one'); + + await waitFor(() => { + expect(renderResult.getByTestId('getFile-actionFailure').textContent).toMatch( + // RegExp below taken from: https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js + new RegExp(endpointActionResponseCodes[outputCode].replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')) + ); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts index 6694601cd8930..746b9201cd200 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts @@ -8,6 +8,69 @@ import { i18n } from '@kbn/i18n'; const CODES = Object.freeze({ + // ----------------------------------------------------------------- + // GET-FILE CODES + // ----------------------------------------------------------------- + /** file not found */ + 'ra_get-file_error_not-found': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.getFile.notFound', + { defaultMessage: 'The file specified was not found' } + ), + + /** path is reachable but does not point to a file */ + 'ra_get-file_error_is-directory': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.getFile.isDirectory', + { defaultMessage: 'The path defined is not a file' } + ), + + /** path did not pass basic validation: malformed path, unix instead of windows, invalid characters, not full path, etc */ + 'ra_get-file_error_invalid-input': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.getFile.invalidPath', + { defaultMessage: 'The path defined is not valid' } + ), + + /** Maybe: possible to be able to list the file but not read it's content */ + 'ra_get-file_error_not-permitted': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.getFile.notPermitted', + { defaultMessage: 'Endpoint unable to read file requested (not permitted)' } + ), + + /** file size exceeds hard coded limit (100MB) */ + 'ra_get-file_error_too-big': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.getFile.tooBig', + { defaultMessage: 'The file requested is too large and can not be retrieved' } + ), + + /** Endpoint ran out of file upload queue size */ + 'ra_get-file_error_disk-quota': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.getFile.diskQuota', + { defaultMessage: 'Endpoint ran out of disk quota while attempting to retrieve file' } + ), + + /** Something interrupted preparing the zip: file read error, zip error */ + 'ra_get-file_error_processing': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.getFile.errorProcessing', + { defaultMessage: 'File retrieval was interrupted' } + ), + + /** The fleet upload API was unreachable (not just busy) */ + 'ra_get-file_error_upload-api-unreachable': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.getFile.uploadApiUnreachable', + { defaultMessage: 'File upload api (fleet-server) is unreachable' } + ), + + /** Perhaps internet connection was too slow or unstable to upload all chunks before unique upload-id expired. Endpoint will re-try a bit (3 times?). */ + 'ra_get-file_error_upload-timeout': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.getFile.uploadTimeout', + { defaultMessage: 'File upload timed out' } + ), + + /** Upload API could be busy, endpoint should periodically re-try */ + 'ra_get-file_error_queue-timeout': i18n.translate( + 'xpack.securitySolution.endpointActionResponseCodes.getFile.queueTimeout', + { defaultMessage: 'Endpoint timed out while attempting to connect to upload API' } + ), + // ----------------------------------------------------------------- // SUSPEND-PROCESS CODES // ----------------------------------------------------------------- diff --git a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts index ddbdd660efb52..c4113f0132c05 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts @@ -133,6 +133,12 @@ export const sendEndpointActionResponse = async ( endpointResponse.error = { message: 'Endpoint encountered an error and was unable to apply action to host', }; + + if (endpointResponse.EndpointActions.data.command === 'get-file') { + ( + endpointResponse.EndpointActions.data.output?.content as ResponseActionGetFileOutputContent + ).code = endpointActionGenerator.randomGetFileFailureCode(); + } } await esClient.index({ From 2c909c18cd472d756e7b2013210df88f37056b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Wed, 2 Nov 2022 10:40:07 -0400 Subject: [PATCH 18/86] Add a refresh:false when deleting action_task_params (#144399) * Add a refresh:false when deleting action_task_params * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../actions/server/lib/task_runner_factory.test.ts | 8 ++++++-- x-pack/plugins/actions/server/lib/task_runner_factory.ts | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index 7fd09f2237eac..63ab73ccc0efe 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -304,7 +304,9 @@ test('cleans up action_task_params object', async () => { await taskRunner.run(); - expect(services.savedObjectsClient.delete).toHaveBeenCalledWith('action_task_params', '3'); + expect(services.savedObjectsClient.delete).toHaveBeenCalledWith('action_task_params', '3', { + refresh: false, + }); }); test('task runner should implement CancellableTask cancel method with logging warning message', async () => { @@ -367,7 +369,9 @@ test('runs successfully when cleanup fails and logs the error', async () => { await taskRunner.run(); - expect(services.savedObjectsClient.delete).toHaveBeenCalledWith('action_task_params', '3'); + expect(services.savedObjectsClient.delete).toHaveBeenCalledWith('action_task_params', '3', { + refresh: false, + }); expect(taskRunnerFactoryInitializerParams.logger.error).toHaveBeenCalledWith( 'Failed to cleanup action_task_params object [id="3"]: Fail' ); diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index 51672865d6771..2c23dbc77e316 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -171,7 +171,8 @@ export class TaskRunnerFactory { // Once support for legacy alert RBAC is dropped, this can be secured await getUnsecuredSavedObjectsClient(request).delete( ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, - actionTaskExecutorParams.actionTaskParamsId + actionTaskExecutorParams.actionTaskParamsId, + { refresh: false } ); } catch (e) { // Log error only, we shouldn't fail the task because of an error here (if ever there's retry logic) From 92db69cce5fe42fb0d93ae7ad9f8896442396c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Wed, 2 Nov 2022 15:46:31 +0100 Subject: [PATCH 19/86] [Guided onboarding] Search guide copy (#144234) * [Guided onboarding] Update the search guide config to use the correct copy * [Guided onboarding] Remove the docs link, not needed in 8.6 * [Guided onboarding] Delete the step id type for the removed search step * Update src/plugins/guided_onboarding/public/constants/guides_config/search.ts Co-authored-by: Kelly Murphy * Update src/plugins/guided_onboarding/public/constants/guides_config/search.ts Co-authored-by: Kelly Murphy * Update src/plugins/guided_onboarding/public/constants/guides_config/search.ts Co-authored-by: Kelly Murphy * Update src/plugins/guided_onboarding/public/constants/guides_config/search.ts Co-authored-by: Kelly Murphy * Update src/plugins/guided_onboarding/public/constants/guides_config/search.ts Co-authored-by: Kelly Murphy * Update src/plugins/guided_onboarding/public/constants/guides_config/search.ts Co-authored-by: Kelly Murphy * [Guided onboarding] Update step description and description list * Update src/plugins/guided_onboarding/public/constants/guides_config/search.ts Co-authored-by: Casey Zumwalt * Update src/plugins/guided_onboarding/public/constants/guides_config/search.ts Co-authored-by: Casey Zumwalt * Update src/plugins/guided_onboarding/public/constants/guides_config/search.ts Co-authored-by: Casey Zumwalt * [Guided onboarding] Update the copy Co-authored-by: Kelly Murphy Co-authored-by: Casey Zumwalt --- .../public/components/main.tsx | 3 +- packages/kbn-guided-onboarding/src/types.ts | 2 +- .../public/components/guide_panel.test.tsx | 94 +++++++++---------- .../components/guide_panel_step.styles.ts | 13 ++- .../public/components/guide_panel_step.tsx | 9 +- .../constants/guides_config/observability.ts | 26 ++--- .../public/constants/guides_config/search.ts | 69 +++++++------- .../constants/guides_config/test_guide.ts | 3 +- src/plugins/guided_onboarding/public/types.ts | 5 +- 9 files changed, 113 insertions(+), 111 deletions(-) diff --git a/examples/guided_onboarding_example/public/components/main.tsx b/examples/guided_onboarding_example/public/components/main.tsx index 4c9481d423e4c..d3636470ec64e 100644 --- a/examples/guided_onboarding_example/public/components/main.tsx +++ b/examples/guided_onboarding_example/public/components/main.tsx @@ -216,7 +216,8 @@ export const Main = (props: MainProps) => { )} {(guideState?.isActive === true || guideState?.status === 'in_progress' || - guideState?.status === 'ready_to_complete') && ( + guideState?.status === 'ready_to_complete' || + guideState?.status === 'not_started') && ( { test('should be enabled if there is an active guide', async () => { const { exists, component, find } = testBed; - // Enable the "search" guide - await updateComponentWithState(component, mockActiveSearchGuideState, true); + // Enable the "test" guide + await updateComponentWithState(component, mockActiveTestGuideState, true); expect(exists('disabledGuideButton')).toBe(false); expect(exists('guideButton')).toBe(true); @@ -131,7 +131,7 @@ describe('Guided setup', () => { test('should show the step number in the button label if a step is active', async () => { const { component, find } = testBed; - await updateComponentWithState(component, mockInProgressSearchGuideState, true); + await updateComponentWithState(component, mockInProgressTestGuideState, true); expect(find('guideButton').text()).toEqual('Setup guide: step 1'); }); @@ -139,7 +139,7 @@ describe('Guided setup', () => { test('shows the step number in the button label if a step is ready to complete', async () => { const { component, find } = testBed; - await updateComponentWithState(component, mockReadyToCompleteSearchGuideState, true); + await updateComponentWithState(component, mockReadyToCompleteTestGuideState, true); expect(find('guideButton').text()).toEqual('Setup guide: step 2'); }); @@ -147,7 +147,7 @@ describe('Guided setup', () => { test('shows the manual completion popover if a step is ready to complete', async () => { const { component, exists } = testBed; - await updateComponentWithState(component, mockReadyToCompleteSearchGuideState, false); + await updateComponentWithState(component, mockReadyToCompleteTestGuideState, false); expect(exists('manualCompletionPopover')).toBe(true); }); @@ -155,7 +155,7 @@ describe('Guided setup', () => { test('shows no manual completion popover if a step is in progress', async () => { const { component, exists } = testBed; - await updateComponentWithState(component, mockInProgressSearchGuideState, false); + await updateComponentWithState(component, mockInProgressTestGuideState, false); expect(exists('manualCompletionPopoverPanel')).toBe(false); }); @@ -165,29 +165,29 @@ describe('Guided setup', () => { test('should be enabled if a guide is activated', async () => { const { exists, component, find } = testBed; - await updateComponentWithState(component, mockActiveSearchGuideState, true); + await updateComponentWithState(component, mockActiveTestGuideState, true); expect(exists('guidePanel')).toBe(true); expect(exists('guideProgress')).toBe(false); - expect(find('guidePanelStep').length).toEqual(guidesConfig.search.steps.length); + expect(find('guidePanelStep').length).toEqual(guidesConfig.testGuide.steps.length); }); test('should show the progress bar if the first step has been completed', async () => { const { component, exists } = testBed; - const mockCompleteSearchGuideState: GuideState = { - ...mockActiveSearchGuideState, + const mockCompleteTestGuideState: GuideState = { + ...mockActiveTestGuideState, steps: [ { - id: mockActiveSearchGuideState.steps[0].id, + ...mockActiveTestGuideState.steps[0], status: 'complete', }, - mockActiveSearchGuideState.steps[1], - mockActiveSearchGuideState.steps[2], + mockActiveTestGuideState.steps[1], + mockActiveTestGuideState.steps[2], ], }; - await updateComponentWithState(component, mockCompleteSearchGuideState, true); + await updateComponentWithState(component, mockCompleteTestGuideState, true); expect(exists('guidePanel')).toBe(true); expect(exists('guideProgress')).toBe(true); @@ -197,20 +197,20 @@ describe('Guided setup', () => { const { component, exists, find } = testBed; const readyToCompleteGuideState: GuideState = { - guideId: 'search', + guideId: 'testGuide', status: 'ready_to_complete', isActive: true, steps: [ { - id: 'add_data', + id: 'step1', status: 'complete', }, { - id: 'browse_docs', + id: 'step2', status: 'complete', }, { - id: 'search_experience', + id: 'step3', status: 'complete', }, ], @@ -220,7 +220,7 @@ describe('Guided setup', () => { expect(find('guideTitle').text()).toContain('Well done'); expect(find('guideDescription').text()).toContain( - `You've completed the Elastic Enterprise Search guide` + `You've completed the Elastic Testing example guide` ); expect(exists('useElasticButton')).toBe(true); }); @@ -239,7 +239,7 @@ describe('Guided setup', () => { test('can start a step if step has not been started', async () => { const { component, find, exists } = testBed; - await updateComponentWithState(component, mockActiveSearchGuideState, true); + await updateComponentWithState(component, mockActiveTestGuideState, true); expect(find('activeStepButton').text()).toEqual('Start'); @@ -251,7 +251,7 @@ describe('Guided setup', () => { test('can continue a step if step is in progress', async () => { const { component, find, exists } = testBed; - await updateComponentWithState(component, mockInProgressSearchGuideState, true); + await updateComponentWithState(component, mockInProgressTestGuideState, true); expect(find('activeStepButton').text()).toEqual('Continue'); @@ -263,7 +263,7 @@ describe('Guided setup', () => { test('can mark a step "done" if step is ready to complete', async () => { const { component, find, exists } = testBed; - await updateComponentWithState(component, mockReadyToCompleteSearchGuideState, true); + await updateComponentWithState(component, mockReadyToCompleteTestGuideState, true); expect(find('activeStepButton').text()).toEqual('Mark done'); @@ -279,20 +279,20 @@ describe('Guided setup', () => { const { component, find } = testBed; const mockSingleSentenceStepDescriptionGuideState: GuideState = { - guideId: 'observability', + guideId: 'testGuide', isActive: true, status: 'in_progress', steps: [ { - id: 'add_data', + id: 'step1', status: 'complete', }, { - id: 'view_dashboard', + id: 'step2', status: 'complete', }, { - id: 'tour_observability', + id: 'step3', status: 'in_progress', }, ], @@ -307,23 +307,21 @@ describe('Guided setup', () => { expect( find('guidePanelStepDescription') .last() - .containsMatchingElement( -

    {guidesConfig.observability.steps[2].descriptionList[0]}

    - ) + .containsMatchingElement(

    {guidesConfig.testGuide.steps[2].description}

    ) ).toBe(true); }); test('should render the step description as an unordered list if it is more than one sentence', async () => { const { component, find } = testBed; - await updateComponentWithState(component, mockActiveSearchGuideState, true); + await updateComponentWithState(component, mockActiveTestGuideState, true); expect( find('guidePanelStepDescription') .first() .containsMatchingElement(
      - {guidesConfig.search.steps[0].descriptionList.map((description, i) => ( + {guidesConfig.testGuide.steps[0].descriptionList?.map((description, i) => (
    • {description}
    • ))}
    @@ -337,8 +335,8 @@ describe('Guided setup', () => { const { component, find, exists } = testBed; await act(async () => { - // Enable the "search" guide - await apiService.updateGuideState(mockActiveSearchGuideState, true); + // Enable the "test" guide + await apiService.updateGuideState(mockActiveTestGuideState, true); }); component.update(); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts b/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts index 6994e4235d9d1..24106851dc6e2 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts @@ -29,10 +29,13 @@ export const getGuidePanelStepStyles = (euiTheme: EuiThemeComputed, stepStatus: color: ${euiTheme.colors.title}; } `, - stepDescription: css` - margin-left: 32px; - `, - stepListItems: css` - padding-left: 28px; + description: css` + p { + margin-left: 32px; + margin-block-end: 0; + } + ul { + padding-left: 28px; + } `, }); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx index 4c617a3307171..1def8ce0a266a 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx @@ -93,11 +93,10 @@ export const GuideStep = ({ > <> - - {stepConfig.descriptionList.length === 1 ? ( -

    {stepConfig.descriptionList[0]}

    // If there is only one description, render it as a paragraph - ) : ( -
      + + {stepConfig.description &&

      {stepConfig.description}

      } + {stepConfig.descriptionList && ( +
        {stepConfig.descriptionList.map((description, index) => { return
      • {description}
      • ; })} diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts b/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts index 5862d0d132735..31263e1b0f54b 100644 --- a/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts @@ -31,11 +31,9 @@ export const observabilityConfig: GuideConfig = { defaultMessage: 'Add and verify your data', }), integration: 'kubernetes', - descriptionList: [ - i18n.translate('guidedOnboarding.observabilityGuide.addDataStep.description', { - defaultMessage: 'Start by adding your data by setting up the Kubernetes integration.', - }), - ], + description: i18n.translate('guidedOnboarding.observabilityGuide.addDataStep.description', { + defaultMessage: 'Start by adding your data by setting up the Kubernetes integration.', + }), location: { appID: 'integrations', path: '/detail/kubernetes/overview', @@ -46,11 +44,12 @@ export const observabilityConfig: GuideConfig = { title: i18n.translate('guidedOnboarding.observabilityGuide.viewDashboardStep.title', { defaultMessage: 'Explore Kubernetes metrics', }), - descriptionList: [ - i18n.translate('guidedOnboarding.observabilityGuide.viewDashboardStep.description', { + description: i18n.translate( + 'guidedOnboarding.observabilityGuide.viewDashboardStep.description', + { defaultMessage: 'Stream, visualize, and analyze your Kubernetes infrastructure metrics.', - }), - ], + } + ), location: { appID: 'dashboards', path: '#/view/kubernetes-e0195ce0-bcaf-11ec-b64f-7dd6e8e82013', @@ -76,12 +75,13 @@ export const observabilityConfig: GuideConfig = { title: i18n.translate('guidedOnboarding.observabilityGuide.tourObservabilityStep.title', { defaultMessage: 'Tour Elastic Observability', }), - descriptionList: [ - i18n.translate('guidedOnboarding.observabilityGuide.tourObservabilityStep.description', { + description: i18n.translate( + 'guidedOnboarding.observabilityGuide.tourObservabilityStep.description', + { defaultMessage: 'Get familiar with the rest of Elastic Observability and explore even more integrations.', - }), - ], + } + ), location: { appID: 'observability', path: '/overview', diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/search.ts b/src/plugins/guided_onboarding/public/constants/guides_config/search.ts index f1a8de389ea19..f074d0924fdea 100644 --- a/src/plugins/guided_onboarding/public/constants/guides_config/search.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/search.ts @@ -6,60 +6,59 @@ * Side Public License, v 1. */ +import { i18n } from '@kbn/i18n'; import type { GuideConfig } from '../../types'; export const searchConfig: GuideConfig = { - title: 'Search my data', - description: `We'll help you build world-class search experiences with your data, using Elastic's out-of-the-box web crawler, connectors, and our robust APIs. Gain deep insights from the built-in search analytics and use that data to inform changes to relevance.`, + title: i18n.translate('guidedOnboarding.searchGuide.title', { + defaultMessage: 'Search my data', + }), + description: i18n.translate('guidedOnboarding.searchGuide.description', { + defaultMessage: `Build custom search experiences with your data using Elastic’s out-of-the-box web crawler, connectors, and robust APIs. Gain deep insights from the built-in search analytics to curate results and optimize relevance.`, + }), guideName: 'Enterprise Search', - docs: { - text: 'Enterprise Search 101 Documentation', - url: 'example.com', - }, steps: [ { id: 'add_data', - title: 'Add data', + title: i18n.translate('guidedOnboarding.searchGuide.addDataStep.title', { + defaultMessage: 'Add data', + }), descriptionList: [ - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', - 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', - 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + i18n.translate('guidedOnboarding.searchGuide.addDataStep.description1', { + defaultMessage: 'Select an ingestion method.', + }), + i18n.translate('guidedOnboarding.searchGuide.addDataStep.description2', { + defaultMessage: 'Create a new Elasticsearch index.', + }), + i18n.translate('guidedOnboarding.searchGuide.addDataStep.description3', { + defaultMessage: 'Configure your ingestion settings.', + }), ], location: { appID: 'enterpriseSearch', path: '', }, }, - { - id: 'browse_docs', - title: 'Browse your documents', - descriptionList: [ - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', - 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', - 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', - ], - location: { - appID: 'guidedOnboardingExample', - path: 'stepTwo', - }, - manualCompletion: { - title: 'Manual completion step title', - description: - 'Mark the step complete by opening the panel and clicking the button "Mark done"', - readyToCompleteOnNavigation: true, - }, - }, { id: 'search_experience', - title: 'Build a search experience', + title: i18n.translate('guidedOnboarding.searchGuide.searchExperienceStep.title', { + defaultMessage: 'Build a search experience', + }), descriptionList: [ - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', - 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', - 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + i18n.translate('guidedOnboarding.searchGuide.searchExperienceStep.descriptionList.item1', { + defaultMessage: 'Learn more about Elastic’s Search UI framework.', + }), + i18n.translate('guidedOnboarding.searchGuide.searchExperienceStep.descriptionList.item2', { + defaultMessage: 'Try the Search UI tutorial for Elasticsearch.', + }), + i18n.translate('guidedOnboarding.searchGuide.searchExperienceStep.descriptionList.item3', { + defaultMessage: + 'Build a world-class search experience for your customers, employees, or users.', + }), ], location: { - appID: 'guidedOnboardingExample', - path: 'stepThree', + appID: 'enterpriseSearch', + path: '/search_experiences', }, }, ], diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/test_guide.ts b/src/plugins/guided_onboarding/public/constants/guides_config/test_guide.ts index b357ad497c6b4..fead791d02ed1 100644 --- a/src/plugins/guided_onboarding/public/constants/guides_config/test_guide.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/test_guide.ts @@ -51,9 +51,8 @@ export const testGuideConfig: GuideConfig = { { id: 'step3', title: 'Step 3 (manual completion after click)', - descriptionList: [ + description: 'This step is completed by clicking a button on the page and then clicking the popover on the guide button in the header and marking the step done', - ], manualCompletion: { title: 'Manual completion step title', description: diff --git a/src/plugins/guided_onboarding/public/types.ts b/src/plugins/guided_onboarding/public/types.ts index 3ff0507c494dc..a4d45e8fad4d8 100755 --- a/src/plugins/guided_onboarding/public/types.ts +++ b/src/plugins/guided_onboarding/public/types.ts @@ -54,7 +54,10 @@ export interface GuidedOnboardingApi { export interface StepConfig { id: GuideStepIds; title: string; - descriptionList: string[]; + // description is displayed as a single paragraph, can be combined with description list + description?: string; + // description list is displayed as an unordered list, can be combined with description + descriptionList?: string[]; location?: { appID: string; path: string; From 1cc6c228ce28502c2bd59fe7be59b813eaf815d8 Mon Sep 17 00:00:00 2001 From: doakalexi <109488926+doakalexi@users.noreply.github.com> Date: Wed, 2 Nov 2022 10:46:54 -0400 Subject: [PATCH 20/86] Adding new task types (#144419) --- .../test_suites/task_manager/check_registered_task_types.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index 1cd6614bf5833..43319d48ec0bd 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -35,13 +35,13 @@ export default function ({ getService }: FtrProviderContext) { // This test is meant to fail when any change is made in task manager registered types. // The intent is to trigger a code review from the Response Ops team to review the new task type changes. - // Failing: See https://github.com/elastic/kibana/issues/144369 - describe.skip('check_registered_task_types', () => { + describe('check_registered_task_types', () => { it('should check changes on all registered task types', async () => { const types = (await getRegisteredTypes()) .filter((t: string) => !TEST_TYPES.includes(t)) .sort(); expect(types).to.eql([ + 'Fleet-Usage-Logger', 'Fleet-Usage-Sender', 'ML:saved-objects-sync', 'UPTIME:SyntheticsService:Sync-Saved-Monitor-Objects', @@ -125,6 +125,7 @@ export default function ({ getService }: FtrProviderContext) { 'security:endpoint-meta-telemetry', 'security:telemetry-configuration', 'security:telemetry-detection-rules', + 'security:telemetry-filterlist-artifact', 'security:telemetry-lists', 'security:telemetry-prebuilt-rule-alerts', 'security:telemetry-timelines', From 404d08f600d8d2b60a118da51f0540a49416df82 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Wed, 2 Nov 2022 15:56:54 +0100 Subject: [PATCH 21/86] add gauge and saved search journeys (#144311) Co-authored-by: Liza Katz --- .buildkite/ftr_configs.yml | 2 + .../ecommerce_dashboard_saved_search_only.ts | 25 ++++ .../ecommerce_dashboard_tsvb_gauge_only.ts | 25 ++++ ...ecommerce_saved_search_only_dashboard.json | 121 ++++++++++++++++++ .../ecommerce_tsvb_gauge_only_dashboard.json | 108 ++++++++++++++++ 5 files changed, 281 insertions(+) create mode 100644 x-pack/performance/journeys/ecommerce_dashboard_saved_search_only.ts create mode 100644 x-pack/performance/journeys/ecommerce_dashboard_tsvb_gauge_only.ts create mode 100644 x-pack/performance/kbn_archives/ecommerce_saved_search_only_dashboard.json create mode 100644 x-pack/performance/kbn_archives/ecommerce_tsvb_gauge_only_dashboard.json diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 97c0b24e70098..a7a96aa346145 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -279,3 +279,5 @@ enabled: - x-pack/performance/journeys/promotion_tracking_dashboard.ts - x-pack/performance/journeys/web_logs_dashboard.ts - x-pack/performance/journeys/data_stress_test_lens.ts + - x-pack/performance/journeys/ecommerce_dashboard_saved_search_only.ts + - x-pack/performance/journeys/ecommerce_dashboard_tsvb_gauge_only.ts diff --git a/x-pack/performance/journeys/ecommerce_dashboard_saved_search_only.ts b/x-pack/performance/journeys/ecommerce_dashboard_saved_search_only.ts new file mode 100644 index 0000000000000..9c454e890af29 --- /dev/null +++ b/x-pack/performance/journeys/ecommerce_dashboard_saved_search_only.ts @@ -0,0 +1,25 @@ +/* + * 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 { Journey } from '@kbn/journeys'; +import { subj } from '@kbn/test-subj-selector'; +import { waitForVisualizations } from '../utils'; + +export const journey = new Journey({ + esArchives: ['x-pack/performance/es_archives/sample_data_ecommerce'], + kbnArchives: ['x-pack/performance/kbn_archives/ecommerce_saved_search_only_dashboard'], +}) + + .step('Go to Dashboards Page', async ({ page, kbnUrl }) => { + await page.goto(kbnUrl.get(`/app/dashboards`)); + await page.waitForSelector('#dashboardListingHeading'); + }) + + .step('Go to Ecommerce Dashboard with Saved Search only', async ({ page }) => { + await page.click(subj('dashboardListingTitleLink-[eCommerce]-Saved-Search-Dashboard')); + await waitForVisualizations(page, 1); + }); diff --git a/x-pack/performance/journeys/ecommerce_dashboard_tsvb_gauge_only.ts b/x-pack/performance/journeys/ecommerce_dashboard_tsvb_gauge_only.ts new file mode 100644 index 0000000000000..1988112b9a397 --- /dev/null +++ b/x-pack/performance/journeys/ecommerce_dashboard_tsvb_gauge_only.ts @@ -0,0 +1,25 @@ +/* + * 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 { Journey } from '@kbn/journeys'; +import { subj } from '@kbn/test-subj-selector'; +import { waitForVisualizations } from '../utils'; + +export const journey = new Journey({ + esArchives: ['x-pack/performance/es_archives/sample_data_ecommerce'], + kbnArchives: ['x-pack/performance/kbn_archives/ecommerce_tsvb_gauge_only_dashboard'], +}) + + .step('Go to Dashboards Page', async ({ page, kbnUrl }) => { + await page.goto(kbnUrl.get(`/app/dashboards`)); + await page.waitForSelector('#dashboardListingHeading'); + }) + + .step('Go to Ecommerce Dashboard with TSVB Gauge only', async ({ page }) => { + await page.click(subj('dashboardListingTitleLink-[eCommerce]-TSVB-Gauge-Only-Dashboard')); + await waitForVisualizations(page, 1); + }); diff --git a/x-pack/performance/kbn_archives/ecommerce_saved_search_only_dashboard.json b/x-pack/performance/kbn_archives/ecommerce_saved_search_only_dashboard.json new file mode 100644 index 0000000000000..bfec7da206c75 --- /dev/null +++ b/x-pack/performance/kbn_archives/ecommerce_saved_search_only_dashboard.json @@ -0,0 +1,121 @@ +{ + "attributes": { + "fieldAttrs": "{\"products.manufacturer\":{\"count\":1},\"products.price\":{\"count\":1},\"products.product_name\":{\"count\":1},\"total_quantity\":{\"count\":1}}", + "fieldFormatMap": "{\"taxful_total_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.[00]\"}},\"products.price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"taxless_total_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.taxless_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.taxful_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.min_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.base_unit_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.base_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}}}", + "fields": "[]", + "name": "Kibana Sample Data eCommerce", + "runtimeFieldMap": "{}", + "timeFieldName": "order_date", + "title": "kibana_sample_data_ecommerce", + "typeMeta": "{}" + }, + "coreMigrationVersion": "8.6.0", + "created_at": "2022-10-31T12:06:45.150Z", + "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2022-10-31T12:06:45.150Z", + "version": "WzEzNywxXQ==" +} + +{ + "attributes": { + "columns": [ + "category", + "taxful_total_price", + "products.price", + "products.product_name", + "products.manufacturer", + "sku" + ], + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"highlightAll\":true,\"version\":true,\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "sort": [ + [ + "order_date", + "desc" + ] + ], + "title": "[eCommerce] Orders", + "version": 1 + }, + "coreMigrationVersion": "8.6.0", + "created_at": "2022-10-31T12:06:45.150Z", + "id": "3ba638e0-b894-11e8-a6d9-e546fe2bba5f", + "migrationVersion": { + "search": "8.0.0" + }, + "references": [ + { + "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "search", + "updated_at": "2022-10-31T12:06:45.150Z", + "version": "WzE0MCwxXQ==" +} + +{ + "attributes": { + "controlGroupInput": { + "chainingSystem": "HIERARCHICAL", + "controlStyle": "oneLine", + "ignoreParentSettingsJSON": "{\"ignoreFilters\":false,\"ignoreQuery\":false,\"ignoreTimerange\":false,\"ignoreValidations\":false}", + "panelsJSON": "{\"1ee1617f-fd8e-45e4-bc6a-d5736710ea20\":{\"order\":0,\"width\":\"small\",\"grow\":true,\"type\":\"optionsListControl\",\"explicitInput\":{\"title\":\"Manufacturer\",\"fieldName\":\"manufacturer.keyword\",\"parentFieldName\":\"manufacturer\",\"id\":\"1ee1617f-fd8e-45e4-bc6a-d5736710ea20\",\"enhancements\":{}}},\"afa9fa0f-a002-41a5-bab9-b738316d2590\":{\"order\":1,\"width\":\"small\",\"grow\":true,\"type\":\"optionsListControl\",\"explicitInput\":{\"title\":\"Category\",\"fieldName\":\"category.keyword\",\"parentFieldName\":\"category\",\"id\":\"afa9fa0f-a002-41a5-bab9-b738316d2590\",\"enhancements\":{}}},\"d3f766cb-5f96-4a12-8d3c-034e08be8855\":{\"order\":2,\"width\":\"small\",\"grow\":true,\"type\":\"rangeSliderControl\",\"explicitInput\":{\"title\":\"Quantity\",\"fieldName\":\"total_quantity\",\"id\":\"d3f766cb-5f96-4a12-8d3c-034e08be8855\",\"enhancements\":{}}}}" + }, + "description": "Analyze mock eCommerce orders and revenue", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}" + }, + "optionsJSON": "{\"hidePanelTitles\":false,\"useMargins\":true}", + "panelsJSON": "[{\"version\":\"8.6.0\",\"type\":\"search\",\"gridData\":{\"x\":0,\"y\":0,\"w\":48,\"h\":18,\"i\":\"10\"},\"panelIndex\":\"10\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_10\"}]", + "refreshInterval": { + "pause": true, + "value": 0 + }, + "timeTo": "2022-10-25T20:00:00.000Z", + "timeFrom": "2022-10-18T20:00:00.000Z", + "timeRestore": true, + "title": "[eCommerce] Saved Search Dashboard", + "version": 1 + }, + "coreMigrationVersion": "8.6.0", + "created_at": "2022-10-31T12:08:58.731Z", + "id": "ccb9a590-5914-11ed-8d12-9d4a72794439", + "migrationVersion": { + "dashboard": "8.6.0" + }, + "references": [ + { + "id": "3ba638e0-b894-11e8-a6d9-e546fe2bba5f", + "name": "10:panel_10", + "type": "search" + }, + { + "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "name": "controlGroup_1ee1617f-fd8e-45e4-bc6a-d5736710ea20:optionsListDataView", + "type": "index-pattern" + }, + { + "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "name": "controlGroup_afa9fa0f-a002-41a5-bab9-b738316d2590:optionsListDataView", + "type": "index-pattern" + }, + { + "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "name": "controlGroup_d3f766cb-5f96-4a12-8d3c-034e08be8855:rangeSliderDataView", + "type": "index-pattern" + } + ], + "type": "dashboard", + "updated_at": "2022-10-31T12:08:58.731Z", + "version": "WzI1NCwxXQ==" +} diff --git a/x-pack/performance/kbn_archives/ecommerce_tsvb_gauge_only_dashboard.json b/x-pack/performance/kbn_archives/ecommerce_tsvb_gauge_only_dashboard.json new file mode 100644 index 0000000000000..1237d5c825f1a --- /dev/null +++ b/x-pack/performance/kbn_archives/ecommerce_tsvb_gauge_only_dashboard.json @@ -0,0 +1,108 @@ +{ + "attributes": { + "fieldAttrs": "{\"products.manufacturer\":{\"count\":1},\"products.price\":{\"count\":1},\"products.product_name\":{\"count\":1},\"total_quantity\":{\"count\":1}}", + "fieldFormatMap": "{\"taxful_total_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.[00]\"}},\"products.price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"taxless_total_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.taxless_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.taxful_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.min_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.base_unit_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.base_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}}}", + "fields": "[]", + "name": "Kibana Sample Data eCommerce", + "runtimeFieldMap": "{}", + "timeFieldName": "order_date", + "title": "kibana_sample_data_ecommerce", + "typeMeta": "{}" + }, + "coreMigrationVersion": "8.6.0", + "created_at": "2022-10-31T12:48:03.382Z", + "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2022-10-31T12:48:03.382Z", + "version": "WzM1NCwxXQ==" +} + +{ + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + }, + "title": "[eCommerce] Sold Products per Day", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"[eCommerce] Sold Products per Day\",\"type\":\"metrics\",\"aggs\":[],\"params\":{\"time_range_mode\":\"entire_time_range\",\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"gauge\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"},{\"id\":\"fd1e1b90-e4e3-11eb-8234-cb7bfd534fce\",\"type\":\"math\",\"variables\":[{\"id\":\"00374270-e4e4-11eb-8234-cb7bfd534fce\",\"name\":\"c\",\"field\":\"61ca57f2-469d-11e7-af02-69e470af7417\"}],\"script\":\"params.c / (params._interval / 1000 / 60 / 60 / 24)\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"0.0\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"label\":\"Trxns / day\",\"split_color_mode\":\"gradient\",\"value_template\":\"\"}],\"time_field\":\"order_date\",\"interval\":\"1d\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"gauge_color_rules\":[{\"value\":150,\"id\":\"6da070c0-b891-11e8-b645-195edeb9de84\",\"gauge\":\"rgba(104,188,0,1)\",\"operator\":\"gte\"},{\"value\":150,\"id\":\"9b0cdbc0-b891-11e8-b645-195edeb9de84\",\"gauge\":\"rgba(244,78,59,1)\",\"operator\":\"lt\"}],\"gauge_width\":\"15\",\"gauge_inner_width\":\"10\",\"gauge_style\":\"half\",\"filter\":\"\",\"gauge_max\":\"300\",\"use_kibana_indexes\":true,\"hide_last_value_indicator\":true,\"tooltip_mode\":\"show_all\",\"drop_last_bucket\":0,\"isModelInvalid\":false,\"index_pattern_ref_name\":\"metrics_0_index_pattern\"}}" + }, + "coreMigrationVersion": "8.6.0", + "created_at": "2022-10-31T12:48:03.382Z", + "id": "b80e6540-b891-11e8-a6d9-e546fe2bba5f", + "migrationVersion": { + "visualization": "8.5.0" + }, + "references": [ + { + "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "name": "metrics_0_index_pattern", + "type": "index-pattern" + } + ], + "type": "visualization", + "updated_at": "2022-10-31T12:48:03.382Z", + "version": "WzM1NiwxXQ==" +} + +{ + "attributes": { + "controlGroupInput": { + "chainingSystem": "HIERARCHICAL", + "controlStyle": "oneLine", + "ignoreParentSettingsJSON": "{\"ignoreFilters\":false,\"ignoreQuery\":false,\"ignoreTimerange\":false,\"ignoreValidations\":false}", + "panelsJSON": "{\"1ee1617f-fd8e-45e4-bc6a-d5736710ea20\":{\"order\":0,\"width\":\"small\",\"grow\":true,\"type\":\"optionsListControl\",\"explicitInput\":{\"title\":\"Manufacturer\",\"fieldName\":\"manufacturer.keyword\",\"parentFieldName\":\"manufacturer\",\"id\":\"1ee1617f-fd8e-45e4-bc6a-d5736710ea20\",\"enhancements\":{}}},\"afa9fa0f-a002-41a5-bab9-b738316d2590\":{\"order\":1,\"width\":\"small\",\"grow\":true,\"type\":\"optionsListControl\",\"explicitInput\":{\"title\":\"Category\",\"fieldName\":\"category.keyword\",\"parentFieldName\":\"category\",\"id\":\"afa9fa0f-a002-41a5-bab9-b738316d2590\",\"enhancements\":{}}},\"d3f766cb-5f96-4a12-8d3c-034e08be8855\":{\"order\":2,\"width\":\"small\",\"grow\":true,\"type\":\"rangeSliderControl\",\"explicitInput\":{\"title\":\"Quantity\",\"fieldName\":\"total_quantity\",\"id\":\"d3f766cb-5f96-4a12-8d3c-034e08be8855\",\"enhancements\":{}}}}" + }, + "description": "Analyze mock eCommerce orders and revenue", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}" + }, + "optionsJSON": "{\"hidePanelTitles\":false,\"useMargins\":true}", + "panelsJSON": "[{\"version\":\"8.6.0\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":0,\"w\":48,\"h\":19,\"i\":\"7\"},\"panelIndex\":\"7\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_7\"}]", + "refreshInterval": { + "pause": true, + "value": 0 + }, + "timeTo": "2022-10-25T20:00:00.000Z", + "timeFrom": "2022-10-18T20:00:00.000Z", + "timeRestore": true, + "title": "[eCommerce] TSVB Gauge Only Dashboard", + "version": 1 + }, + "coreMigrationVersion": "8.6.0", + "created_at": "2022-10-31T12:51:08.112Z", + "id": "b05a8ee0-591a-11ed-8d12-9d4a72794439", + "migrationVersion": { + "dashboard": "8.6.0" + }, + "references": [ + { + "id": "b80e6540-b891-11e8-a6d9-e546fe2bba5f", + "name": "7:panel_7", + "type": "visualization" + }, + { + "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "name": "controlGroup_1ee1617f-fd8e-45e4-bc6a-d5736710ea20:optionsListDataView", + "type": "index-pattern" + }, + { + "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "name": "controlGroup_afa9fa0f-a002-41a5-bab9-b738316d2590:optionsListDataView", + "type": "index-pattern" + }, + { + "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", + "name": "controlGroup_d3f766cb-5f96-4a12-8d3c-034e08be8855:rangeSliderDataView", + "type": "index-pattern" + } + ], + "type": "dashboard", + "updated_at": "2022-10-31T12:51:08.112Z", + "version": "WzQ2MywxXQ==" +} From 14e4ab4367e15549373cc7c8e0d25ff05d6510c6 Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Wed, 2 Nov 2022 16:11:03 +0100 Subject: [PATCH 22/86] [APM] Mobile most used pie charts with Lens (#144232) * WIP * Fix eslint * split code * Add unit tests * respect search bar filter * Fix i18n id * Add open in Lens action * Clean up attributes * Organise react components * Move most used chart to service overview * Remove kuery from filters * Pass mobile filters to Lens * Use same ES fields as mobile filters * Fix tests * Clean up code * Fix i18n * Use i18n for lens labels * Address PR comments --- .../elasticsearch_fieldnames.test.ts.snap | 6 +- .../apm/common/elasticsearch_fieldnames.ts | 2 +- x-pack/plugins/apm/kibana.json | 3 +- .../plugins/apm/public/application/index.tsx | 1 + .../most_used_chart.test.tsx.snap | 101 +++++++++++++++ .../most_used_chart/get_lens_attributes.ts | 115 +++++++++++++++++ .../most_used_chart/index.tsx | 122 ++++++++++++++++++ .../most_used_chart/most_used_chart.test.tsx | 102 +++++++++++++++ .../service_oveview_mobile_charts.tsx | 81 +++++++++++- .../use_filters_for_mobile_charts.ts | 4 +- x-pack/plugins/apm/public/plugin.ts | 2 + .../routes/mobile/get_mobile_filters.ts | 4 +- 12 files changed, 532 insertions(+), 11 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/__snapshots__/most_used_chart.test.tsx.snap create mode 100644 x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/get_lens_attributes.ts create mode 100644 x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/index.tsx create mode 100644 x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/most_used_chart.test.tsx diff --git a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index d30a32fd04fc3..ab7a6d4139d4a 100644 --- a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -45,7 +45,7 @@ exports[`Error CONTAINER_IMAGE 1`] = `undefined`; exports[`Error DESTINATION_ADDRESS 1`] = `undefined`; -exports[`Error DEVICE_MODEL_IDENTIFIER 1`] = `undefined`; +exports[`Error DEVICE_MODEL_NAME 1`] = `undefined`; exports[`Error ERROR_CULPRIT 1`] = `"handleOopsie"`; @@ -310,7 +310,7 @@ exports[`Span CONTAINER_IMAGE 1`] = `undefined`; exports[`Span DESTINATION_ADDRESS 1`] = `undefined`; -exports[`Span DEVICE_MODEL_IDENTIFIER 1`] = `undefined`; +exports[`Span DEVICE_MODEL_NAME 1`] = `undefined`; exports[`Span ERROR_CULPRIT 1`] = `undefined`; @@ -571,7 +571,7 @@ exports[`Transaction CONTAINER_IMAGE 1`] = `undefined`; exports[`Transaction DESTINATION_ADDRESS 1`] = `undefined`; -exports[`Transaction DEVICE_MODEL_IDENTIFIER 1`] = `undefined`; +exports[`Transaction DEVICE_MODEL_NAME 1`] = `undefined`; exports[`Transaction ERROR_CULPRIT 1`] = `undefined`; diff --git a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts index 4915dff2da4f3..1734742c31d86 100644 --- a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts +++ b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts @@ -161,5 +161,5 @@ export const TIER = '_tier'; export const INDEX = '_index'; // Mobile -export const DEVICE_MODEL_IDENTIFIER = 'device.model.identifier'; export const NETWORK_CONNECTION_TYPE = 'network.connection.type'; +export const DEVICE_MODEL_NAME = 'device.model.name'; diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index 9572e06b63483..ff8f2c24cfd93 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -21,6 +21,7 @@ "unifiedSearch", "dataViews", "advancedSettings", + "lens", "maps" ], "optionalPlugins": [ @@ -51,4 +52,4 @@ "esUiShared", "maps" ] -} +} \ No newline at end of file diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index c3cf929a40209..438283a9128e4 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -53,6 +53,7 @@ export const renderApp = ({ observabilityRuleTypeRegistry, dataViews: pluginsStart.dataViews, unifiedSearch: pluginsStart.unifiedSearch, + lens: pluginsStart.lens, }; // render APM feedback link in global help menu diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/__snapshots__/most_used_chart.test.tsx.snap b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/__snapshots__/most_used_chart.test.tsx.snap new file mode 100644 index 0000000000000..b98475f93e0aa --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/__snapshots__/most_used_chart.test.tsx.snap @@ -0,0 +1,101 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Most used chart with Lens gets lens attributes 1`] = ` +Object { + "references": Array [ + Object { + "id": "apm_static_index_pattern_id", + "name": "indexpattern-datasource-layer-host-os-version", + "type": "index-pattern", + }, + ], + "state": Object { + "datasourceStates": Object { + "formBased": Object { + "layers": Object { + "host-os-version": Object { + "columnOrder": Array [ + "termsColumn", + "countColumn", + ], + "columns": Object { + "countColumn": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of records", + "operationType": "count", + "scale": "ratio", + "sourceField": "___records___", + }, + "termsColumn": Object { + "dataType": "string", + "isBucketed": true, + "label": "Top 5 values of host.os.version", + "operationType": "terms", + "params": Object { + "orderBy": Object { + "columnId": "countColumn", + "type": "column", + }, + "orderDirection": "desc", + "size": 5, + }, + "scale": "ordinal", + "sourceField": "host.os.version", + }, + }, + }, + }, + }, + }, + "filters": Array [ + Object { + "meta": Object {}, + "query": Object { + "term": Object { + "processor.event": "transaction", + }, + }, + }, + Object { + "meta": Object {}, + "query": Object { + "term": Object { + "service.name": "opbeans-swift", + }, + }, + }, + Object { + "meta": Object {}, + "query": Object { + "term": Object { + "transaction.type": "request", + }, + }, + }, + ], + "query": Object { + "language": "kuery", + "query": "", + }, + "visualization": Object { + "layers": Array [ + Object { + "categoryDisplay": "default", + "layerId": "host-os-version", + "layerType": "data", + "legendDisplay": "hide", + "metric": "countColumn", + "numberDisplay": "percent", + "primaryGroups": Array [ + "termsColumn", + ], + }, + ], + "shape": "pie", + }, + }, + "title": "most-used-host-os-version", + "visualizationType": "lnsPie", +} +`; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/get_lens_attributes.ts b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/get_lens_attributes.ts new file mode 100644 index 0000000000000..87435bbe68969 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/get_lens_attributes.ts @@ -0,0 +1,115 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { + CountIndexPatternColumn, + TermsIndexPatternColumn, + PersistedIndexPatternLayer, + PieVisualizationState, + TypedLensByValueInput, +} from '@kbn/lens-plugin/public'; +import type { Filter } from '@kbn/es-query'; +import { APM_STATIC_DATA_VIEW_ID } from '../../../../../../common/data_view_constants'; +import { MostUsedMetricTypes } from '.'; + +const BUCKET_SIZE = 5; + +export function getLensAttributes({ + metric, + filters, + kuery = '', +}: { + metric: MostUsedMetricTypes; + filters: Filter[]; + kuery?: string; +}): TypedLensByValueInput['attributes'] { + const metricId = metric.replaceAll('.', '-'); + + const columnA = 'termsColumn'; + const columnB = 'countColumn'; + + const dataLayer: PersistedIndexPatternLayer = { + columnOrder: [columnA, columnB], + columns: { + [columnA]: { + label: i18n.translate( + 'xpack.apm.serviceOverview.lensFlyout.topValues', + { + defaultMessage: 'Top {BUCKET_SIZE} values of {metric}', + values: { + BUCKET_SIZE, + metric, + }, + } + ), + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: metric, + isBucketed: true, + params: { + size: BUCKET_SIZE, + orderBy: { + type: 'column', + columnId: columnB, + }, + orderDirection: 'desc', + }, + } as TermsIndexPatternColumn, + [columnB]: { + label: i18n.translate( + 'xpack.apm.serviceOverview.lensFlyout.countRecords', + { + defaultMessage: 'Count of records', + } + ), + dataType: 'number', + operationType: 'count', + scale: 'ratio', + isBucketed: false, + sourceField: '___records___', + } as CountIndexPatternColumn, + }, + }; + + return { + title: `most-used-${metricId}`, + visualizationType: 'lnsPie', + references: [ + { + type: 'index-pattern', + id: APM_STATIC_DATA_VIEW_ID, + name: `indexpattern-datasource-layer-${metricId}`, + }, + ], + state: { + visualization: { + shape: 'pie', + layers: [ + { + layerId: metricId, + primaryGroups: [columnA], + metric: columnB, + categoryDisplay: 'default', + legendDisplay: 'hide', + numberDisplay: 'percent', + layerType: 'data', + }, + ], + } as PieVisualizationState, + datasourceStates: { + formBased: { + layers: { + [metricId]: dataLayer, + }, + }, + }, + filters, + query: { language: 'kuery', query: kuery }, + }, + }; +} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/index.tsx new file mode 100644 index 0000000000000..b78df50106e7e --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/index.tsx @@ -0,0 +1,122 @@ +/* + * 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 { EuiTitle, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import React, { useMemo, useCallback } from 'react'; +import type { Filter } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ApmPluginStartDeps } from '../../../../../plugin'; +import { getLensAttributes } from './get_lens_attributes'; +import { + DEVICE_MODEL_NAME, + HOST_OS_VERSION, + NETWORK_CONNECTION_TYPE, + SERVICE_VERSION, +} from '../../../../../../common/elasticsearch_fieldnames'; + +export type MostUsedMetricTypes = + | typeof DEVICE_MODEL_NAME + | typeof SERVICE_VERSION + | typeof HOST_OS_VERSION + | typeof NETWORK_CONNECTION_TYPE; + +export function MostUsedChart({ + title, + start, + end, + kuery, + filters, + metric, +}: { + title: React.ReactNode; + start: string; + end: string; + kuery?: string; + filters: Filter[]; + metric: MostUsedMetricTypes; +}) { + const { services } = useKibana(); + const { + lens: { EmbeddableComponent, navigateToPrefilledEditor, canUseEditor }, + } = services; + + const lensAttributes = useMemo( + () => + getLensAttributes({ + kuery, + filters, + metric, + }), + [kuery, filters, metric] + ); + + const openInLens = useCallback(() => { + if (lensAttributes) { + navigateToPrefilledEditor( + { + id: `dataVisualizer-${metric}`, + timeRange: { + from: start, + to: end, + }, + attributes: lensAttributes, + }, + { + openInNewTab: true, + } + ); + } + }, [navigateToPrefilledEditor, lensAttributes, start, end, metric]); + + const getOpenInLensAction = () => { + return { + id: 'openInLens', + type: 'link', + getDisplayName() { + return i18n.translate('xpack.apm.serviceOverview.openInLens', { + defaultMessage: 'Open in Lens', + }); + }, + getIconType() { + return 'visArea'; + }, + async isCompatible() { + return true; + }, + async execute() { + openInLens(); + return; + }, + }; + }; + + return ( + + + +

        {title}

        +
        +
        + + + +
        + ); +} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/most_used_chart.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/most_used_chart.test.tsx new file mode 100644 index 0000000000000..fa4a5bf3657f6 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/most_used_chart/most_used_chart.test.tsx @@ -0,0 +1,102 @@ +/* + * 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 { render } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import { CoreStart } from '@kbn/core/public'; +import React, { ReactNode } from 'react'; +import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; +import { getLensAttributes } from './get_lens_attributes'; +import { MostUsedChart, MostUsedMetricTypes } from '.'; +import { HOST_OS_VERSION } from '../../../../../../common/elasticsearch_fieldnames'; + +const mockEmbeddableComponent = jest.fn(); + +function Wrapper({ children }: { children?: ReactNode }) { + const KibanaReactContext = createKibanaReactContext({ + lens: { + EmbeddableComponent: mockEmbeddableComponent.mockReturnValue( +
        + ), + canUseEditor: jest.fn(() => true), + navigateToPrefilledEditor: jest.fn(), + }, + } as Partial); + + return ( + + + {children} + + + ); +} + +const renderOptions = { wrapper: Wrapper }; + +describe('Most used chart with Lens', () => { + const props = { + metric: HOST_OS_VERSION as MostUsedMetricTypes, + filters: [ + { + meta: {}, + query: { + term: { + 'processor.event': 'transaction', + }, + }, + }, + { + meta: {}, + query: { + term: { + 'service.name': 'opbeans-swift', + }, + }, + }, + { + meta: {}, + query: { + term: { + 'transaction.type': 'request', + }, + }, + }, + ], + }; + test('gets lens attributes', () => { + expect(getLensAttributes(props)).toMatchSnapshot(); + }); + + test('Renders most used chart with Lens', () => { + const start = '2022-10-30T20%3A52%3A47.080Z'; + const end = '2022-10-31T20%3A52%3A47.080Z'; + + render( + , + renderOptions + ); + + expect(mockEmbeddableComponent).toHaveBeenCalledTimes(1); + expect(mockEmbeddableComponent.mock.calls[0][0]).toEqual( + expect.objectContaining({ + timeRange: { + from: start, + to: end, + }, + attributes: getLensAttributes(props), + }) + ); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/service_oveview_mobile_charts.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/service_oveview_mobile_charts.tsx index cf603a189e178..62e8743049552 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/service_oveview_mobile_charts.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/service_oveview_mobile_charts.tsx @@ -18,10 +18,16 @@ import { TransactionsTable } from '../../../shared/transactions_table'; import { AggregatedTransactionsBadge } from '../../../shared/aggregated_transactions_badge'; import { useApmParams } from '../../../../hooks/use_apm_params'; import { useTimeRange } from '../../../../hooks/use_time_range'; +import { MostUsedChart } from './most_used_chart'; import { LatencyMap } from './latency_map'; import { MobileFilters } from './filters'; import { useFiltersForMobileCharts } from './use_filters_for_mobile_charts'; - +import { + DEVICE_MODEL_NAME, + HOST_OS_VERSION, + NETWORK_CONNECTION_TYPE, + SERVICE_VERSION, +} from '../../../../../common/elasticsearch_fieldnames'; interface Props { latencyChartHeight: number; rowDirection: 'column' | 'row'; @@ -46,10 +52,10 @@ export function ServiceOverviewMobileCharts({ kuery, rangeFrom, rangeTo, + netConnectionType, device, osVersion, appVersion, - netConnectionType, }, } = useApmParams('/services/{serviceName}/overview'); @@ -148,11 +154,82 @@ export function ServiceOverviewMobileCharts({ + + + + + {/* Device */} + + + + {/* NCT */} + + + + + + + + {/* OS Version */} + + + + {/* App version */} + + + + + ); } diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/use_filters_for_mobile_charts.ts b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/use_filters_for_mobile_charts.ts index 5116a79494978..2c79c5a34dc75 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/use_filters_for_mobile_charts.ts +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/use_filters_for_mobile_charts.ts @@ -16,7 +16,7 @@ import { TRANSACTION_TYPE, PROCESSOR_EVENT, HOST_OS_VERSION, - DEVICE_MODEL_IDENTIFIER, + DEVICE_MODEL_NAME, NETWORK_CONNECTION_TYPE, SERVICE_VERSION, } from '../../../../../common/elasticsearch_fieldnames'; @@ -52,7 +52,7 @@ export function useFiltersForMobileCharts() { ...termQuery(SERVICE_NAME, serviceName), ...termQuery(TRANSACTION_TYPE, transactionType), ...termQuery(HOST_OS_VERSION, osVersion), - ...termQuery(DEVICE_MODEL_IDENTIFIER, device), + ...termQuery(DEVICE_MODEL_NAME, device), ...termQuery(NETWORK_CONNECTION_TYPE, netConnectionType), ...termQuery(SERVICE_VERSION, appVersion), ...environmentQuery(environment), diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 0b3137ee6ad99..0ea3fa56d1dda 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -21,6 +21,7 @@ import type { DataPublicPluginStart, DataPublicPluginSetup, } from '@kbn/data-plugin/public'; +import { LensPublicStart } from '@kbn/lens-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { EmbeddableStart } from '@kbn/embeddable-plugin/public'; import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; @@ -97,6 +98,7 @@ export interface ApmPluginStartDeps { dataViews: DataViewsPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; storage: IStorageWrapper; + lens: LensPublicStart; } const servicesTitle = i18n.translate('xpack.apm.navigation.servicesTitle', { diff --git a/x-pack/plugins/apm/server/routes/mobile/get_mobile_filters.ts b/x-pack/plugins/apm/server/routes/mobile/get_mobile_filters.ts index 3bbd43e8ca9ec..2aac36c721ba1 100644 --- a/x-pack/plugins/apm/server/routes/mobile/get_mobile_filters.ts +++ b/x-pack/plugins/apm/server/routes/mobile/get_mobile_filters.ts @@ -12,7 +12,7 @@ import { rangeQuery, } from '@kbn/observability-plugin/server'; import { - DEVICE_MODEL_IDENTIFIER, + DEVICE_MODEL_NAME, HOST_OS_VERSION, NETWORK_CONNECTION_TYPE, SERVICE_NAME, @@ -76,7 +76,7 @@ export async function getMobileFilters({ aggs: { devices: { terms: { - field: DEVICE_MODEL_IDENTIFIER, + field: DEVICE_MODEL_NAME, size: 10, }, }, From 79f3e448733153304f4dcf8d0117f050f1e9a8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?= Date: Wed, 2 Nov 2022 16:14:21 +0100 Subject: [PATCH 23/86] [Security Solution][Endpoint] Guided onboarding configures events only for endpoint policy (#144087) * update comment * use previous setting instead of possibly enabling features * disable protections for default Endpoint config * refactor tests * extract config preset selection logic * fix fleet_integration jest tests * use original logic for basic tier policy factory * update tests for `disableProtections` * apply license limitations as the final step Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/endpoint/models/policy_config.ts | 8 +- .../models/policy_config_helpers.test.ts | 175 ++++++++++++++++++ .../endpoint/models/policy_config_helpers.ts | 105 +++++++++++ .../fleet_integration.test.ts | 3 +- .../handlers/create_default_policy.test.ts | 54 +++++- .../handlers/create_default_policy.ts | 43 +++-- 6 files changed, 360 insertions(+), 28 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts create mode 100644 x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts index 1b3c3105121e5..eb5389586b2a8 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts @@ -200,7 +200,7 @@ export const policyFactoryWithoutPaidFeatures = ( ...policy.windows.popup, malware: { message: '', - enabled: true, + enabled: true, // disabling/configuring malware popup is a paid feature }, ransomware: { message: '', @@ -230,7 +230,7 @@ export const policyFactoryWithoutPaidFeatures = ( ...policy.mac.popup, malware: { message: '', - enabled: true, + enabled: true, // disabling/configuring malware popup is a paid feature }, memory_protection: { message: '', @@ -256,7 +256,7 @@ export const policyFactoryWithoutPaidFeatures = ( ...policy.linux.popup, malware: { message: '', - enabled: true, + enabled: true, // disabling/configuring malware popup is a paid feature }, memory_protection: { message: '', @@ -272,7 +272,7 @@ export const policyFactoryWithoutPaidFeatures = ( }; /** - * Strips paid features from an existing or new `PolicyConfig` for gold and below license + * Enables support for paid features for an existing or new `PolicyConfig` for platinum and above license */ export const policyFactoryWithSupportedFeatures = ( policy: PolicyConfig = policyFactory() diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts new file mode 100644 index 0000000000000..d5ba6d03cd430 --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts @@ -0,0 +1,175 @@ +/* + * 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 { PolicyConfig } from '../types'; +import { ProtectionModes } from '../types'; +import { policyFactory } from './policy_config'; +import { disableProtections } from './policy_config_helpers'; + +describe('Policy Config helpers', () => { + describe('disableProtections', () => { + it('disables all the protections in the default policy', () => { + expect(disableProtections(policyFactory())).toEqual(eventsOnlyPolicy); + }); + + it('does not enable supported fields', () => { + const defaultPolicy: PolicyConfig = policyFactory(); + + const notSupported: PolicyConfig['windows']['memory_protection'] = { + mode: ProtectionModes.off, + supported: false, + }; + + const inputPolicyWithoutSupportedProtections: PolicyConfig = { + ...defaultPolicy, + windows: { + ...defaultPolicy.windows, + memory_protection: notSupported, + behavior_protection: notSupported, + ransomware: notSupported, + }, + mac: { + ...defaultPolicy.mac, + memory_protection: notSupported, + behavior_protection: notSupported, + }, + linux: { + ...defaultPolicy.linux, + memory_protection: notSupported, + behavior_protection: notSupported, + }, + }; + + const expectedPolicyWithoutSupportedProtections: PolicyConfig = { + ...eventsOnlyPolicy, + windows: { + ...eventsOnlyPolicy.windows, + memory_protection: notSupported, + behavior_protection: notSupported, + ransomware: notSupported, + }, + mac: { + ...eventsOnlyPolicy.mac, + memory_protection: notSupported, + behavior_protection: notSupported, + }, + linux: { + ...eventsOnlyPolicy.linux, + memory_protection: notSupported, + behavior_protection: notSupported, + }, + }; + + const policy = disableProtections(inputPolicyWithoutSupportedProtections); + + expect(policy).toEqual(expectedPolicyWithoutSupportedProtections); + }); + + it('does not enable events', () => { + const defaultPolicy: PolicyConfig = policyFactory(); + + const windowsEvents: typeof defaultPolicy.windows.events = { + dll_and_driver_load: false, + dns: false, + file: false, + network: false, + process: false, + registry: false, + security: false, + }; + + const macEvents: typeof defaultPolicy.mac.events = { + file: false, + process: false, + network: false, + }; + + const linuxEvents: typeof defaultPolicy.linux.events = { + file: false, + process: false, + network: false, + session_data: false, + tty_io: false, + }; + + const expectedPolicy: PolicyConfig = { + ...eventsOnlyPolicy, + windows: { ...eventsOnlyPolicy.windows, events: { ...windowsEvents } }, + mac: { ...eventsOnlyPolicy.mac, events: { ...macEvents } }, + linux: { ...eventsOnlyPolicy.linux, events: { ...linuxEvents } }, + }; + + const inputPolicy = { + ...defaultPolicy, + windows: { ...defaultPolicy.windows, events: { ...windowsEvents } }, + mac: { ...defaultPolicy.mac, events: { ...macEvents } }, + linux: { ...defaultPolicy.linux, events: { ...linuxEvents } }, + }; + + expect(disableProtections(inputPolicy)).toEqual(expectedPolicy); + }); + }); +}); + +// This constant makes sure that if the type `PolicyConfig` is ever modified, +// the logic for disabling protections is also modified due to type check. +export const eventsOnlyPolicy: PolicyConfig = { + windows: { + events: { + dll_and_driver_load: true, + dns: true, + file: true, + network: true, + process: true, + registry: true, + security: true, + }, + malware: { mode: ProtectionModes.off, blocklist: false }, + ransomware: { mode: ProtectionModes.off, supported: true }, + memory_protection: { mode: ProtectionModes.off, supported: true }, + behavior_protection: { mode: ProtectionModes.off, supported: true }, + popup: { + malware: { message: '', enabled: false }, + ransomware: { message: '', enabled: false }, + memory_protection: { message: '', enabled: false }, + behavior_protection: { message: '', enabled: false }, + }, + logging: { file: 'info' }, + antivirus_registration: { enabled: false }, + attack_surface_reduction: { credential_hardening: { enabled: false } }, + }, + mac: { + events: { process: true, file: true, network: true }, + malware: { mode: ProtectionModes.off, blocklist: false }, + behavior_protection: { mode: ProtectionModes.off, supported: true }, + memory_protection: { mode: ProtectionModes.off, supported: true }, + popup: { + malware: { message: '', enabled: false }, + behavior_protection: { message: '', enabled: false }, + memory_protection: { message: '', enabled: false }, + }, + logging: { file: 'info' }, + }, + linux: { + events: { + process: true, + file: true, + network: true, + session_data: false, + tty_io: false, + }, + malware: { mode: ProtectionModes.off, blocklist: false }, + behavior_protection: { mode: ProtectionModes.off, supported: true }, + memory_protection: { mode: ProtectionModes.off, supported: true }, + popup: { + malware: { message: '', enabled: false }, + behavior_protection: { message: '', enabled: false }, + memory_protection: { message: '', enabled: false }, + }, + logging: { file: 'info' }, + }, +}; diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts new file mode 100644 index 0000000000000..27a15e0be5401 --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts @@ -0,0 +1,105 @@ +/* + * 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 { PolicyConfig } from '../types'; +import { ProtectionModes } from '../types'; + +/** + * Returns a copy of the passed `PolicyConfig` with all protections set to disabled. + * + * @param policy + * @returns + */ +export const disableProtections = (policy: PolicyConfig): PolicyConfig => { + const result = disableCommonProtections(policy); + + return { + ...result, + windows: { + ...result.windows, + ...getDisabledWindowsSpecificProtections(result), + popup: { + ...result.windows.popup, + ...getDisabledWindowsSpecificPopups(result), + }, + }, + }; +}; + +const disableCommonProtections = (policy: PolicyConfig) => { + let policyOutput = policy; + + for (const key in policyOutput) { + if (Object.prototype.hasOwnProperty.call(policyOutput, key)) { + const os = key as keyof PolicyConfig; + + policyOutput = { + ...policyOutput, + [os]: { + ...policyOutput[os], + ...getDisabledCommonProtectionsForOS(policyOutput, os), + popup: { + ...policyOutput[os].popup, + ...getDisabledCommonPopupsForOS(policyOutput, os), + }, + }, + }; + } + } + return policyOutput; +}; + +const getDisabledCommonProtectionsForOS = (policy: PolicyConfig, os: keyof PolicyConfig) => ({ + behavior_protection: { + ...policy[os].behavior_protection, + mode: ProtectionModes.off, + }, + memory_protection: { + ...policy[os].memory_protection, + mode: ProtectionModes.off, + }, + malware: { + ...policy[os].malware, + blocklist: false, + mode: ProtectionModes.off, + }, +}); + +const getDisabledCommonPopupsForOS = (policy: PolicyConfig, os: keyof PolicyConfig) => ({ + behavior_protection: { + ...policy[os].popup.behavior_protection, + enabled: false, + }, + malware: { + ...policy[os].popup.malware, + enabled: false, + }, + memory_protection: { + ...policy[os].popup.memory_protection, + enabled: false, + }, +}); + +const getDisabledWindowsSpecificProtections = (policy: PolicyConfig) => ({ + ransomware: { + ...policy.windows.ransomware, + mode: ProtectionModes.off, + }, + attack_surface_reduction: { + ...policy.windows.attack_surface_reduction, + credential_hardening: { + enabled: false, + }, + }, +}); + +const getDisabledWindowsSpecificPopups = (policy: PolicyConfig) => ({ + ransomware: { + ...policy.windows.popup.ransomware, + enabled: false, + }, +}); diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index 17d1d8f9a9295..44f9a8bafae3d 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -47,6 +47,7 @@ import type { DeletePackagePoliciesResponse } from '@kbn/fleet-plugin/common'; import { createMockPolicyData } from '../endpoint/services/feature_usage/mocks'; import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../common/endpoint/service/artifacts/constants'; import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants'; +import { disableProtections } from '../../common/endpoint/models/policy_config_helpers'; jest.mock('uuid', () => ({ v4: (): string => 'NEW_UUID', @@ -89,7 +90,7 @@ describe('ingest_integration tests ', () => { streams: [], config: { integration_config: {}, - policy: { value: policyFactory() }, + policy: { value: disableProtections(policyFactory()) }, artifact_manifest: { value: manifest }, }, }); diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts index 73440a310b625..916d3adbe96ae 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts @@ -9,11 +9,9 @@ import type { ILicense } from '@kbn/licensing-plugin/common/types'; import { licenseMock } from '@kbn/licensing-plugin/common/licensing.mock'; import { LicenseService } from '../../../common/license'; import { createDefaultPolicy } from './create_default_policy'; +import { ProtectionModes } from '../../../common/endpoint/types'; import type { PolicyConfig } from '../../../common/endpoint/types'; -import { - policyFactory as policyConfigFactory, - policyFactoryWithoutPaidFeatures as policyConfigFactoryWithoutPaidFeatures, -} from '../../../common/endpoint/models/policy_config'; +import { policyFactory } from '../../../common/endpoint/models/policy_config'; import type { AnyPolicyCreateConfig, PolicyCreateCloudConfig, @@ -37,16 +35,52 @@ describe('Create Default Policy tests ', () => { licenseEmitter.next(Platinum); // set license level to platinum }); describe('When no config is set', () => { - it('Should return the Default Policy Config when license is at least platinum', () => { + it('Should return PolicyConfig for events only when license is at least platinum', () => { + const defaultPolicy = policyFactory(); + const policy = createDefaultPolicyCallback(undefined); - expect(policy).toEqual(policyConfigFactory()); + + // events are the same + expect(policy.windows.events).toEqual(defaultPolicy.windows.events); + expect(policy.linux.events).toEqual(defaultPolicy.linux.events); + expect(policy.mac.events).toEqual(defaultPolicy.mac.events); + + // check some of the protections to be disabled + const disabledButSupported = { mode: ProtectionModes.off, supported: true }; + expect(policy.windows.behavior_protection).toEqual(disabledButSupported); + expect(policy.mac.memory_protection).toEqual(disabledButSupported); + expect(policy.linux.behavior_protection).toEqual(disabledButSupported); + + // malware popups should be disabled + expect(policy.windows.popup.malware.enabled).toBeFalsy(); + expect(policy.mac.popup.malware.enabled).toBeFalsy(); + expect(policy.linux.popup.malware.enabled).toBeFalsy(); }); - it('Should return the Default Policy Config without paid features when license is below platinum', () => { + + it('Should return PolicyConfig for events only without paid features when license is below platinum', () => { + const defaultPolicy = policyFactory(); licenseEmitter.next(Gold); + const policy = createDefaultPolicyCallback(undefined); - expect(policy).toEqual(policyConfigFactoryWithoutPaidFeatures()); + + // events are the same + expect(policy.windows.events).toEqual(defaultPolicy.windows.events); + expect(policy.linux.events).toEqual(defaultPolicy.linux.events); + expect(policy.mac.events).toEqual(defaultPolicy.mac.events); + + // check some of the protections to be disabled and unsupported + const disabledAndUnsupported = { mode: ProtectionModes.off, supported: false }; + expect(policy.windows.behavior_protection).toEqual(disabledAndUnsupported); + expect(policy.mac.memory_protection).toEqual(disabledAndUnsupported); + expect(policy.linux.behavior_protection).toEqual(disabledAndUnsupported); + + // malware popups are enabled on unpaid license + expect(policy.windows.popup.malware.enabled).toBeTruthy(); + expect(policy.mac.popup.malware.enabled).toBeTruthy(); + expect(policy.linux.popup.malware.enabled).toBeTruthy(); }); }); + describe('When endpoint config is set', () => { const createEndpointConfig = ( endpointConfig: PolicyCreateEndpointConfig['endpointConfig'] @@ -112,8 +146,8 @@ describe('Create Default Policy tests ', () => { it('Should return the default config when preset is EDR Complete', () => { const config = createEndpointConfig({ preset: 'EDRComplete' }); const policy = createDefaultPolicyCallback(config); - const policyFactory = policyConfigFactory(); - expect(policy).toMatchObject(policyFactory); + const defaultPolicy = policyFactory(); + expect(policy).toMatchObject(defaultPolicy); }); }); describe('When cloud config is set', () => { diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts index a2da989a9c08d..9e598198ba9a0 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts @@ -10,11 +10,15 @@ import { policyFactoryWithoutPaidFeatures as policyConfigFactoryWithoutPaidFeatures, } from '../../../common/endpoint/models/policy_config'; import type { LicenseService } from '../../../common/license/license'; -import { isAtLeast } from '../../../common/license/license'; import { ProtectionModes } from '../../../common/endpoint/types'; import type { PolicyConfig } from '../../../common/endpoint/types'; import type { AnyPolicyCreateConfig, PolicyCreateEndpointConfig } from '../types'; -import { ENDPOINT_CONFIG_PRESET_EDR_ESSENTIAL, ENDPOINT_CONFIG_PRESET_NGAV } from '../constants'; +import { + ENDPOINT_CONFIG_PRESET_EDR_COMPLETE, + ENDPOINT_CONFIG_PRESET_EDR_ESSENTIAL, + ENDPOINT_CONFIG_PRESET_NGAV, +} from '../constants'; +import { disableProtections } from '../../../common/endpoint/models/policy_config_helpers'; /** * Create the default endpoint policy based on the current license and configuration type @@ -23,19 +27,21 @@ export const createDefaultPolicy = ( licenseService: LicenseService, config: AnyPolicyCreateConfig | undefined ): PolicyConfig => { - const policy = isAtLeast(licenseService.getLicenseInformation(), 'platinum') - ? policyConfigFactory() - : policyConfigFactoryWithoutPaidFeatures(); + const factoryPolicy = policyConfigFactory(); - if (config?.type === 'cloud') { - return getCloudPolicyConfig(policy); - } + const defaultPolicyPerType = + config?.type === 'cloud' + ? getCloudPolicyConfig(factoryPolicy) + : getEndpointPolicyWithIntegrationConfig(factoryPolicy, config); - return getEndpointPolicyWithIntegrationConfig(policy, config); + // Apply license limitations in the final step, so it's not overriden (see malware popup) + return licenseService.isPlatinumPlus() + ? defaultPolicyPerType + : policyConfigFactoryWithoutPaidFeatures(defaultPolicyPerType); }; /** - * Set all keys of the given object to false + * Create a copy of an object with all keys set to false */ const falsyObjectKeys = >(obj: T): T => { return Object.keys(obj).reduce((accumulator, key) => { @@ -43,6 +49,14 @@ const falsyObjectKeys = >(obj: T): T => { }, {} as T); }; +const getEndpointPolicyConfigPreset = (config: PolicyCreateEndpointConfig | undefined) => { + const isNGAV = config?.endpointConfig?.preset === ENDPOINT_CONFIG_PRESET_NGAV; + const isEDREssential = config?.endpointConfig?.preset === ENDPOINT_CONFIG_PRESET_EDR_ESSENTIAL; + const isEDRComplete = config?.endpointConfig?.preset === ENDPOINT_CONFIG_PRESET_EDR_COMPLETE; + + return { isNGAV, isEDREssential, isEDRComplete }; +}; + /** * Retrieve policy for endpoint based on the preset selected in the endpoint integration config */ @@ -50,9 +64,11 @@ const getEndpointPolicyWithIntegrationConfig = ( policy: PolicyConfig, config: PolicyCreateEndpointConfig | undefined ): PolicyConfig => { - const isEDREssential = config?.endpointConfig?.preset === ENDPOINT_CONFIG_PRESET_EDR_ESSENTIAL; + const { isNGAV, isEDREssential, isEDRComplete } = getEndpointPolicyConfigPreset(config); - if (config?.endpointConfig?.preset === ENDPOINT_CONFIG_PRESET_NGAV || isEDREssential) { + if (isEDRComplete) { + return policy; + } else if (isNGAV || isEDREssential) { const events = { process: true, file: isEDREssential, @@ -85,7 +101,8 @@ const getEndpointPolicyWithIntegrationConfig = ( }; } - return policy; + // data collection by default + return disableProtections(policy); }; /** From 2db1920f94d548afda38ae22e4916c4acaf61e8e Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Wed, 2 Nov 2022 17:19:06 +0200 Subject: [PATCH 24/86] [Lens] Legacy metric expression types improvement. (#144236) * FontFn converted to builder. * labelFontFn added. * visdimensionFn added. * Added legacyMetricVisFn. * Added expression-legacy-metric-plugin as optional dependency to lens. --- .../expression_functions/vis_dimension.ts | 6 +- x-pack/plugins/lens/kibana.json | 1 + .../legacy_metric/visualization.test.ts | 4 +- .../legacy_metric/visualization.tsx | 119 ++++++++---------- x-pack/plugins/lens/tsconfig.json | 1 + 5 files changed, 58 insertions(+), 73 deletions(-) diff --git a/src/plugins/visualizations/common/expression_functions/vis_dimension.ts b/src/plugins/visualizations/common/expression_functions/vis_dimension.ts index 99574e1c36756..88397e9218563 100644 --- a/src/plugins/visualizations/common/expression_functions/vis_dimension.ts +++ b/src/plugins/visualizations/common/expression_functions/vis_dimension.ts @@ -32,12 +32,14 @@ export type ExpressionValueVisDimension = ExpressionValueBoxed< } >; -export const visDimension = (): ExpressionFunctionDefinition< +export type ExpressionFunctionVisDimension = ExpressionFunctionDefinition< 'visdimension', Datatable, Arguments, ExpressionValueVisDimension -> => ({ +>; + +export const visDimension = (): ExpressionFunctionVisDimension => ({ name: 'visdimension', help: i18n.translate('visualizations.function.visDimension.help', { defaultMessage: 'Generates visConfig dimension object', diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index 5f0b48608dab0..6921d2afa5082 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -31,6 +31,7 @@ "unifiedFieldList" ], "optionalPlugins": [ + "expressionLegacyMetricVis", "usageCollection", "taskManager", "globalSearch", diff --git a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.test.ts index 517e331634880..6724b9aafa0d1 100644 --- a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.test.ts @@ -368,7 +368,9 @@ describe('metric_visualization', () => { "type": "expression", }, ], - "palette": Array [], + "percentageMode": Array [ + false, + ], "showLabels": Array [ true, ], diff --git a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx index c4546fc8e7141..c98aa37dfd641 100644 --- a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx @@ -16,6 +16,15 @@ import { ColorMode, CustomPaletteState } from '@kbn/charts-plugin/common'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { IconChartMetric } from '@kbn/chart-icons'; import { LayerTypes } from '@kbn/expression-xy-plugin/public'; +import { + buildExpression, + buildExpressionFunction, + ExpressionFunctionFont, + FontWeight, + TextAlignment, +} from '@kbn/expressions-plugin/common'; +import { ExpressionFunctionVisDimension } from '@kbn/visualizations-plugin/common'; +import type { MetricVisExpressionFunctionDefinition } from '@kbn/expression-legacy-metric-vis-plugin/common'; import { getSuggestions } from './metric_suggestions'; import { Visualization, OperationMetadata, DatasourceLayers } from '../../types'; import type { LegacyMetricState } from '../../../common/types'; @@ -97,78 +106,48 @@ const toExpression = ( }; const metricFontSize = labelToMetricFontSizeMap[state?.size || DEFAULT_TITLE_SIZE]; + const fontFn = buildExpressionFunction('font', { + align: (state?.textAlign || DEFAULT_TEXT_ALIGNMENT) as TextAlignment, + size: metricFontSize, + weight: '600' as FontWeight, + lHeight: metricFontSize * 1.5, + sizeUnit: labelFont.sizeUnit, + }); + + const labelFontFn = buildExpressionFunction('font', { + align: (state?.textAlign || DEFAULT_TEXT_ALIGNMENT) as TextAlignment, + size: labelFont.size, + lHeight: labelFont.size * 1.5, + sizeUnit: labelFont.sizeUnit, + }); + + const visdimensionFn = buildExpressionFunction('visdimension', { + accessor: state.accessor, + }); + + const legacyMetricVisFn = buildExpressionFunction( + 'legacyMetricVis', + { + autoScaleMetricAlignment: state?.autoScaleMetricAlignment, + labelPosition: state?.titlePosition || DEFAULT_TITLE_POSITION, + font: buildExpression([fontFn]), + labelFont: buildExpression([labelFontFn]), + metric: buildExpression([visdimensionFn]), + showLabels: !attributes?.mode || attributes?.mode === 'full', + colorMode: !canColor ? ColorMode.None : state?.colorMode || ColorMode.None, + autoScale: true, + colorFullBackground: true, + palette: + state?.colorMode && state?.colorMode !== ColorMode.None + ? paletteService.get(CUSTOM_PALETTE).toExpression(paletteParams) + : undefined, + percentageMode: false, + } + ); + return { type: 'expression', - chain: [ - ...(datasourceExpression?.chain ?? []), - { - type: 'function', - function: 'legacyMetricVis', - arguments: { - ...(state?.autoScaleMetricAlignment - ? { autoScaleMetricAlignment: [state?.autoScaleMetricAlignment] } - : {}), - labelPosition: [state?.titlePosition || DEFAULT_TITLE_POSITION], - font: [ - { - type: 'expression', - chain: [ - { - type: 'function', - function: 'font', - arguments: { - align: [state?.textAlign || DEFAULT_TEXT_ALIGNMENT], - size: [metricFontSize], - weight: ['600'], - lHeight: [metricFontSize * 1.5], - sizeUnit: [labelFont.sizeUnit], - }, - }, - ], - }, - ], - labelFont: [ - { - type: 'expression', - chain: [ - { - type: 'function', - function: 'font', - arguments: { - align: [state?.textAlign || DEFAULT_TEXT_ALIGNMENT], - size: [labelFont.size], - lHeight: [labelFont.size * 1.5], - sizeUnit: [labelFont.sizeUnit], - }, - }, - ], - }, - ], - metric: [ - { - type: 'expression', - chain: [ - { - type: 'function', - function: 'visdimension', - arguments: { - accessor: [state.accessor], - }, - }, - ], - }, - ], - showLabels: [!attributes?.mode || attributes?.mode === 'full'], - colorMode: !canColor ? [ColorMode.None] : [state?.colorMode || ColorMode.None], - autoScale: [true], - colorFullBackground: [true], - palette: - state?.colorMode && state?.colorMode !== ColorMode.None - ? [paletteService.get(CUSTOM_PALETTE).toExpression(paletteParams)] - : [], - }, - }, - ], + chain: [...(datasourceExpression?.chain ?? []), legacyMetricVisFn.toAst()], }; }; diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index 3a70a796373e6..f4f343bbb4e5b 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -33,6 +33,7 @@ { "path": "../../../src/plugins/field_formats/tsconfig.json"}, { "path": "../../../src/plugins/chart_expressions/expression_heatmap/tsconfig.json"}, { "path": "../../../src/plugins/chart_expressions/expression_gauge/tsconfig.json"}, + { "path": "../../../src/plugins/chart_expressions/expression_legacy_metric/tsconfig.json"}, { "path": "../../../src/plugins/chart_expressions/expression_metric/tsconfig.json"}, { "path": "../../../src/plugins/data_view_editor/tsconfig.json"}, { "path": "../../../src/plugins/event_annotation/tsconfig.json"}, From cbaa32bc2caf47b2167a35e2dabadded2e1752b8 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 2 Nov 2022 11:22:35 -0400 Subject: [PATCH 25/86] [Fleet] Support per agent policy Fleet Server hosts (#144242) --- .../migrations/check_registered_types.test.ts | 2 +- .../hooks.test.tsx | 16 ++--- .../agent_policy_advanced_fields/hooks.tsx | 67 ++++++++++++++++-- .../agent_policy_advanced_fields/index.tsx | 58 ++++++++++++--- .../multi_page_layout/index.tsx | 10 +-- .../components/settings/index.tsx | 6 +- .../agent_enrollment_flyout/index.tsx | 15 ++-- .../agent_enrollment_flyout/instructions.tsx | 8 +-- .../steps/compute_steps.tsx | 10 +-- .../agent_enrollment_flyout/types.ts | 5 +- x-pack/plugins/fleet/public/hooks/index.ts | 1 + ...use_fleet_server_hosts_for_policy.test.tsx | 70 +++++++++++++++++++ .../use_fleet_server_hosts_for_policy.ts | 36 ++++++++++ .../fleet_server_policy_config/handler.ts | 17 ++++- .../fleet/server/saved_objects/index.ts | 1 + .../fleet/server/services/agent_policy.ts | 67 ++++++++++++++++++ .../server/services/fleet_server_host.ts | 7 +- .../preconfiguration/fleet_server_host.ts | 8 ++- x-pack/plugins/fleet/server/services/setup.ts | 1 + .../fleet/server/types/models/agent_policy.ts | 1 + 20 files changed, 342 insertions(+), 64 deletions(-) create mode 100644 x-pack/plugins/fleet/public/hooks/use_fleet_server_hosts_for_policy.test.tsx create mode 100644 x-pack/plugins/fleet/public/hooks/use_fleet_server_hosts_for_policy.ts diff --git a/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts index af572532a13e6..ad86d9d3cd980 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts @@ -95,7 +95,7 @@ describe('checking migration metadata changes on all registered SO types', () => "index-pattern": "48e77ca393c254e93256f11a7cdc0232dd754c08", "infrastructure-monitoring-log-view": "e2c78c1076bd35e57d7c5fa1b410e5c126d12327", "infrastructure-ui-source": "7c8dbbc0a608911f1b683a944f4a65383f6153ed", - "ingest-agent-policies": "5d728f483dc3b14dcfa6bbad95c2024d2da68890", + "ingest-agent-policies": "9170cdad95d887c036b87adf0ff38a3f12800c05", "ingest-download-sources": "1e69dabd6db5e320fe08c5bda8f35f29bafc6b54", "ingest-outputs": "29b867bf7bfd28b1e17c84697dce5c6d078f9705", "ingest-package-policies": "e8707a8c7821ea085e67c2d213e24efa56307393", diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.test.tsx index 874f60a604bfe..446d33a91af91 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.test.tsx @@ -113,7 +113,7 @@ describe('useOutputOptions', () => { Object { "disabled": false, "inputDisplay": "Default (currently Output 1)", - "value": "@@##DEFAULT_OUTPUT_VALUE##@@", + "value": "@@##DEFAULT_SELECT##@@", }, Object { "disabled": false, @@ -137,7 +137,7 @@ describe('useOutputOptions', () => { Object { "disabled": undefined, "inputDisplay": "Default (currently Output 1)", - "value": "@@##DEFAULT_OUTPUT_VALUE##@@", + "value": "@@##DEFAULT_SELECT##@@", }, Object { "disabled": false, @@ -175,7 +175,7 @@ describe('useOutputOptions', () => { Object { "disabled": false, "inputDisplay": "Default (currently Output 1)", - "value": "@@##DEFAULT_OUTPUT_VALUE##@@", + "value": "@@##DEFAULT_SELECT##@@", }, Object { "disabled": true, @@ -199,7 +199,7 @@ describe('useOutputOptions', () => { Object { "disabled": undefined, "inputDisplay": "Default (currently Output 1)", - "value": "@@##DEFAULT_OUTPUT_VALUE##@@", + "value": "@@##DEFAULT_SELECT##@@", }, Object { "disabled": true, @@ -237,7 +237,7 @@ describe('useOutputOptions', () => { Object { "disabled": false, "inputDisplay": "Default (currently Logstash 1)", - "value": "@@##DEFAULT_OUTPUT_VALUE##@@", + "value": "@@##DEFAULT_SELECT##@@", }, Object { "disabled": false, @@ -256,7 +256,7 @@ describe('useOutputOptions', () => { Object { "disabled": undefined, "inputDisplay": "Default (currently Logstash 1)", - "value": "@@##DEFAULT_OUTPUT_VALUE##@@", + "value": "@@##DEFAULT_SELECT##@@", }, Object { "disabled": false, @@ -315,7 +315,7 @@ describe('useOutputOptions', () => { /> , - "value": "@@##DEFAULT_OUTPUT_VALUE##@@", + "value": "@@##DEFAULT_SELECT##@@", }, Object { "disabled": false, @@ -352,7 +352,7 @@ describe('useOutputOptions', () => { Object { "disabled": undefined, "inputDisplay": "Default (currently Logstash 1)", - "value": "@@##DEFAULT_OUTPUT_VALUE##@@", + "value": "@@##DEFAULT_SELECT##@@", }, Object { "disabled": false, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.tsx index a5c8c68564d39..83573a17c2c8e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.tsx @@ -10,7 +10,12 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiText, EuiSpacer } from '@elastic/eui'; -import { useGetOutputs, useLicense, useGetDownloadSources } from '../../../../hooks'; +import { + useGetOutputs, + useLicense, + useGetDownloadSources, + useGetFleetServerHosts, +} from '../../../../hooks'; import { LICENCE_FOR_PER_POLICY_OUTPUT, FLEET_APM_PACKAGE, @@ -19,8 +24,7 @@ import { import type { NewAgentPolicy, AgentPolicy } from '../../../../types'; // The super select component do not support null or '' as a value -export const DEFAULT_OUTPUT_VALUE = '@@##DEFAULT_OUTPUT_VALUE##@@'; -export const DEFAULT_DOWNLOAD_SOURCE_VALUE = '@@##DEFAULT_DOWNLOAD_SOURCE_VALUE##@@'; +export const DEFAULT_SELECT_VALUE = '@@##DEFAULT_SELECT##@@'; function getOutputLabel(name: string, disabledMessage?: React.ReactNode) { if (!disabledMessage) { @@ -49,7 +53,7 @@ function getDefaultOutput( }), defaultOutputDisabledMessage ), - value: DEFAULT_OUTPUT_VALUE, + value: DEFAULT_SELECT_VALUE, disabled: defaultOutputDisabled, }; } @@ -184,7 +188,60 @@ function getDefaultDownloadSource( }), defaultDownloadSourceDisabledMessage ), - value: DEFAULT_DOWNLOAD_SOURCE_VALUE, + value: DEFAULT_SELECT_VALUE, disabled: defaultDownloadSourceDisabled, }; } + +export function useFleetServerHostsOptions(agentPolicy: Partial) { + const fleetServerHostsRequest = useGetFleetServerHosts(); + + const fleetServerHostsOptions = useMemo(() => { + if (fleetServerHostsRequest.isLoading || !fleetServerHostsRequest.data) { + return []; + } + + const defaultFleetServerHosts = fleetServerHostsRequest.data.items.find( + (item) => item.is_default + ); + const defaultFleetServerHostsName = defaultFleetServerHosts?.name; + + return [ + getDefaultFleetServerHosts(defaultFleetServerHostsName), + ...fleetServerHostsRequest.data.items + .filter((item) => !item.is_default) + .map((item) => { + return { + value: item.id, + inputDisplay: item.name, + }; + }), + ]; + }, [fleetServerHostsRequest]); + + return useMemo( + () => ({ + fleetServerHostsOptions, + isLoading: fleetServerHostsRequest.isLoading, + }), + [fleetServerHostsOptions, fleetServerHostsRequest.isLoading] + ); +} + +function getDefaultFleetServerHosts( + defaultFleetServerHostsName?: string, + defaultFleetServerHostsDisabled?: boolean, + defaultFleetServerHostsDisabledMessage?: React.ReactNode +) { + return { + inputDisplay: getOutputLabel( + i18n.translate('xpack.fleet.agentPolicy.fleetServerHostsOptions.defaultOutputText', { + defaultMessage: 'Default (currently {defaultFleetServerHostsName})', + values: { defaultFleetServerHostsName }, + }), + defaultFleetServerHostsDisabledMessage + ), + value: DEFAULT_SELECT_VALUE, + disabled: defaultFleetServerHostsDisabled, + }; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx index f8eab1fe8513e..179f61db9a5b7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx @@ -36,8 +36,8 @@ import { policyHasFleetServer } from '../../../../services'; import { useOutputOptions, useDownloadSourcesOptions, - DEFAULT_OUTPUT_VALUE, - DEFAULT_DOWNLOAD_SOURCE_VALUE, + DEFAULT_SELECT_VALUE, + useFleetServerHostsOptions, } from './hooks'; interface Props { @@ -65,6 +65,9 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent = const { dataDownloadSourceOptions, isLoading: isLoadingDownloadSources } = useDownloadSourcesOptions(agentPolicy); + const { fleetServerHostsOptions, isLoading: isLoadingFleetServerHostsOption } = + useFleetServerHostsOptions(agentPolicy); + // agent monitoring checkbox group can appear multiple times in the DOM, ids have to be unique to work correctly const monitoringCheckboxIdSuffix = Date.now(); @@ -292,6 +295,45 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent = /> + + + + } + description={ + + } + > + + { + updateAgentPolicy({ + fleet_server_host_id: e !== DEFAULT_SELECT_VALUE ? e : null, + }); + }} + options={fleetServerHostsOptions} + /> + + @@ -319,12 +361,12 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent = > { updateAgentPolicy({ - data_output_id: e !== DEFAULT_OUTPUT_VALUE ? e : null, + data_output_id: e !== DEFAULT_SELECT_VALUE ? e : null, }); }} options={dataOutputOptions} @@ -358,12 +400,12 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent = > { updateAgentPolicy({ - monitoring_output_id: e !== DEFAULT_OUTPUT_VALUE ? e : null, + monitoring_output_id: e !== DEFAULT_SELECT_VALUE ? e : null, }); }} options={monitoringOutputOptions} @@ -397,12 +439,12 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent = isInvalid={Boolean(touchedFields.download_source_id && validation.download_source_id)} > { updateAgentPolicy({ - download_source_id: e !== DEFAULT_DOWNLOAD_SOURCE_VALUE ? e : null, + download_source_id: e !== DEFAULT_SELECT_VALUE ? e : null, }); }} options={dataDownloadSourceOptions} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx index ff6168dd2423e..e11d30511c9af 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { splitPkgKey } from '../../../../../../../common/services'; -import { useGetPackageInfoByKey, useGetFleetServerHosts, useLink } from '../../../../hooks'; +import { useGetPackageInfoByKey, useLink, useFleetServerHostsForPolicy } from '../../../../hooks'; import type { AddToPolicyParams, CreatePackagePolicyParams } from '../types'; @@ -92,9 +92,7 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({ setOnSplash(false); }; - const fleetServerHostsRequest = useGetFleetServerHosts(); - const fleetServerHosts = - fleetServerHostsRequest.data?.items?.filter((f) => true)?.[0]?.host_urls ?? []; + const { fleetServerHosts, isLoadingInitialRequest } = useFleetServerHostsForPolicy(agentPolicy); const cancelUrl = getHref('add_integration_to_policy', { pkgkey, @@ -106,9 +104,7 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({ if (onSplash || !packageInfo) { return ( ( monitoring_output_id, // eslint-disable-next-line @typescript-eslint/naming-convention download_source_id, + // eslint-disable-next-line @typescript-eslint/naming-convention + fleet_server_host_id, } = agentPolicy; const { data, error } = await sendUpdateAgentPolicy(agentPolicy.id, { name, @@ -101,6 +103,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( data_output_id, monitoring_output_id, download_source_id, + fleet_server_host_id, }); if (data) { notifications.toasts.addSuccess( @@ -144,7 +147,8 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( 'unenroll_timeout', 'data_output_id', 'monitoring_output_id', - 'download_source_id' + 'download_source_id', + 'fleet_server_host_id' ) ), [agentPolicy] diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx index bccda5dfa6276..4773a30f1e822 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx @@ -27,7 +27,7 @@ import { useStartServices, useFleetStatus, useAgentEnrollmentFlyoutData, - useGetFleetServerHosts, + useFleetServerHostsForPolicy, } from '../../hooks'; import { FLEET_SERVER_PACKAGE } from '../../constants'; import type { PackagePolicy, AgentPolicy } from '../../types'; @@ -57,12 +57,8 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ return policies.find((p) => p.id === id); }; - const fleetServerHostsRequest = useGetFleetServerHosts(); - const fleetStatus = useFleetStatus(); const { docLinks } = useStartServices(); - const fleetServerHosts = - fleetServerHostsRequest.data?.items?.filter((f) => true)?.[0]?.host_urls ?? []; const [selectedPolicyId, setSelectedPolicyId] = useState(agentPolicy?.id); const [isFleetServerPolicySelected, setIsFleetServerPolicySelected] = useState(false); @@ -79,6 +75,10 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ const { agentPolicyWithPackagePolicies } = useAgentPolicyWithPackagePolicies(selectedPolicyId); + const { fleetServerHosts, isLoadingInitialRequest } = useFleetServerHostsForPolicy( + agentPolicyWithPackagePolicies + ); + const selectedPolicy = agentPolicyWithPackagePolicies ? agentPolicyWithPackagePolicies : findPolicyById(agentPolicies, selectedPolicyId); @@ -101,9 +101,6 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ const { isK8s } = useIsK8sPolicy(selectedPolicy ? selectedPolicy : undefined); - const isLoadingInitialRequest = - fleetServerHostsRequest.isLoading && fleetServerHostsRequest.isInitialRequest; - return ( @@ -192,7 +189,7 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ ) : ( { const fleetServers = agents?.items || []; - const displayFleetServerHosts = useMemo(() => { - return fleetServerHosts?.filter((f) => f.is_default)?.[0]?.host_urls || []; - }, [fleetServerHosts]); - if (isLoadingAgents || isLoadingAgentPolicies) return ; - const hasNoFleetServerHost = fleetStatus.isReady && displayFleetServerHosts.length === 0; + const hasNoFleetServerHost = fleetStatus.isReady && (fleetServerHosts?.length ?? 0) === 0; const showAgentEnrollment = fleetStatus.isReady && !isFleetServerUnhealthy && fleetServers.length > 0 && - displayFleetServerHosts.length > 0; + (fleetServerHosts?.length ?? 0) > 0; const showFleetServerEnrollment = fleetServers.length === 0 || diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx index 4bb167fb2494e..9d39364876da0 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx @@ -207,15 +207,7 @@ export const ManagedSteps: React.FunctionComponent = ({ const enrolledAgentIds = usePollingAgentCount(selectedPolicy?.id || ''); - const displayFleetServerHosts = useMemo(() => { - return fleetServerHosts?.filter((f) => f.is_default)?.[0]?.host_urls ?? []; - }, [fleetServerHosts]); - - const installManagedCommands = ManualInstructions( - enrollToken, - displayFleetServerHosts, - kibanaVersion - ); + const installManagedCommands = ManualInstructions(enrollToken, fleetServerHosts, kibanaVersion); const instructionsSteps = useMemo(() => { const steps: EuiContainedStepProps[] = !agentPolicy diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts index 7215cba9e417f..0cbae55c6ffd9 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { AgentPolicy, FleetServerHost } from '../../types'; +import type { AgentPolicy } from '../../types'; import type { InstalledIntegrationPolicy } from './use_get_agent_incoming_data'; @@ -19,8 +19,6 @@ export interface BaseProps { */ agentPolicy?: AgentPolicy; - fleetServerHosts?: FleetServerHost[]; - isFleetServerPolicySelected?: boolean; isK8s?: K8sMode; @@ -51,4 +49,5 @@ export interface InstructionProps extends BaseProps { setSelectionType: (type: SelectionType) => void; selectedApiKeyId?: string; setSelectedAPIKeyId: (key?: string) => void; + fleetServerHosts: string[]; } diff --git a/x-pack/plugins/fleet/public/hooks/index.ts b/x-pack/plugins/fleet/public/hooks/index.ts index b155ccf63a0db..236392144e18b 100644 --- a/x-pack/plugins/fleet/public/hooks/index.ts +++ b/x-pack/plugins/fleet/public/hooks/index.ts @@ -29,3 +29,4 @@ export * from './use_package_installations'; export * from './use_agent_enrollment_flyout_data'; export * from './use_flyout_context'; export * from './use_is_guided_onboarding_active'; +export * from './use_fleet_server_hosts_for_policy'; diff --git a/x-pack/plugins/fleet/public/hooks/use_fleet_server_hosts_for_policy.test.tsx b/x-pack/plugins/fleet/public/hooks/use_fleet_server_hosts_for_policy.test.tsx new file mode 100644 index 0000000000000..9da0b8ea1704c --- /dev/null +++ b/x-pack/plugins/fleet/public/hooks/use_fleet_server_hosts_for_policy.test.tsx @@ -0,0 +1,70 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; + +import { useFleetServerHostsForPolicy } from './use_fleet_server_hosts_for_policy'; +import { useGetFleetServerHosts } from './use_request/fleet_server_hosts'; + +jest.mock('./use_request/fleet_server_hosts'); + +const mockedUseGetFleetServerHosts = useGetFleetServerHosts as jest.MockedFunction< + typeof useGetFleetServerHosts +>; + +describe('useFleetServerHostsForPolicy', () => { + beforeEach(() => { + mockedUseGetFleetServerHosts.mockReturnValue({ + isLoading: false, + isInitialRequest: false, + data: { + items: [ + { + id: 'default', + is_default: true, + host_urls: ['https://defaultfleetserver:8220'], + is_preconfigured: false, + name: 'Default', + }, + { + id: 'custom1', + is_default: false, + host_urls: ['https://custom1:8220'], + is_preconfigured: false, + name: 'Custom 1', + }, + ], + page: 1, + perPage: 100, + total: 2, + }, + } as any); + }); + it('should return default hosts if used without agent policy', () => { + const { result } = renderHook(() => useFleetServerHostsForPolicy()); + expect(result.current.fleetServerHosts).toEqual(['https://defaultfleetserver:8220']); + }); + + it('should return default hosts if used with agent policy that do not override fleet server host', () => { + const { result } = renderHook(() => + useFleetServerHostsForPolicy({ + id: 'testpolicy1', + } as any) + ); + expect(result.current.fleetServerHosts).toEqual(['https://defaultfleetserver:8220']); + }); + + it('should return custom hosts if used with agent policy that override fleet server hosts', () => { + const { result } = renderHook(() => + useFleetServerHostsForPolicy({ + id: 'testpolicy1', + fleet_server_host_id: 'custom1', + } as any) + ); + expect(result.current.fleetServerHosts).toEqual(['https://custom1:8220']); + }); +}); diff --git a/x-pack/plugins/fleet/public/hooks/use_fleet_server_hosts_for_policy.ts b/x-pack/plugins/fleet/public/hooks/use_fleet_server_hosts_for_policy.ts new file mode 100644 index 0000000000000..14b00a4f13763 --- /dev/null +++ b/x-pack/plugins/fleet/public/hooks/use_fleet_server_hosts_for_policy.ts @@ -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 { useMemo } from 'react'; + +import type { AgentPolicy } from '../types'; + +import { useGetFleetServerHosts } from './use_request'; + +/** + * Return Fleet server hosts urls for a given agent policy + */ +export function useFleetServerHostsForPolicy(agentPolicy?: AgentPolicy | null) { + const fleetServerHostsRequest = useGetFleetServerHosts(); + const fleetServerHosts = useMemo(() => { + return ( + fleetServerHostsRequest.data?.items.filter((item) => + agentPolicy?.fleet_server_host_id + ? item.id === agentPolicy?.fleet_server_host_id + : item.is_default + )?.[0]?.host_urls ?? [] + ); + }, [agentPolicy, fleetServerHostsRequest]); + + const isLoadingInitialRequest = + fleetServerHostsRequest.isLoading && fleetServerHostsRequest.isInitialRequest; + + return useMemo( + () => ({ isLoadingInitialRequest, fleetServerHosts }), + [fleetServerHosts, isLoadingInitialRequest] + ); +} diff --git a/x-pack/plugins/fleet/server/routes/fleet_server_policy_config/handler.ts b/x-pack/plugins/fleet/server/routes/fleet_server_policy_config/handler.ts index 3df4daf126119..705cde4d87e32 100644 --- a/x-pack/plugins/fleet/server/routes/fleet_server_policy_config/handler.ts +++ b/x-pack/plugins/fleet/server/routes/fleet_server_policy_config/handler.ts @@ -78,9 +78,11 @@ export const getFleetServerPolicyHandler: RequestHandler< export const deleteFleetServerPolicyHandler: RequestHandler< TypeOf > = async (context, request, response) => { - const soClient = (await context.core).savedObjects.client; try { - await deleteFleetServerHost(soClient, request.params.itemId); + const coreContext = await context.core; + const soClient = coreContext.savedObjects.client; + const esClient = coreContext.elasticsearch.client.asInternalUser; + await deleteFleetServerHost(soClient, esClient, request.params.itemId); const body = { id: request.params.itemId, }; @@ -102,13 +104,22 @@ export const putFleetServerPolicyHandler: RequestHandler< undefined, TypeOf > = async (context, request, response) => { - const soClient = (await context.core).savedObjects.client; try { + const coreContext = await await context.core; + const esClient = coreContext.elasticsearch.client.asInternalUser; + const soClient = coreContext.savedObjects.client; + const item = await updateFleetServerHost(soClient, request.params.itemId, request.body); const body = { item, }; + if (item.is_default) { + await agentPolicyService.bumpAllAgentPolicies(soClient, esClient); + } else { + await agentPolicyService.bumpAllAgentPoliciesForFleetServerHosts(soClient, esClient, item.id); + } + return response.ok({ body }); } catch (error) { if (SavedObjectsErrorHelpers.isNotFoundError(error)) { diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 5721d2e6b7ed6..b0fbe456246c6 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -105,6 +105,7 @@ const getSavedObjectTypes = ( data_output_id: { type: 'keyword' }, monitoring_output_id: { type: 'keyword' }, download_source_id: { type: 'keyword' }, + fleet_server_host_id: { type: 'keyword' }, }, }, migrations: { diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index b9978d85ee73d..44696a7f1a997 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -545,6 +545,41 @@ class AgentPolicyService { } } + /** + * Remove a Fleet Server from all agent policies that are using it, to use the default one instead. + */ + public async removeFleetServerHostFromAll( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + fleetServerHostId: string + ) { + const agentPolicies = ( + await soClient.find({ + type: SAVED_OBJECT_TYPE, + fields: ['revision', 'fleet_server_host_id'], + searchFields: ['fleet_server_host_id'], + search: escapeSearchQueryPhrase(fleetServerHostId), + perPage: SO_SEARCH_LIMIT, + }) + ).saved_objects.map((so) => ({ + id: so.id, + ...so.attributes, + })); + + if (agentPolicies.length > 0) { + await pMap( + agentPolicies, + (agentPolicy) => + this.update(soClient, esClient, agentPolicy.id, { + fleet_server_host_id: null, + }), + { + concurrency: 50, + } + ); + } + } + public async bumpAllAgentPoliciesForOutput( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, @@ -955,6 +990,38 @@ class AgentPolicyService { return res; } + + public async bumpAllAgentPoliciesForFleetServerHosts( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + fleetServerHostId: string, + options?: { user?: AuthenticatedUser } + ): Promise> { + const currentPolicies = await soClient.find({ + type: SAVED_OBJECT_TYPE, + fields: ['revision', 'fleet_server_host_id'], + searchFields: ['fleet_server_host_id'], + search: escapeSearchQueryPhrase(fleetServerHostId), + perPage: SO_SEARCH_LIMIT, + }); + const bumpedPolicies = currentPolicies.saved_objects.map((policy) => { + policy.attributes = { + ...policy.attributes, + revision: policy.attributes.revision + 1, + updated_at: new Date().toISOString(), + updated_by: options?.user ? options.user.username : 'system', + }; + return policy; + }); + const res = await soClient.bulkUpdate(bumpedPolicies); + await pMap( + currentPolicies.saved_objects, + (policy) => this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'updated', policy.id), + { concurrency: 50 } + ); + + return res; + } } export const agentPolicyService = new AgentPolicyService(); diff --git a/x-pack/plugins/fleet/server/services/fleet_server_host.ts b/x-pack/plugins/fleet/server/services/fleet_server_host.ts index 42f03d6e269c0..68b688bcbfcc1 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server_host.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server_host.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SavedObjectsClientContract } from '@kbn/core/server'; +import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; import { normalizeHostsForAgents } from '../../common/services'; import { @@ -24,6 +24,8 @@ import type { } from '../types'; import { FleetServerHostUnauthorizedError } from '../errors'; +import { agentPolicyService } from './agent_policy'; + export async function createFleetServerHost( soClient: SavedObjectsClientContract, data: NewFleetServerHost, @@ -91,6 +93,7 @@ export async function listFleetServerHosts(soClient: SavedObjectsClientContract) export async function deleteFleetServerHost( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, id: string, options?: { fromPreconfiguration?: boolean } ) { @@ -108,6 +111,8 @@ export async function deleteFleetServerHost( ); } + await agentPolicyService.removeFleetServerHostFromAll(soClient, esClient, id); + return await soClient.delete(FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, id); } diff --git a/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.ts b/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.ts index 13de4e8ec64b5..c666bc0a343a5 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration/fleet_server_host.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SavedObjectsClientContract } from '@kbn/core/server'; +import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; import { isEqual } from 'lodash'; import { decodeCloudId, normalizeHostsForAgents } from '../../../common/services'; @@ -79,10 +79,11 @@ export function getPreconfiguredFleetServerHostFromConfig(config?: FleetConfigTy export async function ensurePreconfiguredFleetServerHosts( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, preconfiguredFleetServerHosts: FleetServerHost[] ) { await createOrUpdatePreconfiguredFleetServerHosts(soClient, preconfiguredFleetServerHosts); - await cleanPreconfiguredFleetServerHosts(soClient, preconfiguredFleetServerHosts); + await cleanPreconfiguredFleetServerHosts(soClient, esClient, preconfiguredFleetServerHosts); } export async function createOrUpdatePreconfiguredFleetServerHosts( @@ -141,6 +142,7 @@ export async function createOrUpdatePreconfiguredFleetServerHosts( export async function cleanPreconfiguredFleetServerHosts( soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, preconfiguredFleetServerHosts: FleetServerHost[] ) { const existingFleetServerHosts = await listFleetServerHosts(soClient); @@ -166,7 +168,7 @@ export async function cleanPreconfiguredFleetServerHosts( } ); } else { - await deleteFleetServerHost(soClient, existingFleetServerHost.id, { + await deleteFleetServerHost(soClient, esClient, existingFleetServerHost.id, { fromPreconfiguration: true, }); } diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index 1f39062bf9393..3cb2dc030cf75 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -86,6 +86,7 @@ async function createSetupSideEffects( logger.debug('Setting up Fleet Sever Hosts'); await ensurePreconfiguredFleetServerHosts( soClient, + esClient, getPreconfiguredFleetServerHostFromConfig(appContextService.getConfig()) ); diff --git a/x-pack/plugins/fleet/server/types/models/agent_policy.ts b/x-pack/plugins/fleet/server/types/models/agent_policy.ts index 406dcbafc8894..487969cb93eb9 100644 --- a/x-pack/plugins/fleet/server/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/agent_policy.ts @@ -35,6 +35,7 @@ export const AgentPolicyBaseSchema = { data_output_id: schema.maybe(schema.nullable(schema.string())), monitoring_output_id: schema.maybe(schema.nullable(schema.string())), download_source_id: schema.maybe(schema.nullable(schema.string())), + fleet_server_host_id: schema.maybe(schema.nullable(schema.string())), }; export const NewAgentPolicySchema = schema.object({ From 979b96b248c652dc1576b06a1c72bb956cb3ece7 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 2 Nov 2022 16:29:49 +0100 Subject: [PATCH 26/86] [Synthetics] Step details add network timings breakdown (#144336) --- .../observability_data_views.ts | 13 +++- .../e2e/page_objects/synthetics_app.tsx | 2 +- .../network_timings_breakdown.tsx | 60 +++++++++++++++++++ .../step_details_page/step_detail_page.tsx | 5 +- 4 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/network_timings_breakdown.tsx diff --git a/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts b/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts index 241e2ba021f3e..12bbf802538c0 100644 --- a/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts +++ b/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts @@ -118,12 +118,23 @@ export class ObservabilityDataViews { async createDataView(app: AppDataType, indices: string) { const appIndicesPattern = getAppIndicesWithPattern(app, indices); - return await this.dataViews.create({ + + const { runtimeFields } = getFieldFormatsForApp(app); + + const dataView = await this.dataViews.create({ title: appIndicesPattern, id: getAppDataViewId(app, indices), timeFieldName: '@timestamp', fieldFormats: this.getFieldFormats(app), }); + + if (runtimeFields !== null) { + runtimeFields.forEach(({ name, field }) => { + dataView.addRuntimeField(name, field); + }); + } + + return dataView; } async createAndSavedDataView(app: AppDataType, indices: string) { diff --git a/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx b/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx index 66d086fcea9d7..2813427ae5225 100644 --- a/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx +++ b/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx @@ -82,7 +82,7 @@ export function syntheticsAppPageProvider({ page, kibanaUrl }: { page: Page; kib async navigateToEditMonitor() { await this.clickByTestSubj('syntheticsMonitorListActions'); - await page.click('text=Edit', { timeout: 2 * 60 * 1000 }); + await page.click('text=Edit', { timeout: 2 * 60 * 1000, delay: 800 }); await this.findByText('Edit monitor'); }, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/network_timings_breakdown.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/network_timings_breakdown.tsx new file mode 100644 index 0000000000000..2413d917665d4 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/network_timings_breakdown.tsx @@ -0,0 +1,60 @@ +/* + * 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 { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useParams } from 'react-router-dom'; +import { ReportTypes } from '@kbn/observability-plugin/public'; +import { EuiSpacer, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ClientPluginsStart } from '../../../../plugin'; + +export const NetworkTimingsBreakdown = () => { + const { observability } = useKibana().services; + + const ExploratoryViewEmbeddable = observability.ExploratoryViewEmbeddable; + + const { checkGroupId, stepIndex } = useParams<{ checkGroupId: string; stepIndex: string }>(); + + return ( + <> + +

        {LAST_24_HOURS}

        +
        + + + + ); +}; + +const LAST_24_HOURS = i18n.translate('xpack.synthetics.stepDetailsRoute.last24Hours', { + defaultMessage: 'Last 24 hours', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx index f7e2752dcfd9d..f0bb1b91d5a8e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx @@ -18,6 +18,7 @@ import { } from '@elastic/eui'; import { WaterfallChartContainer } from './components/network_waterfall/step_detail/waterfall/waterfall_chart_container'; import { ObjectWeightList } from './components/object_weight_list'; +import { NetworkTimingsBreakdown } from './network_timings_breakdown'; import { StepImage } from './components/step_image'; import { useJourneySteps } from '../monitor_details/hooks/use_journey_steps'; import { MonitorDetailsLinkPortal } from '../monitor_add_edit/monitor_details_portal'; @@ -73,8 +74,8 @@ export const StepDetailPage = () => { {/* TODO: Add breakdown of network timings donut*/} - - {/* TODO: Add breakdown of network events*/} + + From 2e23aa0c3178452015e88807733f2f00a23e62b2 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Wed, 2 Nov 2022 16:38:32 +0100 Subject: [PATCH 27/86] added fix for add and edit integrations page not loading for prerelease versions (#144431) --- .../create_package_policy_page/multi_page_layout/index.tsx | 2 +- .../create_package_policy_page/single_page_layout/index.tsx | 2 +- .../sections/agent_policy/edit_package_policy_page/index.tsx | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx index e11d30511c9af..46f12816f02c0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx @@ -70,7 +70,7 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({ data: packageInfoData, error: packageInfoError, isLoading: isPackageInfoLoading, - } = useGetPackageInfoByKey(pkgName, pkgVersion); + } = useGetPackageInfoByKey(pkgName, pkgVersion, { prerelease: true }); const { agentPolicy, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx index 02f36e2cadcfe..0192b7a4a7928 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx @@ -99,7 +99,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ data: packageInfoData, error: packageInfoError, isLoading: isPackageInfoLoading, - } = useGetPackageInfoByKey(pkgName, pkgVersion); + } = useGetPackageInfoByKey(pkgName, pkgVersion, { prerelease: true }); const packageInfo = useMemo(() => { if (packageInfoData && packageInfoData.item) { return packageInfoData.item; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index e11d1ccda3b91..aa9ca6277a43c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -265,7 +265,8 @@ export const EditPackagePolicyForm = memo<{ const { data: packageData } = await sendGetPackageInfoByKey( _packageInfo!.name, - _packageInfo!.version + _packageInfo!.version, + { prerelease: true } ); if (packageData?.item) { From c1070e63a1e591ab9a47eb6a61de35b65a1b52e6 Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Wed, 2 Nov 2022 11:41:48 -0400 Subject: [PATCH 28/86] [Security Solution] [Exceptions] Updates the exceptions list table to match mockups (#142289) Co-authored-by: Gloria Hornero --- .../src/import_query_schema/index.test.ts | 3 + .../src/import_query_schema/index.ts | 4 +- .../server/routes/import_exceptions_route.ts | 1 + .../exception_list_client.test.ts | 1 + .../exception_lists/exception_list_client.ts | 3 + .../exception_list_client_types.ts | 1 + .../import_exception_list_and_items.test.ts | 3 + .../import_exception_list_and_items.ts | 38 ++- .../utils/import/import_exception_lists.ts | 3 + ...xception_items_to_create_or_update.test.ts | 14 +- .../sort_exception_items_to_create_update.ts | 25 -- ...xception_lists_to_create_or_update.test.ts | 4 + .../sort_exception_lists_to_create_update.ts | 23 ++ .../exceptions_table.cy.ts | 11 +- .../cypress/screens/exceptions.ts | 7 +- .../cypress/tasks/exceptions_table.ts | 34 ++- .../public/app/translations.ts | 2 +- .../navigation/breadcrumbs/index.test.ts | 4 +- .../__snapshots__/index.test.tsx.snap | 2 +- .../pages/exceptions/columns.tsx | 175 ----------- .../pages/exceptions/types.ts | 13 - .../components/rules_table/helpers.test.ts | 2 +- .../public/exceptions/jest.config.js | 26 ++ .../exceptions_list_card.tsx | 150 ++++++++++ .../exceptions_search_bar.tsx | 2 +- .../exceptions_table.test.tsx | 41 +-- .../manage_exceptions}/exceptions_table.tsx | 272 +++++++++++------- .../exceptions_table_utility_bar.test.tsx | 2 +- .../exceptions_table_utility_bar.tsx | 4 +- .../import_exceptions_list_flyout.tsx | 201 +++++++++++++ .../manage_exceptions/title_badge.tsx | 37 +++ .../manage_exceptions/translations.ts | 106 +++++++ .../translations_exceptions_table.ts} | 36 ++- .../use_all_exception_lists.tsx | 4 +- .../use_import_exception_list.tsx | 48 ++++ .../public/exceptions/routes.tsx | 2 +- 36 files changed, 914 insertions(+), 390 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/columns.tsx delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/types.ts create mode 100644 x-pack/plugins/security_solution/public/exceptions/jest.config.js create mode 100644 x-pack/plugins/security_solution/public/exceptions/manage_exceptions/exceptions_list_card.tsx rename x-pack/plugins/security_solution/public/{detection_engine/rule_exceptions_ui/pages/exceptions => exceptions/manage_exceptions}/exceptions_search_bar.tsx (95%) rename x-pack/plugins/security_solution/public/{detection_engine/rule_exceptions_ui/pages/exceptions => exceptions/manage_exceptions}/exceptions_table.test.tsx (74%) rename x-pack/plugins/security_solution/public/{detection_engine/rule_exceptions_ui/pages/exceptions => exceptions/manage_exceptions}/exceptions_table.tsx (61%) rename x-pack/plugins/security_solution/public/{detection_engine/rule_exceptions_ui/pages/exceptions => exceptions/manage_exceptions}/exceptions_table_utility_bar.test.tsx (96%) rename x-pack/plugins/security_solution/public/{detection_engine/rule_exceptions_ui/pages/exceptions => exceptions/manage_exceptions}/exceptions_table_utility_bar.tsx (92%) create mode 100644 x-pack/plugins/security_solution/public/exceptions/manage_exceptions/import_exceptions_list_flyout.tsx create mode 100644 x-pack/plugins/security_solution/public/exceptions/manage_exceptions/title_badge.tsx create mode 100644 x-pack/plugins/security_solution/public/exceptions/manage_exceptions/translations.ts rename x-pack/plugins/security_solution/public/{detection_engine/rule_exceptions_ui/pages/exceptions/translations.ts => exceptions/manage_exceptions/translations_exceptions_table.ts} (84%) rename x-pack/plugins/security_solution/public/{detection_engine/rule_exceptions_ui/pages/exceptions => exceptions/manage_exceptions}/use_all_exception_lists.tsx (95%) create mode 100644 x-pack/plugins/security_solution/public/exceptions/manage_exceptions/use_import_exception_list.tsx diff --git a/packages/kbn-securitysolution-io-ts-types/src/import_query_schema/index.test.ts b/packages/kbn-securitysolution-io-ts-types/src/import_query_schema/index.test.ts index 31a3eed8b7fbf..d2f84b4ee3708 100644 --- a/packages/kbn-securitysolution-io-ts-types/src/import_query_schema/index.test.ts +++ b/packages/kbn-securitysolution-io-ts-types/src/import_query_schema/index.test.ts @@ -13,6 +13,7 @@ import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts describe('importQuerySchema', () => { test('it should validate proper schema', () => { const payload: ImportQuerySchema = { + as_new_list: false, overwrite: true, overwrite_exceptions: true, }; @@ -26,6 +27,7 @@ describe('importQuerySchema', () => { test('it should NOT validate a non boolean value for "overwrite"', () => { const payload: Omit & { overwrite: string } = { + as_new_list: false, overwrite: 'wrong', overwrite_exceptions: true, }; @@ -43,6 +45,7 @@ describe('importQuerySchema', () => { const payload: Omit & { overwrite_exceptions: string; } = { + as_new_list: false, overwrite: true, overwrite_exceptions: 'wrong', }; diff --git a/packages/kbn-securitysolution-io-ts-types/src/import_query_schema/index.ts b/packages/kbn-securitysolution-io-ts-types/src/import_query_schema/index.ts index f84f16fddabdb..ca0e35c4aa176 100644 --- a/packages/kbn-securitysolution-io-ts-types/src/import_query_schema/index.ts +++ b/packages/kbn-securitysolution-io-ts-types/src/import_query_schema/index.ts @@ -14,14 +14,16 @@ export const importQuerySchema = t.exact( t.partial({ overwrite: DefaultStringBooleanFalse, overwrite_exceptions: DefaultStringBooleanFalse, + as_new_list: DefaultStringBooleanFalse, }) ); export type ImportQuerySchema = t.TypeOf; export type ImportQuerySchemaDecoded = Omit< ImportQuerySchema, - 'overwrite' | 'overwrite_exceptions' + 'overwrite' | 'overwrite_exceptions' | 'as_new_list' > & { overwrite: boolean; overwrite_exceptions: boolean; + as_new_list: boolean; }; diff --git a/x-pack/plugins/lists/server/routes/import_exceptions_route.ts b/x-pack/plugins/lists/server/routes/import_exceptions_route.ts index 78e0d571b2dc2..955e2debdea27 100644 --- a/x-pack/plugins/lists/server/routes/import_exceptions_route.ts +++ b/x-pack/plugins/lists/server/routes/import_exceptions_route.ts @@ -58,6 +58,7 @@ export const importExceptionsRoute = (router: ListsPluginRouter, config: ConfigT const importsSummary = await exceptionListsClient.importExceptionListAndItems({ exceptionsToImport: request.body.file, + generateNewListId: request.query.as_new_list, maxExceptionsImportSize: config.maxExceptionsImportSize, overwrite: request.query.overwrite, }); diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.test.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.test.ts index 043ffcd48fe77..8ad92f5d14bff 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.test.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.test.ts @@ -299,6 +299,7 @@ describe('exception_list_client', () => { (): ReturnType => { return exceptionListClient.importExceptionListAndItems({ exceptionsToImport: toReadable([getExceptionListItemSchemaMock()]), + generateNewListId: false, maxExceptionsImportSize: 10_000, overwrite: true, }); diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts index baa9d943127f7..ecdaa70d7869b 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts @@ -984,6 +984,7 @@ export class ExceptionListClient { exceptionsToImport, maxExceptionsImportSize, overwrite, + generateNewListId, }: ImportExceptionListAndItemsOptions): Promise => { const { savedObjectsClient, user } = this; @@ -1004,6 +1005,7 @@ export class ExceptionListClient { return importExceptions({ exceptions: parsedObjects, + generateNewListId, overwrite, savedObjectsClient, user, @@ -1038,6 +1040,7 @@ export class ExceptionListClient { return importExceptions({ exceptions: parsedObjects, + generateNewListId: false, overwrite, savedObjectsClient, user, diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts index 048930e51b93d..35a28c0116035 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts @@ -502,6 +502,7 @@ export interface ImportExceptionListAndItemsOptions { maxExceptionsImportSize: number; /** whether or not to overwrite an exception list with imported list if a matching list_id found */ overwrite: boolean; + generateNewListId: boolean; } /** diff --git a/x-pack/plugins/lists/server/services/exception_lists/import_exception_list_and_items.test.ts b/x-pack/plugins/lists/server/services/exception_lists/import_exception_list_and_items.test.ts index 375e211cabe46..e1181cc656d3e 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/import_exception_list_and_items.test.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/import_exception_list_and_items.test.ts @@ -56,6 +56,7 @@ describe('import_exception_list_and_items', () => { getImportExceptionsListSchemaMock('test_list_id'), getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), ]), + generateNewListId: false, maxExceptionsImportSize: 10000, overwrite: false, }); @@ -82,6 +83,7 @@ describe('import_exception_list_and_items', () => { getImportExceptionsListSchemaMock('test_list_id'), getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), ]), + generateNewListId: false, maxExceptionsImportSize: 10000, overwrite: false, }); @@ -102,6 +104,7 @@ describe('import_exception_list_and_items', () => { getImportExceptionsListSchemaMock('test_list_id'), getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), ]), + generateNewListId: false, maxExceptionsImportSize: 10000, overwrite: false, }); diff --git a/x-pack/plugins/lists/server/services/exception_lists/import_exception_list_and_items.ts b/x-pack/plugins/lists/server/services/exception_lists/import_exception_list_and_items.ts index deef7b7b21917..46fe83bccd6dd 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/import_exception_list_and_items.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/import_exception_list_and_items.ts @@ -18,6 +18,7 @@ import { import { createPromiseFromStreams } from '@kbn/utils'; import { SavedObjectsClientContract } from '@kbn/core/server'; import { chunk } from 'lodash/fp'; +import uuid from 'uuid'; import { importExceptionLists } from './utils/import/import_exception_lists'; import { importExceptionListItems } from './utils/import/import_exception_list_items'; @@ -49,6 +50,7 @@ export interface ImportDataResponse { interface ImportExceptionListAndItemsOptions { exceptions: PromiseFromStreams; overwrite: boolean; + generateNewListId: boolean; savedObjectsClient: SavedObjectsClientContract; user: string; } @@ -90,6 +92,7 @@ export const importExceptionsAsStream = async ({ return importExceptions({ exceptions: parsedObjects, + generateNewListId: false, overwrite, savedObjectsClient, user, @@ -99,14 +102,44 @@ export const importExceptionsAsStream = async ({ export const importExceptions = async ({ exceptions, overwrite, + generateNewListId, savedObjectsClient, user, }: ImportExceptionListAndItemsOptions): Promise => { + let exceptionsToValidate = exceptions; + if (generateNewListId) { + // we need to generate a new list id and update the old list id references + // in each list item to point to the new list id + exceptionsToValidate = exceptions.lists.reduce( + (acc, exceptionList) => { + if (exceptionList instanceof Error) { + return { items: [...acc.items], lists: [...acc.lists] }; + } + const newListId = uuid.v4(); + + return { + items: [ + ...acc.items, + ...exceptions.items + .filter( + (item) => + !(item instanceof Error) && + !(exceptionList instanceof Error) && + item?.list_id === exceptionList?.list_id + ) + .map((item) => ({ ...item, list_id: newListId })), + ], + lists: [...acc.lists, { ...exceptionList, list_id: newListId }], + }; + }, + { items: [], lists: [] } as PromiseFromStreams + ); + } // removal of duplicates const [exceptionListDuplicateErrors, uniqueExceptionLists] = - getTupleErrorsAndUniqueExceptionLists(exceptions.lists); + getTupleErrorsAndUniqueExceptionLists(exceptionsToValidate.lists); const [exceptionListItemsDuplicateErrors, uniqueExceptionListItems] = - getTupleErrorsAndUniqueExceptionListItems(exceptions.items); + getTupleErrorsAndUniqueExceptionListItems(exceptionsToValidate.items); // chunking of validated import stream const chunkParsedListObjects = chunk(CHUNK_PARSED_OBJECT_SIZE, uniqueExceptionLists); @@ -115,6 +148,7 @@ export const importExceptions = async ({ // where the magic happens - purposely importing parent exception // containers first, items second const importExceptionListsResponse = await importExceptionLists({ + generateNewListId, isOverwrite: overwrite, listsChunks: chunkParsedListObjects, savedObjectsClient, diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils/import/import_exception_lists.ts b/x-pack/plugins/lists/server/services/exception_lists/utils/import/import_exception_lists.ts index 13141a7a16400..cd2d730c584f7 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils/import/import_exception_lists.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils/import/import_exception_lists.ts @@ -26,11 +26,13 @@ import { sortImportResponses } from './sort_import_responses'; */ export const importExceptionLists = async ({ isOverwrite, + generateNewListId, listsChunks, savedObjectsClient, user, }: { isOverwrite: boolean; + generateNewListId: boolean; listsChunks: ImportExceptionListSchemaDecoded[][]; savedObjectsClient: SavedObjectsClientContract; user: string; @@ -56,6 +58,7 @@ export const importExceptionLists = async ({ const { errors, listItemsToDelete, listsToCreate, listsToUpdate } = sortExceptionListsToUpdateOrCreate({ existingLists: foundLists, + generateNewListId, isOverwrite, lists: listChunk, user, diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_items_to_create_or_update.test.ts b/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_items_to_create_or_update.test.ts index a4d1e3d0691ce..54029c53593b9 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_items_to_create_or_update.test.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_items_to_create_or_update.test.ts @@ -169,7 +169,7 @@ describe('sort_exception_lists_items_to_create_update', () => { }); }); - it('assigns error if matching item_id found but differing list_id', () => { + it('assigns no error if matching item_id found but differing list_id', () => { const result = sortExceptionItemsToUpdateOrCreate({ existingItems: { 'item-id-1': { @@ -185,17 +185,7 @@ describe('sort_exception_lists_items_to_create_update', () => { }); expect(result).toEqual({ - errors: [ - { - error: { - message: - 'Error trying to update item_id: "item-id-1" and list_id: "list-id-1". The item already exists under list_id: list-id-2', - status_code: 409, - }, - item_id: 'item-id-1', - list_id: 'list-id-1', - }, - ], + errors: [], itemsToCreate: [], itemsToUpdate: [], }); diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_items_to_create_update.ts b/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_items_to_create_update.ts index 9bfc1a333969e..d9c9b6702fc49 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_items_to_create_update.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_items_to_create_update.ts @@ -131,31 +131,6 @@ export const sortExceptionItemsToUpdateOrCreate = ({ type: savedObjectType, }, ]; - } else { - // If overwrite is true, the list parent container is deleted first along - // with its items, so to get here would mean the user hit a bit of an odd scenario. - // Sample scenario would be as follows: - // In system we have: - // List A ---> with item list_item_id - // Import is: - // List A ---> with item list_item_id_1 - // List B ---> with item list_item_id_1 - // If we just did an update of the item, we would overwrite - // list_item_id_1 of List A, which would be weird behavior - // What happens: - // List A and items are deleted and recreated - // List B is created, but list_item_id_1 already exists under List A and user warned - results.errors = [ - ...results.errors, - { - error: { - message: `Error trying to update item_id: "${itemId}" and list_id: "${listId}". The item already exists under list_id: ${existingItems[itemId].list_id}`, - status_code: 409, - }, - item_id: itemId, - list_id: listId, - }, - ]; } } else if (existingItems[itemId] != null) { results.errors = [ diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_lists_to_create_or_update.test.ts b/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_lists_to_create_or_update.test.ts index 0e47292e7d4e5..a5b5974772669 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_lists_to_create_or_update.test.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_lists_to_create_or_update.test.ts @@ -25,6 +25,7 @@ describe('sort_exception_lists_to_create_update', () => { it('assigns list to create if its list_id does not match an existing one', () => { const result = sortExceptionListsToUpdateOrCreate({ existingLists: {}, + generateNewListId: false, isOverwrite: false, lists: [getImportExceptionsListSchemaDecodedMock('list-id-1')], user: 'elastic', @@ -66,6 +67,7 @@ describe('sort_exception_lists_to_create_update', () => { existingLists: { 'list-id-1': { ...getExceptionListSchemaMock(), list_id: 'list-id-1' }, }, + generateNewListId: false, isOverwrite: false, lists: [getImportExceptionsListSchemaDecodedMock('list-id-1')], user: 'elastic', @@ -93,6 +95,7 @@ describe('sort_exception_lists_to_create_update', () => { it('assigns list to be created if its list_id does not match an existing one', () => { const result = sortExceptionListsToUpdateOrCreate({ existingLists: {}, + generateNewListId: false, isOverwrite: true, lists: [getImportExceptionsListSchemaDecodedMock('list-id-1')], user: 'elastic', @@ -134,6 +137,7 @@ describe('sort_exception_lists_to_create_update', () => { existingLists: { 'list-id-1': { ...getExceptionListSchemaMock(), list_id: 'list-id-1' }, }, + generateNewListId: false, isOverwrite: true, lists: [getImportExceptionsListSchemaDecodedMock('list-id-1')], user: 'elastic', diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_lists_to_create_update.ts b/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_lists_to_create_update.ts index 3c7e6217559e5..da91b04d6acf5 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_lists_to_create_update.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils/import/sort_exception_lists_to_create_update.ts @@ -21,11 +21,13 @@ export const sortExceptionListsToUpdateOrCreate = ({ lists, existingLists, isOverwrite, + generateNewListId, user, }: { lists: ImportExceptionListSchemaDecoded[]; existingLists: Record; isOverwrite: boolean; + generateNewListId: boolean; user: string; }): { errors: BulkErrorSchema[]; @@ -102,6 +104,27 @@ export const sortExceptionListsToUpdateOrCreate = ({ type: savedObjectType, }, ]; + } else if (existingLists[listId] != null && generateNewListId) { + const attributes: ExceptionListSoSchema = { + ...existingLists[listId], + comments: undefined, + created_at: dateNow, + created_by: user, + description, + entries: undefined, + immutable: false, + item_id: undefined, + list_type: 'list', + tie_breaker_id: uuid.v4(), + updated_by: user, + }; + results.listsToCreate = [ + ...results.listsToCreate, + { + attributes, + type: savedObjectType, + }, + ]; } else if (existingLists[listId] != null) { results.errors = [ ...results.errors, diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/exceptions_table.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/exceptions_table.cy.ts index 213ea64fc4ceb..4a806eae4d53d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/exceptions_table.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/exceptions_table.cy.ts @@ -20,6 +20,7 @@ import { searchForExceptionList, waitForExceptionsTableToBeLoaded, clearSearchSelection, + expandExceptionActions, } from '../../../tasks/exceptions_table'; import { EXCEPTIONS_TABLE_DELETE_BTN, @@ -67,16 +68,11 @@ describe('Exceptions Table', () => { ); visitWithoutDateRange(EXCEPTIONS_URL); - - // Using cy.contains because we do not care about the exact text, - // just checking number of lists shown - cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '3'); }); it('Exports exception list', function () { cy.intercept(/(\/api\/exception_lists\/_export)/).as('export'); - visitWithoutDateRange(EXCEPTIONS_URL); waitForExceptionsTableToBeLoaded(); exportExceptionList(); @@ -91,7 +87,6 @@ describe('Exceptions Table', () => { }); it('Filters exception lists on search', () => { - visitWithoutDateRange(EXCEPTIONS_URL); waitForExceptionsTableToBeLoaded(); // Using cy.contains because we do not care about the exact text, @@ -142,7 +137,6 @@ describe('Exceptions Table', () => { }); it('Deletes exception list without rule reference', () => { - visitWithoutDateRange(EXCEPTIONS_URL); waitForExceptionsTableToBeLoaded(); // Using cy.contains because we do not care about the exact text, @@ -189,6 +183,7 @@ describe('Exceptions Table - read only', () => { }); it('Delete icon is not shown', () => { - cy.get(EXCEPTIONS_TABLE_DELETE_BTN).should('not.exist'); + expandExceptionActions(); + cy.get(EXCEPTIONS_TABLE_DELETE_BTN).should('be.disabled'); }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/exceptions.ts b/x-pack/plugins/security_solution/cypress/screens/exceptions.ts index bf97d3e2e2039..00f06777bd111 100644 --- a/x-pack/plugins/security_solution/cypress/screens/exceptions.ts +++ b/x-pack/plugins/security_solution/cypress/screens/exceptions.ts @@ -34,7 +34,10 @@ export const ENTRY_DELETE_BTN = '[data-test-subj="builderItemEntryDeleteButton"] export const CANCEL_BTN = '[data-test-subj="cancelExceptionAddButton"]'; -export const EXCEPTIONS_TABLE = '[data-test-subj="exceptions-table"]'; +export const EXCEPTIONS_OVERFLOW_ACTIONS_BTN = + '[data-test-subj="exceptionsListCardOverflowActions"]'; + +export const EXCEPTIONS_TABLE = '[data-test-subj="pageContainer"]'; export const EXCEPTIONS_TABLE_SEARCH = '[data-test-subj="exceptionsHeaderSearchInput"]'; @@ -47,7 +50,7 @@ export const EXCEPTIONS_TABLE_EXPORT_BTN = '[data-test-subj="exceptionsTableExpo export const EXCEPTIONS_TABLE_SEARCH_CLEAR = '[data-test-subj="allExceptionListsPanel"] button.euiFormControlLayoutClearButton'; -export const EXCEPTIONS_TABLE_LIST_NAME = '[data-test-subj="exceptionsTableName"]'; +export const EXCEPTIONS_TABLE_LIST_NAME = '[data-test-subj="exception-list-name"]'; export const EXCEPTIONS_TABLE_MODAL = '[data-test-subj="referenceErrorModal"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts b/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts index f2bc0c1f7e6ed..cee6929b3887d 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts @@ -13,37 +13,45 @@ import { EXCEPTIONS_TABLE_MODAL, EXCEPTIONS_TABLE_MODAL_CONFIRM_BTN, EXCEPTIONS_TABLE_EXPORT_BTN, + EXCEPTIONS_OVERFLOW_ACTIONS_BTN, } from '../screens/exceptions'; -export const waitForExceptionsTableToBeLoaded = () => { - cy.get(EXCEPTIONS_TABLE).should('exist'); - cy.get(EXCEPTIONS_TABLE_SEARCH).should('exist'); +export const clearSearchSelection = () => { + cy.get(EXCEPTIONS_TABLE_SEARCH_CLEAR).first().click(); }; -export const searchForExceptionList = (searchText: string) => { - if (Cypress.browser.name === 'firefox') { - cy.get(EXCEPTIONS_TABLE_SEARCH).type(`${searchText}{enter}`, { force: true }); - } else { - cy.get(EXCEPTIONS_TABLE_SEARCH).type(searchText, { force: true }).trigger('search'); - } +export const expandExceptionActions = () => { + cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click(); +}; + +export const exportExceptionList = () => { + cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click(); + cy.get(EXCEPTIONS_TABLE_EXPORT_BTN).first().click(); }; export const deleteExceptionListWithoutRuleReference = () => { + cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click(); cy.get(EXCEPTIONS_TABLE_DELETE_BTN).first().click(); cy.get(EXCEPTIONS_TABLE_MODAL).should('not.exist'); }; export const deleteExceptionListWithRuleReference = () => { + cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click(); cy.get(EXCEPTIONS_TABLE_DELETE_BTN).first().click(); cy.get(EXCEPTIONS_TABLE_MODAL).should('exist'); cy.get(EXCEPTIONS_TABLE_MODAL_CONFIRM_BTN).first().click(); cy.get(EXCEPTIONS_TABLE_MODAL).should('not.exist'); }; -export const exportExceptionList = () => { - cy.get(EXCEPTIONS_TABLE_EXPORT_BTN).first().click(); +export const searchForExceptionList = (searchText: string) => { + if (Cypress.browser.name === 'firefox') { + cy.get(EXCEPTIONS_TABLE_SEARCH).type(`${searchText}{enter}`, { force: true }); + } else { + cy.get(EXCEPTIONS_TABLE_SEARCH).type(searchText, { force: true }).trigger('search'); + } }; -export const clearSearchSelection = () => { - cy.get(EXCEPTIONS_TABLE_SEARCH_CLEAR).first().click(); +export const waitForExceptionsTableToBeLoaded = () => { + cy.get(EXCEPTIONS_TABLE).should('exist'); + cy.get(EXCEPTIONS_TABLE_SEARCH).should('exist'); }; diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index 0e74f701eefdf..e7c4e3eeb3003 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -54,7 +54,7 @@ export const RULES = i18n.translate('xpack.securitySolution.navigation.rules', { }); export const EXCEPTIONS = i18n.translate('xpack.securitySolution.navigation.exceptions', { - defaultMessage: 'Exception lists', + defaultMessage: 'Rule Exceptions', }); export const ALERTS = i18n.translate('xpack.securitySolution.navigation.alerts', { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts index e0b200d94012b..84aec14891328 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts @@ -304,7 +304,7 @@ describe('Navigation Breadcrumbs', () => { expect(breadcrumbs).toEqual([ securityBreadCrumb, { - text: 'Exception lists', + text: 'Rule Exceptions', href: '', }, ]); @@ -623,7 +623,7 @@ describe('Navigation Breadcrumbs', () => { securityBreadCrumb, manageBreadcrumbs, { - text: 'Exception lists', + text: 'Rule Exceptions', href: '', }, ]); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap index ec824632e1aee..c4639b8ff8a82 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap @@ -97,7 +97,7 @@ Object { "href": "securitySolutionUI/exceptions", "id": "exceptions", "isSelected": false, - "name": "Exception lists", + "name": "Rule Exceptions", "onClick": [Function], }, ], diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/columns.tsx deleted file mode 100644 index 4b196968de008..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/columns.tsx +++ /dev/null @@ -1,175 +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 React from 'react'; -import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; - -import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; -import { DEFAULT_RELATIVE_DATE_THRESHOLD } from '../../../../../common/constants'; -import type { FormatUrl } from '../../../../common/components/link_to'; -import { PopoverItems } from '../../../../common/components/popover_items'; -import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; -import { getRuleDetailsUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; -import { LinkAnchor } from '../../../../common/components/links'; -import * as i18n from './translations'; -import type { ExceptionListInfo } from './use_all_exception_lists'; -import type { ExceptionsTableItem } from './types'; - -export type AllExceptionListsColumns = EuiBasicTableColumn; - -const RULES_TO_DISPLAY = 1; - -export const getAllExceptionListsColumns = ( - onExport: (arg: { id: string; listId: string; namespaceType: NamespaceType }) => () => void, - onDelete: (arg: { id: string; listId: string; namespaceType: NamespaceType }) => () => void, - formatUrl: FormatUrl, - navigateToUrl: (url: string) => Promise, - isKibanaReadOnly: boolean -): AllExceptionListsColumns[] => [ - { - align: 'left', - field: 'list_id', - name: i18n.EXCEPTION_LIST_ID_TITLE, - truncateText: true, - dataType: 'string', - width: '20%', - render: (value: ExceptionListInfo['list_id']) => ( - - {value} - - ), - }, - { - align: 'left', - field: 'name', - name: i18n.EXCEPTION_LIST_NAME, - truncateText: true, - dataType: 'string', - width: '20%', - render: (value: ExceptionListInfo['name']) => ( - - {value} - - ), - }, - { - field: 'rules', - name: i18n.RULES_ASSIGNED_TO_TITLE, - dataType: 'string', - width: '30%', - render: (rules: ExceptionListInfo['rules']) => { - const renderItem = ( - { id, name }: T, - index: number, - items: T[] - ) => { - const ruleHref = formatUrl(getRuleDetailsUrl(id)); - const isSeparator = index !== items.length - 1; - return ( - <> - - <> - void }) => { - ev.preventDefault(); - navigateToUrl(ruleHref); - }} - href={ruleHref} - > - {name} - {isSeparator && ','} - - - - {isSeparator && ' '} - - ); - }; - - return ( - - ); - }, - }, - { - align: 'left', - field: 'created_at', - name: i18n.LIST_DATE_CREATED_TITLE, - truncateText: true, - dataType: 'date', - width: '15%', - render: (value: ExceptionListInfo['created_at']) => ( - - ), - }, - { - align: 'left', - field: 'updated_at', - name: i18n.LIST_DATE_UPDATED_TITLE, - truncateText: true, - width: '15%', - render: (value: ExceptionListInfo['updated_at']) => ( - - ), - }, - { - align: 'left', - width: '76px', - name: i18n.EXCEPTION_LIST_ACTIONS, - actions: [ - { - render: ({ id, list_id: listId, namespace_type: namespaceType }: ExceptionListInfo) => ( - - ), - }, - { - render: ({ id, list_id: listId, namespace_type: namespaceType }: ExceptionListInfo) => { - return listId === 'endpoint_list' || isKibanaReadOnly ? ( - <> - ) : ( - - ); - }, - }, - ], - }, -]; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/types.ts deleted file mode 100644 index 316ad93ad8183..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/types.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ExceptionListInfo } from './use_all_exception_lists'; - -export interface ExceptionsTableItem extends ExceptionListInfo { - isDeleting: boolean; - isExporting: boolean; -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.test.ts index babedf3c14905..1ef07451103e6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.test.ts @@ -6,7 +6,7 @@ */ import { Query } from '@elastic/eui'; -import { EXCEPTIONS_SEARCH_SCHEMA } from '../../../rule_exceptions_ui/pages/exceptions/exceptions_search_bar'; +import { EXCEPTIONS_SEARCH_SCHEMA } from '../../../../exceptions/manage_exceptions/exceptions_search_bar'; import { caseInsensitiveSort, getSearchFilters } from './helpers'; describe('AllRulesTable Helpers', () => { diff --git a/x-pack/plugins/security_solution/public/exceptions/jest.config.js b/x-pack/plugins/security_solution/public/exceptions/jest.config.js new file mode 100644 index 0000000000000..f4b96b3f07e7a --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/jest.config.js @@ -0,0 +1,26 @@ +/* + * 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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../..', + roots: ['/x-pack/plugins/security_solution/public/exceptions'], + coverageDirectory: + '/target/kibana-coverage/jest/x-pack/plugins/security_solution/public/exceptions', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/plugins/security_solution/public/exceptions/**/*.{ts,tsx}', + ], + // See: https://github.com/elastic/kibana/issues/117255, the moduleNameMapper creates mocks to avoid memory leaks from kibana core. + moduleNameMapper: { + 'core/server$': '/x-pack/plugins/security_solution/server/__mocks__/core.mock.ts', + 'task_manager/server$': + '/x-pack/plugins/security_solution/server/__mocks__/task_manager.mock.ts', + 'alerting/server$': '/x-pack/plugins/security_solution/server/__mocks__/alert.mock.ts', + 'actions/server$': '/x-pack/plugins/security_solution/server/__mocks__/action.mock.ts', + }, +}; diff --git a/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/exceptions_list_card.tsx b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/exceptions_list_card.tsx new file mode 100644 index 0000000000000..cc2552b4f9fd7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/exceptions_list_card.tsx @@ -0,0 +1,150 @@ +/* + * 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, { memo, useState } from 'react'; + +import { + EuiLink, + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiFlexGroup, + EuiFlexItem, + EuiTextColor, + EuiPanel, + EuiPopover, + EuiText, +} from '@elastic/eui'; +import type { HttpSetup } from '@kbn/core-http-browser'; +import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; +import type { ExceptionListInfo } from './use_all_exception_lists'; +import { TitleBadge } from './title_badge'; +import * as i18n from './translations'; + +interface ExceptionsListCardProps { + exceptionsList: ExceptionListInfo; + http: HttpSetup; + handleDelete: ({ + id, + listId, + namespaceType, + }: { + id: string; + listId: string; + namespaceType: NamespaceType; + }) => () => Promise; + handleExport: ({ + id, + listId, + namespaceType, + }: { + id: string; + listId: string; + namespaceType: NamespaceType; + }) => () => Promise; + readOnly: boolean; +} + +export const ExceptionsListCard = memo( + ({ exceptionsList, http, handleDelete, handleExport, readOnly }) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const onItemActionsClick = () => setIsPopoverOpen((isOpen) => !isOpen); + const onClosePopover = () => setIsPopoverOpen(false); + + return ( + + + + + + + + + + + {exceptionsList.name.toString()} + + + + + {exceptionsList.description} + + + + + + + + + + + + + + + } + panelPaddingSize="none" + isOpen={isPopoverOpen} + closePopover={onClosePopover} + > + { + onClosePopover(); + handleDelete({ + id: exceptionsList.id, + listId: exceptionsList.list_id, + namespaceType: exceptionsList.namespace_type, + })(); + }} + > + {i18n.DELETE_EXCEPTION_LIST} + , + { + onClosePopover(); + handleExport({ + id: exceptionsList.id, + listId: exceptionsList.list_id, + namespaceType: exceptionsList.namespace_type, + })(); + }} + > + {i18n.EXPORT_EXCEPTION_LIST} + , + ]} + /> + + + + + + + ); + } +); + +ExceptionsListCard.displayName = 'ExceptionsListCard'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_search_bar.tsx b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/exceptions_search_bar.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_search_bar.tsx rename to x-pack/plugins/security_solution/public/exceptions/manage_exceptions/exceptions_search_bar.tsx index 0a348a28a0a38..bcd9e99498f58 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_search_bar.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/exceptions_search_bar.tsx @@ -9,7 +9,7 @@ import React from 'react'; import type { EuiSearchBarProps } from '@elastic/eui'; import { EuiSearchBar } from '@elastic/eui'; -import * as i18n from './translations'; +import * as i18n from './translations_exceptions_table'; interface ExceptionListsTableSearchProps { onSearch: (args: Parameters>[0]) => void; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table.test.tsx b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/exceptions_table.test.tsx similarity index 74% rename from x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table.test.tsx rename to x-pack/plugins/security_solution/public/exceptions/manage_exceptions/exceptions_table.test.tsx index c443968c14015..03196264b79c2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table.test.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/exceptions_table.test.tsx @@ -8,18 +8,18 @@ import React from 'react'; import { mount } from 'enzyme'; -import { TestProviders } from '../../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; -import { useUserData } from '../../../../detections/components/user_info'; +import { useUserData } from '../../detections/components/user_info'; import { ExceptionListsTable } from './exceptions_table'; import { useApi, useExceptionLists } from '@kbn/securitysolution-list-hooks'; import { useAllExceptionLists } from './use_all_exception_lists'; import { useHistory } from 'react-router-dom'; -import { generateHistoryMock } from '../../../../common/utils/route/mocks'; +import { generateHistoryMock } from '../../common/utils/route/mocks'; -jest.mock('../../../../detections/components/user_info'); -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../detections/components/user_info'); +jest.mock('../../common/lib/kibana'); jest.mock('./use_all_exception_lists'); jest.mock('@kbn/securitysolution-list-hooks'); jest.mock('react-router-dom', () => { @@ -39,7 +39,7 @@ jest.mock('@kbn/i18n-react', () => { }; }); -jest.mock('../../../../detections/containers/detection_engine/lists/use_lists_config', () => ({ +jest.mock('../../detections/containers/detection_engine/lists/use_lists_config', () => ({ useListsConfig: jest.fn().mockReturnValue({ loading: false }), })); @@ -90,26 +90,25 @@ describe('ExceptionListsTable', () => { ]); }); - it('does not render delete option if list is "endpoint_list"', async () => { + it('renders delete option as disabled if list is "endpoint_list"', async () => { const wrapper = mount( ); - expect(wrapper.find('[data-test-subj="exceptionsTableListId"]').at(0).text()).toEqual( - 'endpoint_list' - ); - expect(wrapper.find('[data-test-subj="exceptionsTableListId"]').at(1).text()).toEqual( - 'not_endpoint_list' - ); + wrapper + .find('[data-test-subj="exceptionsListCardOverflowActions"] button') + .at(0) + .simulate('click'); + expect(wrapper.find('[data-test-subj="exceptionsTableDeleteButton"] button')).toHaveLength(1); expect( wrapper.find('[data-test-subj="exceptionsTableDeleteButton"] button').at(0).prop('disabled') - ).toBeFalsy(); + ).toBeTruthy(); }); - it('does not render delete option if user is read only', async () => { + it('renders delete option as disabled if user is read only', async () => { (useUserData as jest.Mock).mockReturnValue([ { loading: false, @@ -123,10 +122,12 @@ describe('ExceptionListsTable', () => { ); - - expect(wrapper.find('[data-test-subj="exceptionsTableListId"]').at(1).text()).toEqual( - 'not_endpoint_list' - ); - expect(wrapper.find('[data-test-subj="exceptionsTableDeleteButton"] button')).toHaveLength(0); + wrapper + .find('[data-test-subj="exceptionsListCardOverflowActions"] button') + .at(0) + .simulate('click'); + expect( + wrapper.find('[data-test-subj="exceptionsTableDeleteButton"] button').at(0).prop('disabled') + ).toBeTruthy(); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table.tsx b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/exceptions_table.tsx similarity index 61% rename from x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table.tsx rename to x-pack/plugins/security_solution/public/exceptions/manage_exceptions/exceptions_table.tsx index b2d3d078abb54..3eb7b0bdd2889 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/exceptions_table.tsx @@ -5,11 +5,18 @@ * 2.0. */ -import React, { useMemo, useEffect, useCallback, useState } from 'react'; -import type { CriteriaWithPagination, EuiSearchBarProps } from '@elastic/eui'; +import React, { useEffect, useCallback, useState } from 'react'; +import type { EuiSearchBarProps } from '@elastic/eui'; + import { - EuiBasicTable, - EuiEmptyPrompt, + EuiButtonEmpty, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiPagination, + EuiPopover, + EuiButton, + EuiFlexGroup, + EuiFlexItem, EuiLoadingContent, EuiProgress, EuiSpacer, @@ -20,28 +27,25 @@ import { import type { NamespaceType, ExceptionListFilter } from '@kbn/securitysolution-io-ts-list-types'; import { useApi, useExceptionLists } from '@kbn/securitysolution-list-hooks'; -import { AutoDownload } from '../../../../common/components/auto_download/auto_download'; -import { useFormatUrl } from '../../../../common/components/link_to'; -import { Loader } from '../../../../common/components/loader'; -import { useKibana } from '../../../../common/lib/kibana'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { hasUserCRUDPermission } from '../../../../common/utils/privileges'; +import { AutoDownload } from '../../common/components/auto_download/auto_download'; +import { Loader } from '../../common/components/loader'; +import { useKibana } from '../../common/lib/kibana'; +import { useAppToasts } from '../../common/hooks/use_app_toasts'; -import * as i18n from './translations'; +import * as i18n from './translations_exceptions_table'; import { ExceptionsTableUtilityBar } from './exceptions_table_utility_bar'; -import type { AllExceptionListsColumns } from './columns'; -import { getAllExceptionListsColumns } from './columns'; import { useAllExceptionLists } from './use_all_exception_lists'; -import { ReferenceErrorModal } from '../../../../detections/components/value_lists_management_flyout/reference_error_modal'; -import { patchRule } from '../../../rule_management/api/api'; +import { ReferenceErrorModal } from '../../detections/components/value_lists_management_flyout/reference_error_modal'; +import { patchRule } from '../../detection_engine/rule_management/api/api'; import { ExceptionsSearchBar } from './exceptions_search_bar'; -import { getSearchFilters } from '../../../rule_management_ui/components/rules_table/helpers'; -import { SecurityPageName } from '../../../../../common/constants'; -import { useUserData } from '../../../../detections/components/user_info'; -import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; -import type { ExceptionsTableItem } from './types'; -import { MissingPrivilegesCallOut } from '../../../../detections/components/callouts/missing_privileges_callout'; -import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../../../common/endpoint/service/artifacts/constants'; +import { getSearchFilters } from '../../detection_engine/rule_management_ui/components/rules_table/helpers'; +import { useUserData } from '../../detections/components/user_info'; +import { useListsConfig } from '../../detections/containers/detection_engine/lists/use_lists_config'; +import { MissingPrivilegesCallOut } from '../../detections/components/callouts/missing_privileges_callout'; +import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../common/endpoint/service/artifacts/constants'; +import { ExceptionsListCard } from './exceptions_list_card'; + +import { ImportExceptionListFlyout } from './import_exceptions_list_flyout'; export type Func = () => Promise; @@ -62,15 +66,13 @@ const exceptionReferenceModalInitialState: ReferenceModalState = { }; export const ExceptionListsTable = React.memo(() => { - const { formatUrl } = useFormatUrl(SecurityPageName.rules); const [{ loading: userInfoLoading, canUserCRUD, canUserREAD }] = useUserData(); - const hasPermissions = hasUserCRUDPermission(canUserCRUD); const { loading: listsConfigLoading } = useListsConfig(); const loading = userInfoLoading || listsConfigLoading; const { - services: { http, notifications, timelines, application }, + services: { http, notifications, timelines }, } = useKibana(); const { exportExceptionList, deleteExceptionList } = useApi(http); @@ -91,12 +93,12 @@ export const ExceptionListsTable = React.memo(() => { const [loadingTableInfo, exceptionListsWithRuleRefs, exceptionsListsRef] = useAllExceptionLists({ exceptionLists: exceptions ?? [], }); + const [initLoading, setInitLoading] = useState(true); const [lastUpdated, setLastUpdated] = useState(Date.now()); - const [deletingListIds, setDeletingListIds] = useState([]); - const [exportingListIds, setExportingListIds] = useState([]); + const [exportDownload, setExportDownload] = useState<{ name?: string; blob?: Blob }>({}); - const { navigateToUrl } = application; + const [displayImportListFlyout, setDisplayImportListFlyout] = useState(false); const { addError, addSuccess } = useAppToasts(); const handleDeleteSuccess = useCallback( @@ -121,11 +123,6 @@ export const ExceptionListsTable = React.memo(() => { ({ id, listId, namespaceType }: { id: string; listId: string; namespaceType: NamespaceType }) => async () => { try { - setDeletingListIds((ids) => [...ids, id]); - if (refreshExceptions != null) { - refreshExceptions(); - } - if (exceptionsListsRef[id] != null && exceptionsListsRef[id].rules.length === 0) { await deleteExceptionList({ id, @@ -150,8 +147,6 @@ export const ExceptionListsTable = React.memo(() => { // route to patch rules with associated exception list } catch (error) { handleDeleteError(error); - } finally { - setDeletingListIds((ids) => ids.filter((_id) => _id !== id)); } }, [ @@ -182,7 +177,6 @@ export const ExceptionListsTable = React.memo(() => { const handleExport = useCallback( ({ id, listId, namespaceType }: { id: string; listId: string; namespaceType: NamespaceType }) => async () => { - setExportingListIds((ids) => [...ids, id]); await exportExceptionList({ id, listId, @@ -194,18 +188,6 @@ export const ExceptionListsTable = React.memo(() => { [exportExceptionList, handleExportError, handleExportSuccess] ); - const exceptionsColumns = useMemo((): AllExceptionListsColumns[] => { - // Defaulting to true to default to the lower privilege first - const isKibanaReadOnly = (canUserREAD && !canUserCRUD) ?? true; - return getAllExceptionListsColumns( - handleExport, - handleDelete, - formatUrl, - navigateToUrl, - isKibanaReadOnly - ); - }, [handleExport, handleDelete, formatUrl, navigateToUrl, canUserREAD, canUserCRUD]); - const handleRefresh = useCallback((): void => { if (refreshExceptions != null) { setLastUpdated(Date.now()); @@ -219,16 +201,6 @@ export const ExceptionListsTable = React.memo(() => { } }, [initLoading, loading, loadingExceptions, loadingTableInfo]); - const emptyPrompt = useMemo((): JSX.Element => { - return ( - {i18n.NO_EXCEPTION_LISTS}} - titleSize="xs" - body={i18n.NO_LISTS_BODY} - /> - ); - }, []); - const handleSearch = useCallback( async ({ query, @@ -253,7 +225,6 @@ export const ExceptionListsTable = React.memo(() => { ); const handleCloseReferenceErrorModal = useCallback((): void => { - setDeletingListIds([]); setShowReferenceErrorModal(false); setReferenceModalState({ contentText: '', @@ -297,7 +268,6 @@ export const ExceptionListsTable = React.memo(() => { handleDeleteError(err); } finally { setReferenceModalState(exceptionReferenceModalInitialState); - setDeletingListIds([]); setShowReferenceErrorModal(false); if (refreshExceptions != null) { refreshExceptions(); @@ -313,51 +283,108 @@ export const ExceptionListsTable = React.memo(() => { refreshExceptions, ]); - const paginationMemo = useMemo( - () => ({ - pageIndex: pagination.page - 1, - pageSize: pagination.perPage, - totalItemCount: pagination.total || 0, - pageSizeOptions: [5, 10, 20, 50, 100, 200, 300], - }), - [pagination] - ); - const handleOnDownload = useCallback(() => { setExportDownload({}); }, []); - const tableItems = useMemo( - () => - (exceptionListsWithRuleRefs ?? []).map((item) => ({ - ...item, - isDeleting: deletingListIds.includes(item.id), - isExporting: exportingListIds.includes(item.id), - })), - [deletingListIds, exceptionListsWithRuleRefs, exportingListIds] + const [activePage, setActivePage] = useState(0); + const [rowSize, setRowSize] = useState(5); + const [isRowSizePopoverOpen, setIsRowSizePopoverOpen] = useState(false); + const onRowSizeButtonClick = () => setIsRowSizePopoverOpen((val) => !val); + const closeRowSizePopover = () => setIsRowSizePopoverOpen(false); + + const rowSizeButton = ( + + {`Rows per page: ${rowSize}`} + ); - const handlePaginationChange = useCallback( - (criteria: CriteriaWithPagination) => { - const { index, size } = criteria.page; - setPagination((currentPagination) => ({ - ...currentPagination, - perPage: size, - page: index + 1, - })); - }, - [setPagination] - ); + const getIconType = (size: number) => { + return size === rowSize ? 'check' : 'empty'; + }; + + const rowSizeItems = [ + { + closeRowSizePopover(); + setRowSize(5); + }} + > + {'5 rows'} + , + { + closeRowSizePopover(); + setRowSize(10); + }} + > + {'10 rows'} + , + { + closeRowSizePopover(); + setRowSize(25); + }} + > + {'25 rows'} + , + ]; + + useEffect(() => { + setPagination({ + // off-by-one error + // we should really update the api to be zero-index based + // the same way the pagination component in EUI is zero based. + page: activePage + 1, + perPage: rowSize, + total: 0, + }); + }, [activePage, rowSize, setPagination]); + + const goToPage = (pageNumber: number) => setActivePage(pageNumber); return ( <> - {timelines.getLastUpdated({ showUpdating: loading, updatedAt: lastUpdated })}

        - } - /> + + + + + + setDisplayImportListFlyout(true)}> + {i18n.IMPORT_EXCEPTION_LIST} + + + + + {displayImportListFlyout && ( + + )} +
        {loadingTableInfo && ( @@ -375,7 +402,7 @@ export const ExceptionListsTable = React.memo(() => { )} - {initLoading ? ( + {initLoading || loadingTableInfo ? ( ) : ( <> @@ -383,18 +410,51 @@ export const ExceptionListsTable = React.memo(() => { totalExceptionLists={exceptionListsWithRuleRefs.length} onRefresh={handleRefresh} /> - - data-test-subj="exceptions-table" - columns={exceptionsColumns} - isSelectable={hasPermissions} - itemId="id" - items={tableItems} - noItemsMessage={emptyPrompt} - onChange={handlePaginationChange} - pagination={paginationMemo} - /> + + {exceptionListsWithRuleRefs.length > 0 && canUserCRUD !== null && canUserREAD !== null && ( + + {exceptionListsWithRuleRefs.map((excList) => ( + + ))} + + )} )} + + + + + + + + + + + + + + + + + + void; diff --git a/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/import_exceptions_list_flyout.tsx b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/import_exceptions_list_flyout.tsx new file mode 100644 index 0000000000000..ab19d153181b7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/import_exceptions_list_flyout.tsx @@ -0,0 +1,201 @@ +/* + * 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 { SetStateAction, Dispatch } from 'react'; +import React, { useEffect, useRef, useCallback, useState } from 'react'; + +import { + useGeneratedHtmlId, + EuiButton, + EuiButtonEmpty, + EuiCheckbox, + EuiFilePicker, + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiSpacer, + EuiText, + EuiTitle, + EuiFlyoutFooter, + EuiTextColor, + EuiFlyout, +} from '@elastic/eui'; +import type { + BulkErrorSchema, + ImportExceptionsResponseSchema, +} from '@kbn/securitysolution-io-ts-list-types'; +import type { HttpSetup } from '@kbn/core-http-browser'; +import type { ToastInput, Toast, ErrorToastOptions } from '@kbn/core-notifications-browser'; + +import { useImportExceptionList } from './use_import_exception_list'; + +import * as i18n from './translations'; + +export const ImportExceptionListFlyout = React.memo( + ({ + handleRefresh, + http, + addSuccess, + addError, + setDisplayImportListFlyout, + }: { + handleRefresh: () => void; + http: HttpSetup; + addSuccess: (toastOrTitle: ToastInput, options?: unknown) => Toast; + addError: (error: unknown, options: ErrorToastOptions) => Toast; + setDisplayImportListFlyout: Dispatch>; + }) => { + const filePickerRef = useRef(null); + + const filePickerId = useGeneratedHtmlId({ prefix: 'filePicker' }); + const [file, setFile] = useState(null); + const [overwrite, setOverwrite] = useState(false); + const [asNewList, setAsNewList] = useState(false); + + const resetForm = useCallback(() => { + if (filePickerRef.current?.fileInput) { + filePickerRef.current.fileInput.value = ''; + filePickerRef.current.handleChange(); + } + setFile(null); + setAlreadyExistingItem(false); + setAsNewList(false); + setOverwrite(false); + }, []); + const { start: importExceptionList, ...importExceptionListState } = useImportExceptionList(); + const ctrl = useRef(new AbortController()); + + const handleImportExceptionList = useCallback(() => { + if (!importExceptionListState.loading && file) { + ctrl.current = new AbortController(); + + importExceptionList({ + file, + http, + signal: ctrl.current.signal, + overwrite, + overwriteExceptions: overwrite, + asNewList, + }); + } + }, [asNewList, file, http, importExceptionList, importExceptionListState.loading, overwrite]); + + const handleImportSuccess = useCallback( + (response: ImportExceptionsResponseSchema) => { + resetForm(); + addSuccess({ + text: i18n.uploadSuccessMessage(file?.name ?? ''), + title: i18n.UPLOAD_SUCCESS_TITLE, + }); + handleRefresh(); + }, + // looking for file.name but we don't wan't to render success every time file name changes. + // eslint-disable-next-line react-hooks/exhaustive-deps + [resetForm, addSuccess, handleRefresh] + ); + const handleImportError = useCallback( + (errors: BulkErrorSchema[]) => { + errors.forEach((error) => { + if (!error.error.message.includes('AbortError')) { + addError(error.error.message, { title: i18n.UPLOAD_ERROR }); + } + }); + }, + [addError] + ); + const [alreadyExistingItem, setAlreadyExistingItem] = useState(false); + + useEffect(() => { + if (!importExceptionListState.loading) { + if (importExceptionListState?.result?.success) { + handleImportSuccess(importExceptionListState?.result); + } else if (importExceptionListState?.result?.errors) { + handleImportError(importExceptionListState?.result?.errors); + } + } + }, [ + handleImportError, + handleImportSuccess, + importExceptionListState.error, + importExceptionListState.loading, + importExceptionListState.result, + setAlreadyExistingItem, + ]); + const handleFileChange = useCallback((files: FileList | null) => { + setFile(files?.item(0) ?? null); + }, []); + return ( + setDisplayImportListFlyout(false)}> + + +

        {i18n.IMPORT_EXCEPTION_LIST_HEADER}

        +
        +
        + + {i18n.IMPORT_EXCEPTION_LIST_BODY} + + + {alreadyExistingItem && ( + <> + + {i18n.IMPORT_EXCEPTION_LIST_WARNING} + + { + setOverwrite(!overwrite); + setAsNewList(false); + }} + /> + { + setAsNewList(!asNewList); + setOverwrite(false); + }} + /> + + )} + + + + + setDisplayImportListFlyout(false)} + flush="left" + > + {i18n.CLOSE_FLYOUT} + + + + + {i18n.UPLOAD_BUTTON} + + + + +
        + ); + } +); diff --git a/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/title_badge.tsx b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/title_badge.tsx new file mode 100644 index 0000000000000..a4572cc65788b --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/title_badge.tsx @@ -0,0 +1,37 @@ +/* + * 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, { memo } from 'react'; +import styled from 'styled-components'; + +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; + +interface TitleBadgeProps { + title: string; + badgeString: string; +} + +const StyledFlexItem = styled(EuiFlexItem)` + border-right: 1px solid #d3dae6; + padding: 4px 12px 4px 0; +`; +export const TitleBadge = memo(({ title, badgeString }) => { + return ( + + + + {title} + + + {badgeString}{' '} + + + + ); +}); + +TitleBadge.displayName = 'TitleBadge'; diff --git a/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/translations.ts b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/translations.ts new file mode 100644 index 0000000000000..ada66d4e9b602 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/translations.ts @@ -0,0 +1,106 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const uploadSuccessMessage = (fileName: string) => + i18n.translate('xpack.securitySolution.lists.exceptionListImportSuccess', { + defaultMessage: "Exception list '{fileName}' was imported", + values: { fileName }, + }); + +export const CREATED_BY = i18n.translate('xpack.securitySolution.exceptionsTable.createdBy', { + defaultMessage: 'Created By', +}); + +export const CREATED_AT = i18n.translate('xpack.securitySolution.exceptionsTable.createdAt', { + defaultMessage: 'Created At', +}); + +export const DELETE_EXCEPTION_LIST = i18n.translate( + 'xpack.securitySolution.exceptionsTable.deleteExceptionList', + { + defaultMessage: 'Delete Exception List', + } +); + +export const EXPORT_EXCEPTION_LIST = i18n.translate( + 'xpack.securitySolution.exceptionsTable.exportExceptionList', + { + defaultMessage: 'Export Exception List', + } +); + +export const IMPORT_EXCEPTION_LIST_HEADER = i18n.translate( + 'xpack.securitySolution.exceptionsTable.importExceptionListFlyoutHeader', + { + defaultMessage: 'Import shared exception list', + } +); + +export const IMPORT_EXCEPTION_LIST_BODY = i18n.translate( + 'xpack.securitySolution.exceptionsTable.importExceptionListFlyoutBody', + { + defaultMessage: 'Select shared exception lists to import', + } +); + +export const IMPORT_EXCEPTION_LIST_WARNING = i18n.translate( + 'xpack.securitySolution.exceptionsTable.importExceptionListWarning', + { + defaultMessage: 'We found a pre-existing list with that id', + } +); + +export const IMPORT_EXCEPTION_LIST_OVERWRITE = i18n.translate( + 'xpack.securitySolution.exceptionsTable.importExceptionListOverwrite', + { + defaultMessage: 'Overwrite the existing list', + } +); + +export const IMPORT_EXCEPTION_LIST_AS_NEW_LIST = i18n.translate( + 'xpack.securitySolution.exceptionsTable.importExceptionListAsNewList', + { + defaultMessage: 'Create new list', + } +); + +export const UPLOAD_SUCCESS_TITLE = i18n.translate( + 'xpack.securitySolution.lists.exceptionListImportSuccessTitle', + { + defaultMessage: 'Exception list imported', + } +); + +export const UPLOAD_ERROR = i18n.translate( + 'xpack.securitySolution.lists.exceptionListUploadError', + { + defaultMessage: 'There was an error uploading the exception list.', + } +); + +export const UPLOAD_BUTTON = i18n.translate( + 'xpack.securitySolution.exceptions.exceptionListsImportButton', + { + defaultMessage: 'Import list', + } +); + +export const CLOSE_FLYOUT = i18n.translate( + 'xpack.securitySolution.exceptions.exceptionListsCloseImportFlyout', + { + defaultMessage: 'Close', + } +); + +export const IMPORT_PROMPT = i18n.translate( + 'xpack.securitySolution.exceptions.exceptionListsFilePickerPrompt', + { + defaultMessage: 'Select or drag and drop multiple files', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/translations.ts b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/translations_exceptions_table.ts similarity index 84% rename from x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/translations.ts rename to x-pack/plugins/security_solution/public/exceptions/manage_exceptions/translations_exceptions_table.ts index 3d817adb2605c..a862a25b2d773 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/translations.ts +++ b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/translations_exceptions_table.ts @@ -88,7 +88,7 @@ export const EXCEPTIONS_LISTS_SEARCH_PLACEHOLDER = i18n.translate( export const ALL_EXCEPTIONS = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.allExceptions.tableTitle', { - defaultMessage: 'Exception lists', + defaultMessage: 'Rule Exceptions', } ); @@ -167,3 +167,37 @@ export const REFRESH_EXCEPTIONS_TABLE = i18n.translate( defaultMessage: 'Refresh', } ); + +export const UPLOAD_BUTTON = i18n.translate( + 'xpack.securitySolution.exceptions.exceptionListsImportButton', + { + defaultMessage: 'Import list', + } +); + +export const uploadSuccessMessage = (fileName: string) => + i18n.translate('xpack.securitySolution.lists.exceptionListImportSuccess', { + defaultMessage: "Exception list '{fileName}' was imported", + values: { fileName }, + }); + +export const UPLOAD_SUCCESS_TITLE = i18n.translate( + 'xpack.securitySolution.lists.exceptionListImportSuccessTitle', + { + defaultMessage: 'Exception list imported', + } +); + +export const UPLOAD_ERROR = i18n.translate( + 'xpack.securitySolution.lists.exceptionListUploadError', + { + defaultMessage: 'There was an error uploading the exception list.', + } +); + +export const IMPORT_EXCEPTION_LIST = i18n.translate( + 'xpack.securitySolution.lists.importExceptionListButton', + { + defaultMessage: 'Import exception list', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/use_all_exception_lists.tsx b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/use_all_exception_lists.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/use_all_exception_lists.tsx rename to x-pack/plugins/security_solution/public/exceptions/manage_exceptions/use_all_exception_lists.tsx index cd77e72722132..688ad352a147f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/use_all_exception_lists.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/use_all_exception_lists.tsx @@ -8,8 +8,8 @@ import { useCallback, useEffect, useState } from 'react'; import type { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; -import type { Rule } from '../../../rule_management/logic'; -import { fetchRules } from '../../../rule_management/api/api'; +import type { Rule } from '../../detection_engine/rule_management/logic'; +import { fetchRules } from '../../detection_engine/rule_management/api/api'; export interface ExceptionListInfo extends ExceptionListSchema { rules: Rule[]; } diff --git a/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/use_import_exception_list.tsx b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/use_import_exception_list.tsx new file mode 100644 index 0000000000000..ca766c102efd3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/manage_exceptions/use_import_exception_list.tsx @@ -0,0 +1,48 @@ +/* + * 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 { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; + +import type { ImportExceptionsResponseSchema } from '@kbn/securitysolution-io-ts-list-types'; + +import type { HttpStart } from '@kbn/core/public'; +import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; + +export const importExceptionList = async ({ + file, + http, + signal, + overwrite, + overwriteExceptions, + asNewList, +}: { + // TODO: Replace these with kbn packaged versions once we have those available to us + // These originally came from this location below before moving them to this hacked "any" types: + // import { HttpStart, NotificationsStart } from '../../../../../src/core/public'; + http: HttpStart; + signal: AbortSignal; + file: File; + overwrite: boolean; + overwriteExceptions: boolean; + asNewList: boolean; +}): Promise => { + const formData = new FormData(); + formData.append('file', file as Blob); + + const res = await http.post(`${EXCEPTION_LIST_URL}/_import`, { + body: formData, + query: { overwrite, overwrite_exceptions: overwriteExceptions, as_new_list: asNewList }, + headers: { 'Content-Type': undefined }, + method: 'POST', + signal, + }); + return res; +}; + +const importListWithOptionalSignal = withOptionalSignal(importExceptionList); + +export const useImportExceptionList = () => useAsync(importListWithOptionalSignal); diff --git a/x-pack/plugins/security_solution/public/exceptions/routes.tsx b/x-pack/plugins/security_solution/public/exceptions/routes.tsx index b977a98722444..367474aa12ae7 100644 --- a/x-pack/plugins/security_solution/public/exceptions/routes.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/routes.tsx @@ -11,7 +11,7 @@ import { Route } from '@kbn/kibana-react-plugin/public'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import * as i18n from './translations'; import { EXCEPTIONS_PATH, SecurityPageName } from '../../common/constants'; -import { ExceptionListsTable } from '../detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table'; +import { ExceptionListsTable } from './manage_exceptions/exceptions_table'; import { SpyRoute } from '../common/utils/route/spy_routes'; import { NotFoundPage } from '../app/404'; import { useReadonlyHeader } from '../use_readonly_header'; From 6412057399114ef647364ef79c24b3853f58dbdf Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:02:47 +0100 Subject: [PATCH 29/86] adding missing action types to Agent activity UI (#144437) --- .../components/agent_activity_flyout.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx index c3b5605ecd484..d40948f2323c6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx @@ -259,6 +259,16 @@ const actionNames: { completedText: 'updated settings', cancelledText: 'update settings', }, + POLICY_CHANGE: { + inProgressText: 'Changing policy of', + completedText: 'changed policy', + cancelledText: 'change policy', + }, + INPUT_ACTION: { + inProgressText: 'Input action in progress of', + completedText: 'input action completed', + cancelledText: 'input action', + }, ACTION: { inProgressText: 'Actioning', completedText: 'actioned', cancelledText: 'action' }, }; From 8ea2f3b7157ac1610a8253fb8d108b5e7050e7e6 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 2 Nov 2022 10:04:21 -0600 Subject: [PATCH 30/86] [Maps] add in product help for layer group drag and drop instructions (#144259) * [Maps] add in product help for layer group drag and drop instructions * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * render callout above panel * review feedback on copy * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Update x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/layer_settings.tsx Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/layer_settings.tsx Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * Update x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/layer_settings.tsx Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * update title Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> --- .../layer_settings/layer_settings.tsx | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/layer_settings.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/layer_settings.tsx index 6d63fc08ef85e..162f43f0911a6 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/layer_settings.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/layer_settings.tsx @@ -7,6 +7,8 @@ import React, { ChangeEvent, Fragment } from 'react'; import { + EuiCallOut, + EuiText, EuiTitle, EuiPanel, EuiFormRow, @@ -242,8 +244,44 @@ export function LayerSettings(props: Props) { ); }; + const renderLayerGroupInstructions = () => { + return isLayerGroup(props.layer) ? ( + <> + + +
          +
        • + {i18n.translate('xpack.maps.layerPanel.settingsPanel.layerGroupAddToFront', { + defaultMessage: 'To add your first layer, drag it onto the group name.', + })} +
        • +
        • + {i18n.translate('xpack.maps.layerPanel.settingsPanel.layerGroupAddToPosition', { + defaultMessage: + 'To add another layer, drag it anywhere above the last layer in the group.', + })} +
        • +
        • + {i18n.translate('xpack.maps.layerPanel.settingsPanel.layerGroupRemove', { + defaultMessage: 'To remove a layer, drag it above or below the group.', + })} +
        • +
        +
        +
        + + + ) : null; + }; + return ( + {renderLayerGroupInstructions()}
        From 02a2a6a5004f82385f2f459d17e1e1a4f778b38b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:32:34 +0100 Subject: [PATCH 31/86] [Guided onboarding] Fix multiple API requests when fetching guide state (#144160) * [Guided onboarding] Fix multiple API requests when fetching guide state * [Guided onboarding] Skip the Observability tour test if not on Cloud * [Guided onboarding] Fix the logic for the loading const when the request fails and when the subscription unsubscribes * [Guided onboarding] Fix the logic when a subsequent subscription was not getting the new state value * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * [Guided onboarding] Fix requests in a loop when there is no state * [Guided onboarding] Fix the completed state not being broadcast * [Guided onboarding] Fix types error Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-guided-onboarding/index.ts | 9 +- .../public/components/guide_panel.test.tsx | 17 +- .../public/components/guide_panel.tsx | 2 +- .../public/services/api.mocks.ts | 18 ++ .../public/services/api.test.ts | 95 +++++-- .../guided_onboarding/public/services/api.ts | 57 ++-- x-pack/test/functional/apps/infra/tour.ts | 257 +++++++++--------- 7 files changed, 283 insertions(+), 172 deletions(-) diff --git a/packages/kbn-guided-onboarding/index.ts b/packages/kbn-guided-onboarding/index.ts index f98f330cd4be3..b22a8a2057c16 100644 --- a/packages/kbn-guided-onboarding/index.ts +++ b/packages/kbn-guided-onboarding/index.ts @@ -6,6 +6,13 @@ * Side Public License, v 1. */ -export type { GuideState, GuideId, GuideStepIds, StepStatus, GuideStep } from './src/types'; +export type { + GuideState, + GuideId, + GuideStepIds, + StepStatus, + GuideStep, + GuideStatus, +} from './src/types'; export { GuideCard, ObservabilityLinkCard } from './src/components/landing_page'; export type { UseCase } from './src/components/landing_page'; diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx index 9420e05e9d3fe..9be6326da1d06 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx @@ -109,10 +109,21 @@ describe('Guided setup', () => { }); describe('Button component', () => { - // TODO check for the correct button behavior once https://github.com/elastic/kibana/issues/141129 is implemented - test.skip('should be disabled in there is no active guide', async () => { + test('should be hidden in there is no guide state', async () => { const { exists } = testBed; - expect(exists('disabledGuideButton')).toBe(true); + expect(exists('guideButton')).toBe(false); + expect(exists('guidePanel')).toBe(false); + }); + + test('should be hidden if the guide is not active', async () => { + const { component, exists } = testBed; + + await updateComponentWithState( + component, + { ...mockActiveSearchGuideState, isActive: false }, + true + ); + expect(exists('guideButton')).toBe(false); expect(exists('guidePanel')).toBe(false); }); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx index 759f4a83c6852..823637b23247a 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx @@ -139,7 +139,7 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { // TODO handle loading, error state // https://github.com/elastic/kibana/issues/139799, https://github.com/elastic/kibana/issues/139798 - if (!guideConfig) { + if (!guideConfig || !guideState || !guideState.isActive) { // TODO button show/hide logic https://github.com/elastic/kibana/issues/141129 return null; } diff --git a/src/plugins/guided_onboarding/public/services/api.mocks.ts b/src/plugins/guided_onboarding/public/services/api.mocks.ts index 2294607f91b38..47b7f6c9900e0 100644 --- a/src/plugins/guided_onboarding/public/services/api.mocks.ts +++ b/src/plugins/guided_onboarding/public/services/api.mocks.ts @@ -77,6 +77,24 @@ export const testGuideStep2InProgressState: GuideState = { ], }; +export const readyToCompleteGuideState: GuideState = { + ...testGuideStep1ActiveState, + steps: [ + { + ...testGuideStep1ActiveState.steps[0], + status: 'complete', + }, + { + ...testGuideStep1ActiveState.steps[1], + status: 'complete', + }, + { + ...testGuideStep1ActiveState.steps[2], + status: 'complete', + }, + ], +}; + export const testGuideNotActiveState: GuideState = { ...testGuideStep1ActiveState, isActive: false, diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts index 56a5755f0ee55..24ade7de1a849 100644 --- a/src/plugins/guided_onboarding/public/services/api.test.ts +++ b/src/plugins/guided_onboarding/public/services/api.test.ts @@ -11,6 +11,7 @@ import { httpServiceMock } from '@kbn/core/public/mocks'; import type { GuideState } from '@kbn/guided-onboarding'; import { firstValueFrom, Subscription } from 'rxjs'; +import { GuideStatus } from '@kbn/guided-onboarding'; import { API_BASE_PATH } from '../../common/constants'; import { ApiService } from './api'; import { @@ -24,12 +25,14 @@ import { testIntegration, wrongIntegration, testGuideStep2InProgressState, + readyToCompleteGuideState, } from './api.mocks'; describe('GuidedOnboarding ApiService', () => { let httpClient: jest.Mocked; let apiService: ApiService; let subscription: Subscription; + let anotherSubscription: Subscription; beforeEach(() => { httpClient = httpServiceMock.createStartContract({ basePath: '/base/path' }); @@ -41,9 +44,8 @@ describe('GuidedOnboarding ApiService', () => { }); afterEach(() => { - if (subscription) { - subscription.unsubscribe(); - } + subscription?.unsubscribe(); + anotherSubscription?.unsubscribe(); jest.restoreAllMocks(); }); @@ -53,6 +55,64 @@ describe('GuidedOnboarding ApiService', () => { expect(httpClient.get).toHaveBeenCalledTimes(1); expect(httpClient.get).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { query: { active: true }, + signal: new AbortController().signal, + }); + }); + + it(`doesn't send multiple requests when there are several subscriptions`, () => { + subscription = apiService.fetchActiveGuideState$().subscribe(); + anotherSubscription = apiService.fetchActiveGuideState$().subscribe(); + expect(httpClient.get).toHaveBeenCalledTimes(1); + }); + + it(`re-sends the request if the previous one failed`, async () => { + httpClient.get.mockRejectedValueOnce(new Error('request failed')); + subscription = apiService.fetchActiveGuideState$().subscribe(); + // wait until the request fails + await new Promise((resolve) => process.nextTick(resolve)); + anotherSubscription = apiService.fetchActiveGuideState$().subscribe(); + expect(httpClient.get).toHaveBeenCalledTimes(2); + }); + + it(`re-sends the request if there is no guide state and there is another subscription`, async () => { + httpClient.get.mockResolvedValueOnce({ + state: [], + }); + subscription = apiService.fetchActiveGuideState$().subscribe(); + // wait until the request completes + await new Promise((resolve) => process.nextTick(resolve)); + anotherSubscription = apiService.fetchActiveGuideState$().subscribe(); + expect(httpClient.get).toHaveBeenCalledTimes(2); + }); + + it(`doesn't send multiple requests in a loop when there is no state`, async () => { + httpClient.get.mockResolvedValueOnce({ + state: [], + }); + subscription = apiService.fetchActiveGuideState$().subscribe(); + // wait until the request completes + await new Promise((resolve) => process.nextTick(resolve)); + expect(httpClient.get).toHaveBeenCalledTimes(1); + }); + + it(`re-sends the request if the subscription was unsubscribed before the request completed`, async () => { + httpClient.get.mockImplementationOnce(() => { + return new Promise((resolve) => setTimeout(resolve)); + }); + // subscribe and immediately unsubscribe + apiService.fetchActiveGuideState$().subscribe().unsubscribe(); + anotherSubscription = apiService.fetchActiveGuideState$().subscribe(); + expect(httpClient.get).toHaveBeenCalledTimes(2); + }); + + it(`the second subscription gets the state broadcast to it`, (done) => { + // first subscription + apiService.fetchActiveGuideState$().subscribe(); + // second subscription + anotherSubscription = apiService.fetchActiveGuideState$().subscribe((state) => { + if (state) { + done(); + } }); }); @@ -95,6 +155,17 @@ describe('GuidedOnboarding ApiService', () => { body: JSON.stringify(updatedState), }); }); + + it('the completed state is being broadcast after the update', async () => { + const completedState = { + ...readyToCompleteGuideState, + isActive: false, + status: 'complete' as GuideStatus, + }; + await apiService.updateGuideState(completedState, false); + const state = await firstValueFrom(apiService.fetchActiveGuideState$()); + expect(state).toMatchObject(completedState); + }); }); describe('isGuideStepActive$', () => { @@ -149,24 +220,6 @@ describe('GuidedOnboarding ApiService', () => { }); describe('completeGuide', () => { - const readyToCompleteGuideState: GuideState = { - ...testGuideStep1ActiveState, - steps: [ - { - ...testGuideStep1ActiveState.steps[0], - status: 'complete', - }, - { - ...testGuideStep1ActiveState.steps[1], - status: 'complete', - }, - { - ...testGuideStep1ActiveState.steps[2], - status: 'complete', - }, - ], - }; - beforeEach(async () => { await apiService.updateGuideState(readyToCompleteGuideState, false); }); diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index cd33f9505c546..89790024cdde7 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -7,7 +7,7 @@ */ import { HttpSetup } from '@kbn/core/public'; -import { BehaviorSubject, map, from, concatMap, of, Observable, firstValueFrom } from 'rxjs'; +import { BehaviorSubject, map, concatMap, of, Observable, firstValueFrom } from 'rxjs'; import type { GuideState, GuideId, GuideStep, GuideStepIds } from '@kbn/guided-onboarding'; import { GuidedOnboardingApi } from '../types'; @@ -26,6 +26,7 @@ import { API_BASE_PATH } from '../../common/constants'; export class ApiService implements GuidedOnboardingApi { private client: HttpSetup | undefined; private onboardingGuideState$!: BehaviorSubject; + private isGuideStateLoading: boolean | undefined; public isGuidePanelOpen$: BehaviorSubject = new BehaviorSubject(false); public setup(httpClient: HttpSetup): void { @@ -33,30 +34,46 @@ export class ApiService implements GuidedOnboardingApi { this.onboardingGuideState$ = new BehaviorSubject(undefined); } + private createGetStateObservable(): Observable { + return new Observable((observer) => { + const controller = new AbortController(); + const signal = controller.signal; + this.isGuideStateLoading = true; + this.client!.get<{ state: GuideState[] }>(`${API_BASE_PATH}/state`, { + query: { + active: true, + }, + signal, + }) + .then((response) => { + this.isGuideStateLoading = false; + // There should only be 1 active guide + const hasState = response.state.length === 1; + if (hasState) { + this.onboardingGuideState$.next(response.state[0]); + } + observer.complete(); + }) + .catch((error) => { + this.isGuideStateLoading = false; + observer.error(error); + }); + return () => { + this.isGuideStateLoading = false; + controller.abort(); + }; + }); + } + /** * An Observable with the active guide state. * Initially the state is fetched from the backend. * Subsequently, the observable is updated automatically, when the state changes. */ public fetchActiveGuideState$(): Observable { - // TODO add error handling if this.client has not been initialized or request fails return this.onboardingGuideState$.pipe( concatMap((state) => - state === undefined - ? from( - this.client!.get<{ state: GuideState[] }>(`${API_BASE_PATH}/state`, { - query: { - active: true, - }, - }) - ).pipe( - map((response) => { - // There should only be 1 active guide - const hasState = response.state.length === 1; - return hasState ? response.state[0] : undefined; - }) - ) - : of(state) + !state && !this.isGuideStateLoading ? this.createGetStateObservable() : of(state) ) ); } @@ -83,7 +100,7 @@ export class ApiService implements GuidedOnboardingApi { /** * Updates the SO with the updated guide state and refreshes the observables * This is largely used internally and for tests - * @param {GuideState} guideState the updated guide state + * @param {GuideState} newState the updated guide state * @param {boolean} panelState boolean to determine whether the dropdown panel should open or not * @return {Promise} a promise with the updated guide state */ @@ -99,8 +116,8 @@ export class ApiService implements GuidedOnboardingApi { const response = await this.client.put<{ state: GuideState }>(`${API_BASE_PATH}/state`, { body: JSON.stringify(newState), }); - // If the guide has been deactivated, we return undefined - this.onboardingGuideState$.next(newState.isActive ? newState : undefined); + // broadcast the newState + this.onboardingGuideState$.next(newState); this.isGuidePanelOpen$.next(panelState); return response; } catch (error) { diff --git a/x-pack/test/functional/apps/infra/tour.ts b/x-pack/test/functional/apps/infra/tour.ts index 81ca07eef4c05..2f9dfa314c168 100644 --- a/x-pack/test/functional/apps/infra/tour.ts +++ b/x-pack/test/functional/apps/infra/tour.ts @@ -15,6 +15,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'infraHome']); const find = getService('find'); const supertest = getService('supertest'); + const deployment = getService('deployment'); const setInitialTourState = async (activeStep?: number) => { await browser.setLocalStorageItem(observTourStepStorageKey, String(activeStep || 1)); @@ -23,8 +24,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Onboarding Observability tour', function () { this.tags('includeFirefox'); - + let isCloud: boolean; before(async () => { + isCloud = await deployment.isCloud(); await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); await pageObjects.common.navigateToApp('observability'); // Need to increase the browser height so the tour steps fit to screen @@ -37,131 +39,134 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); describe('Tour enabled', () => { - beforeEach(async () => { - // Activate the Observability guide, step 3, in order to trigger the EuiTour - await supertest - .put(`/api/guided_onboarding/state`) - .set('kbn-xsrf', 'true') - .send({ - status: 'in_progress', - guideId: 'observability', - isActive: true, - steps: [ - { - id: 'add_data', - status: 'complete', - }, - { - id: 'view_dashboard', - status: 'complete', - }, - { - id: 'tour_observability', - status: 'in_progress', - }, - ], - }) - .expect(200); - }); - - it('can complete tour', async () => { - await setInitialTourState(); - - // Step 1: Overview - await pageObjects.infraHome.waitForTourStep('overviewStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); - - // Step 2: Streams - await pageObjects.infraHome.waitForTourStep('streamStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('streamStep'); - - // Step 3: Metrics explorer - await pageObjects.infraHome.waitForTourStep('metricsExplorerStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('metricsExplorerStep'); - - // Step 4: Services - await pageObjects.infraHome.waitForTourStep('servicesStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('servicesStep'); - - // Step 5: Alerts - await pageObjects.infraHome.waitForTourStep('alertStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('alertStep'); - - // Step 6: Guided setup - await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); - await pageObjects.infraHome.clickTourEndButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); - }); - - it('can skip tour', async () => { - await setInitialTourState(); - - await pageObjects.infraHome.waitForTourStep('overviewStep'); - await pageObjects.infraHome.clickTourSkipButton(); - - // Verify current step ("Overview") is not displayed - await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); - // Verify next step ("Streams") is not displayed - await pageObjects.infraHome.ensureTourStepIsClosed('streamStep'); - - await browser.refresh(); - - // Verify current step ("Overview") is not displayed after browser refresh, - // i.e., localStorage has been updated to not show the tour again - await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); - }); - - it('can start mid-tour', async () => { - await setInitialTourState(5); - - // Step 5: Alerts - await pageObjects.infraHome.waitForTourStep('alertStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('alertStep'); - - // Step 6: Guided setup - await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); - await pageObjects.infraHome.clickTourEndButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); - }); - - it('navigates the user to the guided setup step', async () => { - // For brevity, starting the tour at step 5 - await setInitialTourState(5); - - await pageObjects.infraHome.waitForTourStep('alertStep'); - - // Click on Alerts link - await (await find.byCssSelector('[data-nav-id="alerts"]')).click(); - - // Verify user correctly navigated to the Alerts page - const alertsPageUrl = await browser.getCurrentUrl(); - expect(alertsPageUrl).to.contain('/app/observability/alerts'); - - // Verify Step 5 persists on Alerts page, then continue with tour - await pageObjects.infraHome.waitForTourStep('alertStep'); - await pageObjects.infraHome.clickTourNextButton(); - - // Verify user navigated back to the overview page, and guided setup step renders (Step 6) - await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); - const overviewPageUrl = await browser.getCurrentUrl(); - expect(overviewPageUrl).to.contain('/app/observability/overview'); - }); - - it('ends the tour if the user clicks on the guided setup button', async () => { - // For brevity, starting the tour at step 5, "Alerts" - await setInitialTourState(5); - - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); - await pageObjects.infraHome.clickGuidedSetupButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); - }); + // only run these tests on Cloud + if (isCloud) { + beforeEach(async () => { + // Activate the Observability guide, step 3, in order to trigger the EuiTour + await supertest + .put(`/api/guided_onboarding/state`) + .set('kbn-xsrf', 'true') + .send({ + status: 'in_progress', + guideId: 'observability', + isActive: true, + steps: [ + { + id: 'add_data', + status: 'complete', + }, + { + id: 'view_dashboard', + status: 'complete', + }, + { + id: 'tour_observability', + status: 'in_progress', + }, + ], + }) + .expect(200); + }); + + it('can complete tour', async () => { + await setInitialTourState(); + + // Step 1: Overview + await pageObjects.infraHome.waitForTourStep('overviewStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); + + // Step 2: Streams + await pageObjects.infraHome.waitForTourStep('streamStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('streamStep'); + + // Step 3: Metrics explorer + await pageObjects.infraHome.waitForTourStep('metricsExplorerStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('metricsExplorerStep'); + + // Step 4: Services + await pageObjects.infraHome.waitForTourStep('servicesStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('servicesStep'); + + // Step 5: Alerts + await pageObjects.infraHome.waitForTourStep('alertStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('alertStep'); + + // Step 6: Guided setup + await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); + await pageObjects.infraHome.clickTourEndButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); + }); + + it('can skip tour', async () => { + await setInitialTourState(); + + await pageObjects.infraHome.waitForTourStep('overviewStep'); + await pageObjects.infraHome.clickTourSkipButton(); + + // Verify current step ("Overview") is not displayed + await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); + // Verify next step ("Streams") is not displayed + await pageObjects.infraHome.ensureTourStepIsClosed('streamStep'); + + await browser.refresh(); + + // Verify current step ("Overview") is not displayed after browser refresh, + // i.e., localStorage has been updated to not show the tour again + await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); + }); + + it('can start mid-tour', async () => { + await setInitialTourState(5); + + // Step 5: Alerts + await pageObjects.infraHome.waitForTourStep('alertStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('alertStep'); + + // Step 6: Guided setup + await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); + await pageObjects.infraHome.clickTourEndButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); + }); + + it('navigates the user to the guided setup step', async () => { + // For brevity, starting the tour at step 5 + await setInitialTourState(5); + + await pageObjects.infraHome.waitForTourStep('alertStep'); + + // Click on Alerts link + await (await find.byCssSelector('[data-nav-id="alerts"]')).click(); + + // Verify user correctly navigated to the Alerts page + const alertsPageUrl = await browser.getCurrentUrl(); + expect(alertsPageUrl).to.contain('/app/observability/alerts'); + + // Verify Step 5 persists on Alerts page, then continue with tour + await pageObjects.infraHome.waitForTourStep('alertStep'); + await pageObjects.infraHome.clickTourNextButton(); + + // Verify user navigated back to the overview page, and guided setup step renders (Step 6) + await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); + const overviewPageUrl = await browser.getCurrentUrl(); + expect(overviewPageUrl).to.contain('/app/observability/overview'); + }); + + it('ends the tour if the user clicks on the guided setup button', async () => { + // For brevity, starting the tour at step 5, "Alerts" + await setInitialTourState(5); + + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); + await pageObjects.infraHome.clickGuidedSetupButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); + }); + } }); }); }; From 20d91f1be00ffff7eb6a6042e368ef71c86e8444 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 2 Nov 2022 18:40:23 +0200 Subject: [PATCH 32/86] [Security Solution] Fix rules table refresh after rule's import (#144359) **Resolves:** https://github.com/elastic/kibana/issues/136758 ## Summary It fixes the problem of the rules table refresh after importing a rule. *Before:* https://user-images.githubusercontent.com/3775283/199319505-e70918ec-1621-4048-92ba-0b83a5b19652.mov *After:* https://user-images.githubusercontent.com/3775283/199319768-3513dfc7-f9d2-4a84-a7ff-0b78dcf4795f.mov ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../cypress/e2e/detection_rules/import_rules.cy.ts | 12 +++++++++++- .../pages/rule_management/index.tsx | 10 ++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts index 48f3ffaf18412..7ee3a7e40647f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TOASTER } from '../../screens/alerts_detection_rules'; +import { RULES_ROW, RULES_TABLE, TOASTER } from '../../screens/alerts_detection_rules'; import { importRules, importRulesWithOverwriteAll } from '../../tasks/alerts_detection_rules'; import { cleanKibana, deleteAlertsAndRules, reload } from '../../tasks/common'; import { login, visitWithoutDateRange } from '../../tasks/login'; @@ -24,6 +24,9 @@ describe('Import rules', () => { }); it('Imports a custom rule with exceptions', function () { + const expectedNumberOfRules = 1; + const expectedImportedRuleName = 'Test Custom Rule'; + importRules('7_16_rules.ndjson'); cy.wait('@import').then(({ response }) => { @@ -32,6 +35,13 @@ describe('Import rules', () => { 'have.text', 'Successfully imported 1 ruleSuccessfully imported 2 exceptions.' ); + + cy.get(RULES_TABLE).then(($table) => { + const rulesRow = cy.wrap($table.find(RULES_ROW)); + + rulesRow.should('have.length', expectedNumberOfRules); + rulesRow.should('include.text', expectedImportedRuleName); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx index e49c6b61a0019..16a30acb29651 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { APP_UI_ID } from '../../../../../common/constants'; @@ -32,6 +32,7 @@ import { useListsConfig } from '../../../../detections/containers/detection_engi import { redirectToDetections } from '../../../../detections/pages/detection_engine/rules/helpers'; import { useInvalidateFindRulesQuery } from '../../../rule_management/api/hooks/use_find_rules_query'; +import { useInvalidateFetchPrebuiltRulesStatusQuery } from '../../../rule_management/api/hooks/use_fetch_prebuilt_rules_status_query'; import { importRules } from '../../../rule_management/logic'; import { usePrePackagedRulesInstallationStatus } from '../../../rule_management/logic/use_pre_packaged_rules_installation_status'; import { usePrePackagedTimelinesInstallationStatus } from '../../../rule_management/logic/use_pre_packaged_timelines_installation_status'; @@ -47,6 +48,11 @@ const RulesPageComponent: React.FC = () => { const [isValueListFlyoutVisible, showValueListFlyout, hideValueListFlyout] = useBoolState(); const { navigateToApp } = useKibana().services.application; const invalidateFindRulesQuery = useInvalidateFindRulesQuery(); + const invalidateFetchPrebuiltRulesStatusQuery = useInvalidateFetchPrebuiltRulesStatusQuery(); + const invalidateRules = useCallback(() => { + invalidateFindRulesQuery(); + invalidateFetchPrebuiltRulesStatusQuery(); + }, [invalidateFindRulesQuery, invalidateFetchPrebuiltRulesStatusQuery]); const [ { @@ -94,7 +100,7 @@ const RulesPageComponent: React.FC = () => { description={i18n.SELECT_RULE} errorMessage={i18n.IMPORT_FAILED} failedDetailed={i18n.IMPORT_FAILED_DETAILED} - importComplete={invalidateFindRulesQuery} + importComplete={invalidateRules} importData={importRules} successMessage={i18n.SUCCESSFULLY_IMPORTED_RULES} showModal={isImportModalVisible} From a4445f959da98c03da2a755da26d63a36f44b8be Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Wed, 2 Nov 2022 09:44:16 -0700 Subject: [PATCH 33/86] Revert "[Guided onboarding] Fix multiple API requests when fetching guide state (#144160)" This reverts commit 02a2a6a5004f82385f2f459d17e1e1a4f778b38b. --- packages/kbn-guided-onboarding/index.ts | 9 +- .../public/components/guide_panel.test.tsx | 17 +- .../public/components/guide_panel.tsx | 2 +- .../public/services/api.mocks.ts | 18 -- .../public/services/api.test.ts | 95 ++----- .../guided_onboarding/public/services/api.ts | 57 ++-- x-pack/test/functional/apps/infra/tour.ts | 257 +++++++++--------- 7 files changed, 172 insertions(+), 283 deletions(-) diff --git a/packages/kbn-guided-onboarding/index.ts b/packages/kbn-guided-onboarding/index.ts index b22a8a2057c16..f98f330cd4be3 100644 --- a/packages/kbn-guided-onboarding/index.ts +++ b/packages/kbn-guided-onboarding/index.ts @@ -6,13 +6,6 @@ * Side Public License, v 1. */ -export type { - GuideState, - GuideId, - GuideStepIds, - StepStatus, - GuideStep, - GuideStatus, -} from './src/types'; +export type { GuideState, GuideId, GuideStepIds, StepStatus, GuideStep } from './src/types'; export { GuideCard, ObservabilityLinkCard } from './src/components/landing_page'; export type { UseCase } from './src/components/landing_page'; diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx index 9be6326da1d06..9420e05e9d3fe 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx @@ -109,21 +109,10 @@ describe('Guided setup', () => { }); describe('Button component', () => { - test('should be hidden in there is no guide state', async () => { + // TODO check for the correct button behavior once https://github.com/elastic/kibana/issues/141129 is implemented + test.skip('should be disabled in there is no active guide', async () => { const { exists } = testBed; - expect(exists('guideButton')).toBe(false); - expect(exists('guidePanel')).toBe(false); - }); - - test('should be hidden if the guide is not active', async () => { - const { component, exists } = testBed; - - await updateComponentWithState( - component, - { ...mockActiveSearchGuideState, isActive: false }, - true - ); - + expect(exists('disabledGuideButton')).toBe(true); expect(exists('guideButton')).toBe(false); expect(exists('guidePanel')).toBe(false); }); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx index 823637b23247a..759f4a83c6852 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx @@ -139,7 +139,7 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { // TODO handle loading, error state // https://github.com/elastic/kibana/issues/139799, https://github.com/elastic/kibana/issues/139798 - if (!guideConfig || !guideState || !guideState.isActive) { + if (!guideConfig) { // TODO button show/hide logic https://github.com/elastic/kibana/issues/141129 return null; } diff --git a/src/plugins/guided_onboarding/public/services/api.mocks.ts b/src/plugins/guided_onboarding/public/services/api.mocks.ts index 47b7f6c9900e0..2294607f91b38 100644 --- a/src/plugins/guided_onboarding/public/services/api.mocks.ts +++ b/src/plugins/guided_onboarding/public/services/api.mocks.ts @@ -77,24 +77,6 @@ export const testGuideStep2InProgressState: GuideState = { ], }; -export const readyToCompleteGuideState: GuideState = { - ...testGuideStep1ActiveState, - steps: [ - { - ...testGuideStep1ActiveState.steps[0], - status: 'complete', - }, - { - ...testGuideStep1ActiveState.steps[1], - status: 'complete', - }, - { - ...testGuideStep1ActiveState.steps[2], - status: 'complete', - }, - ], -}; - export const testGuideNotActiveState: GuideState = { ...testGuideStep1ActiveState, isActive: false, diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts index 24ade7de1a849..56a5755f0ee55 100644 --- a/src/plugins/guided_onboarding/public/services/api.test.ts +++ b/src/plugins/guided_onboarding/public/services/api.test.ts @@ -11,7 +11,6 @@ import { httpServiceMock } from '@kbn/core/public/mocks'; import type { GuideState } from '@kbn/guided-onboarding'; import { firstValueFrom, Subscription } from 'rxjs'; -import { GuideStatus } from '@kbn/guided-onboarding'; import { API_BASE_PATH } from '../../common/constants'; import { ApiService } from './api'; import { @@ -25,14 +24,12 @@ import { testIntegration, wrongIntegration, testGuideStep2InProgressState, - readyToCompleteGuideState, } from './api.mocks'; describe('GuidedOnboarding ApiService', () => { let httpClient: jest.Mocked; let apiService: ApiService; let subscription: Subscription; - let anotherSubscription: Subscription; beforeEach(() => { httpClient = httpServiceMock.createStartContract({ basePath: '/base/path' }); @@ -44,8 +41,9 @@ describe('GuidedOnboarding ApiService', () => { }); afterEach(() => { - subscription?.unsubscribe(); - anotherSubscription?.unsubscribe(); + if (subscription) { + subscription.unsubscribe(); + } jest.restoreAllMocks(); }); @@ -55,64 +53,6 @@ describe('GuidedOnboarding ApiService', () => { expect(httpClient.get).toHaveBeenCalledTimes(1); expect(httpClient.get).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { query: { active: true }, - signal: new AbortController().signal, - }); - }); - - it(`doesn't send multiple requests when there are several subscriptions`, () => { - subscription = apiService.fetchActiveGuideState$().subscribe(); - anotherSubscription = apiService.fetchActiveGuideState$().subscribe(); - expect(httpClient.get).toHaveBeenCalledTimes(1); - }); - - it(`re-sends the request if the previous one failed`, async () => { - httpClient.get.mockRejectedValueOnce(new Error('request failed')); - subscription = apiService.fetchActiveGuideState$().subscribe(); - // wait until the request fails - await new Promise((resolve) => process.nextTick(resolve)); - anotherSubscription = apiService.fetchActiveGuideState$().subscribe(); - expect(httpClient.get).toHaveBeenCalledTimes(2); - }); - - it(`re-sends the request if there is no guide state and there is another subscription`, async () => { - httpClient.get.mockResolvedValueOnce({ - state: [], - }); - subscription = apiService.fetchActiveGuideState$().subscribe(); - // wait until the request completes - await new Promise((resolve) => process.nextTick(resolve)); - anotherSubscription = apiService.fetchActiveGuideState$().subscribe(); - expect(httpClient.get).toHaveBeenCalledTimes(2); - }); - - it(`doesn't send multiple requests in a loop when there is no state`, async () => { - httpClient.get.mockResolvedValueOnce({ - state: [], - }); - subscription = apiService.fetchActiveGuideState$().subscribe(); - // wait until the request completes - await new Promise((resolve) => process.nextTick(resolve)); - expect(httpClient.get).toHaveBeenCalledTimes(1); - }); - - it(`re-sends the request if the subscription was unsubscribed before the request completed`, async () => { - httpClient.get.mockImplementationOnce(() => { - return new Promise((resolve) => setTimeout(resolve)); - }); - // subscribe and immediately unsubscribe - apiService.fetchActiveGuideState$().subscribe().unsubscribe(); - anotherSubscription = apiService.fetchActiveGuideState$().subscribe(); - expect(httpClient.get).toHaveBeenCalledTimes(2); - }); - - it(`the second subscription gets the state broadcast to it`, (done) => { - // first subscription - apiService.fetchActiveGuideState$().subscribe(); - // second subscription - anotherSubscription = apiService.fetchActiveGuideState$().subscribe((state) => { - if (state) { - done(); - } }); }); @@ -155,17 +95,6 @@ describe('GuidedOnboarding ApiService', () => { body: JSON.stringify(updatedState), }); }); - - it('the completed state is being broadcast after the update', async () => { - const completedState = { - ...readyToCompleteGuideState, - isActive: false, - status: 'complete' as GuideStatus, - }; - await apiService.updateGuideState(completedState, false); - const state = await firstValueFrom(apiService.fetchActiveGuideState$()); - expect(state).toMatchObject(completedState); - }); }); describe('isGuideStepActive$', () => { @@ -220,6 +149,24 @@ describe('GuidedOnboarding ApiService', () => { }); describe('completeGuide', () => { + const readyToCompleteGuideState: GuideState = { + ...testGuideStep1ActiveState, + steps: [ + { + ...testGuideStep1ActiveState.steps[0], + status: 'complete', + }, + { + ...testGuideStep1ActiveState.steps[1], + status: 'complete', + }, + { + ...testGuideStep1ActiveState.steps[2], + status: 'complete', + }, + ], + }; + beforeEach(async () => { await apiService.updateGuideState(readyToCompleteGuideState, false); }); diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index 89790024cdde7..cd33f9505c546 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -7,7 +7,7 @@ */ import { HttpSetup } from '@kbn/core/public'; -import { BehaviorSubject, map, concatMap, of, Observable, firstValueFrom } from 'rxjs'; +import { BehaviorSubject, map, from, concatMap, of, Observable, firstValueFrom } from 'rxjs'; import type { GuideState, GuideId, GuideStep, GuideStepIds } from '@kbn/guided-onboarding'; import { GuidedOnboardingApi } from '../types'; @@ -26,7 +26,6 @@ import { API_BASE_PATH } from '../../common/constants'; export class ApiService implements GuidedOnboardingApi { private client: HttpSetup | undefined; private onboardingGuideState$!: BehaviorSubject; - private isGuideStateLoading: boolean | undefined; public isGuidePanelOpen$: BehaviorSubject = new BehaviorSubject(false); public setup(httpClient: HttpSetup): void { @@ -34,46 +33,30 @@ export class ApiService implements GuidedOnboardingApi { this.onboardingGuideState$ = new BehaviorSubject(undefined); } - private createGetStateObservable(): Observable { - return new Observable((observer) => { - const controller = new AbortController(); - const signal = controller.signal; - this.isGuideStateLoading = true; - this.client!.get<{ state: GuideState[] }>(`${API_BASE_PATH}/state`, { - query: { - active: true, - }, - signal, - }) - .then((response) => { - this.isGuideStateLoading = false; - // There should only be 1 active guide - const hasState = response.state.length === 1; - if (hasState) { - this.onboardingGuideState$.next(response.state[0]); - } - observer.complete(); - }) - .catch((error) => { - this.isGuideStateLoading = false; - observer.error(error); - }); - return () => { - this.isGuideStateLoading = false; - controller.abort(); - }; - }); - } - /** * An Observable with the active guide state. * Initially the state is fetched from the backend. * Subsequently, the observable is updated automatically, when the state changes. */ public fetchActiveGuideState$(): Observable { + // TODO add error handling if this.client has not been initialized or request fails return this.onboardingGuideState$.pipe( concatMap((state) => - !state && !this.isGuideStateLoading ? this.createGetStateObservable() : of(state) + state === undefined + ? from( + this.client!.get<{ state: GuideState[] }>(`${API_BASE_PATH}/state`, { + query: { + active: true, + }, + }) + ).pipe( + map((response) => { + // There should only be 1 active guide + const hasState = response.state.length === 1; + return hasState ? response.state[0] : undefined; + }) + ) + : of(state) ) ); } @@ -100,7 +83,7 @@ export class ApiService implements GuidedOnboardingApi { /** * Updates the SO with the updated guide state and refreshes the observables * This is largely used internally and for tests - * @param {GuideState} newState the updated guide state + * @param {GuideState} guideState the updated guide state * @param {boolean} panelState boolean to determine whether the dropdown panel should open or not * @return {Promise} a promise with the updated guide state */ @@ -116,8 +99,8 @@ export class ApiService implements GuidedOnboardingApi { const response = await this.client.put<{ state: GuideState }>(`${API_BASE_PATH}/state`, { body: JSON.stringify(newState), }); - // broadcast the newState - this.onboardingGuideState$.next(newState); + // If the guide has been deactivated, we return undefined + this.onboardingGuideState$.next(newState.isActive ? newState : undefined); this.isGuidePanelOpen$.next(panelState); return response; } catch (error) { diff --git a/x-pack/test/functional/apps/infra/tour.ts b/x-pack/test/functional/apps/infra/tour.ts index 2f9dfa314c168..81ca07eef4c05 100644 --- a/x-pack/test/functional/apps/infra/tour.ts +++ b/x-pack/test/functional/apps/infra/tour.ts @@ -15,7 +15,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'infraHome']); const find = getService('find'); const supertest = getService('supertest'); - const deployment = getService('deployment'); const setInitialTourState = async (activeStep?: number) => { await browser.setLocalStorageItem(observTourStepStorageKey, String(activeStep || 1)); @@ -24,9 +23,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Onboarding Observability tour', function () { this.tags('includeFirefox'); - let isCloud: boolean; + before(async () => { - isCloud = await deployment.isCloud(); await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); await pageObjects.common.navigateToApp('observability'); // Need to increase the browser height so the tour steps fit to screen @@ -39,134 +37,131 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); describe('Tour enabled', () => { - // only run these tests on Cloud - if (isCloud) { - beforeEach(async () => { - // Activate the Observability guide, step 3, in order to trigger the EuiTour - await supertest - .put(`/api/guided_onboarding/state`) - .set('kbn-xsrf', 'true') - .send({ - status: 'in_progress', - guideId: 'observability', - isActive: true, - steps: [ - { - id: 'add_data', - status: 'complete', - }, - { - id: 'view_dashboard', - status: 'complete', - }, - { - id: 'tour_observability', - status: 'in_progress', - }, - ], - }) - .expect(200); - }); - - it('can complete tour', async () => { - await setInitialTourState(); - - // Step 1: Overview - await pageObjects.infraHome.waitForTourStep('overviewStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); - - // Step 2: Streams - await pageObjects.infraHome.waitForTourStep('streamStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('streamStep'); - - // Step 3: Metrics explorer - await pageObjects.infraHome.waitForTourStep('metricsExplorerStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('metricsExplorerStep'); - - // Step 4: Services - await pageObjects.infraHome.waitForTourStep('servicesStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('servicesStep'); - - // Step 5: Alerts - await pageObjects.infraHome.waitForTourStep('alertStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('alertStep'); - - // Step 6: Guided setup - await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); - await pageObjects.infraHome.clickTourEndButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); - }); - - it('can skip tour', async () => { - await setInitialTourState(); - - await pageObjects.infraHome.waitForTourStep('overviewStep'); - await pageObjects.infraHome.clickTourSkipButton(); - - // Verify current step ("Overview") is not displayed - await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); - // Verify next step ("Streams") is not displayed - await pageObjects.infraHome.ensureTourStepIsClosed('streamStep'); - - await browser.refresh(); - - // Verify current step ("Overview") is not displayed after browser refresh, - // i.e., localStorage has been updated to not show the tour again - await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); - }); - - it('can start mid-tour', async () => { - await setInitialTourState(5); - - // Step 5: Alerts - await pageObjects.infraHome.waitForTourStep('alertStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('alertStep'); - - // Step 6: Guided setup - await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); - await pageObjects.infraHome.clickTourEndButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); - }); - - it('navigates the user to the guided setup step', async () => { - // For brevity, starting the tour at step 5 - await setInitialTourState(5); - - await pageObjects.infraHome.waitForTourStep('alertStep'); - - // Click on Alerts link - await (await find.byCssSelector('[data-nav-id="alerts"]')).click(); - - // Verify user correctly navigated to the Alerts page - const alertsPageUrl = await browser.getCurrentUrl(); - expect(alertsPageUrl).to.contain('/app/observability/alerts'); - - // Verify Step 5 persists on Alerts page, then continue with tour - await pageObjects.infraHome.waitForTourStep('alertStep'); - await pageObjects.infraHome.clickTourNextButton(); - - // Verify user navigated back to the overview page, and guided setup step renders (Step 6) - await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); - const overviewPageUrl = await browser.getCurrentUrl(); - expect(overviewPageUrl).to.contain('/app/observability/overview'); - }); - - it('ends the tour if the user clicks on the guided setup button', async () => { - // For brevity, starting the tour at step 5, "Alerts" - await setInitialTourState(5); - - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); - await pageObjects.infraHome.clickGuidedSetupButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); - }); - } + beforeEach(async () => { + // Activate the Observability guide, step 3, in order to trigger the EuiTour + await supertest + .put(`/api/guided_onboarding/state`) + .set('kbn-xsrf', 'true') + .send({ + status: 'in_progress', + guideId: 'observability', + isActive: true, + steps: [ + { + id: 'add_data', + status: 'complete', + }, + { + id: 'view_dashboard', + status: 'complete', + }, + { + id: 'tour_observability', + status: 'in_progress', + }, + ], + }) + .expect(200); + }); + + it('can complete tour', async () => { + await setInitialTourState(); + + // Step 1: Overview + await pageObjects.infraHome.waitForTourStep('overviewStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); + + // Step 2: Streams + await pageObjects.infraHome.waitForTourStep('streamStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('streamStep'); + + // Step 3: Metrics explorer + await pageObjects.infraHome.waitForTourStep('metricsExplorerStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('metricsExplorerStep'); + + // Step 4: Services + await pageObjects.infraHome.waitForTourStep('servicesStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('servicesStep'); + + // Step 5: Alerts + await pageObjects.infraHome.waitForTourStep('alertStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('alertStep'); + + // Step 6: Guided setup + await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); + await pageObjects.infraHome.clickTourEndButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); + }); + + it('can skip tour', async () => { + await setInitialTourState(); + + await pageObjects.infraHome.waitForTourStep('overviewStep'); + await pageObjects.infraHome.clickTourSkipButton(); + + // Verify current step ("Overview") is not displayed + await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); + // Verify next step ("Streams") is not displayed + await pageObjects.infraHome.ensureTourStepIsClosed('streamStep'); + + await browser.refresh(); + + // Verify current step ("Overview") is not displayed after browser refresh, + // i.e., localStorage has been updated to not show the tour again + await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); + }); + + it('can start mid-tour', async () => { + await setInitialTourState(5); + + // Step 5: Alerts + await pageObjects.infraHome.waitForTourStep('alertStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('alertStep'); + + // Step 6: Guided setup + await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); + await pageObjects.infraHome.clickTourEndButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); + }); + + it('navigates the user to the guided setup step', async () => { + // For brevity, starting the tour at step 5 + await setInitialTourState(5); + + await pageObjects.infraHome.waitForTourStep('alertStep'); + + // Click on Alerts link + await (await find.byCssSelector('[data-nav-id="alerts"]')).click(); + + // Verify user correctly navigated to the Alerts page + const alertsPageUrl = await browser.getCurrentUrl(); + expect(alertsPageUrl).to.contain('/app/observability/alerts'); + + // Verify Step 5 persists on Alerts page, then continue with tour + await pageObjects.infraHome.waitForTourStep('alertStep'); + await pageObjects.infraHome.clickTourNextButton(); + + // Verify user navigated back to the overview page, and guided setup step renders (Step 6) + await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); + const overviewPageUrl = await browser.getCurrentUrl(); + expect(overviewPageUrl).to.contain('/app/observability/overview'); + }); + + it('ends the tour if the user clicks on the guided setup button', async () => { + // For brevity, starting the tour at step 5, "Alerts" + await setInitialTourState(5); + + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); + await pageObjects.infraHome.clickGuidedSetupButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); + }); }); }); }; From 32bbab85d05ada01b942072d5a3e6e8c9a8fc5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Wed, 2 Nov 2022 17:49:04 +0100 Subject: [PATCH 34/86] [8.5] [APM] Disable refresh in ML links (#144390) (#144426) (cherry picked from commit 0f79e349cd508bcc98515c5f434379d933fc9deb) --- .../machine_learning_links/mlexplorer_link.test.tsx | 2 +- .../machine_learning_links/mlexplorer_link.tsx | 11 +---------- .../mlmanage_jobs_link.test.tsx | 2 +- .../mlsingle_metric_link.test.tsx | 4 ++-- .../machine_learning_links/mlsingle_metric_link.tsx | 13 ++----------- .../apm/public/hooks/use_ml_manage_jobs_href.ts | 11 +---------- 6 files changed, 8 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlexplorer_link.test.tsx b/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlexplorer_link.test.tsx index 49c9c6c244c01..ad0b06b73df53 100644 --- a/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlexplorer_link.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlexplorer_link.test.tsx @@ -23,7 +23,7 @@ describe('MLExplorerLink', () => { ); expect(href).toMatchInlineSnapshot( - `"/app/ml/explorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:0),time:(from:now%2Fw,to:now-4h))&_a=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:()))"` + `"/app/ml/explorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:10000),time:(from:now%2Fw,to:now-4h))&_a=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:()))"` ); }); diff --git a/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlexplorer_link.tsx b/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlexplorer_link.tsx index 9da957f97c974..0990a961c0d4b 100644 --- a/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlexplorer_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlexplorer_link.tsx @@ -7,11 +7,9 @@ import React, { ReactNode } from 'react'; import { EuiLink } from '@elastic/eui'; -import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { useMlHref, ML_PAGES } from '@kbn/ml-plugin/public'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { TimePickerRefreshInterval } from '../../date_picker/typings'; interface Props { children?: ReactNode; @@ -39,17 +37,10 @@ export function useExplorerHref({ jobId }: { jobId: string }) { } = useApmPluginContext(); const { urlParams } = useLegacyUrlParams(); - const timePickerRefreshIntervalDefaults = - core.uiSettings.get( - UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS - ); - const { // hardcoding a custom default of 1 hour since the default kibana timerange of 15 minutes is shorter than the ML interval rangeFrom = 'now-1h', rangeTo = 'now', - refreshInterval = timePickerRefreshIntervalDefaults.value, - refreshPaused = timePickerRefreshIntervalDefaults.pause, } = urlParams; const href = useMlHref(ml, core.http.basePath.get(), { @@ -57,7 +48,7 @@ export function useExplorerHref({ jobId }: { jobId: string }) { pageState: { jobIds: [jobId], timeRange: { from: rangeFrom, to: rangeTo }, - refreshInterval: { pause: refreshPaused, value: refreshInterval }, + refreshInterval: { pause: true, value: 10000 }, }, }); diff --git a/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlmanage_jobs_link.test.tsx b/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlmanage_jobs_link.test.tsx index c2ce967768502..c7f3186670248 100644 --- a/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlmanage_jobs_link.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlmanage_jobs_link.test.tsx @@ -17,6 +17,6 @@ test('MLManageJobsLink', async () => { } as Location); expect(href).toMatchInlineSnapshot( - `"/app/ml/jobs?_a=(jobs:(queryText:'groups:(apm)'))&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-5h,to:now-2h))"` + `"/app/ml/jobs?_a=(jobs:(queryText:'groups:(apm)'))&_g=(refreshInterval:(pause:!t,value:10000),time:(from:now-5h,to:now-2h))"` ); }); diff --git a/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlsingle_metric_link.test.tsx b/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlsingle_metric_link.test.tsx index dcf9cc9a06ffc..eb6ae275563a3 100644 --- a/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlsingle_metric_link.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlsingle_metric_link.test.tsx @@ -23,7 +23,7 @@ describe('MLSingleMetricLink', () => { ); expect(href).toMatchInlineSnapshot( - `"/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:0),time:(from:now%2Fw,to:now-4h))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:()))"` + `"/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:10000),time:(from:now%2Fw,to:now-4h))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:()))"` ); }); it('should produce the correct URL with jobId, serviceName, and transactionType', async () => { @@ -42,7 +42,7 @@ describe('MLSingleMetricLink', () => { ); expect(href).toMatchInlineSnapshot( - `"/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:0),time:(from:now%2Fw,to:now-4h))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:(entities:(service.name:opbeans-test,transaction.type:request))))"` + `"/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:!t,value:10000),time:(from:now%2Fw,to:now-4h))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:(entities:(service.name:opbeans-test,transaction.type:request))))"` ); }); diff --git a/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlsingle_metric_link.tsx b/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlsingle_metric_link.tsx index cc2d855550af3..7c4232866157b 100644 --- a/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlsingle_metric_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/links/machine_learning_links/mlsingle_metric_link.tsx @@ -7,11 +7,9 @@ import React, { ReactNode } from 'react'; import { EuiLink } from '@elastic/eui'; -import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { useMlHref, ML_PAGES } from '@kbn/ml-plugin/public'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { TimePickerRefreshInterval } from '../../date_picker/typings'; interface Props { children?: ReactNode; @@ -40,7 +38,7 @@ export function MLSingleMetricLink({ ); } -export function useSingleMetricHref({ +function useSingleMetricHref({ jobId, serviceName, transactionType, @@ -55,17 +53,10 @@ export function useSingleMetricHref({ } = useApmPluginContext(); const { urlParams } = useLegacyUrlParams(); - const timePickerRefreshIntervalDefaults = - core.uiSettings.get( - UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS - ); - const { // hardcoding a custom default of 1 hour since the default kibana timerange of 15 minutes is shorter than the ML interval rangeFrom = 'now-1h', rangeTo = 'now', - refreshInterval = timePickerRefreshIntervalDefaults.value, - refreshPaused = timePickerRefreshIntervalDefaults.pause, } = urlParams; const entities = @@ -83,7 +74,7 @@ export function useSingleMetricHref({ pageState: { jobIds: [jobId], timeRange: { from: rangeFrom, to: rangeTo }, - refreshInterval: { pause: refreshPaused, value: refreshInterval }, + refreshInterval: { pause: true, value: 10000 }, ...entities, }, }); diff --git a/x-pack/plugins/apm/public/hooks/use_ml_manage_jobs_href.ts b/x-pack/plugins/apm/public/hooks/use_ml_manage_jobs_href.ts index 5eb1f6bb8f0bf..c5b9b0dba5a3a 100644 --- a/x-pack/plugins/apm/public/hooks/use_ml_manage_jobs_href.ts +++ b/x-pack/plugins/apm/public/hooks/use_ml_manage_jobs_href.ts @@ -5,9 +5,7 @@ * 2.0. */ -import { UI_SETTINGS } from '@kbn/data-plugin/public'; import { ML_PAGES, useMlHref } from '@kbn/ml-plugin/public'; -import { TimePickerRefreshInterval } from '../components/shared/date_picker/typings'; import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; import { useLegacyUrlParams } from '../context/url_params_context/use_url_params'; @@ -19,17 +17,10 @@ export function useMlManageJobsHref({ jobId }: { jobId?: string } = {}) { const { urlParams } = useLegacyUrlParams(); - const timePickerRefreshIntervalDefaults = - core.uiSettings.get( - UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS - ); - const { // hardcoding a custom default of 1 hour since the default kibana timerange of 15 minutes is shorter than the ML interval rangeFrom = 'now-1h', rangeTo = 'now', - refreshInterval = timePickerRefreshIntervalDefaults.value, - refreshPaused = timePickerRefreshIntervalDefaults.pause, } = urlParams; const mlADLink = useMlHref(ml, core.http.basePath.get(), { @@ -39,7 +30,7 @@ export function useMlManageJobsHref({ jobId }: { jobId?: string } = {}) { jobId, globalState: { time: { from: rangeFrom, to: rangeTo }, - refreshInterval: { pause: refreshPaused, value: refreshInterval }, + refreshInterval: { pause: true, value: 10000 }, }, }, }); From 34af9e79138866617e8292fd2d238fc61ea166d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Wed, 2 Nov 2022 18:07:49 +0100 Subject: [PATCH 35/86] =?UTF-8?q?[APM]=20Prefix=20services=20from=20syntht?= =?UTF-8?q?race=20scenarios=20with=20=E2=80=9Csynth=E2=80=9D=20instead=20o?= =?UTF-8?q?f=20=E2=80=9Copbeans=E2=80=9D=20(#144416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/scenarios/distributed_trace.ts | 44 +++++++++---------- .../src/scenarios/distributed_trace_long.ts | 34 +++++++------- .../src/scenarios/simple_trace.ts | 2 +- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/packages/kbn-apm-synthtrace/src/scenarios/distributed_trace.ts b/packages/kbn-apm-synthtrace/src/scenarios/distributed_trace.ts index 7834011afa69a..319534f821719 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/distributed_trace.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/distributed_trace.ts @@ -23,61 +23,61 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const transactionName = '240rpm/75% 1000ms'; const successfulTimestamps = range.interval('1s').rate(3); - const opbeansRum = apm - .service({ name: 'opbeans-rum', environment: ENVIRONMENT, agentName: 'rum-js' }) + const synthRum = apm + .service({ name: 'synth-rum', environment: ENVIRONMENT, agentName: 'rum-js' }) .instance('my-instance'); - const opbeansNode = apm - .service({ name: 'opbeans-node', environment: ENVIRONMENT, agentName: 'nodejs' }) + const synthNode = apm + .service({ name: 'synth-node', environment: ENVIRONMENT, agentName: 'nodejs' }) .instance('my-instance'); - const opbeansGo = apm - .service({ name: 'opbeans-go', environment: ENVIRONMENT, agentName: 'go' }) + const synthGo = apm + .service({ name: 'synth-go', environment: ENVIRONMENT, agentName: 'go' }) .instance('my-instance'); const traces = successfulTimestamps.generator((timestamp) => { - // opbeans-rum - return opbeansRum + // synth-rum + return synthRum .transaction({ transactionName }) .duration(400) .timestamp(timestamp) .children( - // opbeans-rum -> opbeans-node - opbeansRum + // synth-rum -> synth-node + synthRum .span( httpExitSpan({ spanName: 'GET /api/products/top', - destinationUrl: 'http://opbeans-node:3000', + destinationUrl: 'http://synth-node:3000', }) ) .duration(300) .timestamp(timestamp) .children( - // opbeans-node - opbeansNode - .transaction({ transactionName: 'Initial transaction in opbeans-node' }) + // synth-node + synthNode + .transaction({ transactionName: 'Initial transaction in synth-node' }) .duration(300) .timestamp(timestamp) .children( - opbeansNode - // opbeans-node -> opbeans-go + synthNode + // synth-node -> synth-go .span( httpExitSpan({ - spanName: 'GET opbeans-go:3000', - destinationUrl: 'http://opbeans-go:3000', + spanName: 'GET synth-go:3000', + destinationUrl: 'http://synth-go:3000', }) ) .timestamp(timestamp) .duration(400) .children( - // opbeans-go - opbeansGo + // synth-go + synthGo - .transaction({ transactionName: 'Initial transaction in opbeans-go' }) + .transaction({ transactionName: 'Initial transaction in synth-go' }) .timestamp(timestamp) .duration(200) .children( - opbeansGo + synthGo .span({ spanName: 'custom_operation', spanType: 'custom' }) .timestamp(timestamp) .duration(100) diff --git a/packages/kbn-apm-synthtrace/src/scenarios/distributed_trace_long.ts b/packages/kbn-apm-synthtrace/src/scenarios/distributed_trace_long.ts index 32542ee2c1d49..a55710c6478ef 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/distributed_trace_long.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/distributed_trace_long.ts @@ -24,48 +24,48 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const traceDuration = 1100; const rootTransactionName = `${ratePerMinute}rpm / ${traceDuration}ms`; - const opbeansRum = apm - .service({ name: 'opbeans-rum', environment: ENVIRONMENT, agentName: 'rum-js' }) + const synthRum = apm + .service({ name: 'synth-rum', environment: ENVIRONMENT, agentName: 'rum-js' }) .instance('my-instance'); - const opbeansNode = apm - .service({ name: 'opbeans-node', environment: ENVIRONMENT, agentName: 'nodejs' }) + const synthNode = apm + .service({ name: 'synth-node', environment: ENVIRONMENT, agentName: 'nodejs' }) .instance('my-instance'); - const opbeansGo = apm - .service({ name: 'opbeans-go', environment: ENVIRONMENT, agentName: 'go' }) + const synthGo = apm + .service({ name: 'synth-go', environment: ENVIRONMENT, agentName: 'go' }) .instance('my-instance'); - const opbeansDotnet = apm - .service({ name: 'opbeans-dotnet', environment: ENVIRONMENT, agentName: 'dotnet' }) + const synthDotnet = apm + .service({ name: 'synth-dotnet', environment: ENVIRONMENT, agentName: 'dotnet' }) .instance('my-instance'); - const opbeansJava = apm - .service({ name: 'opbeans-java', environment: ENVIRONMENT, agentName: 'java' }) + const synthJava = apm + .service({ name: 'synth-java', environment: ENVIRONMENT, agentName: 'java' }) .instance('my-instance'); const traces = timerange(from, to) .ratePerMinute(ratePerMinute) .generator((timestamp) => { return new DistributedTrace({ - serviceInstance: opbeansRum, + serviceInstance: synthRum, transactionName: rootTransactionName, timestamp, children: (_) => { _.service({ repeat: 10, - serviceInstance: opbeansNode, + serviceInstance: synthNode, transactionName: 'GET /nodejs/products', latency: 100, children: (_) => { _.service({ - serviceInstance: opbeansGo, + serviceInstance: synthGo, transactionName: 'GET /go', children: (_) => { _.service({ repeat: 20, - serviceInstance: opbeansJava, + serviceInstance: synthJava, transactionName: 'GET /java', children: (_) => { _.external({ @@ -84,19 +84,19 @@ const scenario: Scenario = async (runOptions: RunOptions) => { }); _.service({ - serviceInstance: opbeansNode, + serviceInstance: synthNode, transactionName: 'GET /nodejs/users', latency: 100, repeat: 10, children: (_) => { _.service({ - serviceInstance: opbeansGo, + serviceInstance: synthGo, transactionName: 'GET /go/security', latency: 50, children: (_) => { _.service({ repeat: 10, - serviceInstance: opbeansDotnet, + serviceInstance: synthDotnet, transactionName: 'GET /dotnet/cases/4', latency: 50, children: (_) => diff --git a/packages/kbn-apm-synthtrace/src/scenarios/simple_trace.ts b/packages/kbn-apm-synthtrace/src/scenarios/simple_trace.ts index 8f3c84564787c..c125196999a92 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/simple_trace.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/simple_trace.ts @@ -32,7 +32,7 @@ const scenario: Scenario = async (runOptions: RunOptions) => { const instances = [...Array(numServices).keys()].map((index) => apm - .service({ name: `opbeans-go-${index}`, environment: ENVIRONMENT, agentName: 'go' }) + .service({ name: `synth-go-${index}`, environment: ENVIRONMENT, agentName: 'go' }) .instance('instance') ); const instanceSpans = (instance: Instance) => { From a1150b549deb0e7a633b5835af67283d6f375502 Mon Sep 17 00:00:00 2001 From: Saarika <55930906+saarikabhasi@users.noreply.github.com> Date: Wed, 2 Nov 2022 13:31:45 -0400 Subject: [PATCH 36/86] [App-search] should not show 'nested' option in managed docs (#144333) * [App-search] should not show 'nested' option in managed docs * Changed variable name - IgnoreSchemaType to plural --- .../applications/shared/schema/field_type_select/index.tsx | 7 +++++-- .../public/applications/shared/schema/types.ts | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/schema/field_type_select/index.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/schema/field_type_select/index.tsx index fb6c0f2047b12..8fd744c4ebe6f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/schema/field_type_select/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/schema/field_type_select/index.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiSelect } from '@elastic/eui'; -import { SchemaType } from '../types'; +import { SchemaType, IgnoreSchemaTypes } from '../types'; interface Props { fieldName: string; @@ -25,7 +25,10 @@ export const SchemaFieldTypeSelect: React.FC = ({ disabled, ...rest }) => { - const fieldTypeOptions = Object.values(SchemaType).map((type) => ({ value: type, text: type })); + const fieldTypeOptions = Object.values(SchemaType) + .filter((type) => !IgnoreSchemaTypes.includes(type)) + .map((type) => ({ value: type, text: type })); + return ( Date: Wed, 2 Nov 2022 17:47:57 +0000 Subject: [PATCH 37/86] [Fleet] Fix test packages & add validation test (#144432) * add script to verify test packages * fix test packages * add jest test * validate zip packages * remove commented code --- .../scripts/verify_test_packages/index.js | 9 ++ .../verify_test_packages.test.ts | 15 +++ .../verify_test_packages.ts | 106 ++++++++++++++++++ .../server/services/epm/archive/parse.ts | 50 ++++++--- .../all_assets/0.1.0/manifest.yml | 2 + .../all_assets/0.2.0/manifest.yml | 2 + .../datastreams/0.1.0/manifest.yml | 2 + .../datastreams/0.2.0/manifest.yml | 2 + .../deprecated/0.1.0/manifest.yml | 2 + .../error_handling/0.1.0/manifest.yml | 2 + .../error_handling/0.2.0/manifest.yml | 4 +- .../experimental/0.1.0/manifest.yml | 2 + .../experimental2/0.1.0/manifest.yml | 2 + .../test_packages/filetest/0.1.0/manifest.yml | 2 + .../multiple_versions/0.1.0/manifest.yml | 4 +- .../multiple_versions/0.2.0/manifest.yml | 4 +- .../multiple_versions/0.3.0/manifest.yml | 4 +- .../only_dashboard/0.1.0/manifest.yml | 2 + .../overrides/0.1.0/manifest.yml | 2 + .../package_policy_upgrade/0.1.0/manifest.yml | 2 + .../manifest.yml | 3 + .../0.2.5-non-breaking-change/manifest.yml | 3 + .../0.3.0-remove-test-var/manifest.yml | 3 + .../0.4.0-add-test-var-as-bool/manifest.yml | 3 + .../0.5.0-restructure-inputs/manifest.yml | 3 + .../manifest.yml | 3 + .../manifest.yml | 3 + .../manifest.yml | 3 + .../prerelease/0.1.0-dev.0+abc/manifest.yml | 2 + .../0.1.0/manifest.yml | 2 + 30 files changed, 230 insertions(+), 18 deletions(-) create mode 100644 x-pack/plugins/fleet/scripts/verify_test_packages/index.js create mode 100644 x-pack/plugins/fleet/scripts/verify_test_packages/verify_test_packages.test.ts create mode 100644 x-pack/plugins/fleet/scripts/verify_test_packages/verify_test_packages.ts diff --git a/x-pack/plugins/fleet/scripts/verify_test_packages/index.js b/x-pack/plugins/fleet/scripts/verify_test_packages/index.js new file mode 100644 index 0000000000000..2c3f64739962b --- /dev/null +++ b/x-pack/plugins/fleet/scripts/verify_test_packages/index.js @@ -0,0 +1,9 @@ +/* + * 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. + */ + +require('../../../../../src/setup_node_env'); +require('./verify_test_packages').run(); diff --git a/x-pack/plugins/fleet/scripts/verify_test_packages/verify_test_packages.test.ts b/x-pack/plugins/fleet/scripts/verify_test_packages/verify_test_packages.test.ts new file mode 100644 index 0000000000000..219d101d6e1ad --- /dev/null +++ b/x-pack/plugins/fleet/scripts/verify_test_packages/verify_test_packages.test.ts @@ -0,0 +1,15 @@ +/* + * 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 { verifyAllTestPackages } from './verify_test_packages'; + +describe('Test packages', () => { + test('All test packages should be valid (node scripts/verify_test_packages) ', async () => { + const { errors } = await verifyAllTestPackages(); + expect(errors).toEqual([]); + }); +}); diff --git a/x-pack/plugins/fleet/scripts/verify_test_packages/verify_test_packages.ts b/x-pack/plugins/fleet/scripts/verify_test_packages/verify_test_packages.ts new file mode 100644 index 0000000000000..ff7347e599ee6 --- /dev/null +++ b/x-pack/plugins/fleet/scripts/verify_test_packages/verify_test_packages.ts @@ -0,0 +1,106 @@ +/* + * 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 path from 'path'; +import { readdirSync, statSync, readFile } from 'fs'; +import { promisify } from 'util'; + +import partition from 'lodash/partition'; +import type { Logger } from '@kbn/core/server'; + +import { ToolingLog } from '@kbn/tooling-log'; + +import { + _generatePackageInfoFromPaths, + generatePackageInfoFromArchiveBuffer, +} from '../../server/services/epm/archive/parse'; + +const readFileAsync = promisify(readFile); + +const TEST_PACKAGE_DIRECTORIES = [ + '../../../../test/fleet_api_integration/apis/fixtures/bundled_packages', + '../../../../test/fleet_api_integration/apis/fixtures/test_packages', + '../../../../test/fleet_api_integration/apis/fixtures/package_verification/packages', +]; + +const getAllPathsFromDir = (dir: string): string[] => + readdirSync(dir).flatMap((file) => { + const joinedPath = path.join(dir, file); + if (!statSync(joinedPath).isDirectory()) return joinedPath; + + return getAllPathsFromDir(joinedPath); + }); + +const getAllPackagesFromDir = (packagesDir: string) => + readdirSync(packagesDir).flatMap((packageDir) => { + const packagePath = path.join(packagesDir, packageDir); + + if (packagePath.endsWith('.zip')) return packagePath; + + if (!statSync(packagePath).isDirectory()) return []; + + return readdirSync(packagePath) + .map((version) => path.join(packagePath, version)) + .filter((versionPath) => statSync(versionPath).isDirectory() || packagePath.endsWith('.zip')); + }); + +export const run = async () => { + const logger = new ToolingLog({ + level: 'info', + writeTo: process.stdout, + }); + + const { errors } = await verifyAllTestPackages(logger); + + if (errors.length) { + logger.error(`${errors.length} packages failed validation. Exiting with error.`); + process.exit(1); + } +}; + +export const verifyAllTestPackages = async ( + logger?: ToolingLog | Logger +): Promise<{ successCount: number; errors: Error[] }> => { + const errors = []; + let successCount = 0; + for (const dir of TEST_PACKAGE_DIRECTORIES) { + const packageVersionPaths = getAllPackagesFromDir(path.join(__dirname, dir)); + + const [zips, dirs] = partition(packageVersionPaths, (p) => p.endsWith('.zip')); + + for (const zipPath of zips) { + const buffer = await readFileAsync(zipPath); + + try { + const { packageInfo } = await generatePackageInfoFromArchiveBuffer( + buffer, + 'application/zip' + ); + logger?.info(`Successfully parsed zip pkg ${packageInfo.name}-${packageInfo.version}`); + successCount++; + } catch (e) { + logger?.error(`Error parsing ${zipPath} : ${e}`); + errors.push(e); + } + } + + const allPackageDirPaths = dirs.map(getAllPathsFromDir); + for (const [i, packagePaths] of allPackageDirPaths.entries()) { + const topLevelDir = packageVersionPaths[i]; + try { + const packageInfo = await _generatePackageInfoFromPaths(packagePaths, topLevelDir); + logger?.info(`Successfully parsed ${packageInfo.name}-${packageInfo.version}`); + successCount++; + } catch (e) { + logger?.error(`Error parsing ${topLevelDir} : ${e}`); + errors.push(e); + } + } + } + + return { successCount, errors }; +}; diff --git a/x-pack/plugins/fleet/server/services/epm/archive/parse.ts b/x-pack/plugins/fleet/server/services/epm/archive/parse.ts index 3fe7087ffb79b..ba190a30c8c0d 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/parse.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/parse.ts @@ -5,6 +5,11 @@ * 2.0. */ +import { readFile } from 'fs'; + +import { promisify } from 'util'; +import path from 'path'; + import { merge } from '@kbn/std'; import yaml from 'js-yaml'; import { pick, uniq } from 'lodash'; @@ -32,6 +37,7 @@ import { pkgToPkgKey } from '../registry'; import { unpackBufferEntries } from '.'; +const readFileAsync = promisify(readFile); const MANIFESTS: Record = {}; const MANIFEST_NAME = 'manifest.yml'; @@ -136,9 +142,9 @@ export async function generatePackageInfoFromArchiveBuffer( ): Promise<{ paths: string[]; packageInfo: ArchivePackage }> { const entries = await unpackBufferEntries(archiveBuffer, contentType); const paths: string[] = []; - entries.forEach(({ path, buffer }) => { - paths.push(path); - if (path.endsWith(MANIFEST_NAME) && buffer) MANIFESTS[path] = buffer; + entries.forEach(({ path: bufferPath, buffer }) => { + paths.push(bufferPath); + if (bufferPath.endsWith(MANIFEST_NAME) && buffer) MANIFESTS[bufferPath] = buffer; }); return { @@ -147,17 +153,33 @@ export async function generatePackageInfoFromArchiveBuffer( }; } -function parseAndVerifyArchive(paths: string[]): ArchivePackage { +/* +This is a util function for verifying packages from a directory not an archive. +It is only to be called from test scripts. +*/ +export async function _generatePackageInfoFromPaths( + paths: string[], + topLevelDir: string +): Promise { + await Promise.all( + paths.map(async (filePath) => { + if (filePath.endsWith(MANIFEST_NAME)) MANIFESTS[filePath] = await readFileAsync(filePath); + }) + ); + return parseAndVerifyArchive(paths, topLevelDir); +} + +function parseAndVerifyArchive(paths: string[], topLevelDirOverride?: string): ArchivePackage { // The top-level directory must match pkgName-pkgVersion, and no other top-level files or directories may be present - const toplevelDir = paths[0].split('/')[0]; - paths.forEach((path) => { - if (path.split('/')[0] !== toplevelDir) { + const toplevelDir = topLevelDirOverride || paths[0].split('/')[0]; + paths.forEach((filePath) => { + if (!filePath.startsWith(toplevelDir)) { throw new PackageInvalidArchiveError('Package contains more than one top-level directory.'); } }); // The package must contain a manifest file ... - const manifestFile = `${toplevelDir}/${MANIFEST_NAME}`; + const manifestFile = path.join(toplevelDir, MANIFEST_NAME); const manifestBuffer = MANIFESTS[manifestFile]; if (!paths.includes(manifestFile) || !manifestBuffer) { throw new PackageInvalidArchiveError(`Package must contain a top-level ${MANIFEST_NAME} file.`); @@ -189,7 +211,7 @@ function parseAndVerifyArchive(paths: string[]): ArchivePackage { // Package name and version from the manifest must match those from the toplevel directory const pkgKey = pkgToPkgKey({ name: parsed.name, version: parsed.version }); - if (toplevelDir !== pkgKey) { + if (!topLevelDirOverride && toplevelDir !== pkgKey) { throw new PackageInvalidArchiveError( `Name ${parsed.name} and version ${parsed.version} do not match top-level directory ${toplevelDir}` ); @@ -238,9 +260,9 @@ export function parseAndVerifyDataStreams( // pick all paths matching name-version/data_stream/DATASTREAM_PATH/... // from those, pick all unique data stream paths paths - .filter((path) => path.startsWith(`${pkgKey}/data_stream/`)) - .forEach((path) => { - const parts = path.split('/'); + .filter((filePath) => filePath.startsWith(`${pkgKey}/data_stream/`)) + .forEach((filePath) => { + const parts = filePath.split('/'); if (parts.length > 2 && parts[2]) dataStreamPaths.push(parts[2]); }); @@ -280,8 +302,8 @@ export function parseAndVerifyDataStreams( } let ingestPipeline; - const ingestPipelinePaths = paths.filter((path) => - path.startsWith(`${pkgKey}/data_stream/${dataStreamPath}/elasticsearch/ingest_pipeline`) + const ingestPipelinePaths = paths.filter((filePath) => + filePath.startsWith(`${pkgKey}/data_stream/${dataStreamPath}/elasticsearch/ingest_pipeline`) ); if ( diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/manifest.yml index 3c11b5103fbeb..fa6e65f497068 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/manifest.yml @@ -7,6 +7,8 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet requirement: elasticsearch: diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/manifest.yml index 70da51a14bce8..33c7c020b4c49 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.2.0/manifest.yml @@ -7,6 +7,8 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet requirement: elasticsearch: diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/datastreams/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/datastreams/0.1.0/manifest.yml index 28979cca0771b..9cddc5f1f863e 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/datastreams/0.1.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/datastreams/0.1.0/manifest.yml @@ -7,6 +7,8 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet requirement: elasticsearch: diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/datastreams/0.2.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/datastreams/0.2.0/manifest.yml index b389a86702fa8..55c50f340d110 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/datastreams/0.2.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/datastreams/0.2.0/manifest.yml @@ -7,6 +7,8 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet requirement: elasticsearch: diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/deprecated/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/deprecated/0.1.0/manifest.yml index 755c49e1af388..da357e14a0d45 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/deprecated/0.1.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/deprecated/0.1.0/manifest.yml @@ -7,6 +7,8 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet conditions: # Version number is not compatible with current version diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/manifest.yml index 312cd2874804c..4b5afcac953eb 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/manifest.yml @@ -7,6 +7,8 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet requirement: elasticsearch: diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/manifest.yml index c473ce29b87d5..d45489caee7b4 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/manifest.yml @@ -7,7 +7,9 @@ categories: [] release: beta type: integration license: basic - +owner: + github: elastic/fleet + requirement: elasticsearch: versions: '>7.7.0' diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/experimental/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/experimental/0.1.0/manifest.yml index 62f605c5828f8..c626ae209c490 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/experimental/0.1.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/experimental/0.1.0/manifest.yml @@ -7,6 +7,8 @@ categories: [] release: experimental type: integration license: basic +owner: + github: elastic/fleet requirement: elasticsearch: diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/experimental2/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/experimental2/0.1.0/manifest.yml index 766835dbde037..c72242b1bc7ce 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/experimental2/0.1.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/experimental2/0.1.0/manifest.yml @@ -7,6 +7,8 @@ categories: [] release: experimental type: integration license: basic +owner: + github: elastic/fleet requirement: elasticsearch: diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/filetest/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/filetest/0.1.0/manifest.yml index c4fb3f967913d..f5b8ba129cb61 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/filetest/0.1.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/filetest/0.1.0/manifest.yml @@ -10,6 +10,8 @@ release: beta # The default type is integration and will be set if empty. type: integration license: basic +owner: + github: elastic/fleet requirement: elasticsearch: diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/multiple_versions/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/multiple_versions/0.1.0/manifest.yml index f4b6eccbda957..16cb91526651f 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/multiple_versions/0.1.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/multiple_versions/0.1.0/manifest.yml @@ -7,7 +7,9 @@ categories: [] release: beta type: integration license: basic - +owner: + github: elastic/fleet + requirement: elasticsearch: versions: '>7.7.0' diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/multiple_versions/0.2.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/multiple_versions/0.2.0/manifest.yml index be7c93484d987..d3f7f8b5f88d4 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/multiple_versions/0.2.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/multiple_versions/0.2.0/manifest.yml @@ -7,7 +7,9 @@ categories: [] release: beta type: integration license: basic - +owner: + github: elastic/fleet + requirement: elasticsearch: versions: '>7.7.0' diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/multiple_versions/0.3.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/multiple_versions/0.3.0/manifest.yml index 630788b00fca7..15411c855cd09 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/multiple_versions/0.3.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/multiple_versions/0.3.0/manifest.yml @@ -7,7 +7,9 @@ categories: [] release: beta type: integration license: basic - +owner: + github: elastic/fleet + requirement: elasticsearch: versions: '>7.7.0' diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/manifest.yml index 129ee4e2c133c..48b7db29c8296 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/only_dashboard/0.1.0/manifest.yml @@ -7,6 +7,8 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet requirement: elasticsearch: diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/overrides/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/overrides/0.1.0/manifest.yml index ba9fd0fada006..2189a9c30754c 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/overrides/0.1.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/overrides/0.1.0/manifest.yml @@ -7,6 +7,8 @@ categories: ['security'] release: beta type: integration license: basic +owner: + github: elastic/fleet requirement: elasticsearch: diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.1.0/manifest.yml index 7856c6eb9df34..aa64596bba6a0 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.1.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.1.0/manifest.yml @@ -7,6 +7,8 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet requirement: elasticsearch: diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.2.0-add-non-required-test-var/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.2.0-add-non-required-test-var/manifest.yml index 58688c78519f6..2d5dcb7f8d6d4 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.2.0-add-non-required-test-var/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.2.0-add-non-required-test-var/manifest.yml @@ -7,6 +7,9 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet + requirement: elasticsearch: versions: '>7.7.0' diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.2.5-non-breaking-change/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.2.5-non-breaking-change/manifest.yml index 2105ee451ffae..04d03dd2b5b8d 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.2.5-non-breaking-change/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.2.5-non-breaking-change/manifest.yml @@ -7,6 +7,9 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet + requirement: elasticsearch: versions: '>7.7.0' diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.3.0-remove-test-var/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.3.0-remove-test-var/manifest.yml index 5f3f493869273..e58b0406d75ab 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.3.0-remove-test-var/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.3.0-remove-test-var/manifest.yml @@ -7,6 +7,9 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet + requirement: elasticsearch: versions: '>7.7.0' diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.4.0-add-test-var-as-bool/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.4.0-add-test-var-as-bool/manifest.yml index 8cd629c98d3eb..b50e3d1378722 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.4.0-add-test-var-as-bool/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.4.0-add-test-var-as-bool/manifest.yml @@ -7,6 +7,9 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet + requirement: elasticsearch: versions: '>7.7.0' diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.5.0-restructure-inputs/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.5.0-restructure-inputs/manifest.yml index 6c26bb545424d..d1967d77c6a2e 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.5.0-restructure-inputs/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.5.0-restructure-inputs/manifest.yml @@ -7,6 +7,9 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet + requirement: elasticsearch: versions: '>7.7.0' diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.6.0-restructure-policy-templates/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.6.0-restructure-policy-templates/manifest.yml index 82c9d7059e2a6..a9b2a39028e3f 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.6.0-restructure-policy-templates/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.6.0-restructure-policy-templates/manifest.yml @@ -7,6 +7,9 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet + requirement: elasticsearch: versions: '>7.7.0' diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/manifest.yml index 346ea4d2bcfad..9ad64c2d8e54b 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.7.0-add-stream-with-no-vars/manifest.yml @@ -7,6 +7,9 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet + requirement: elasticsearch: versions: '>7.7.0' diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/manifest.yml index bd61453fdaac8..2ea0bfae993a1 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/package_policy_upgrade/0.8.0-add-vars-to-stream-with-no-vars/manifest.yml @@ -7,6 +7,9 @@ categories: [] release: beta type: integration license: basic +owner: + github: elastic/fleet + requirement: elasticsearch: versions: '>7.7.0' diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/prerelease/0.1.0-dev.0+abc/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/prerelease/0.1.0-dev.0+abc/manifest.yml index a0adb184cfc59..a01ab8e438d55 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/prerelease/0.1.0-dev.0+abc/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/prerelease/0.1.0-dev.0+abc/manifest.yml @@ -7,6 +7,8 @@ categories: ['security'] release: beta type: integration license: basic +owner: + github: elastic/fleet requirement: elasticsearch: diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/with_required_variables/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/with_required_variables/0.1.0/manifest.yml index 472888818e717..cb9799e4d04ef 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/with_required_variables/0.1.0/manifest.yml +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/with_required_variables/0.1.0/manifest.yml @@ -10,6 +10,8 @@ release: beta # The default type is integration and will be set if empty. type: integration license: basic +owner: + github: elastic/fleet requirement: elasticsearch: From 259caffdd2c3f7b42dfb8eb17906c63ddfd475e1 Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Wed, 2 Nov 2022 10:56:02 -0700 Subject: [PATCH 38/86] [Input Controls Vis] Fixes list control popover background color in dark mode (#144204) * Fixed combo box popover background color in dark mode * Wrap formrow instead * Removed unused var * Updated snapshots --- .../__snapshots__/list_control.test.tsx.snap | 96 ++++++++++--------- .../components/vis/input_control_vis.tsx | 2 + .../public/components/vis/list_control.tsx | 23 +++-- .../public/vis_controller.tsx | 4 + 4 files changed, 71 insertions(+), 54 deletions(-) diff --git a/src/plugins/input_control_vis/public/components/vis/__snapshots__/list_control.test.tsx.snap b/src/plugins/input_control_vis/public/components/vis/__snapshots__/list_control.test.tsx.snap index eab52795fefaa..f8e7048f7a9d6 100644 --- a/src/plugins/input_control_vis/public/components/vis/__snapshots__/list_control.test.tsx.snap +++ b/src/plugins/input_control_vis/public/components/vis/__snapshots__/list_control.test.tsx.snap @@ -1,53 +1,61 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`disableMsg 1`] = ` - - - + + + + `; exports[`renders ListControl 1`] = ` - - - + + + + `; diff --git a/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx b/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx index faeae649021df..3331f0eb012d5 100644 --- a/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx +++ b/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx @@ -39,6 +39,7 @@ interface InputControlVisProps { hasChanges: () => boolean; hasValues: () => boolean; refreshControl: (controlIndex: number, query: any) => Promise; + isDarkMode?: boolean; } export class InputControlVis extends Component { @@ -83,6 +84,7 @@ export class InputControlVis extends Component { fetchOptions={(query) => { this.props.refreshControl(index, query); }} + isDarkMode={this.props.isDarkMode} /> ); } else if (isRangeControl(control)) { diff --git a/src/plugins/input_control_vis/public/components/vis/list_control.tsx b/src/plugins/input_control_vis/public/components/vis/list_control.tsx index 6ffc342bede2a..729f095392744 100644 --- a/src/plugins/input_control_vis/public/components/vis/list_control.tsx +++ b/src/plugins/input_control_vis/public/components/vis/list_control.tsx @@ -10,7 +10,7 @@ import React, { PureComponent } from 'react'; import _ from 'lodash'; import { injectI18n, InjectedIntlProps } from '@kbn/i18n-react'; -import { EuiFieldText, EuiComboBox } from '@elastic/eui'; +import { EuiFieldText, EuiComboBox, EuiThemeProvider } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormRow } from './form_row'; @@ -31,6 +31,7 @@ export type ListControlUiProps = InjectedIntlProps & { controlIndex: number; stageFilter: (controlIndex: number, value: any) => void; fetchOptions?: (searchValue: string) => void; + isDarkMode?: boolean; }; class ListControlUi extends PureComponent { @@ -164,15 +165,17 @@ class ListControlUi extends PureComponent - {this.renderControl()} - + + + {this.renderControl()} + + ); } } diff --git a/src/plugins/input_control_vis/public/vis_controller.tsx b/src/plugins/input_control_vis/public/vis_controller.tsx index 8916f311bf912..e13b29d41a6e9 100644 --- a/src/plugins/input_control_vis/public/vis_controller.tsx +++ b/src/plugins/input_control_vis/public/vis_controller.tsx @@ -42,6 +42,7 @@ export const createInputControlVisController = ( updateSubsciption: any; timeFilterSubscription: Subscription; visParams?: InputControlVisParams; + isDarkMode?: boolean; constructor() { this.controls = []; @@ -59,6 +60,8 @@ export const createInputControlVisController = ( isLoaded = false; } }); + + this.isDarkMode = deps.core.uiSettings.get('theme:darkMode'); } async render(visParams: InputControlVisParams) { @@ -100,6 +103,7 @@ export const createInputControlVisController = ( hasChanges={this.hasChanges} hasValues={this.hasValues} refreshControl={this.refreshControl} + isDarkMode={this.isDarkMode} /> , From 610f04a833b439bef66e1257b3d405a4b8e5b6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Wed, 2 Nov 2022 18:57:50 +0100 Subject: [PATCH 39/86] Revert "Revert "[Guided onboarding] Fix multiple API requests when fetching guide state (#144160)"" (#144459) * Revert "Revert "[Guided onboarding] Fix multiple API requests when fetching guide state (#144160)"" This reverts commit a4445f959da98c03da2a755da26d63a36f44b8be. * [Guided onboarding] Fix type errors --- packages/kbn-guided-onboarding/index.ts | 9 +- .../public/components/guide_panel.test.tsx | 18 +- .../public/components/guide_panel.tsx | 2 +- .../public/services/api.mocks.ts | 18 ++ .../public/services/api.test.ts | 95 +++++-- .../guided_onboarding/public/services/api.ts | 57 ++-- x-pack/test/functional/apps/infra/tour.ts | 257 +++++++++--------- 7 files changed, 283 insertions(+), 173 deletions(-) diff --git a/packages/kbn-guided-onboarding/index.ts b/packages/kbn-guided-onboarding/index.ts index f98f330cd4be3..b22a8a2057c16 100644 --- a/packages/kbn-guided-onboarding/index.ts +++ b/packages/kbn-guided-onboarding/index.ts @@ -6,6 +6,13 @@ * Side Public License, v 1. */ -export type { GuideState, GuideId, GuideStepIds, StepStatus, GuideStep } from './src/types'; +export type { + GuideState, + GuideId, + GuideStepIds, + StepStatus, + GuideStep, + GuideStatus, +} from './src/types'; export { GuideCard, ObservabilityLinkCard } from './src/components/landing_page'; export type { UseCase } from './src/components/landing_page'; diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx index 9420e05e9d3fe..3bf4ae8f91c8c 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx @@ -109,10 +109,21 @@ describe('Guided setup', () => { }); describe('Button component', () => { - // TODO check for the correct button behavior once https://github.com/elastic/kibana/issues/141129 is implemented - test.skip('should be disabled in there is no active guide', async () => { + test('should be hidden in there is no guide state', async () => { const { exists } = testBed; - expect(exists('disabledGuideButton')).toBe(true); + expect(exists('guideButton')).toBe(false); + expect(exists('guidePanel')).toBe(false); + }); + + test('should be hidden if the guide is not active', async () => { + const { component, exists } = testBed; + + await updateComponentWithState( + component, + { ...mockActiveTestGuideState, isActive: false }, + true + ); + expect(exists('guideButton')).toBe(false); expect(exists('guidePanel')).toBe(false); }); @@ -123,7 +134,6 @@ describe('Guided setup', () => { // Enable the "test" guide await updateComponentWithState(component, mockActiveTestGuideState, true); - expect(exists('disabledGuideButton')).toBe(false); expect(exists('guideButton')).toBe(true); expect(find('guideButton').text()).toEqual('Setup guide'); }); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx index 759f4a83c6852..823637b23247a 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx @@ -139,7 +139,7 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { // TODO handle loading, error state // https://github.com/elastic/kibana/issues/139799, https://github.com/elastic/kibana/issues/139798 - if (!guideConfig) { + if (!guideConfig || !guideState || !guideState.isActive) { // TODO button show/hide logic https://github.com/elastic/kibana/issues/141129 return null; } diff --git a/src/plugins/guided_onboarding/public/services/api.mocks.ts b/src/plugins/guided_onboarding/public/services/api.mocks.ts index 2294607f91b38..47b7f6c9900e0 100644 --- a/src/plugins/guided_onboarding/public/services/api.mocks.ts +++ b/src/plugins/guided_onboarding/public/services/api.mocks.ts @@ -77,6 +77,24 @@ export const testGuideStep2InProgressState: GuideState = { ], }; +export const readyToCompleteGuideState: GuideState = { + ...testGuideStep1ActiveState, + steps: [ + { + ...testGuideStep1ActiveState.steps[0], + status: 'complete', + }, + { + ...testGuideStep1ActiveState.steps[1], + status: 'complete', + }, + { + ...testGuideStep1ActiveState.steps[2], + status: 'complete', + }, + ], +}; + export const testGuideNotActiveState: GuideState = { ...testGuideStep1ActiveState, isActive: false, diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts index 56a5755f0ee55..24ade7de1a849 100644 --- a/src/plugins/guided_onboarding/public/services/api.test.ts +++ b/src/plugins/guided_onboarding/public/services/api.test.ts @@ -11,6 +11,7 @@ import { httpServiceMock } from '@kbn/core/public/mocks'; import type { GuideState } from '@kbn/guided-onboarding'; import { firstValueFrom, Subscription } from 'rxjs'; +import { GuideStatus } from '@kbn/guided-onboarding'; import { API_BASE_PATH } from '../../common/constants'; import { ApiService } from './api'; import { @@ -24,12 +25,14 @@ import { testIntegration, wrongIntegration, testGuideStep2InProgressState, + readyToCompleteGuideState, } from './api.mocks'; describe('GuidedOnboarding ApiService', () => { let httpClient: jest.Mocked; let apiService: ApiService; let subscription: Subscription; + let anotherSubscription: Subscription; beforeEach(() => { httpClient = httpServiceMock.createStartContract({ basePath: '/base/path' }); @@ -41,9 +44,8 @@ describe('GuidedOnboarding ApiService', () => { }); afterEach(() => { - if (subscription) { - subscription.unsubscribe(); - } + subscription?.unsubscribe(); + anotherSubscription?.unsubscribe(); jest.restoreAllMocks(); }); @@ -53,6 +55,64 @@ describe('GuidedOnboarding ApiService', () => { expect(httpClient.get).toHaveBeenCalledTimes(1); expect(httpClient.get).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { query: { active: true }, + signal: new AbortController().signal, + }); + }); + + it(`doesn't send multiple requests when there are several subscriptions`, () => { + subscription = apiService.fetchActiveGuideState$().subscribe(); + anotherSubscription = apiService.fetchActiveGuideState$().subscribe(); + expect(httpClient.get).toHaveBeenCalledTimes(1); + }); + + it(`re-sends the request if the previous one failed`, async () => { + httpClient.get.mockRejectedValueOnce(new Error('request failed')); + subscription = apiService.fetchActiveGuideState$().subscribe(); + // wait until the request fails + await new Promise((resolve) => process.nextTick(resolve)); + anotherSubscription = apiService.fetchActiveGuideState$().subscribe(); + expect(httpClient.get).toHaveBeenCalledTimes(2); + }); + + it(`re-sends the request if there is no guide state and there is another subscription`, async () => { + httpClient.get.mockResolvedValueOnce({ + state: [], + }); + subscription = apiService.fetchActiveGuideState$().subscribe(); + // wait until the request completes + await new Promise((resolve) => process.nextTick(resolve)); + anotherSubscription = apiService.fetchActiveGuideState$().subscribe(); + expect(httpClient.get).toHaveBeenCalledTimes(2); + }); + + it(`doesn't send multiple requests in a loop when there is no state`, async () => { + httpClient.get.mockResolvedValueOnce({ + state: [], + }); + subscription = apiService.fetchActiveGuideState$().subscribe(); + // wait until the request completes + await new Promise((resolve) => process.nextTick(resolve)); + expect(httpClient.get).toHaveBeenCalledTimes(1); + }); + + it(`re-sends the request if the subscription was unsubscribed before the request completed`, async () => { + httpClient.get.mockImplementationOnce(() => { + return new Promise((resolve) => setTimeout(resolve)); + }); + // subscribe and immediately unsubscribe + apiService.fetchActiveGuideState$().subscribe().unsubscribe(); + anotherSubscription = apiService.fetchActiveGuideState$().subscribe(); + expect(httpClient.get).toHaveBeenCalledTimes(2); + }); + + it(`the second subscription gets the state broadcast to it`, (done) => { + // first subscription + apiService.fetchActiveGuideState$().subscribe(); + // second subscription + anotherSubscription = apiService.fetchActiveGuideState$().subscribe((state) => { + if (state) { + done(); + } }); }); @@ -95,6 +155,17 @@ describe('GuidedOnboarding ApiService', () => { body: JSON.stringify(updatedState), }); }); + + it('the completed state is being broadcast after the update', async () => { + const completedState = { + ...readyToCompleteGuideState, + isActive: false, + status: 'complete' as GuideStatus, + }; + await apiService.updateGuideState(completedState, false); + const state = await firstValueFrom(apiService.fetchActiveGuideState$()); + expect(state).toMatchObject(completedState); + }); }); describe('isGuideStepActive$', () => { @@ -149,24 +220,6 @@ describe('GuidedOnboarding ApiService', () => { }); describe('completeGuide', () => { - const readyToCompleteGuideState: GuideState = { - ...testGuideStep1ActiveState, - steps: [ - { - ...testGuideStep1ActiveState.steps[0], - status: 'complete', - }, - { - ...testGuideStep1ActiveState.steps[1], - status: 'complete', - }, - { - ...testGuideStep1ActiveState.steps[2], - status: 'complete', - }, - ], - }; - beforeEach(async () => { await apiService.updateGuideState(readyToCompleteGuideState, false); }); diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index cd33f9505c546..89790024cdde7 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -7,7 +7,7 @@ */ import { HttpSetup } from '@kbn/core/public'; -import { BehaviorSubject, map, from, concatMap, of, Observable, firstValueFrom } from 'rxjs'; +import { BehaviorSubject, map, concatMap, of, Observable, firstValueFrom } from 'rxjs'; import type { GuideState, GuideId, GuideStep, GuideStepIds } from '@kbn/guided-onboarding'; import { GuidedOnboardingApi } from '../types'; @@ -26,6 +26,7 @@ import { API_BASE_PATH } from '../../common/constants'; export class ApiService implements GuidedOnboardingApi { private client: HttpSetup | undefined; private onboardingGuideState$!: BehaviorSubject; + private isGuideStateLoading: boolean | undefined; public isGuidePanelOpen$: BehaviorSubject = new BehaviorSubject(false); public setup(httpClient: HttpSetup): void { @@ -33,30 +34,46 @@ export class ApiService implements GuidedOnboardingApi { this.onboardingGuideState$ = new BehaviorSubject(undefined); } + private createGetStateObservable(): Observable { + return new Observable((observer) => { + const controller = new AbortController(); + const signal = controller.signal; + this.isGuideStateLoading = true; + this.client!.get<{ state: GuideState[] }>(`${API_BASE_PATH}/state`, { + query: { + active: true, + }, + signal, + }) + .then((response) => { + this.isGuideStateLoading = false; + // There should only be 1 active guide + const hasState = response.state.length === 1; + if (hasState) { + this.onboardingGuideState$.next(response.state[0]); + } + observer.complete(); + }) + .catch((error) => { + this.isGuideStateLoading = false; + observer.error(error); + }); + return () => { + this.isGuideStateLoading = false; + controller.abort(); + }; + }); + } + /** * An Observable with the active guide state. * Initially the state is fetched from the backend. * Subsequently, the observable is updated automatically, when the state changes. */ public fetchActiveGuideState$(): Observable { - // TODO add error handling if this.client has not been initialized or request fails return this.onboardingGuideState$.pipe( concatMap((state) => - state === undefined - ? from( - this.client!.get<{ state: GuideState[] }>(`${API_BASE_PATH}/state`, { - query: { - active: true, - }, - }) - ).pipe( - map((response) => { - // There should only be 1 active guide - const hasState = response.state.length === 1; - return hasState ? response.state[0] : undefined; - }) - ) - : of(state) + !state && !this.isGuideStateLoading ? this.createGetStateObservable() : of(state) ) ); } @@ -83,7 +100,7 @@ export class ApiService implements GuidedOnboardingApi { /** * Updates the SO with the updated guide state and refreshes the observables * This is largely used internally and for tests - * @param {GuideState} guideState the updated guide state + * @param {GuideState} newState the updated guide state * @param {boolean} panelState boolean to determine whether the dropdown panel should open or not * @return {Promise} a promise with the updated guide state */ @@ -99,8 +116,8 @@ export class ApiService implements GuidedOnboardingApi { const response = await this.client.put<{ state: GuideState }>(`${API_BASE_PATH}/state`, { body: JSON.stringify(newState), }); - // If the guide has been deactivated, we return undefined - this.onboardingGuideState$.next(newState.isActive ? newState : undefined); + // broadcast the newState + this.onboardingGuideState$.next(newState); this.isGuidePanelOpen$.next(panelState); return response; } catch (error) { diff --git a/x-pack/test/functional/apps/infra/tour.ts b/x-pack/test/functional/apps/infra/tour.ts index 81ca07eef4c05..2f9dfa314c168 100644 --- a/x-pack/test/functional/apps/infra/tour.ts +++ b/x-pack/test/functional/apps/infra/tour.ts @@ -15,6 +15,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'infraHome']); const find = getService('find'); const supertest = getService('supertest'); + const deployment = getService('deployment'); const setInitialTourState = async (activeStep?: number) => { await browser.setLocalStorageItem(observTourStepStorageKey, String(activeStep || 1)); @@ -23,8 +24,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Onboarding Observability tour', function () { this.tags('includeFirefox'); - + let isCloud: boolean; before(async () => { + isCloud = await deployment.isCloud(); await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); await pageObjects.common.navigateToApp('observability'); // Need to increase the browser height so the tour steps fit to screen @@ -37,131 +39,134 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); describe('Tour enabled', () => { - beforeEach(async () => { - // Activate the Observability guide, step 3, in order to trigger the EuiTour - await supertest - .put(`/api/guided_onboarding/state`) - .set('kbn-xsrf', 'true') - .send({ - status: 'in_progress', - guideId: 'observability', - isActive: true, - steps: [ - { - id: 'add_data', - status: 'complete', - }, - { - id: 'view_dashboard', - status: 'complete', - }, - { - id: 'tour_observability', - status: 'in_progress', - }, - ], - }) - .expect(200); - }); - - it('can complete tour', async () => { - await setInitialTourState(); - - // Step 1: Overview - await pageObjects.infraHome.waitForTourStep('overviewStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); - - // Step 2: Streams - await pageObjects.infraHome.waitForTourStep('streamStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('streamStep'); - - // Step 3: Metrics explorer - await pageObjects.infraHome.waitForTourStep('metricsExplorerStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('metricsExplorerStep'); - - // Step 4: Services - await pageObjects.infraHome.waitForTourStep('servicesStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('servicesStep'); - - // Step 5: Alerts - await pageObjects.infraHome.waitForTourStep('alertStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('alertStep'); - - // Step 6: Guided setup - await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); - await pageObjects.infraHome.clickTourEndButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); - }); - - it('can skip tour', async () => { - await setInitialTourState(); - - await pageObjects.infraHome.waitForTourStep('overviewStep'); - await pageObjects.infraHome.clickTourSkipButton(); - - // Verify current step ("Overview") is not displayed - await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); - // Verify next step ("Streams") is not displayed - await pageObjects.infraHome.ensureTourStepIsClosed('streamStep'); - - await browser.refresh(); - - // Verify current step ("Overview") is not displayed after browser refresh, - // i.e., localStorage has been updated to not show the tour again - await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); - }); - - it('can start mid-tour', async () => { - await setInitialTourState(5); - - // Step 5: Alerts - await pageObjects.infraHome.waitForTourStep('alertStep'); - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('alertStep'); - - // Step 6: Guided setup - await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); - await pageObjects.infraHome.clickTourEndButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); - }); - - it('navigates the user to the guided setup step', async () => { - // For brevity, starting the tour at step 5 - await setInitialTourState(5); - - await pageObjects.infraHome.waitForTourStep('alertStep'); - - // Click on Alerts link - await (await find.byCssSelector('[data-nav-id="alerts"]')).click(); - - // Verify user correctly navigated to the Alerts page - const alertsPageUrl = await browser.getCurrentUrl(); - expect(alertsPageUrl).to.contain('/app/observability/alerts'); - - // Verify Step 5 persists on Alerts page, then continue with tour - await pageObjects.infraHome.waitForTourStep('alertStep'); - await pageObjects.infraHome.clickTourNextButton(); - - // Verify user navigated back to the overview page, and guided setup step renders (Step 6) - await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); - const overviewPageUrl = await browser.getCurrentUrl(); - expect(overviewPageUrl).to.contain('/app/observability/overview'); - }); - - it('ends the tour if the user clicks on the guided setup button', async () => { - // For brevity, starting the tour at step 5, "Alerts" - await setInitialTourState(5); - - await pageObjects.infraHome.clickTourNextButton(); - await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); - await pageObjects.infraHome.clickGuidedSetupButton(); - await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); - }); + // only run these tests on Cloud + if (isCloud) { + beforeEach(async () => { + // Activate the Observability guide, step 3, in order to trigger the EuiTour + await supertest + .put(`/api/guided_onboarding/state`) + .set('kbn-xsrf', 'true') + .send({ + status: 'in_progress', + guideId: 'observability', + isActive: true, + steps: [ + { + id: 'add_data', + status: 'complete', + }, + { + id: 'view_dashboard', + status: 'complete', + }, + { + id: 'tour_observability', + status: 'in_progress', + }, + ], + }) + .expect(200); + }); + + it('can complete tour', async () => { + await setInitialTourState(); + + // Step 1: Overview + await pageObjects.infraHome.waitForTourStep('overviewStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); + + // Step 2: Streams + await pageObjects.infraHome.waitForTourStep('streamStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('streamStep'); + + // Step 3: Metrics explorer + await pageObjects.infraHome.waitForTourStep('metricsExplorerStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('metricsExplorerStep'); + + // Step 4: Services + await pageObjects.infraHome.waitForTourStep('servicesStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('servicesStep'); + + // Step 5: Alerts + await pageObjects.infraHome.waitForTourStep('alertStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('alertStep'); + + // Step 6: Guided setup + await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); + await pageObjects.infraHome.clickTourEndButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); + }); + + it('can skip tour', async () => { + await setInitialTourState(); + + await pageObjects.infraHome.waitForTourStep('overviewStep'); + await pageObjects.infraHome.clickTourSkipButton(); + + // Verify current step ("Overview") is not displayed + await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); + // Verify next step ("Streams") is not displayed + await pageObjects.infraHome.ensureTourStepIsClosed('streamStep'); + + await browser.refresh(); + + // Verify current step ("Overview") is not displayed after browser refresh, + // i.e., localStorage has been updated to not show the tour again + await pageObjects.infraHome.ensureTourStepIsClosed('overviewStep'); + }); + + it('can start mid-tour', async () => { + await setInitialTourState(5); + + // Step 5: Alerts + await pageObjects.infraHome.waitForTourStep('alertStep'); + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('alertStep'); + + // Step 6: Guided setup + await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); + await pageObjects.infraHome.clickTourEndButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); + }); + + it('navigates the user to the guided setup step', async () => { + // For brevity, starting the tour at step 5 + await setInitialTourState(5); + + await pageObjects.infraHome.waitForTourStep('alertStep'); + + // Click on Alerts link + await (await find.byCssSelector('[data-nav-id="alerts"]')).click(); + + // Verify user correctly navigated to the Alerts page + const alertsPageUrl = await browser.getCurrentUrl(); + expect(alertsPageUrl).to.contain('/app/observability/alerts'); + + // Verify Step 5 persists on Alerts page, then continue with tour + await pageObjects.infraHome.waitForTourStep('alertStep'); + await pageObjects.infraHome.clickTourNextButton(); + + // Verify user navigated back to the overview page, and guided setup step renders (Step 6) + await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); + const overviewPageUrl = await browser.getCurrentUrl(); + expect(overviewPageUrl).to.contain('/app/observability/overview'); + }); + + it('ends the tour if the user clicks on the guided setup button', async () => { + // For brevity, starting the tour at step 5, "Alerts" + await setInitialTourState(5); + + await pageObjects.infraHome.clickTourNextButton(); + await pageObjects.infraHome.waitForTourStep('guidedSetupStep'); + await pageObjects.infraHome.clickGuidedSetupButton(); + await pageObjects.infraHome.ensureTourStepIsClosed('guidedSetupStep'); + }); + } }); }); }; From 4103969a64047efb5a05121e308c63dfb382d120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Wed, 2 Nov 2022 19:12:21 +0100 Subject: [PATCH 40/86] [Osquery] Fix missing data view when viewing results in Discover/Lens in non-default Kibana space (#144210) --- .../osquery/cypress/e2e/all/discover.cy.ts | 66 +++++++++++++------ .../common/hooks/use_logs_data_view.tsx | 38 ++++++----- .../discover/view_results_in_discover.tsx | 4 +- .../public/lens/view_results_in_lens.tsx | 2 +- .../packs/pack_queries_status_table.tsx | 4 +- .../scripts/roles_users/soc_manager/role.json | 1 + 6 files changed, 74 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/osquery/cypress/e2e/all/discover.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/discover.cy.ts index 3e47b983dcda4..4494757128a64 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/discover.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/discover.cy.ts @@ -12,28 +12,54 @@ import { ROLES } from '../../test'; // TODO: So far just one test, but this is a good place to start. Move tests from pack view into here. describe('ALL - Discover', () => { - beforeEach(() => { - login(ROLES.soc_manager); - navigateTo('/app/osquery'); + before(() => { + login(ROLES.admin); + cy.request({ + method: 'POST', + url: '/api/spaces/space', + body: { + id: 'custom-space', + name: 'custom-space', + }, + headers: { 'kbn-xsrf': 'create-space' }, + }); }); - it('should be opened in new tab in results table', () => { - cy.contains('New live query').click(); - selectAllAgents(); - inputQuery('select * from uptime; '); - submitQuery(); - checkResults(); - cy.contains('View in Lens').should('exist'); - cy.contains('View in Discover') - .should('exist') - .should('have.attr', 'href') - .then(($href) => { - // @ts-expect-error-next-line href string - check types - cy.visit($href); - cy.getBySel('breadcrumbs').contains('Discover').should('exist'); - cy.getBySel('discoverDocTable', { timeout: 60000 }).contains( - 'action_data.queryselect * from uptime' - ); + after(() => { + login(ROLES.admin); + cy.request({ + method: 'DELETE', + url: '/api/spaces/space/custom-space', + headers: { 'kbn-xsrf': 'delete-space' }, + }); + }); + + ['default', 'custom-space'].forEach((space) => { + describe(`[${space}]`, () => { + beforeEach(() => { + login(ROLES.soc_manager); + navigateTo(`/s/${space}/app/osquery`); + }); + + it('should be opened in new tab in results table', () => { + cy.contains('New live query').click(); + selectAllAgents(); + inputQuery('select * from uptime; '); + submitQuery(); + checkResults(); + cy.contains('View in Lens').should('exist'); + cy.contains('View in Discover') + .should('exist') + .should('have.attr', 'href') + .then(($href) => { + // @ts-expect-error-next-line href string - check types + cy.visit($href); + cy.getBySel('breadcrumbs').contains('Discover').should('exist'); + cy.getBySel('discoverDocTable', { timeout: 60000 }).contains( + 'action_data.queryselect * from uptime' + ); + }); }); + }); }); }); 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 index cf5a9e42d8911..8af764e6c8399 100644 --- 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 @@ -16,6 +16,11 @@ export interface LogsDataView extends DataView { interface UseLogsDataView { skip?: boolean; + /* + this flag is used to retrieve the persistent logs data view + and should be used only for external links, eg. discover, lens + */ + checkOnly?: boolean; } export const useLogsDataView = (payload?: UseLogsDataView) => { @@ -24,34 +29,35 @@ export const useLogsDataView = (payload?: UseLogsDataView) => { return useQuery( ['logsDataView'], async () => { - try { - await dataViews.getFieldsForWildcard({ - pattern: 'logs-osquery_manager.result*', - }); - } catch (e) { - return undefined; - } - let dataView; try { const data = await dataViews.find('logs-osquery_manager.result*', 1); if (data.length) { dataView = data[0]; + } else { + throw new Error('No data view found'); } - } catch (e) { - if (dataViews.getCanSaveSync()) { + // eslint-disable-next-line no-empty + } catch (e) {} + + if (!dataView && dataViews.getCanSaveSync()) { + try { dataView = await dataViews.createAndSave({ title: 'logs-osquery_manager.result*', timeFieldName: '@timestamp', }); - } + // eslint-disable-next-line no-empty + } catch (e) {} } - if (!dataView) { - dataView = await dataViews.create({ - title: 'logs-osquery_manager.result*', - timeFieldName: '@timestamp', - }); + if (!dataView && !payload?.checkOnly) { + try { + dataView = await dataViews.create({ + title: 'logs-osquery_manager.result*', + timeFieldName: '@timestamp', + }); + // eslint-disable-next-line no-empty + } catch (e) {} } return dataView as LogsDataView; diff --git a/x-pack/plugins/osquery/public/discover/view_results_in_discover.tsx b/x-pack/plugins/osquery/public/discover/view_results_in_discover.tsx index 2106f11b89220..127b01b5259f9 100644 --- a/x-pack/plugins/osquery/public/discover/view_results_in_discover.tsx +++ b/x-pack/plugins/osquery/public/discover/view_results_in_discover.tsx @@ -32,7 +32,7 @@ const ViewResultsInDiscoverActionComponent: React.FC(''); @@ -125,7 +125,7 @@ const ViewResultsInDiscoverActionComponent: React.FC ); diff --git a/x-pack/plugins/osquery/public/lens/view_results_in_lens.tsx b/x-pack/plugins/osquery/public/lens/view_results_in_lens.tsx index a61e986fd1201..1e3c8b896f048 100644 --- a/x-pack/plugins/osquery/public/lens/view_results_in_lens.tsx +++ b/x-pack/plugins/osquery/public/lens/view_results_in_lens.tsx @@ -39,7 +39,7 @@ const ViewResultsInLensActionComponent: React.FC = mode, }) => { const lensService = useKibana().services.lens; - const { data: logsDataView } = useLogsDataView({ skip: !actionId }); + const { data: logsDataView } = useLogsDataView({ skip: !actionId, checkOnly: true }); const handleClick = useCallback( (event) => { diff --git a/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx b/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx index df08fa6d1bf9b..c9a323d1c4a5a 100644 --- a/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx +++ b/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx @@ -211,7 +211,7 @@ const ViewResultsInLensActionComponent: React.FC { const lensService = useKibana().services.lens; const isLensAvailable = lensService?.canUseEditor(); - const { data: logsDataView } = useLogsDataView({ skip: !actionId }); + const { data: logsDataView } = useLogsDataView({ skip: !actionId, checkOnly: true }); const handleClick = useCallback( (event) => { @@ -274,7 +274,7 @@ const ViewResultsInDiscoverActionComponent: React.FC(''); diff --git a/x-pack/plugins/osquery/scripts/roles_users/soc_manager/role.json b/x-pack/plugins/osquery/scripts/roles_users/soc_manager/role.json index adfd538923418..332a06db68314 100644 --- a/x-pack/plugins/osquery/scripts/roles_users/soc_manager/role.json +++ b/x-pack/plugins/osquery/scripts/roles_users/soc_manager/role.json @@ -17,6 +17,7 @@ "feature": { "discover": ["all"], "infrastructure": ["read"], + "indexPatterns": ["all"], "observabilityCases": ["all"], "securitySolutionCases": ["all"], "ml": ["all"], From 21472f81256da61561d28d424fedb0c8d54df23f Mon Sep 17 00:00:00 2001 From: Alexander Wert Date: Wed, 2 Nov 2022 19:13:11 +0100 Subject: [PATCH 41/86] Correct compute usage: sum over all invocations in a bucket (#144382) * Correct compute usage a sum over all invocations in a bucket (rather than the average) * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * fixing tests and clean up Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: cauemarcondes --- .../serverless/get_compute_usage_chart.ts | 45 ++++++------------- .../serverless/get_serverless_summary.ts | 2 + .../routes/metrics/serverless/helper.test.ts | 2 + .../routes/metrics/serverless/helper.ts | 15 +++++-- .../serverless_metrics_charts.spec.ts | 8 +++- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/get_compute_usage_chart.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_compute_usage_chart.ts index 201961113cac0..1c08588d9ec64 100644 --- a/x-pack/plugins/apm/server/routes/metrics/serverless/get_compute_usage_chart.ts +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_compute_usage_chart.ts @@ -22,32 +22,10 @@ import { SERVICE_NAME, } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; -import { isFiniteNumber } from '../../../../common/utils/is_finite_number'; import { getMetricsDateHistogramParams } from '../../../lib/helpers/metrics'; import { GenericMetricsChart } from '../fetch_and_transform_metrics'; import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; - -/** - * To calculate the compute usage we need to multiply the "system.memory.total" by "faas.billed_duration". - * But the result of this calculation is in Bytes-milliseconds, as the "system.memory.total" is stored in bytes and the "faas.billed_duration" is stored in milliseconds. - * But to calculate the overall cost AWS uses GB-second, so we need to convert the result to this unit. - */ -const GB = 1024 ** 3; -function calculateComputeUsageGBSeconds({ - faasBilledDuration, - totalMemory, -}: { - faasBilledDuration?: number | null; - totalMemory?: number | null; -}) { - if (!isFiniteNumber(faasBilledDuration) || !isFiniteNumber(totalMemory)) { - return 0; - } - - const totalMemoryGB = totalMemory / GB; - const faasBilledDurationSec = faasBilledDuration / 1000; - return totalMemoryGB * faasBilledDurationSec; -} +import { calcComputeUsageGBSeconds } from './helper'; export async function getComputeUsageChart({ environment, @@ -71,6 +49,7 @@ export async function getComputeUsageChart({ const aggs = { avgFaasBilledDuration: { avg: { field: FAAS_BILLED_DURATION } }, avgTotalMemory: { avg: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, + countInvocations: { value_count: { field: FAAS_BILLED_DURATION } }, }; const params = { @@ -137,16 +116,20 @@ export async function getComputeUsageChart({ ), key: 'compute_usage', type: 'bar', - overallValue: calculateComputeUsageGBSeconds({ - faasBilledDuration: aggregations?.avgFaasBilledDuration.value, - totalMemory: aggregations?.avgTotalMemory.value, - }), + overallValue: + calcComputeUsageGBSeconds({ + billedDuration: aggregations?.avgFaasBilledDuration.value, + totalMemory: aggregations?.avgTotalMemory.value, + countInvocations: aggregations?.countInvocations.value, + }) ?? 0, color: theme.euiColorVis0, data: timeseriesData.buckets.map((bucket) => { - const computeUsage = calculateComputeUsageGBSeconds({ - faasBilledDuration: bucket.avgFaasBilledDuration.value, - totalMemory: bucket.avgTotalMemory.value, - }); + const computeUsage = + calcComputeUsageGBSeconds({ + billedDuration: bucket.avgFaasBilledDuration.value, + totalMemory: bucket.avgTotalMemory.value, + countInvocations: bucket.countInvocations.value, + }) ?? 0; return { x: bucket.key, y: computeUsage, diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_summary.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_summary.ts index 1b35e8fd22ed3..7d8f94606f86b 100644 --- a/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_summary.ts +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_summary.ts @@ -120,6 +120,7 @@ export async function getServerlessSummary({ faasBilledDurationAvg: { avg: { field: FAAS_BILLED_DURATION } }, avgTotalMemory: { avg: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, avgFreeMemory: { avg: { field: METRIC_SYSTEM_FREE_MEMORY } }, + countInvocations: { value_count: { field: FAAS_BILLED_DURATION } }, sample: { top_metrics: { metrics: [{ field: HOST_ARCHITECTURE }], @@ -160,6 +161,7 @@ export async function getServerlessSummary({ transactionThroughput, billedDuration: response.aggregations?.faasBilledDurationAvg.value, totalMemory: response.aggregations?.avgTotalMemory.value, + countInvocations: response.aggregations?.countInvocations.value, }), }; } diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/helper.test.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/helper.test.ts index d92b7edece8dc..23b322663353e 100644 --- a/x-pack/plugins/apm/server/routes/metrics/serverless/helper.test.ts +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/helper.test.ts @@ -104,6 +104,7 @@ describe('calcEstimatedCost', () => { totalMemory: 536870912, // 0.5gb transactionThroughput: 100000, awsLambdaRequestCostPerMillion: 0.2, + countInvocations: 1, }) ).toEqual(0.03); }); @@ -119,6 +120,7 @@ describe('calcEstimatedCost', () => { totalMemory: 536870912, // 0.5gb transactionThroughput: 200000, awsLambdaRequestCostPerMillion: 0.2, + countInvocations: 1, }) ).toEqual(0.05); }); diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/helper.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/helper.ts index 2026cc9990a7b..df3ba8c4b9898 100644 --- a/x-pack/plugins/apm/server/routes/metrics/serverless/helper.ts +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/helper.ts @@ -47,17 +47,23 @@ const GB = 1024 ** 3; export function calcComputeUsageGBSeconds({ billedDuration, totalMemory, + countInvocations, }: { billedDuration?: number | null; totalMemory?: number | null; + countInvocations?: number | null; }) { - if (!isFiniteNumber(billedDuration) || !isFiniteNumber(totalMemory)) { + if ( + !isFiniteNumber(billedDuration) || + !isFiniteNumber(totalMemory) || + !isFiniteNumber(countInvocations) + ) { return undefined; } const totalMemoryGB = totalMemory / GB; - const billedDurationSec = billedDuration / 1000; - return totalMemoryGB * billedDurationSec; + const faasBilledDurationSec = billedDuration / 1000; + return totalMemoryGB * faasBilledDurationSec * countInvocations; } export function calcEstimatedCost({ @@ -67,6 +73,7 @@ export function calcEstimatedCost({ billedDuration, totalMemory, awsLambdaRequestCostPerMillion, + countInvocations, }: { awsLambdaPriceFactor?: AWSLambdaPriceFactor; architecture?: AwsLambdaArchitecture; @@ -74,11 +81,13 @@ export function calcEstimatedCost({ billedDuration?: number | null; totalMemory?: number | null; awsLambdaRequestCostPerMillion?: number; + countInvocations?: number | null; }) { try { const computeUsage = calcComputeUsageGBSeconds({ billedDuration, totalMemory, + countInvocations, }); if ( !awsLambdaPriceFactor || diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts index f660e218eba1b..2915ff95e86cf 100644 --- a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts +++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts @@ -177,7 +177,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('Compute usage', () => { const GBSeconds = 1024 * 1024 * 1024 * 1000; - const expectedValue = (memoryTotal * billedDurationMs) / GBSeconds; let computeUsageMetric: typeof serverlessMetrics.charts[0] | undefined; before(() => { computeUsageMetric = serverlessMetrics.charts.find((chart) => { @@ -185,10 +184,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); it('returns correct overall value', () => { + const expectedValue = + ((memoryTotal * billedDurationMs) / GBSeconds) * numberOfTransactionsCreated * 2; expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); }); it('returns correct mean value', () => { + const expectedValue = ((memoryTotal * billedDurationMs) / GBSeconds) * 2; const meanValue = meanBy( computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), 'y' @@ -298,7 +300,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('Compute usage', () => { const GBSeconds = 1024 * 1024 * 1024 * 1000; - const expectedValue = (memoryTotal * billedDurationMs) / GBSeconds; let computeUsageMetric: typeof serverlessMetrics.charts[0] | undefined; before(() => { computeUsageMetric = serverlessMetrics.charts.find((chart) => { @@ -306,10 +307,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); it('returns correct overall value', () => { + const expectedValue = + ((memoryTotal * billedDurationMs) / GBSeconds) * numberOfTransactionsCreated; expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); }); it('returns correct mean value', () => { + const expectedValue = (memoryTotal * billedDurationMs) / GBSeconds; const meanValue = meanBy( computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), 'y' From 57dce56bba0f4105bae4ff25be35ed3f89173a7b Mon Sep 17 00:00:00 2001 From: Katerina Patticha Date: Wed, 2 Nov 2022 19:55:28 +0100 Subject: [PATCH 42/86] [APM] Use consistent filtering for fetching mobile data (#144453) * [APM] Use consistent filtering for fetching mobile data * Clean up console logs --- .../service_overview_charts/filters/index.tsx | 6 ++-- .../service_oveview_mobile_charts.tsx | 2 ++ .../use_filters_for_mobile_charts.ts | 3 -- .../routes/mobile/get_mobile_filters.ts | 20 ++++++------- .../plugins/apm/server/routes/mobile/route.ts | 28 ++++++++----------- 5 files changed, 28 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/filters/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/filters/index.tsx index 8cad711759aa1..22a1544d2013f 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/filters/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/filters/index.tsx @@ -19,6 +19,7 @@ type MobileFilter = interface Props { end: string; environment: Environment; + transactionType?: string; kuery: string; start: string; filters: Record; @@ -32,6 +33,7 @@ const ALL_OPTION = { export function MobileFilters({ end, environment, + transactionType, kuery, start, filters, @@ -45,12 +47,12 @@ export function MobileFilters({ { params: { path: { serviceName }, - query: { end, environment, kuery, start }, + query: { end, environment, kuery, start, transactionType }, }, } ); }, - [end, environment, kuery, serviceName, start] + [end, environment, kuery, serviceName, start, transactionType] ); function toSelectOptions(items?: string[]) { diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/service_oveview_mobile_charts.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/service_oveview_mobile_charts.tsx index 62e8743049552..12948d098af57 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/service_oveview_mobile_charts.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/service_oveview_mobile_charts.tsx @@ -56,6 +56,7 @@ export function ServiceOverviewMobileCharts({ device, osVersion, appVersion, + transactionType, }, } = useApmParams('/services/{serviceName}/overview'); @@ -75,6 +76,7 @@ export function ServiceOverviewMobileCharts({ start={start} end={end} environment={environment} + transactionType={transactionType} kuery={kuery} filters={{ device, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/use_filters_for_mobile_charts.ts b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/use_filters_for_mobile_charts.ts index 2c79c5a34dc75..3412decc3f9be 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/use_filters_for_mobile_charts.ts +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/use_filters_for_mobile_charts.ts @@ -8,13 +8,11 @@ import { useMemo } from 'react'; import { type QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { isNil, isEmpty } from 'lodash'; -import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { environmentQuery } from '../../../../../common/utils/environment_query'; import { useApmParams } from '../../../../hooks/use_apm_params'; import { SERVICE_NAME, TRANSACTION_TYPE, - PROCESSOR_EVENT, HOST_OS_VERSION, DEVICE_MODEL_NAME, NETWORK_CONNECTION_TYPE, @@ -48,7 +46,6 @@ export function useFiltersForMobileCharts() { return useMemo( () => [ - ...termQuery(PROCESSOR_EVENT, ProcessorEvent.transaction), ...termQuery(SERVICE_NAME, serviceName), ...termQuery(TRANSACTION_TYPE, transactionType), ...termQuery(HOST_OS_VERSION, osVersion), diff --git a/x-pack/plugins/apm/server/routes/mobile/get_mobile_filters.ts b/x-pack/plugins/apm/server/routes/mobile/get_mobile_filters.ts index 2aac36c721ba1..bdd1667b4c8e8 100644 --- a/x-pack/plugins/apm/server/routes/mobile/get_mobile_filters.ts +++ b/x-pack/plugins/apm/server/routes/mobile/get_mobile_filters.ts @@ -11,19 +11,17 @@ import { kqlQuery, rangeQuery, } from '@kbn/observability-plugin/server'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { DEVICE_MODEL_NAME, HOST_OS_VERSION, NETWORK_CONNECTION_TYPE, SERVICE_NAME, SERVICE_VERSION, + TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; -import { - getDocumentTypeFilterForTransactions, - getProcessorEventForTransactions, -} from '../../lib/helpers/transactions'; type MobileFiltersTypes = | 'device' @@ -40,22 +38,26 @@ export async function getMobileFilters({ kuery, apmEventClient, serviceName, + transactionType, environment, start, end, - searchAggregatedTransactions, }: { kuery: string; apmEventClient: APMEventClient; serviceName: string; + transactionType?: string; environment: string; start: number; end: number; - searchAggregatedTransactions: boolean; }): Promise { const response = await apmEventClient.search('get_mobile_filters', { apm: { - events: [getProcessorEventForTransactions(searchAggregatedTransactions)], + events: [ + ProcessorEvent.error, + ProcessorEvent.metric, + ProcessorEvent.transaction, + ], }, body: { track_total_hits: false, @@ -64,12 +66,10 @@ export async function getMobileFilters({ bool: { filter: [ ...termQuery(SERVICE_NAME, serviceName), + ...termQuery(TRANSACTION_TYPE, transactionType), ...rangeQuery(start, end), ...environmentQuery(environment), ...kqlQuery(kuery), - ...getDocumentTypeFilterForTransactions( - searchAggregatedTransactions - ), ], }, }, diff --git a/x-pack/plugins/apm/server/routes/mobile/route.ts b/x-pack/plugins/apm/server/routes/mobile/route.ts index 584d6d85f362f..81042f9522d92 100644 --- a/x-pack/plugins/apm/server/routes/mobile/route.ts +++ b/x-pack/plugins/apm/server/routes/mobile/route.ts @@ -7,8 +7,6 @@ import * as t from 'io-ts'; import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; -import { setupRequest } from '../../lib/helpers/setup_request'; -import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { getMobileFilters } from './get_mobile_filters'; @@ -19,7 +17,14 @@ const mobileFilters = createApmServerRoute({ path: t.type({ serviceName: t.string, }), - query: t.intersection([kueryRt, rangeRt, environmentRt]), + query: t.intersection([ + kueryRt, + rangeRt, + environmentRt, + t.partial({ + transactionType: t.string, + }), + ]), }), options: { tags: ['access:apm'] }, handler: async ( @@ -27,28 +32,19 @@ const mobileFilters = createApmServerRoute({ ): Promise<{ mobileFilters: Awaited>; }> => { - const [setup, apmEventClient] = await Promise.all([ - setupRequest(resources), - getApmEventClient(resources), - ]); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.path; - const { kuery, environment, start, end } = params.query; - const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient, - config: setup.config, - kuery, - start, - end, - }); + const { kuery, environment, start, end, transactionType } = params.query; + const filters = await getMobileFilters({ kuery, environment, + transactionType, start, end, serviceName, apmEventClient, - searchAggregatedTransactions, }); return { mobileFilters: filters }; }, From 0ba81e12d40561f675b0a11977117b4c165417f6 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Wed, 2 Nov 2022 12:00:17 -0700 Subject: [PATCH 43/86] IDM: Migrate core server-side core_app to package (#144075) Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: spalger --- .github/CODEOWNERS | 1 + package.json | 1 + packages/BUILD.bazel | 2 + .../core-apps-server-internal/BUILD.bazel | 140 + .../apps/core-apps-server-internal/README.md | 3 + .../apps/core-apps-server-internal/index.ts | 15 + .../core-apps-server-internal/jest.config.js | 6 +- .../core-apps-server-internal/kibana.jsonc | 7 + .../core-apps-server-internal/package.json | 9 + .../assets/favicons/favicon.distribution.ico | Bin 0 -> 15086 bytes .../assets/favicons/favicon.distribution.png | Bin 0 -> 5234 bytes .../assets/favicons/favicon.distribution.svg | 4 + .../src/assets/favicons/favicon.ico | Bin 0 -> 15086 bytes .../src/assets/favicons/favicon.png | Bin 0 -> 4335 bytes .../src/assets/favicons/favicon.svg | 4 + .../src}/assets/fonts/inter/Inter-Black.woff | Bin .../src}/assets/fonts/inter/Inter-Black.woff2 | Bin .../assets/fonts/inter/Inter-BlackItalic.woff | Bin .../fonts/inter/Inter-BlackItalic.woff2 | Bin .../src}/assets/fonts/inter/Inter-Bold.woff | Bin .../src}/assets/fonts/inter/Inter-Bold.woff2 | Bin .../assets/fonts/inter/Inter-BoldItalic.woff | Bin .../assets/fonts/inter/Inter-BoldItalic.woff2 | Bin .../assets/fonts/inter/Inter-ExtraBold.woff | Bin .../assets/fonts/inter/Inter-ExtraBold.woff2 | Bin .../fonts/inter/Inter-ExtraBoldItalic.woff | Bin .../fonts/inter/Inter-ExtraBoldItalic.woff2 | Bin .../assets/fonts/inter/Inter-ExtraLight.woff | Bin .../assets/fonts/inter/Inter-ExtraLight.woff2 | Bin .../fonts/inter/Inter-ExtraLightItalic.woff | Bin .../fonts/inter/Inter-ExtraLightItalic.woff2 | Bin .../src}/assets/fonts/inter/Inter-Italic.woff | Bin .../assets/fonts/inter/Inter-Italic.woff2 | Bin .../src}/assets/fonts/inter/Inter-Light.woff | Bin .../src}/assets/fonts/inter/Inter-Light.woff2 | Bin .../assets/fonts/inter/Inter-LightItalic.woff | Bin .../fonts/inter/Inter-LightItalic.woff2 | Bin .../src}/assets/fonts/inter/Inter-Medium.woff | Bin .../assets/fonts/inter/Inter-Medium.woff2 | Bin .../fonts/inter/Inter-MediumItalic.woff | Bin .../fonts/inter/Inter-MediumItalic.woff2 | Bin .../assets/fonts/inter/Inter-Regular.woff | Bin .../assets/fonts/inter/Inter-Regular.woff2 | Bin .../assets/fonts/inter/Inter-SemiBold.woff | Bin .../assets/fonts/inter/Inter-SemiBold.woff2 | Bin .../fonts/inter/Inter-SemiBoldItalic.woff | Bin .../fonts/inter/Inter-SemiBoldItalic.woff2 | Bin .../src}/assets/fonts/inter/Inter-Thin.woff | Bin .../src}/assets/fonts/inter/Inter-Thin.woff2 | Bin .../assets/fonts/inter/Inter-ThinItalic.woff | Bin .../assets/fonts/inter/Inter-ThinItalic.woff2 | Bin .../assets/fonts/inter/Inter-italic.var.woff2 | Bin .../assets/fonts/inter/Inter-roman.var.woff2 | Bin .../src}/assets/fonts/inter/Inter.var.woff2 | Bin .../src}/assets/fonts/inter/LICENSE.txt | 0 .../src}/assets/fonts/readme.md | 0 .../src}/assets/fonts/roboto_mono/LICENSE.txt | 0 .../fonts/roboto_mono/RobotoMono-Bold.ttf | Bin .../roboto_mono/RobotoMono-BoldItalic.ttf | Bin .../fonts/roboto_mono/RobotoMono-Italic.ttf | Bin .../fonts/roboto_mono/RobotoMono-Light.ttf | Bin .../roboto_mono/RobotoMono-LightItalic.ttf | Bin .../fonts/roboto_mono/RobotoMono-Medium.ttf | Bin .../roboto_mono/RobotoMono-MediumItalic.ttf | Bin .../fonts/roboto_mono/RobotoMono-Regular.ttf | Bin .../fonts/roboto_mono/RobotoMono-Thin.ttf | Bin .../roboto_mono/RobotoMono-ThinItalic.ttf | Bin .../src}/assets/legacy_dark_theme.css | 0 .../src}/assets/legacy_dark_theme.min.css | 0 .../src/assets/legacy_light_theme.css | 4419 +++++++++++++++++ .../src}/assets/legacy_light_theme.min.css | 0 .../bundle_routes/bundle_route.test.mocks.ts | 0 .../src}/bundle_routes/bundle_route.test.ts | 0 .../src}/bundle_routes/bundles_route.ts | 0 .../bundle_routes/dynamic_asset_response.ts | 2 +- .../bundle_routes/file_hash.test.mocks.ts | 0 .../src}/bundle_routes/file_hash.test.ts | 0 .../src}/bundle_routes/file_hash.ts | 2 +- .../bundle_routes/file_hash_cache.test.ts | 0 .../src}/bundle_routes/file_hash_cache.ts | 0 .../src}/bundle_routes/fs.ts | 0 .../src}/bundle_routes/index.ts | 2 + .../register_bundle_routes.test.mocks.ts | 0 .../register_bundle_routes.test.ts | 2 +- .../bundle_routes/register_bundle_routes.ts | 6 +- .../bundle_routes/select_compressed_file.ts | 0 .../src}/bundle_routes/utils.ts | 0 .../src}/core_app.test.mocks.ts | 0 .../src}/core_app.test.ts | 21 +- .../src}/core_app.ts | 8 +- .../core-apps-server-internal/src/index.ts | 15 + .../src}/internal_types.ts | 6 +- .../core-apps-server-internal/tsconfig.json | 16 + .../core_app/bundle_routes.test.ts | 3 +- src/core/server/server.ts | 6 +- src/dev/precommit_hook/casing_check_config.js | 2 +- tsconfig.base.json | 2 + yarn.lock | 4 + 98 files changed, 4679 insertions(+), 29 deletions(-) create mode 100644 packages/core/apps/core-apps-server-internal/BUILD.bazel create mode 100644 packages/core/apps/core-apps-server-internal/README.md create mode 100644 packages/core/apps/core-apps-server-internal/index.ts rename src/core/server/core_app/index.ts => packages/core/apps/core-apps-server-internal/jest.config.js (70%) create mode 100644 packages/core/apps/core-apps-server-internal/kibana.jsonc create mode 100644 packages/core/apps/core-apps-server-internal/package.json create mode 100644 packages/core/apps/core-apps-server-internal/src/assets/favicons/favicon.distribution.ico create mode 100644 packages/core/apps/core-apps-server-internal/src/assets/favicons/favicon.distribution.png create mode 100644 packages/core/apps/core-apps-server-internal/src/assets/favicons/favicon.distribution.svg create mode 100644 packages/core/apps/core-apps-server-internal/src/assets/favicons/favicon.ico create mode 100644 packages/core/apps/core-apps-server-internal/src/assets/favicons/favicon.png create mode 100644 packages/core/apps/core-apps-server-internal/src/assets/favicons/favicon.svg rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Black.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Black.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-BlackItalic.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-BlackItalic.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Bold.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Bold.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-BoldItalic.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-BoldItalic.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-ExtraBold.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-ExtraBold.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-ExtraBoldItalic.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-ExtraBoldItalic.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-ExtraLight.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-ExtraLight.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-ExtraLightItalic.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-ExtraLightItalic.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Italic.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Italic.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Light.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Light.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-LightItalic.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-LightItalic.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Medium.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Medium.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-MediumItalic.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-MediumItalic.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Regular.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Regular.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-SemiBold.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-SemiBold.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-SemiBoldItalic.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-SemiBoldItalic.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Thin.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-Thin.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-ThinItalic.woff (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-ThinItalic.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-italic.var.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter-roman.var.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/Inter.var.woff2 (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/inter/LICENSE.txt (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/readme.md (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/roboto_mono/LICENSE.txt (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/roboto_mono/RobotoMono-Bold.ttf (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/roboto_mono/RobotoMono-BoldItalic.ttf (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/roboto_mono/RobotoMono-Italic.ttf (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/roboto_mono/RobotoMono-Light.ttf (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/roboto_mono/RobotoMono-LightItalic.ttf (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/roboto_mono/RobotoMono-Medium.ttf (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/roboto_mono/RobotoMono-MediumItalic.ttf (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/roboto_mono/RobotoMono-Regular.ttf (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/roboto_mono/RobotoMono-Thin.ttf (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/fonts/roboto_mono/RobotoMono-ThinItalic.ttf (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/legacy_dark_theme.css (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/legacy_dark_theme.min.css (100%) create mode 100644 packages/core/apps/core-apps-server-internal/src/assets/legacy_light_theme.css rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/assets/legacy_light_theme.min.css (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/bundle_route.test.mocks.ts (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/bundle_route.test.ts (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/bundles_route.ts (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/dynamic_asset_response.ts (98%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/file_hash.test.mocks.ts (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/file_hash.test.ts (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/file_hash.ts (94%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/file_hash_cache.test.ts (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/file_hash_cache.ts (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/fs.ts (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/index.ts (76%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/register_bundle_routes.test.mocks.ts (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/register_bundle_routes.test.ts (98%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/register_bundle_routes.ts (94%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/select_compressed_file.ts (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/bundle_routes/utils.ts (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/core_app.test.mocks.ts (100%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/core_app.test.ts (90%) rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/core_app.ts (95%) create mode 100644 packages/core/apps/core-apps-server-internal/src/index.ts rename {src/core/server/core_app => packages/core/apps/core-apps-server-internal/src}/internal_types.ts (72%) create mode 100644 packages/core/apps/core-apps-server-internal/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 43ff2c0fa9791..9cdf6e013bd25 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -708,6 +708,7 @@ packages/core/application/core-application-browser-mocks @elastic/kibana-core packages/core/application/core-application-common @elastic/kibana-core packages/core/apps/core-apps-browser-internal @elastic/kibana-core packages/core/apps/core-apps-browser-mocks @elastic/kibana-core +packages/core/apps/core-apps-server-internal @elastic/kibana-core packages/core/base/core-base-browser-internal @elastic/kibana-core packages/core/base/core-base-browser-mocks @elastic/kibana-core packages/core/base/core-base-common @elastic/kibana-core diff --git a/package.json b/package.json index a63be94afb18f..e95bd6e7d8116 100644 --- a/package.json +++ b/package.json @@ -161,6 +161,7 @@ "@kbn/core-application-common": "link:bazel-bin/packages/core/application/core-application-common", "@kbn/core-apps-browser-internal": "link:bazel-bin/packages/core/apps/core-apps-browser-internal", "@kbn/core-apps-browser-mocks": "link:bazel-bin/packages/core/apps/core-apps-browser-mocks", + "@kbn/core-apps-server-internal": "link:bazel-bin/packages/core/apps/core-apps-server-internal", "@kbn/core-base-browser-internal": "link:bazel-bin/packages/core/base/core-base-browser-internal", "@kbn/core-base-browser-mocks": "link:bazel-bin/packages/core/base/core-base-browser-mocks", "@kbn/core-base-common": "link:bazel-bin/packages/core/base/core-base-common", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index f01c019499c71..81aec2ac8a1ba 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -28,6 +28,7 @@ filegroup( "//packages/core/application/core-application-common:build", "//packages/core/apps/core-apps-browser-internal:build", "//packages/core/apps/core-apps-browser-mocks:build", + "//packages/core/apps/core-apps-server-internal:build", "//packages/core/base/core-base-browser-internal:build", "//packages/core/base/core-base-browser-mocks:build", "//packages/core/base/core-base-common:build", @@ -386,6 +387,7 @@ filegroup( "//packages/core/application/core-application-common:build_types", "//packages/core/apps/core-apps-browser-internal:build_types", "//packages/core/apps/core-apps-browser-mocks:build_types", + "//packages/core/apps/core-apps-server-internal:build_types", "//packages/core/base/core-base-browser-internal:build_types", "//packages/core/base/core-base-browser-mocks:build_types", "//packages/core/base/core-base-common:build_types", diff --git a/packages/core/apps/core-apps-server-internal/BUILD.bazel b/packages/core/apps/core-apps-server-internal/BUILD.bazel new file mode 100644 index 0000000000000..adf8562deb1aa --- /dev/null +++ b/packages/core/apps/core-apps-server-internal/BUILD.bazel @@ -0,0 +1,140 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-apps-server-internal" +PKG_REQUIRE_NAME = "@kbn/core-apps-server-internal" + +SOURCE_FILES = glob( + [ + "**/*.ts", + "src/**/*.css", + "src/**/*.ttf", + "src/**/*.woff", + "src/**/*.woff2", + "src/**/*.ico", + "src/**/*.png", + "src/**/*.svg", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ + "@npm//elastic-apm-node", + "@npm//lru-cache", + "@npm//mime-types", + "//packages/kbn-config", + "//packages/kbn-config-schema", + "//packages/kbn-utils", + "//packages/kbn-logging", + "//packages/kbn-ui-shared-deps-npm", + "//packages/kbn-ui-shared-deps-src", + "//packages/core/base/core-base-server-internal", + "//packages/core/lifecycle/core-lifecycle-server-internal", + "//packages/core/plugins/core-plugins-base-server-internal", +] + +TYPES_DEPS = [ + "@npm//elastic-apm-node", + "@npm//@types/node", + "@npm//@types/jest", + "@npm//@types/lru-cache", + "@npm//@types/mime-types", + "//packages/kbn-config:npm_module_types", + "//packages/kbn-config-schema:npm_module_types", + "//packages/kbn-ui-shared-deps-npm:npm_module_types", + "//packages/kbn-ui-shared-deps-src:npm_module_types", + "//packages/kbn-utils:npm_module_types", + "//packages/kbn-logging:npm_module_types", + "//packages/core/base/core-base-server-internal:npm_module_types", + "//packages/core/http/core-http-resources-server:npm_module_types", + "//packages/core/http/core-http-server:npm_module_types", + "//packages/core/lifecycle/core-lifecycle-server-internal:npm_module_types", + "//packages/core/plugins/core-plugins-base-server-internal:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), + additional_args = [ + "--copy-files", + "--quiet" + ], +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +js_library( + name = "npm_module_types", + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":tsc_types"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "build_types", + deps = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/apps/core-apps-server-internal/README.md b/packages/core/apps/core-apps-server-internal/README.md new file mode 100644 index 0000000000000..979146f37207c --- /dev/null +++ b/packages/core/apps/core-apps-server-internal/README.md @@ -0,0 +1,3 @@ +# @kbn/core-apps-server-internal + +This package contains the internal types and implementation of Core's server-side `coreApps` service. diff --git a/packages/core/apps/core-apps-server-internal/index.ts b/packages/core/apps/core-apps-server-internal/index.ts new file mode 100644 index 0000000000000..7a28e9f9d2f87 --- /dev/null +++ b/packages/core/apps/core-apps-server-internal/index.ts @@ -0,0 +1,15 @@ +/* + * 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 { CoreAppsService } from './src'; +export type { + InternalCoreAppsServiceRequestHandlerContext, + InternalCoreAppsServiceRouter, +} from './src'; +// only used by integration tests +export { FileHashCache, registerRouteForBundle } from './src'; diff --git a/src/core/server/core_app/index.ts b/packages/core/apps/core-apps-server-internal/jest.config.js similarity index 70% rename from src/core/server/core_app/index.ts rename to packages/core/apps/core-apps-server-internal/jest.config.js index e2846537bda65..d52363b8a2c1e 100644 --- a/src/core/server/core_app/index.ts +++ b/packages/core/apps/core-apps-server-internal/jest.config.js @@ -6,4 +6,8 @@ * Side Public License, v 1. */ -export { CoreApp } from './core_app'; +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../../..', + roots: ['/packages/core/apps/core-apps-server-internal'], +}; diff --git a/packages/core/apps/core-apps-server-internal/kibana.jsonc b/packages/core/apps/core-apps-server-internal/kibana.jsonc new file mode 100644 index 0000000000000..976f82a69e7c9 --- /dev/null +++ b/packages/core/apps/core-apps-server-internal/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-apps-server-internal", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/apps/core-apps-server-internal/package.json b/packages/core/apps/core-apps-server-internal/package.json new file mode 100644 index 0000000000000..04c52e856bf2a --- /dev/null +++ b/packages/core/apps/core-apps-server-internal/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/core-apps-server-internal", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0", + "types": "./target_types/index.d.ts" +} diff --git a/packages/core/apps/core-apps-server-internal/src/assets/favicons/favicon.distribution.ico b/packages/core/apps/core-apps-server-internal/src/assets/favicons/favicon.distribution.ico new file mode 100644 index 0000000000000000000000000000000000000000..b466b3b4f4eb3dada6c419d23a74439e7924579e GIT binary patch literal 15086 zcmche33MG*8OJ9rDz2>pwq$1BOWu2J(ITRC!!2{m$Hb^ z0tQ+|mI`|4K|Jk&B1=F@dt?&|($-j71d_C&P13%;@%O)Tr%tBx<}C^2oSVCR%l+>5 z-S7MEJjZEqCOZ4>>yWoO*G_YscR7yJ)@JLI9p@?9CX)+(!Y+<;HVAux1Br7SN$uvZ z6lUY`f94*0yr$^;#}#w_RmGh5FsY9ekT#MYEBW5<$&dFu?+xS8-+1xJ<#Mkt<@~u; zPRSdkGG&cnORP<>&7^txe16aIlA&2XrJQ>hvOHtu+g8kbHx=^UQ8V)S_Bc&o;xi;JVXGIq#o_kMCV99;H(06|@~=&Wi8#6qRz`0{S~*=FFKdZ>FCl4g25_ zcvcEI?}cfe_wFRTk@{Y)yBoZgn=$0Pi{SZ`El1~md#t{ym}`AsBm9%Ng+gHx{MM5y zrCjT$lkjHh(>>3H-_xO-?uJs{|9Y`F<&EN{IdAv<53nYSLbx~7p38l#nSMkAkMqQ* z;Cojz%Oj1Bv-BM+|5wxCYSUQPCz__^>3dH+{gcxfFsX*gMJ(5zvNAJ4)W3rzNhkw51x6(8Am=!`>Ylg*?%ZkvTm zhba9GeYIb`eX_3iLNiz|elvLRe~u*k^hr`Ar*hd@*yR3YdzWXiY3Fu>??#g<2G3K^t4A^)F$7wiE2LT%<4I%G$XU&@R zGTCd?pC!qL_%-+w;xx^cjqeuv5ySP`;Za|*kB~?EYmMwzwKJBz?ObmzwRxQpuJX3F z*1d$QF-Vt1G;y3p%i6=@CqJ%HTHzzd^`bBM{^x9cNyyJVHjSUP86OJA;4Re|NyDqB zZF(+OfKNYZQ)%kdJ?f!J;#j>J&L{J2409^AhrLSCd~Pnd(`#q=erp_7 zt*kw&GaB~e&8~*mWcvhU31bhT|8DF~`dTA9G>s?c=3FbwnzVI5-pQQJ1Hn5-`&08^ z@teO0xi*Ue~F4?ZTUS~yiC-nzyyL3#Hi0`3qm>>Ggj3LBj zYP2l=^z%@?bh^*MkKgx_w=Dfaet~>E)?%vo!Q0pYjm8G*l|^_uTlCx42#ui&-&CHF z{Zeh+!o6f{r_2~aqkYHm?H#){-d3y3etsQeR?PW^EL_$??-^BN2wAlC!r-s7`)s}V z!b7)ClU%aXvgnGQS2FV37(y0qGcer;@Yi{KxDk5AGOS#;)}U(vZ-9ODd=0#&z|!-Q zqnY?ObnA8Yr8l!W=X@&d7bBob}Tu1?O5?-ZZ_x)kBL|4-`n_!c%{18qwPYHiQA?5#1XHo+`Q%4 z^~NtcJq!0mfK5bx6U!jqD*LgP?uulX*5>Yu9{YxfTIOVb?d|PTkV$7kv4QBERLynIAp5!Xu2-_Z z7prdG4~hAhHNXaDEPbR+B;pPJ1IYY4&gkJ|d1DKLmLLcmCm41*Og>P_o4hyZG5N}% z_Z-2ygMk+19l`K0d8H%RUg;w53d)s^Fb~MfK~PTQ6r%Zur>jFbZx!dLn5diY9UB6U4)|7uQ1|*vC^qDO(~oRZ#XM)*xb?(hzV*G-SIA$L{;VZ~d23VLvcXuR z_!3um2Yr>3dMk|Y8$CC#R{zk?NB=FVg_j0rj(3HATUwlkyY66M`Ys|f9tO_Cx+~N8 z*V6|5g9YC`A&G14cGOAs_lD5-DR!EqueToFDgI?ieA!-h{F!@7{WtO+p6tiIpZ=Zp z35r9?cC+7%{s-QJs&`g7Q?lXFbE_5mPV%j{&N=)D{TTmS*>fI*Ygg zwoP)6N{<4QJ+3#r=Spqvd+O~ojtA}ejNwtecc=LXdK(1iA!N8EqO-B;5zgrM!Z@pU zp==lG`$stSfvIE%-`+cV0<+`TKH zxhDf>2ME0Rq+9JbyUTX~VgR?%F7qdWIJns?l@=$gsU&7u8i!zb_llzqFmxeK|cb%(^*>{PTqtnP8W?Hsrh zLaZUW3uOc7Z4~@pLbr+jpCUhbpD9yb9ieMq(`g`sCL}^wn~W4cv7k z-D}IKAJJLm9K&OxjW2HjZ`Ah?mVXPfuVmhct@}$D&n4CRjoOWF2*3UKQPfr|*LgzL zZKRXqxU`={|1apR9Qlp}zNOatg!T(~eUV=q=a*I1JV7_id6(a~t36kG+daV2$-i7m zTA}yPBrmn;ofY^hz0D@!_0;V*Hu5nB7{}_kog{k2OlZFoxZZbXOCM=XD9bmI{UX|} zF`4V>b`*}C=XIS`_Kd#Kl@6jG`Kaqyqib|maE|mIHbLh~b2o}<0~Kor4c8!kVr@0}`Px8;Ihc*rGf;GP5iSr2WWaC@Oeo@R0oontlz55NKyMGYJuU0m5 z7RI{!YTZX^Ji>Ph-rd6Zsj(X=e^N5Amv1Pxw)$ywYST9s==b};?brQP4X>GY)*`xR zrP~+FFGV*T#=bp@@)@+Avhj-jrpSq3ZhniifWG9H>n@kwsozLlDLXWUW~ZiL zSr7y{#T*}-U(h?6bh>Pt=zCN7 znTB`lwoU*w*r`_dG-_=?!Sq+Cl<)+ jC_O;iQ;u_L&T;1OCB*4Nj&t)i$9W=_Hq%zgJI?({R$ zBOFHM__QdBa~!AIQ8G?yR}{q>e_s|waj*XSq1vtu?@Jb$(4GMTPl}?r#BnTi1}l(! z`A}__(OwY8$b|L)kaz|nb1biGC`-Oj$3wMU^!ZwvkO?h>Q8_*VffbR@B04oi?s-uZ zHxJeJK7#XcicDz7fHj_i$WrcvE8KzD$+#q$(2f9$d{Y#~haR53^0%*2Aa*8nM}Ww) zo>*iP6tSm$c11FwJ783fC$P#c1HBdgG+9eqiO&Yta7BJP{#4>f<+f4aT7EheA@CsROdhGgA0-g%|Hhi;}*VXG^yxOIaZ{; zYT=To)#Wnww0b7A0U-0za@oFx3>K|g(304S)U$_bI~P2)PRN8NFe=CAATxKF-r$D! zCXgDH;~9u;Sdw{zfT{O&5zy7=7S=jCQb$i{T;xehZFQrSP_nAbV6E<%H}&g7wVhd>Q`CUb zSXjgiQIR@ILgS)dfyl1X$rY^6yvdqc1c1>8M_tgOF6C`3aYIyu)`%=!;PVwe?+ce? zRE`%6Ke89{l?zzt?$KP6qtGW%0~hayr)srN%B?_z{|J1)iOkhd$gF@2;YY?x)harN zWesWp)D@ZA*>+H{nY*}AmN>8Rc9{BDhE&I8cd&BU9ZznD_IaiwIEOjI9SLS-Vh2{fwITMY9;)rB@1NpLw!@Rnd17~Xx<%@5;Xa(> zfuYVJXtW+PkeD)MNK5$KUg1-R=IrvQbzae>3J(hPuq=lt&)EQ3QpzYO%zSIpUI>kP z43qDwbl!81YQj13Z-ZAfn3!QTYr-+Zk~@Vj3vCLlb^($WR8W&u4F=$PnC@*_godhN z3c$RQ8G#{%Y@wMzdb}cDeGCqGfe#C93Jg96NLo%0o2 z^Jf3qArK%^R{9JzJK~VG(59s(RKEvGAI<)AJ49inr3p=yW8MW!DN~9|w$LpAD@`dL zUXoN|goe98AxKJ@vGip^w+FR@=a|8X&XBaw6mhD6*4Y#gCf5YqBTPohq%L)aB%!I3 z4>=Yg2ZTa^G1w5Fo~(t=giaTf+fkyhNeq&NZmin~Twk06LRpyZt*F}}LTJNNNG(aC z$N`}|kR+otX;D2|%0g3w4w;1`m%F^p${8pt-Ah7K2&p1a7js4YTZSDdLfh;Zvc}K| zP5E!xI=cgTEhTHDr3g*A6(O6EP!>At&yK)w-Nf7u5keb&ke4EreIQV!cFbf8?G{w< z-#ZH(Bgh9Sw<>TBYL)Jng$9~}EYYBV0Cf?ANr?#~$ciUP=*A!9t2TBIIUw|lAo8pv zp{XTYyh!B$C=EIj+An(PLsNwI+VcW35w&wB6WTAtR_NcQK3MI`8Oh1^p{1MxE)pYXm7$W;^El{QSdI91*h>m6d6zf(eYm{MA!Cin+@ zUbRQ?ww5%PjK+ny35`A%Hv(PwOC0a;Jv)$l4T5vE<4ov*pcA26OiSvDV~|@NUq|A(2(2GFRu(dvSNs>d5ns{qMGe;8WSYamMi=`bFzCI+=;kh6w4C1!X7NZDi93*lL z{QswaLpoe1xer$+`_FP{j9VmEMb=0POET{;N}|)wZV+52xr>Qb`v^LMELC8j>7NZh zPjucRm(Xh_nMLNJ6wmd{I>BA|;n!#fo8bsHLpm{c-RUZ6B(#Ok>~&%kR*g%IG|w&k@Q-LTn9=Rp+lmGIP^)sq2a^+ZFqmwQ&zP=lX@tFj2}w{EyMx}J6P-1EB!%2<0RLHK&&?-gBjK%Tj>@+ zT(c0!E3}Tqn(CoB1xC{zHD)mdemO9-1Xe$;Sg^6&=B7#;)@^n+y&^O|#N^n2?vvaF zzl>@mQ!)k#WdJA-UNU2pTBcLHdTY_pM;X+m?R`09p-uivsJSRzk*%}?K$iQ)B=fDV zIV<7dq1s-8*c^+kzT>;i6s;FNG-DRG@|#clTK_OA$4i5}_cpgPXoh7C&+S9CC5dSH zd8{~R-$EO$SkH@jq8bv}Cm^u4A)nEzBB}L;X|4a__|#7pnjzCAA#aV!@x3A7{UeX1 zcm@=&1v7EAgO+Ti7~wnSj4gXRjL&YoKe~lBStqYMwFQxVX(lv{nM>x&Hu9%h^&hJ3 z1U@$f9GlY09NdKrYu*v$VtFWF_HR=GCGfhh73bR zt*Ke9>`N1XIA9~oox93OFiHKXNzSV+G-aK4frtD^z%(RsD%rz0WeGvXV?R;DS*P>oK&?dqwP2T@dxVM&)>6 zQTqAOSEd-{6?eo-Uah1iLT?h^70vl=C6-}D?5mWBt;upG{kOns_9j48;*7D9KHU=& zp_APejV0D*cf~O>W&f!6_FdYutd)qSke z1DTi1jmI0*?}RjN#(1ax%h8b9WWirtJ8WI{4%J{wX!+3tfi72LUP4E@cB-l?Z{@8eyUQ>h+-&HAo_USO@vOu<r~um&p+t`_wpOZk_p3@4@b!_YML`ry0nWJF@!}{8NNJ zIWk0!K?cpYg~h%%CrhmIH?>{NrW)QRlsM;_*sU{=wmY&_Yk?k?Hc?qWbao4cO z{%u_Qi`bs8$n-}CV%MqlA8k_WXq3oxv%bbA%UOVY0FMmWz~~8Wnf-H4W*utj22uW{ z0u)2KmO`CcXEj=}tV>ZbL!WECNkS`?V}fO>3DNF?wv?qr8*Lu~&TrCjc4GlQeZKGS zw~*zmcVx#1Od&o9MQC+V{vpoWYu{%s5Xmo?L@_o!@o>IVNPb;X?-d@71p;<%qr~3G zavYF^R(&}P9r{-Ye4;_y>|YFk3vtzPP*Uy6aFvWcHP$3yO0Lwh9py(~#8mrhxg%M8 ztB@_U{%&0ZNE=x$rMh;Ki#4H5epM8tu-uXCZL3flXaVFO9RtWF+{RG)=^J`0Fe;@O zyh)=)_OF-0kCs}WI+*wsm7C=fh6az7o+*rWTfc?I+PWQ8XlSYR(~hb2=-e#FAa_{l zgf=x_d9`KS2P`z!WFmPjwQkcOT;aN>y}NBd?ty(GG_(1N*RgGu*}py&*s!1lkZ+g) zWYSXzU{EQUa=qX1`~M^XLlr?~^<;kOrgqN`)+H@8nI!vpfhLNu@j+-=b+?<=^Hm8%AMtXgJCy^jva`z1i^z>=fTam zVUU^RN@qfAC)9Q~b558z0<-+L39CJl`|MxBeGrYSxHCf60I_Ebi=9m4lbB#Z4ih#t zxz(Q%iW45d1=9$s5rf7~Ud{w?`L!CA(sg==cHBk6aEU#g+$q}%pX&M?DQgUw^OmFoC1D#wN9hu0*3 zA|W2Msb!3H3%8W*&x;Y-uuUuL%yJ0XG4?_y?5G@XK<-J4K=t6v8xtfg^dLtrM*!-& zzl92NiQ`;oZexwJ7N5UErnpsnCGB!#cmx7*$`Y=%sjQFc5>`^3;9Z(XcEueUOSWFY zB?(Q{MrR8x1IjvAhlqmzx6pTeAaK)3?jjRRVFO>ev+5O!(AkHU0YzxGjtpWwkqc6w zPVlb1_i9uaapx=-kzJ0oKLRxHet<@H6@GsGiDREaD@@sgN9%Y;q`F=l3)YIa=7ey@;S zj&wkgB)UYF)MPbDLQ@wpnlW)Hz|=lB;2uKn91tV4As;Wchpf9z28@6( zS@-0|K?IngRDW85P-EG!q`Kam1%?;1(L0M?80U=zCKMgMvzsKGP zjU-D&yyRUjSmJ}xCCQs)r%gDcuuF>20DHu3h7pvD=514~MrKoYC95u?ZQ+&B@Si9v zeTEM78D<3cpW$XSwJXrE@-?X;#1CXPvewu46h}?uw%--Z5CtQ2!CyXin1VD| zQadKIB!bVL4BD&_$(FB%m#O7-IdJ7;&LFdzpJKX}lBmb81$7aFTId?oJVOnGv(&K6 zbH-HOr-@Nm_2wRXpFCy&+f1yIKrDpj&Zr&?sV&@$gY&N;*$!~(g zwuMAJ$bKm?7sVx_B6MLtt9s)*6D0|zHea}Ky|4l3w7x9@dDC(1kszckttS zTd8N^ViZ}%q!A^d3wu%*s1KR-=|&^)yJA}90xQxqlernu61uPp6m>)wc^?9{D_vRq zHY=ZbsEC@-h5d4@BRXQkB2QY#X`N?a4+YT^y0A+y9KtQ8mIe!MpfJo(7i?QtbRA35 zq7Uc=p=)57Hf_XgLWM2#b00paCX-EDYQPn&a<5c|(C8JRi*7E6j;hvGp{;gyOSDqU z%Z`<5MRqaTqj!X^fn6Im#pWigkbc*XCY)$31TIl>=_19Pt#U{7me9rXvVPT~_&he$ zTl{abRqlpf6S@Xc=2tzP3UKRdi~kUi1fgqy*wY?Z?AjvFQ%T#$f}{vt1FpyvM@%yj zHz4vNTVxp|NoXCYUaJpR0i+GQ%7sHm@`Tm_$!`K>LdxIXX_DX~yA*ygC_?W81V4xD scCN{B97|s9NqmpHs55~Bq9}_00e?uvBRsGQ9smFU07*qoM6N<$f<%wLlK=n! literal 0 HcmV?d00001 diff --git a/packages/core/apps/core-apps-server-internal/src/assets/favicons/favicon.distribution.svg b/packages/core/apps/core-apps-server-internal/src/assets/favicons/favicon.distribution.svg new file mode 100644 index 0000000000000..2d02461a0b8f9 --- /dev/null +++ b/packages/core/apps/core-apps-server-internal/src/assets/favicons/favicon.distribution.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/core/apps/core-apps-server-internal/src/assets/favicons/favicon.ico b/packages/core/apps/core-apps-server-internal/src/assets/favicons/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7ac743a2d1fe4660ca6dd3675197f7c049b3326a GIT binary patch literal 15086 zcmeHO3zSt=89u{gS`iRPz&-n%bLZYWK(R6&y)$Qezbs2bGc9`()mm2e0JFLzEkUu6 z%#_GREDgvCA0*adK}|8{f|(-42Md{TgwaA~285ZB%bdP%Kkl4+xcA(1FN0)x*V?lm z|MTqg?|=XM-)AhV#;UcZPq$DftyT4w^*YP4l1bekV_A=4Yz%6`=f1$Qt^mSR;DE&X zBnrpnFQckre>1KGll%4VHH)4E{vW@;3$N3`qJ422#=b)45yZpwA8pgf~Lp66_=k2QrQuvQ$=0kRs> z;$60IJ;^rKRw=#B_;BivdH#@;i**>(pJU-4*8RVX4$yfC;j}@F9l_eTll@twjdoyFhs=u3fGvz}50`fQW&oz;9#k^2?+USsW-C2@OtrM<^z9~~qeJRKA&TbgO zJKFAsQU5)sO_=Z!B~#oL)IZYxJ@GJU_=B#0%T2g-u`c}Uy6(#$=dVVV z(VxDMuwVhsHN?xf?kmX!e)u9or|bP0?MpMRvor+D``ya*cWw&mL0<=7`l__&d=2Zc zqd*SUa}YP!1|Iacx0^Ap*#cVmZ1!xz;ZxrohM9il`WL=G!{RRXppS!)ZXTKGGWFPmzsHfoG4qy3uiR+2G&0Zfx6NEALPI>kZ;6I;G^27t! zBa%U!xNYwFnnx>Q@Sma0IPz2F=o5*=i-Eh2_$XV_=?O0{M^h)nX^4S-0erR3su*sA z=Fw&1pmKb|aL_ZxJBacSdDYd`O$g%@>#O+CSxFj{9g|%*RSYlX+HVJ(=RtX{A#TdC z=OSi!5%6nFyp8b#JdYOR5j6(7CnYW7KGspVMPN0cxeA5P(8V((aT;-!LEQ6E*y2_V z^Hppw&IWh{ae_bC(&vGfLphxB#Ak!Bs5Z=Q3}}vUFGtPyx;qNL6pZI4&~koz(vH0l z{C1(uXCm+0&H1VnJ;(8$&V+rbxEee#B+oQaH1Pi)p&V`dooEkCn>Ou*Q8cAsh+!^N z`bxd~(O!{ECYb(<{o@%9&&zd{b*9;AtC*L#y*0Xo>a*a5tJ9w&;X z9E|#4J^UIxxsEy+r_<-@wMJj_OgR2A>jAKK&+{gglR-FbRQq+TBYSQfnhJ16ARqBV z{R}C?in;p&6`Hk5%%_`#mXfMyW--4;cMb$>4P$KHE&6yCAG!2faXZI$py+4f}W zg&f+?qv+B?WTgJC*bs$REo|EO@gu>XYw_=u@X$F)?9bw!GIWgNdgz17;8g{NUA|Ax z&+<;cF){h9 zWjx=g?^|eBJac0FF%93#7_wSfK{|B?k}~&{*>i(rMcdRFbMdIwknD52xZ;1Vz z>vexyoHKDzL3$ARgtvhgW0NDpWs|f&IoFQ(E%a|Pg~G2L$+}@_m#Rna8O44XxxV~amZ_UM{SH58Sq;7Z0$KMD ziY)uN`Pl~6evQ9W){Fd>0rs2xPSj0)gWrqn7VG{l%W50U`Z=6}T6(g+kBmjHrRx4s zs?Gt<@f`3=)q`2UvkgA$!EBSCL+yK>pYu^K80`!e;V!lykxw@4CdA&UF9<62WDZRJG& zu$C6WZvIeA51B`zT_YYX2G?VV&-qQ7{MKb3?=TQIjg7#UAgq@UgNA#=gQzbqh0n7R z;D<7f{jDGl)+6%s@}BDvz`Ma`5!WZ`zmaFTGpEj>8~|-E+d`AK^Z8^CMOc7 z;`^9yqx~eFwF7#6HF)RCa~JLq?*KgV4wUN~exeO{doj-+56cYqwT}k8*9= z82IdtxPu&_ad?h<9J>AzxrHxiKgBhEFW{G!lT!+=_aNSBME$r|@2{&%ObH_?dsZ<` z7#63mnG`d@X8x7vg+9N~VFSJ&NCIi@m(lFynD+`X0t_%sCqx z*b&b(`{B25rJiOyTx0FN26)?~YX*_T7iIEvnJ-a?qlb2>Mr@vS{%GFIs0n#bC^4P|jjdVB`}8$4I8N1TgkkJy`L^ZbS9 z`37x)d`i)q@whehmwWWkJem25RZCiZF6S#%=Q(G!xm%O-Bk!DP%XSm}M&Q-s%*#1) zRtCp_Glo8Z{t&)$3HnLG)&CtitNG1~!N%%q`|R zF@t*0m*X9ukGZ=B{nbWBxTb!L8NmNN28OYT(0P-&dk;U8!80{vEH`b|>U$S;#%7;6 z^hSN}#D4h4xeSjn)`k2t<##SJ=U`yd~8AD+Rva~9u0y%4Vt;_%Pvp<9B}NJ|{+3Ta}iIor;8ngjQV*fxOoEe+Br@ z_4r`uABk&4>8WPwLqbB$_^*(T&4pf&kkIF=Bj5%BIa_acyk=CGdPlN?)91;=bsct} zKDm)BRE-Q1jP;E-8)$^a)OH>A)>;sKE9XT@P-rH{Q`szB-;_p z``IxbMF* zFP5)^Sl=UJxv^-H*8M!5&?BP-K$&+t{Rt?vcQ*QVh)+u5Did5XW6?GkE0*<|KC8Z& zJ@fWAo%GmQauo(C((X^_TaVC6^N!ff!)@ntPo_&?-yLlIi$|_oiSf}yKZ#5SS}K@d?4HC*>gT%0Y+4M96sLgd1FEJ z6mw@}?pXM7P!=nr06me5K0Ce+pY2yE-5Ud z^?Na?ddTl3$H69lxzmE}+CMocG;*plrzZo0o+XXk`=*tvGQH!XggecSK2;d}-8VOW z{DXVd>j=SuFSMWU;A4AI2MgXN3o99LG4Ts@;JSv!NWdAN`pRRFlu^N@L+b_eOxP#I zCh#loWC}R~_TB;y!#Qt%5V+?I!qfMprfWk#@yuK}QrNSLrY0hr8>|>L5_d3e>m@8W z3K*(jSR=2-X_hrYWCozl3KQTL3O15dS$q|NQUyTvP)ShA`oxrTH;J3gsVpT~phX9e z)(bqNLBS%mtt(i);mTuc_SjrkGTYKqzx}8HaQmx9Uo|l&6vP5ONCVQKq9Yv0PCQ)$ zkD$mBy=XqC+dE7@15w+(+82(Fr6haulVJdmRIp>VU#n)yFVNL@p3h*E@XnM(?kh_m zD!)us>;1%3*~&sUhLgfRqrhU*8mj`aHcvfI@sunRS>|RA0?=5=zvAK;Q_{DB^e0QC zwWFeOeRk##<$5)|bd{*4@8)QAVz~q%6;+f0h|(K1i=n=P@RC^r0a6=?HH}+`%_#Hq zYDqE8Cj`-^hOL}g`auVf+sVbA-z%gB5N?yNq4KhcBVQOlL^Xfo>NC1 zaf=3PcU6A)OgFIMjDu05Ry{B+cu@)8Cy&I;vIFj zhB$!??q1HIR;NLz!EZk3Oi=#7Olz&V7Tc^xdFKYISy|urYZ6T#jT+xtX~bVf`r#-m zr4W4%)@zuvO+5{n$Ir!%`tB+2J~K4kj~|&HYZEuIg+$|OFmaNzd$Sg>PFicKS!4I9 zyK9whuV}Ga_>SQQW4N9E@1yXJb;ngLB7P3-(Rgm6DR}7dGp5KyMgVNw{8cwN?hz7-3eP}Md{F(>Xt>1Vj~SNb2{W= z=5Jmxku9(q1rG%rs?(~ny1CMJ4GuUJ=1rSbHfhbVA_i9Or!hV;4U|nvq{7_I*4|ujoeCLjn}dY2UE7aCEsKf(May4 zpGkJ|BMK8Rt%%r+gHeDZl~Cd|UtANIkwiL0pTiiv-7c-9=CdCPPiPZUm2SpGf4C)l zTqkB@enacNe9+nUdH-L%KR=2;!$KQP)?)(95~i#-zVmWi-+hxi)O+WqI;PSWo-=>! z-!^I1#TEys@KW)YB5=B;8jtqT^;J-%&ve^%0;+^8Z#UdgYn>Fa2As||Q{SI{oV!d<$s z53e?eWgB8FdRGu96G!tY?Z+(i1#zJma4UyO!J@bp5`orGK|7{m;rC`G2ftaEzn z_~kb~k%nEvn_c(`kH`z>c$|M6f$ebzE90q*Uh~JFkN7c?Jr|=-22RO#)QgUcqAU_D ziS@o;e`%DDmhuOqTczqJ3GY^vdJWSN8asb<@B0F*`q8qJW+ZLg+Ed=z$-Q-kLND7?0pT2R^WJ4v!iXb^pcJpC7ISH-X77!32r(1YxWh75lJfAc z@+Y5eRvQbSJ%v6U#mJShmNR#k{mR#1Nyn3syLx^r$pOii)Wf`$&pCFjP&WonW2~y z1!m9uJ5T80P75bY;*_;0ErcY~Wk20@uv%H#yy{#?pkW#B0#}&eJv{x&#hm2Jc`IVA z4d%u%txCPl1xVDBG;ZoLalc>5X8XGd4#70P9c(hd3 zz7-%{b|}d#r#M6Spihk7D%B>iFq%iDeV)Ux3jCJjd0`O=gwV>yjywI5d5O=Vp7aV! z!Nr<`ly|#82c^_y7}cG0<(FChh?RMI5iDL&a%sgPVJa!abjqcA=uZxq0j{HmMVtIR za7d+plxy4dYl>uFhvx-2WSGPMS=)%PNMBF=ls_8bt1o67_zjwT(>B%jQ4Gm`ot-}D zO{nu6?1gWDguuI%0-4FfXuqSgXz?HYV_Ftqo>QItN=6BuDlNlfm~7f)WnXM|=J~T} z8z#>>)CfPEt7I@ADd)Zv*R*dhWq3LgPO?7>gnT$AS6g;K3;eAJQaxwjX-K!zG_ zyQ7rGy>Ct8SB>Gzoil=&^TnbuzWgD=!&ZlQyt~8{e+`)%c0NNW!ho1EsG*Re7__X*@L40;c8$&Maw-dFHTO=m$EnVZ#mSK=w?uGJLL-^h)RahxQO@k zaB9On>XQK})V+23J8U{LQN}qV%+w+V5kRw2Dh9kR=>J>a!7{&B0{j<)j;g5m&#pRL zeqooB)DAk-qOI`>wq7{GaQrCkF`z3s*lU0_C@=eKj%8UP3XK?(*FZe zbB4KY&(=O1)jZTk4QJKQ1mt8qR}OK+I}cz@8^LQFvVK8^qMq#HXB)Zr!ls6T<+Kmq z*WYBO-b6`pSv_&G%VyUm53&*ohv^hnurG~B@FVoQV@X8Z?1;#}O>90j1W$Jw247KJ z1S+OrwUI+H_;0~1ZK`t`kD%EEuw9NrTb-3wj4)NP3o2~c8|ohH5-Ef_q7VFEG2uD= zBG+}#-Cnby&!kOC~yk5$Mq9@+{1+_7>5J~ydp>GUW!NFTT$U}c)9HU$1LXX%j_5$ zvuHcT@2sK@gnO{siF~|X=g9$aqEMn4{+2nu49Aj5&(ct6;;%e(jj@GO6O+m)4B-XtV`m6`6ln zS>yEU5QnzI2kmR7as2vkue*V7Cg3Go5Pn!d7Ue%s+FX4$VlsI`FzrsYWxT?S$|v@#__{ot9NB$Zoej@2*(z2 zr4JyencNF5o}(*ZUUvAf@bA`&tiLl02Rh1%vt4ZTmp0G_NpowNg}Ac5&4QG^O%j88 zJB+Ga>KuRXoUSBD%23PbtYCoc>#DU*_t(bTE|J*F_R=E}?yP&bR%MJn3!!$VHwVxH z|Mm{^YF z$tPjWs8gnDnL>d|N}lvAKnwJX;#>Bi#idkydScf~x9!DJMXjJ7(dpw_c4}W|c2S9u zb(R=Y=t=oZ2R|i;65uJf*38_W4cC%Z3JXhCr=aal? + + + \ No newline at end of file diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Black.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Black.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Black.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Black.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Black.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Black.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Black.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Black.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-BlackItalic.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-BlackItalic.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-BlackItalic.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-BlackItalic.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-BlackItalic.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-BlackItalic.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-BlackItalic.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-BlackItalic.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Bold.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Bold.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Bold.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Bold.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Bold.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Bold.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Bold.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Bold.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-BoldItalic.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-BoldItalic.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-BoldItalic.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-BoldItalic.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-BoldItalic.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-BoldItalic.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-BoldItalic.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-BoldItalic.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-ExtraBold.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraBold.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-ExtraBold.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraBold.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-ExtraBold.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraBold.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-ExtraBold.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraBold.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-ExtraBoldItalic.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraBoldItalic.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-ExtraBoldItalic.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraBoldItalic.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-ExtraBoldItalic.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraBoldItalic.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-ExtraBoldItalic.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraBoldItalic.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-ExtraLight.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraLight.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-ExtraLight.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraLight.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-ExtraLight.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraLight.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-ExtraLight.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraLight.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-ExtraLightItalic.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraLightItalic.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-ExtraLightItalic.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraLightItalic.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-ExtraLightItalic.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraLightItalic.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-ExtraLightItalic.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ExtraLightItalic.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Italic.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Italic.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Italic.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Italic.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Italic.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Italic.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Italic.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Italic.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Light.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Light.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Light.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Light.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Light.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Light.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Light.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Light.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-LightItalic.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-LightItalic.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-LightItalic.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-LightItalic.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-LightItalic.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-LightItalic.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-LightItalic.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-LightItalic.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Medium.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Medium.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Medium.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Medium.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Medium.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Medium.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Medium.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Medium.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-MediumItalic.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-MediumItalic.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-MediumItalic.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-MediumItalic.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-MediumItalic.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-MediumItalic.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-MediumItalic.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-MediumItalic.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Regular.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Regular.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Regular.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Regular.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Regular.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Regular.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Regular.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Regular.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-SemiBold.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-SemiBold.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-SemiBold.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-SemiBold.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-SemiBold.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-SemiBold.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-SemiBold.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-SemiBold.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-SemiBoldItalic.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-SemiBoldItalic.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-SemiBoldItalic.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-SemiBoldItalic.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-SemiBoldItalic.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-SemiBoldItalic.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-SemiBoldItalic.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-SemiBoldItalic.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Thin.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Thin.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Thin.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Thin.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-Thin.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Thin.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-Thin.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-Thin.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-ThinItalic.woff b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ThinItalic.woff similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-ThinItalic.woff rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ThinItalic.woff diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-ThinItalic.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ThinItalic.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-ThinItalic.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-ThinItalic.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-italic.var.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-italic.var.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-italic.var.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-italic.var.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter-roman.var.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-roman.var.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter-roman.var.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter-roman.var.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/Inter.var.woff2 b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter.var.woff2 similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/Inter.var.woff2 rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/Inter.var.woff2 diff --git a/src/core/server/core_app/assets/fonts/inter/LICENSE.txt b/packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/LICENSE.txt similarity index 100% rename from src/core/server/core_app/assets/fonts/inter/LICENSE.txt rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/inter/LICENSE.txt diff --git a/src/core/server/core_app/assets/fonts/readme.md b/packages/core/apps/core-apps-server-internal/src/assets/fonts/readme.md similarity index 100% rename from src/core/server/core_app/assets/fonts/readme.md rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/readme.md diff --git a/src/core/server/core_app/assets/fonts/roboto_mono/LICENSE.txt b/packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/LICENSE.txt similarity index 100% rename from src/core/server/core_app/assets/fonts/roboto_mono/LICENSE.txt rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/LICENSE.txt diff --git a/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Bold.ttf b/packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-Bold.ttf similarity index 100% rename from src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Bold.ttf rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-Bold.ttf diff --git a/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-BoldItalic.ttf b/packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-BoldItalic.ttf similarity index 100% rename from src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-BoldItalic.ttf rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-BoldItalic.ttf diff --git a/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Italic.ttf b/packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-Italic.ttf similarity index 100% rename from src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Italic.ttf rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-Italic.ttf diff --git a/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Light.ttf b/packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-Light.ttf similarity index 100% rename from src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Light.ttf rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-Light.ttf diff --git a/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-LightItalic.ttf b/packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-LightItalic.ttf similarity index 100% rename from src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-LightItalic.ttf rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-LightItalic.ttf diff --git a/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Medium.ttf b/packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-Medium.ttf similarity index 100% rename from src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Medium.ttf rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-Medium.ttf diff --git a/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-MediumItalic.ttf b/packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-MediumItalic.ttf similarity index 100% rename from src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-MediumItalic.ttf rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-MediumItalic.ttf diff --git a/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Regular.ttf b/packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-Regular.ttf similarity index 100% rename from src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Regular.ttf rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-Regular.ttf diff --git a/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Thin.ttf b/packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-Thin.ttf similarity index 100% rename from src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-Thin.ttf rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-Thin.ttf diff --git a/src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-ThinItalic.ttf b/packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-ThinItalic.ttf similarity index 100% rename from src/core/server/core_app/assets/fonts/roboto_mono/RobotoMono-ThinItalic.ttf rename to packages/core/apps/core-apps-server-internal/src/assets/fonts/roboto_mono/RobotoMono-ThinItalic.ttf diff --git a/src/core/server/core_app/assets/legacy_dark_theme.css b/packages/core/apps/core-apps-server-internal/src/assets/legacy_dark_theme.css similarity index 100% rename from src/core/server/core_app/assets/legacy_dark_theme.css rename to packages/core/apps/core-apps-server-internal/src/assets/legacy_dark_theme.css diff --git a/src/core/server/core_app/assets/legacy_dark_theme.min.css b/packages/core/apps/core-apps-server-internal/src/assets/legacy_dark_theme.min.css similarity index 100% rename from src/core/server/core_app/assets/legacy_dark_theme.min.css rename to packages/core/apps/core-apps-server-internal/src/assets/legacy_dark_theme.min.css diff --git a/packages/core/apps/core-apps-server-internal/src/assets/legacy_light_theme.css b/packages/core/apps/core-apps-server-internal/src/assets/legacy_light_theme.css new file mode 100644 index 0000000000000..c5c639f60e3be --- /dev/null +++ b/packages/core/apps/core-apps-server-internal/src/assets/legacy_light_theme.css @@ -0,0 +1,4419 @@ +/*! + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +/* @notice + * This product bundles bootstrap@3.3.6 which is available under a + * "MIT" license. + * + * The MIT License (MIT) + * + * Copyright (c) 2011-2015 Twitter, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +.container { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +.row { + margin-left: -15px; + margin-right: -15px; +} +.col-xs-1, +.col-sm-1, +.col-md-1, +.col-lg-1, +.col-xs-2, +.col-sm-2, +.col-md-2, +.col-lg-2, +.col-xs-3, +.col-sm-3, +.col-md-3, +.col-lg-3, +.col-xs-4, +.col-sm-4, +.col-md-4, +.col-lg-4, +.col-xs-5, +.col-sm-5, +.col-md-5, +.col-lg-5, +.col-xs-6, +.col-sm-6, +.col-md-6, +.col-lg-6, +.col-xs-7, +.col-sm-7, +.col-md-7, +.col-lg-7, +.col-xs-8, +.col-sm-8, +.col-md-8, +.col-lg-8, +.col-xs-9, +.col-sm-9, +.col-md-9, +.col-lg-9, +.col-xs-10, +.col-sm-10, +.col-md-10, +.col-lg-10, +.col-xs-11, +.col-sm-11, +.col-md-11, +.col-lg-11, +.col-xs-12, +.col-sm-12, +.col-md-12, +.col-lg-12 { + position: relative; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; +} +.col-xs-1, +.col-xs-2, +.col-xs-3, +.col-xs-4, +.col-xs-5, +.col-xs-6, +.col-xs-7, +.col-xs-8, +.col-xs-9, +.col-xs-10, +.col-xs-11, +.col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0%; +} +@media (min-width: 768px) { + .col-sm-1, + .col-sm-2, + .col-sm-3, + .col-sm-4, + .col-sm-5, + .col-sm-6, + .col-sm-7, + .col-sm-8, + .col-sm-9, + .col-sm-10, + .col-sm-11, + .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 992px) { + .col-md-1, + .col-md-2, + .col-md-3, + .col-md-4, + .col-md-5, + .col-md-6, + .col-md-7, + .col-md-8, + .col-md-9, + .col-md-10, + .col-md-11, + .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 1200px) { + .col-lg-1, + .col-lg-2, + .col-lg-3, + .col-lg-4, + .col-lg-5, + .col-lg-6, + .col-lg-7, + .col-lg-8, + .col-lg-9, + .col-lg-10, + .col-lg-11, + .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0%; + } +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; + font-size: 14px; +} +.table thead { + font-size: 12px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #D3DAE6; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 1px solid #D3DAE6; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #D3DAE6; +} +.table .table { + background-color: #FFF; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; + font-size: 12px; +} +.table-bordered { + border: 1px solid #D3DAE6; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #D3DAE6; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: #D3DAE6; +} +.table-hover > tbody > tr:hover { + background-color: #D3DAE6; +} +table col[class*="col-"] { + position: static; + float: none; + display: table-column; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + float: none; + display: table-cell; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #D3DAE6; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #c3ccdd; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #017D73; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #01645c; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #006BB4; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #005c9b; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #F5A700; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #dc9600; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #BD271E; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #a7221b; +} +.table-responsive { + overflow-x: auto; + min-height: 0.01%; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #D3DAE6; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +.form-control { + display: block; + width: 100%; + height: 32px; + padding: 5px 15px; + font-size: 14px; + line-height: 1.42857143; + color: #343741; + background-color: #fafbfd; + background-image: none; + border: 1px solid #D3DAE6; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #006BB4; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(0, 107, 180, 0.6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(0, 107, 180, 0.6); +} +.form-control::-moz-placeholder { + color: #98A2B3; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #98A2B3; +} +.form-control::-webkit-input-placeholder { + color: #98A2B3; +} +.form-control::-ms-expand { + border: 0; + background-color: transparent; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: #D3DAE6; + opacity: 1; +} +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} +textarea.form-control { + height: auto; +} +.form-group:not(:empty) { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-left: -20px; + margin-top: 4px \9; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + vertical-align: middle; + font-weight: normal; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.form-control-static { + padding-top: 6px; + padding-bottom: 6px; + margin-bottom: 0; + min-height: 34px; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-left: 0; + padding-right: 0; +} +.input-sm { + height: 32px; + padding: 6px 9px; + font-size: 12px; + line-height: 1.5; + border-radius: 4px; +} +select.input-sm { + height: 32px; + line-height: 32px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.form-group-sm .form-control { + height: 32px; + padding: 6px 9px; + font-size: 12px; + line-height: 1.5; + border-radius: 4px; +} +.form-group-sm select.form-control { + height: 32px; + line-height: 32px; +} +.form-group-sm textarea.form-control, +.form-group-sm select[multiple].form-control { + height: auto; +} +.form-group-sm .form-control-static { + height: 32px; + min-height: 32px; + padding: 7px 9px; + font-size: 12px; + line-height: 1.5; +} +.input-lg { + height: 62px; + padding: 18px 27px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 4px; +} +select.input-lg { + height: 62px; + line-height: 62px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.form-group-lg .form-control { + height: 62px; + padding: 18px 27px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 4px; +} +.form-group-lg select.form-control { + height: 62px; + line-height: 62px; +} +.form-group-lg textarea.form-control, +.form-group-lg select[multiple].form-control { + height: auto; +} +.form-group-lg .form-control-static { + height: 62px; + min-height: 38px; + padding: 19px 27px; + font-size: 18px; + line-height: 1.3333333; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 40px; +} +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 32px; + height: 32px; + line-height: 32px; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { + width: 62px; + height: 62px; + line-height: 62px; +} +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { + width: 32px; + height: 32px; + line-height: 32px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #FFF; +} +.has-success .form-control { + border-color: #FFF; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-success .form-control:focus { + border-color: #e6e6e6; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; +} +.has-success .input-group-addon { + color: #FFF; + border-color: #FFF; + background-color: #017D73; +} +.has-success .form-control-feedback { + color: #FFF; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #FFF; +} +.has-warning .form-control { + border-color: #FFF; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-warning .form-control:focus { + border-color: #e6e6e6; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; +} +.has-warning .input-group-addon { + color: #FFF; + border-color: #FFF; + background-color: #F5A700; +} +.has-warning .form-control-feedback { + color: #FFF; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #FFF; +} +.has-error .form-control { + border-color: #FFF; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-error .form-control:focus { + border-color: #e6e6e6; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; +} +.has-error .input-group-addon { + color: #FFF; + border-color: #FFF; + background-color: #BD271E; +} +.has-error .form-control-feedback { + color: #FFF; +} +.has-feedback label ~ .form-control-feedback { + top: 25px; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #6d7388; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-static { + display: inline-block; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + margin-top: 0; + margin-bottom: 0; + padding-top: 6px; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 26px; +} +.form-horizontal .form-group { + margin-left: -15px; + margin-right: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + text-align: right; + margin-bottom: 0; + padding-top: 6px; + } +} +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 19px; + font-size: 18px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 7px; + font-size: 12px; + } +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-muted { + color: #b2bac6; +} +.text-primary { + color: #343741; +} +a.text-primary:hover, +a.text-primary:focus { + color: #1d1f25; +} +.text-success { + color: #FFF; +} +a.text-success:hover, +a.text-success:focus { + color: #e6e6e6; +} +.text-info { + color: #FFF; +} +a.text-info:hover, +a.text-info:focus { + color: #e6e6e6; +} +.text-warning { + color: #FFF; +} +a.text-warning:hover, +a.text-warning:focus { + color: #e6e6e6; +} +.text-danger { + color: #FFF; +} +a.text-danger:hover, +a.text-danger:focus { + color: #e6e6e6; +} +.bg-info { + background-color: #006BB4; +} +a.bg-info:hover, +a.bg-info:focus { + background-color: #004d81; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +@media (min-width: 0) { + .dl-horizontal dt { + float: left; + width: 160px; + clear: left; + text-align: right; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + -o-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-property: height, visibility; + transition-property: height, visibility; + -webkit-transition-duration: 0.35s; + transition-duration: 0.35s; + -webkit-transition-timing-function: ease; + transition-timing-function: ease; +} +/** + * ui/angular-ui-select depends upon these styles. Don't use them in your markup. + * Please use the UI Framework styles instead. + */ +.btn { + display: inline-block; + margin-bottom: 0; + font-weight: normal; + text-align: center; + vertical-align: middle; + touch-action: manipulation; + cursor: pointer; + background-image: none; + border: 1px solid transparent; + white-space: nowrap; + padding: 5px 15px; + font-size: 14px; + line-height: 1.42857143; + border-radius: 4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + box-shadow: 0 0 0 1px white, 0 0 0 2px #0079a5; + /* 3 */ +} +.btn:hover, +.btn:focus, +.btn.focus { + color: #FFF; + text-decoration: none; +} +.btn:active, +.btn.active { + outline: 0; + background-image: none; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; +} +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} +.btn-default { + color: #FFF; + background-color: #006BB4; + border-color: #006BB4; +} +.btn-default:focus, +.btn-default.focus { + color: #FFF; + background-color: #004d81; + border-color: #001f35; +} +.btn-default:hover { + color: #FFF; + background-color: #004d81; + border-color: #004777; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #FFF; + background-color: #004d81; + border-color: #004777; +} +.btn-default:active:hover, +.btn-default.active:hover, +.open > .dropdown-toggle.btn-default:hover, +.btn-default:active:focus, +.btn-default.active:focus, +.open > .dropdown-toggle.btn-default:focus, +.btn-default:active.focus, +.btn-default.active.focus, +.open > .dropdown-toggle.btn-default.focus { + color: #FFF; + background-color: #00375d; + border-color: #001f35; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus { + background-color: #006BB4; + border-color: #006BB4; +} +.btn-default .badge { + color: #006BB4; + background-color: #FFF; +} +.btn-primary { + color: #FFF; + background-color: #006BB4; + border-color: #006BB4; +} +.btn-primary:focus, +.btn-primary.focus { + color: #FFF; + background-color: #004d81; + border-color: #001f35; +} +.btn-primary:hover { + color: #FFF; + background-color: #004d81; + border-color: #004777; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #FFF; + background-color: #004d81; + border-color: #004777; +} +.btn-primary:active:hover, +.btn-primary.active:hover, +.open > .dropdown-toggle.btn-primary:hover, +.btn-primary:active:focus, +.btn-primary.active:focus, +.open > .dropdown-toggle.btn-primary:focus, +.btn-primary:active.focus, +.btn-primary.active.focus, +.open > .dropdown-toggle.btn-primary.focus { + color: #FFF; + background-color: #00375d; + border-color: #001f35; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus { + background-color: #006BB4; + border-color: #006BB4; +} +.btn-primary .badge { + color: #006BB4; + background-color: #FFF; +} +.btn-xs { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 4px; +} +.navbar { + position: relative; + min-height: 45px; + margin-bottom: 0px; + border: 1px solid transparent; +} +@media (min-width: 0) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 0) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + overflow-x: visible; + padding-right: 10px; + padding-left: 10px; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + -webkit-overflow-scrolling: touch; +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 0) { + .navbar-collapse { + width: auto; + border-top: 0; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-left: 0; + padding-right: 0; + } +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -10px; + margin-left: -10px; +} +@media (min-width: 0) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1050; +} +@media (min-width: 0) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + padding: 12.5px 10px; + font-size: 18px; + line-height: 20px; + height: 45px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +.navbar-brand > img { + display: block; +} +@media (min-width: 0) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -10px; + } +} +.navbar-toggle { + position: relative; + float: right; + margin-right: 10px; + padding: 9px 10px; + margin-top: 5.5px; + margin-bottom: 5.5px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 0) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 6.25px -10px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: -1) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 0) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 12.5px; + padding-bottom: 12.5px; + } +} +.navbar-form { + margin-left: -10px; + margin-right: -10px; + padding: 10px 10px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + margin-top: 6.5px; + margin-bottom: 6.5px; +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: -1) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 0) { + .navbar-form { + width: auto; + border: 0; + margin-left: 0; + margin-right: 0; + padding-top: 0; + padding-bottom: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-text { + margin-top: 12.5px; + margin-bottom: 12.5px; +} +@media (min-width: 0) { + .navbar-text { + float: left; + margin-left: 10px; + margin-right: 10px; + } +} +@media (min-width: 0) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -10px; + } + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} +.navbar-default { + background-color: #F5F7FA; + border-color: transparent; +} +.navbar-default .navbar-brand { + color: #69707D; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #69707D; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #69707D; +} +.navbar-default .navbar-nav > li > a { + color: #69707D; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #69707D; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #343741; + background-color: transparent; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #69707D; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #d3dce9; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #d3dce9; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #FFF; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: transparent; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + background-color: transparent; + color: #343741; +} +@media (max-width: -1) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #69707D; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #69707D; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #343741; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #69707D; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #69707D; +} +.navbar-default .navbar-link:hover { + color: #69707D; +} +.navbar-inverse { + background-color: #343741; + border-color: #1d1f25; +} +.navbar-inverse .navbar-brand { + color: #FFF; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #FFF; + background-color: #4b4f5d; +} +.navbar-inverse .navbar-text { + color: #FFF; +} +.navbar-inverse .navbar-nav > li > a { + color: #D3DAE6; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #FFF; + background-color: #61677a; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #FFF; + background-color: #69707D; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #b2bac6; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #1d1f25; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #1d1f25; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #FFF; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #24262d; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + background-color: #69707D; + color: #FFF; +} +@media (max-width: -1) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #1d1f25; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #1d1f25; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #D3DAE6; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #FFF; + background-color: #61677a; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #FFF; + background-color: #69707D; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #b2bac6; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #D3DAE6; +} +.navbar-inverse .navbar-link:hover { + color: #FFF; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: none; + opacity: 0.2; + filter: alpha(opacity=20); +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + opacity: 0.5; + filter: alpha(opacity=50); +} +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} +.modal-open { + overflow: hidden; +} +.modal { + display: none; + overflow: hidden; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1070; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); + -webkit-transition: -webkit-transform 0.3s ease-out; + -moz-transition: -moz-transform 0.3s ease-out; + -o-transition: -o-transform 0.3s ease-out; + transition: transform 0.3s ease-out; +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #FFF; + border: 1px solid #98A2B3; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 4px; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + background-clip: padding-box; + outline: 0; +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1060; + background-color: #000; +} +.modal-backdrop.fade { + opacity: 0; + filter: alpha(opacity=0); +} +.modal-backdrop.in { + opacity: 0.5; + filter: alpha(opacity=50); +} +.modal-header { + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 15px; +} +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + overflow: hidden; + height: 20px; + margin-bottom: 20px; + background-color: #b8bec8; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #FFF; + text-align: center; + background-color: #54B399; + -webkit-transition: width 0.6s ease; + -o-transition: width 0.6s ease; + transition: width 0.6s ease; +} +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #017D73; +} +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #006BB4; +} +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #F5A700; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #BD271E; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.list-group { + margin-bottom: 20px; + padding-left: 0; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #FFF; + border: 1px solid #D3DAE6; +} +.list-group-item:first-child { + border-top-right-radius: 4px; + border-top-left-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.list-group-item--noBorder { + border-top: 0; +} +a.list-group-item, +button.list-group-item { + color: #69707D; +} +a.list-group-item .list-group-item-heading, +button.list-group-item .list-group-item-heading { + color: #343741; +} +a.list-group-item:hover, +button.list-group-item:hover, +a.list-group-item:focus, +button.list-group-item:focus { + text-decoration: none; + color: #69707D; + background-color: #F5F7FA; +} +button.list-group-item { + width: 100%; + text-align: left; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + background-color: #D3DAE6; + color: #b2bac6; + cursor: not-allowed; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #b2bac6; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #343741; + background-color: #343741; + border-color: #343741; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #969bab; +} +.list-group-item-success { + color: #FFF; + background-color: #017D73; +} +a.list-group-item-success, +button.list-group-item-success { + color: #FFF; +} +a.list-group-item-success .list-group-item-heading, +button.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +button.list-group-item-success:hover, +a.list-group-item-success:focus, +button.list-group-item-success:focus { + color: #FFF; + background-color: #01645c; +} +a.list-group-item-success.active, +button.list-group-item-success.active, +a.list-group-item-success.active:hover, +button.list-group-item-success.active:hover, +a.list-group-item-success.active:focus, +button.list-group-item-success.active:focus { + color: #fff; + background-color: #FFF; + border-color: #FFF; +} +.list-group-item-info { + color: #FFF; + background-color: #006BB4; +} +a.list-group-item-info, +button.list-group-item-info { + color: #FFF; +} +a.list-group-item-info .list-group-item-heading, +button.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +button.list-group-item-info:hover, +a.list-group-item-info:focus, +button.list-group-item-info:focus { + color: #FFF; + background-color: #005c9b; +} +a.list-group-item-info.active, +button.list-group-item-info.active, +a.list-group-item-info.active:hover, +button.list-group-item-info.active:hover, +a.list-group-item-info.active:focus, +button.list-group-item-info.active:focus { + color: #fff; + background-color: #FFF; + border-color: #FFF; +} +.list-group-item-warning { + color: #FFF; + background-color: #F5A700; +} +a.list-group-item-warning, +button.list-group-item-warning { + color: #FFF; +} +a.list-group-item-warning .list-group-item-heading, +button.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +button.list-group-item-warning:hover, +a.list-group-item-warning:focus, +button.list-group-item-warning:focus { + color: #FFF; + background-color: #dc9600; +} +a.list-group-item-warning.active, +button.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +button.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus, +button.list-group-item-warning.active:focus { + color: #fff; + background-color: #FFF; + border-color: #FFF; +} +.list-group-item-danger { + color: #FFF; + background-color: #BD271E; +} +a.list-group-item-danger, +button.list-group-item-danger { + color: #FFF; +} +a.list-group-item-danger .list-group-item-heading, +button.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +button.list-group-item-danger:hover, +a.list-group-item-danger:focus, +button.list-group-item-danger:focus { + color: #FFF; + background-color: #a7221b; +} +a.list-group-item-danger.active, +button.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +button.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus, +button.list-group-item-danger.active:focus { + color: #fff; + background-color: #FFF; + border-color: #FFF; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #D3DAE6; +} +.nav > li.disabled > a { + color: #b2bac6; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #b2bac6; + text-decoration: none; + background-color: transparent; + cursor: not-allowed; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #D3DAE6; + border-color: #006BB4; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #D3DAE6; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #D3DAE6; + background-color: #FFF; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #343741; + background-color: #FFF; + border: 1px solid #D3DAE6; + border-bottom-color: transparent; + cursor: default; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + text-align: center; + margin-bottom: 5px; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #FFF; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #FFF; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #FFF; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #FFF; + background-color: #006BB4; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + text-align: center; + margin-bottom: 5px; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #FFF; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #FFF; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #FFF; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + background-color: #017D73; + border-color: #014a44; + color: #FFF; +} +.alert-success hr { + border-top-color: #00312d; +} +.alert-success .alert-link { + color: #e6e6e6; +} +.alert-info { + background-color: #006BB4; + border-color: #004d81; + color: #FFF; +} +.alert-info hr { + border-top-color: #003e68; +} +.alert-info .alert-link { + color: #e6e6e6; +} +.alert-warning { + background-color: #F5A700; + border-color: #c28400; + color: #FFF; +} +.alert-warning hr { + border-top-color: #a97300; +} +.alert-warning .alert-link { + color: #e6e6e6; +} +.alert-danger { + background-color: #BD271E; + border-color: #911e17; + color: #FFF; +} +.alert-danger hr { + border-top-color: #7b1914; +} +.alert-danger .alert-link { + color: #e6e6e6; +} +.bsTooltip { + position: absolute; + z-index: 1040; + display: block; + font-family: 'Open Sans', Helvetica, Arial, sans-serif; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-break: auto; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + font-size: 12px; + opacity: 0; + filter: alpha(opacity=0); +} +.bsTooltip.in { + opacity: 0.8; + filter: alpha(opacity=80); +} +.bsTooltip.top { + margin-top: -3px; + padding: 5px 0; +} +.bsTooltip.right { + margin-left: 3px; + padding: 0 5px; +} +.bsTooltip.bottom { + margin-top: 3px; + padding: 5px 0; +} +.bsTooltip.left { + margin-left: -3px; + padding: 0 5px; +} +.bsTooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 4px; +} +.bsTooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.bsTooltip.top .bsTooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.bsTooltip.top-left .bsTooltip-arrow { + bottom: 0; + right: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.bsTooltip.top-right .bsTooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.bsTooltip.right .bsTooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} +.bsTooltip.left .bsTooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} +.bsTooltip.bottom .bsTooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.bsTooltip.bottom-left .bsTooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.bsTooltip.bottom-right .bsTooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px dashed; + border-top: 4px solid \9; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + font-size: 14px; + text-align: left; + background-color: #FFF; + border: 1px solid #D3DAE6; + border-radius: 4px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1); + background-clip: padding-box; +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #D3DAE6; +} +.dropdown-menu > li > a, +.dropdown-menu > li > button { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #7b7b7b; + white-space: nowrap; +} +.dropdown-menu > li > button { + appearance: none; + background: none; + border: none; + width: 100%; + text-align: left; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > button:hover, +.dropdown-menu > li > a:focus, +.dropdown-menu > li > button:focus { + text-decoration: none; + color: #FFF; + background-color: #343741; +} +.dropdown-menu > .active > button, +.dropdown-menu > .active > a, +.dropdown-menu > .active > button:hover, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > button:focus, +.dropdown-menu > .active > a:focus { + color: #FFF; + text-decoration: none; + outline: 0; + background-color: #343741; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #98A2B3; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + cursor: not-allowed; +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + left: auto; + right: 0; +} +.dropdown-menu-left { + left: 0; + right: auto; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #98A2B3; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + border-top: 0; + border-bottom: 4px dashed; + border-bottom: 4px solid \9; + content: ""; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +@media (min-width: 0) { + .navbar-right .dropdown-menu { + left: auto; + right: 0; + } + .navbar-right .dropdown-menu-left { + left: 0; + right: auto; + } +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-left: 0; + padding-right: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group .form-control:focus { + z-index: 3; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon { + height: 62px; + padding: 18px 27px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 4px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon { + height: 62px; + line-height: 62px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon { + height: 32px; + padding: 6px 9px; + font-size: 12px; + line-height: 1.5; + border-radius: 4px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon { + height: 32px; + line-height: 32px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon { + height: auto; +} +.input-group-addon, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 5px 15px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #343741; + text-align: center; + background-color: #D3DAE6; + border: 1px solid #D3DAE6; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 6px 9px; + font-size: 12px; + border-radius: 4px; +} +.input-group-addon.input-lg { + padding: 18px 27px; + font-size: 18px; + border-radius: 4px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 5px 15px; + line-height: 1.42857143; + text-decoration: none; + color: #006BB4; + background-color: transparent; + border: 1px solid transparent; + margin-left: -1px; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 2; + color: #006BB4; + background-color: rgba(0, 0, 0, 0); + border-color: transparent; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 3; + color: #343741; + background-color: rgba(0, 0, 0, 0); + border-color: transparent; + cursor: default; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #343741; + background-color: rgba(38, 38, 38, 0); + border-color: transparent; + cursor: not-allowed; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 18px 27px; + font-size: 18px; + line-height: 1.3333333; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 6px 9px; + font-size: 12px; + line-height: 1.5; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; +} +.pager { + padding-left: 0; + margin: 20px 0; + list-style: none; + text-align: center; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: transparent; + border: 1px solid transparent; + border-radius: 0; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: rgba(0, 0, 0, 0); +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #FFF; + background-color: transparent; + cursor: not-allowed; +} +.label { + display: inline; + padding: 0.2em 0.6em 0.3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #FFF; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25em; +} +a.label:hover, +a.label:focus { + color: #FFF; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.label-default { + background-color: #006BB4; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #004d81; +} +.label-primary { + background-color: #343741; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #1d1f25; +} +.label-success { + background-color: #017D73; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #014a44; +} +.label-info { + background-color: #006BB4; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #004d81; +} +.label-warning { + background-color: #F5A700; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #c28400; +} +.label-danger { + background-color: #BD271E; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #911e17; +} +.panel { + margin-bottom: 20px; + background-color: #FFF; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.panel-title > .small > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #F5F7FA; + border-top: 1px solid #D3DAE6; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group, +.panel > .panel-collapse > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item, +.panel > .panel-collapse > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child, +.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child, +.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-left: 15px; + padding-right: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive, +.panel > .table + .panel-body, +.panel > .table-responsive + .panel-body { + border-top: 1px solid #D3DAE6; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + border: 0; + margin-bottom: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + border-top: 1px solid #D3DAE6; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #D3DAE6; +} +.panel-default { + border-color: #D3DAE6; +} +.panel-default > .panel-heading { + color: #7b7b7b; + background-color: #F5F7FA; + border-color: #D3DAE6; +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #D3DAE6; +} +.panel-default > .panel-heading .badge { + color: #F5F7FA; + background-color: #7b7b7b; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #D3DAE6; +} +.panel-primary { + border-color: #343741; +} +.panel-primary > .panel-heading { + color: #FFF; + background-color: #343741; + border-color: #343741; +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #343741; +} +.panel-primary > .panel-heading .badge { + color: #343741; + background-color: #FFF; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #343741; +} +.panel-success { + border-color: #014a44; +} +.panel-success > .panel-heading { + color: #FFF; + background-color: #017D73; + border-color: #014a44; +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #014a44; +} +.panel-success > .panel-heading .badge { + color: #017D73; + background-color: #FFF; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #014a44; +} +.panel-info { + border-color: #004d81; +} +.panel-info > .panel-heading { + color: #FFF; + background-color: #006BB4; + border-color: #004d81; +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #004d81; +} +.panel-info > .panel-heading .badge { + color: #006BB4; + background-color: #FFF; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #004d81; +} +.panel-warning { + border-color: #c28400; +} +.panel-warning > .panel-heading { + color: #FFF; + background-color: #F5A700; + border-color: #c28400; +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #c28400; +} +.panel-warning > .panel-heading .badge { + color: #F5A700; + background-color: #FFF; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #c28400; +} +.panel-danger { + border-color: #911e17; +} +.panel-danger > .panel-heading { + color: #FFF; + background-color: #BD271E; + border-color: #911e17; +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #911e17; +} +.panel-danger > .panel-heading .badge { + color: #BD271E; + background-color: #FFF; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #911e17; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1010; + display: none; + max-width: 276px; + padding: 1px; + font-family: 'Open Sans', Helvetica, Arial, sans-serif; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-break: auto; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + font-size: 14px; + background-color: #FFF; + background-clip: padding-box; + border: 1px solid #D3DAE6; + border-radius: 4px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1); +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + margin: 0; + padding: 8px 14px; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 3px 3px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + border-width: 10px; + content: ""; +} +.popover.top > .arrow { + left: 50%; + margin-left: -11px; + border-bottom-width: 0; + border-top-color: #92a3c1; + border-top-color: #d3dae6; + bottom: -11px; +} +.popover.top > .arrow:after { + content: " "; + bottom: 1px; + margin-left: -10px; + border-bottom-width: 0; + border-top-color: #FFF; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-left-width: 0; + border-right-color: #92a3c1; + border-right-color: #d3dae6; +} +.popover.right > .arrow:after { + content: " "; + left: 1px; + bottom: -10px; + border-left-width: 0; + border-right-color: #FFF; +} +.popover.bottom > .arrow { + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #92a3c1; + border-bottom-color: #d3dae6; + top: -11px; +} +.popover.bottom > .arrow:after { + content: " "; + top: 1px; + margin-left: -10px; + border-top-width: 0; + border-bottom-color: #FFF; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #92a3c1; + border-left-color: #d3dae6; +} +.popover.left > .arrow:after { + content: " "; + right: 1px; + border-right-width: 0; + border-left-color: #FFF; + bottom: -10px; +} +.clearfix:before, +.clearfix:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.modal-header:before, +.modal-header:after, +.modal-footer:before, +.modal-footer:after, +.nav:before, +.nav:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after { + content: " "; + display: table; +} +.clearfix:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.dl-horizontal dd:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.modal-header:after, +.modal-footer:after, +.nav:after, +.pager:after, +.panel-body:after { + clear: both; +} +.center-block { + display: block; + margin-left: auto; + margin-right: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; +} +.affix { + position: fixed; +} +.navbar > .container-fluid > .navbar-nav:not(.pull-right):first-child, +.navbar > .container-fluid > .navbar-form:not(.pull-right):first-child { + margin-left: -15px; + margin-top: 4px; +} +.navbar { + border-width: 0; +} +.navbar-btn-link { + margin: 0; + border-radius: 0; +} +@media (max-width: 768px) { + .navbar-btn-link { + width: 100%; + text-align: left; + } +} +.navbar-default .badge { + background-color: #FFF; + color: #F5F7FA; +} +.navbar-inverse .kbnGlobalNav__logoBrand { + height: 45px; + width: 252px; + background-color: #4b4f5d; +} +.navbar-inverse .kbnGlobalNav__smallLogoBrand { + height: 45px; + width: 45px; + background-color: #4b4f5d; +} +.navbar-inverse .badge { + background-color: #FFF; + color: #4b4f5d; +} +.navbar-brand { + cursor: default; + font-size: 1.8em; + user-select: none; +} +.navbar-nav { + font-size: 12px; +} +.navbar-nav > .active > a { + border-bottom-color: #7b7b7b; + background-color: transparent; +} +.navbar-toggle { + margin-top: 4px; +} +.text-primary, +.text-primary:hover { + color: #343741; +} +.text-success, +.text-success:hover { + color: #017D73; +} +.text-danger, +.text-danger:hover { + color: #BD271E; +} +.text-warning, +.text-warning:hover { + color: #F5A700; +} +.text-info, +.text-info:hover { + color: #006BB4; +} +table .success, +.table .success, +table .warning, +.table .warning, +table .danger, +.table .danger, +table .info, +.table .info { + color: #FFF; +} +table .success a, +.table .success a, +table .warning a, +.table .warning a, +table .danger a, +.table .danger a, +table .info a, +.table .info a { + color: #FFF; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #D3DAE6; +} +.form-control, +input { + border-width: 1px; + -webkit-box-shadow: none; + box-shadow: none; +} +.form-control:focus, +input:focus { + -webkit-box-shadow: none; + box-shadow: none; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning .form-control-feedback { + color: #F5A700; +} +.has-warning .form-control, +.has-warning .form-control:focus { + border: 1px solid; + border-color: #F5A700; +} +.has-warning .input-group-addon { + border-color: #F5A700; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error .form-control-feedback { + color: #BD271E; +} +.has-error .form-control, +.has-error .form-control:focus { + border: 1px solid; + border-color: #BD271E; +} +.has-error .input-group-addon { + border-color: #BD271E; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success .form-control-feedback { + color: #017D73; +} +.has-success .form-control, +.has-success .form-control:focus { + border: solid #017D73; +} +.has-success .input-group-addon { + border-color: #017D73; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + border-color: transparent; +} +.pager a, +.pager a:hover { + color: #FFF; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + background-color: rgba(38, 38, 38, 0); +} +.panel { + border-radius: 0; + -webkit-box-shadow: 0 0 0 rgba(0, 0, 0, 0); + box-shadow: 0 0 0 rgba(0, 0, 0, 0); +} +.progress { + -webkit-box-shadow: none; + box-shadow: none; +} +.progress .progress-bar { + font-size: 10px; + line-height: 10px; +} +.well { + -webkit-box-shadow: none; + box-shadow: none; +} diff --git a/src/core/server/core_app/assets/legacy_light_theme.min.css b/packages/core/apps/core-apps-server-internal/src/assets/legacy_light_theme.min.css similarity index 100% rename from src/core/server/core_app/assets/legacy_light_theme.min.css rename to packages/core/apps/core-apps-server-internal/src/assets/legacy_light_theme.min.css diff --git a/src/core/server/core_app/bundle_routes/bundle_route.test.mocks.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/bundle_route.test.mocks.ts similarity index 100% rename from src/core/server/core_app/bundle_routes/bundle_route.test.mocks.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/bundle_route.test.mocks.ts diff --git a/src/core/server/core_app/bundle_routes/bundle_route.test.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/bundle_route.test.ts similarity index 100% rename from src/core/server/core_app/bundle_routes/bundle_route.test.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/bundle_route.test.ts diff --git a/src/core/server/core_app/bundle_routes/bundles_route.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts similarity index 100% rename from src/core/server/core_app/bundle_routes/bundles_route.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts diff --git a/src/core/server/core_app/bundle_routes/dynamic_asset_response.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/dynamic_asset_response.ts similarity index 98% rename from src/core/server/core_app/bundle_routes/dynamic_asset_response.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/dynamic_asset_response.ts index 5287ff483f7ab..219beced6ca65 100644 --- a/src/core/server/core_app/bundle_routes/dynamic_asset_response.ts +++ b/packages/core/apps/core-apps-server-internal/src/bundle_routes/dynamic_asset_response.ts @@ -13,7 +13,7 @@ import agent from 'elastic-apm-node'; import type { RequestHandler } from '@kbn/core-http-server'; import { fstat, close } from './fs'; -import { IFileHashCache } from './file_hash_cache'; +import type { IFileHashCache } from './file_hash_cache'; import { getFileHash } from './file_hash'; import { selectCompressedFile } from './select_compressed_file'; diff --git a/src/core/server/core_app/bundle_routes/file_hash.test.mocks.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/file_hash.test.mocks.ts similarity index 100% rename from src/core/server/core_app/bundle_routes/file_hash.test.mocks.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/file_hash.test.mocks.ts diff --git a/src/core/server/core_app/bundle_routes/file_hash.test.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/file_hash.test.ts similarity index 100% rename from src/core/server/core_app/bundle_routes/file_hash.test.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/file_hash.test.ts diff --git a/src/core/server/core_app/bundle_routes/file_hash.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/file_hash.ts similarity index 94% rename from src/core/server/core_app/bundle_routes/file_hash.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/file_hash.ts index e309873254999..da3c85edb377e 100644 --- a/src/core/server/core_app/bundle_routes/file_hash.ts +++ b/packages/core/apps/core-apps-server-internal/src/bundle_routes/file_hash.ts @@ -8,7 +8,7 @@ import type { Stats } from 'fs'; import { generateFileHash, getFileCacheKey } from './utils'; -import { IFileHashCache } from './file_hash_cache'; +import type { IFileHashCache } from './file_hash_cache'; /** * Get the hash of a file via a file descriptor diff --git a/src/core/server/core_app/bundle_routes/file_hash_cache.test.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/file_hash_cache.test.ts similarity index 100% rename from src/core/server/core_app/bundle_routes/file_hash_cache.test.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/file_hash_cache.test.ts diff --git a/src/core/server/core_app/bundle_routes/file_hash_cache.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/file_hash_cache.ts similarity index 100% rename from src/core/server/core_app/bundle_routes/file_hash_cache.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/file_hash_cache.ts diff --git a/src/core/server/core_app/bundle_routes/fs.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/fs.ts similarity index 100% rename from src/core/server/core_app/bundle_routes/fs.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/fs.ts diff --git a/src/core/server/core_app/bundle_routes/index.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/index.ts similarity index 76% rename from src/core/server/core_app/bundle_routes/index.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/index.ts index 5b2374a74356a..1877d85f83409 100644 --- a/src/core/server/core_app/bundle_routes/index.ts +++ b/packages/core/apps/core-apps-server-internal/src/bundle_routes/index.ts @@ -7,3 +7,5 @@ */ export { registerBundleRoutes } from './register_bundle_routes'; +export { type IFileHashCache, FileHashCache } from './file_hash_cache'; +export { registerRouteForBundle } from './bundles_route'; diff --git a/src/core/server/core_app/bundle_routes/register_bundle_routes.test.mocks.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.test.mocks.ts similarity index 100% rename from src/core/server/core_app/bundle_routes/register_bundle_routes.test.mocks.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.test.mocks.ts diff --git a/src/core/server/core_app/bundle_routes/register_bundle_routes.test.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.test.ts similarity index 98% rename from src/core/server/core_app/bundle_routes/register_bundle_routes.test.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.test.ts index f816a85404f7a..6ec5603137340 100644 --- a/src/core/server/core_app/bundle_routes/register_bundle_routes.test.ts +++ b/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.test.ts @@ -8,7 +8,7 @@ import { registerRouteForBundleMock } from './register_bundle_routes.test.mocks'; -import { PackageInfo } from '@kbn/config'; +import type { PackageInfo } from '@kbn/config'; import { httpServiceMock } from '@kbn/core-http-server-mocks'; import type { InternalPluginInfo, UiPlugins } from '@kbn/core-plugins-base-server-internal'; import { registerBundleRoutes } from './register_bundle_routes'; diff --git a/src/core/server/core_app/bundle_routes/register_bundle_routes.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.ts similarity index 94% rename from src/core/server/core_app/bundle_routes/register_bundle_routes.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.ts index 4adb50fdc96ca..d0b5094edf94c 100644 --- a/src/core/server/core_app/bundle_routes/register_bundle_routes.ts +++ b/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.ts @@ -7,10 +7,10 @@ */ import { join } from 'path'; -import { PackageInfo } from '@kbn/config'; +import type { PackageInfo } from '@kbn/config'; import { fromRoot } from '@kbn/utils'; import UiSharedDepsNpm from '@kbn/ui-shared-deps-npm'; -import * as UiSharedDepsSrc from '@kbn/ui-shared-deps-src'; +import { distDir as UiSharedDepsSrcDistDir } from '@kbn/ui-shared-deps-src'; import type { IRouter } from '@kbn/core-http-server'; import type { UiPlugins } from '@kbn/core-plugins-base-server-internal'; import { FileHashCache } from './file_hash_cache'; @@ -53,7 +53,7 @@ export function registerBundleRoutes({ registerRouteForBundle(router, { publicPath: `${serverBasePath}/${buildNum}/bundles/kbn-ui-shared-deps-src/`, routePath: `/${buildNum}/bundles/kbn-ui-shared-deps-src/`, - bundlesPath: UiSharedDepsSrc.distDir, + bundlesPath: UiSharedDepsSrcDistDir, fileHashCache, isDist, }); diff --git a/src/core/server/core_app/bundle_routes/select_compressed_file.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/select_compressed_file.ts similarity index 100% rename from src/core/server/core_app/bundle_routes/select_compressed_file.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/select_compressed_file.ts diff --git a/src/core/server/core_app/bundle_routes/utils.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/utils.ts similarity index 100% rename from src/core/server/core_app/bundle_routes/utils.ts rename to packages/core/apps/core-apps-server-internal/src/bundle_routes/utils.ts diff --git a/src/core/server/core_app/core_app.test.mocks.ts b/packages/core/apps/core-apps-server-internal/src/core_app.test.mocks.ts similarity index 100% rename from src/core/server/core_app/core_app.test.mocks.ts rename to packages/core/apps/core-apps-server-internal/src/core_app.test.mocks.ts diff --git a/src/core/server/core_app/core_app.test.ts b/packages/core/apps/core-apps-server-internal/src/core_app.test.ts similarity index 90% rename from src/core/server/core_app/core_app.test.ts rename to packages/core/apps/core-apps-server-internal/src/core_app.test.ts index 31e4b6176a889..13122b4b09eb7 100644 --- a/src/core/server/core_app/core_app.test.ts +++ b/packages/core/apps/core-apps-server-internal/src/core_app.test.ts @@ -11,11 +11,12 @@ import { registerBundleRoutesMock } from './core_app.test.mocks'; import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { mockRouter } from '@kbn/core-http-router-server-mocks'; import type { UiPlugins } from '@kbn/core-plugins-base-server-internal'; -import { coreMock, httpServerMock } from '../mocks'; +import { httpServerMock } from '@kbn/core-http-server-mocks'; import { httpResourcesMock } from '@kbn/core-http-resources-server-mocks'; import { PluginType } from '@kbn/core-base-common'; -import { CoreApp } from './core_app'; -import { RequestHandlerContext } from '..'; +import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; +import { coreInternalLifecycleMock } from '@kbn/core-lifecycle-server-mocks'; +import { CoreAppsService } from './core_app'; const emptyPlugins = (): UiPlugins => ({ internal: new Map(), @@ -25,16 +26,17 @@ const emptyPlugins = (): UiPlugins => ({ describe('CoreApp', () => { let coreContext: ReturnType; - let coreApp: CoreApp; - let internalCorePreboot: ReturnType; + let coreApp: CoreAppsService; + let internalCorePreboot: ReturnType; let prebootHTTPResourcesRegistrar: ReturnType; - let internalCoreSetup: ReturnType; + let internalCoreSetup: ReturnType; let httpResourcesRegistrar: ReturnType; beforeEach(() => { coreContext = mockCoreContext.create(); - internalCorePreboot = coreMock.createInternalPreboot(); + internalCorePreboot = coreInternalLifecycleMock.createInternalPreboot(); + internalCorePreboot.http.registerRoutes.mockImplementation((path, callback) => callback(mockRouter.create()) ); @@ -43,10 +45,11 @@ describe('CoreApp', () => { prebootHTTPResourcesRegistrar ); - internalCoreSetup = coreMock.createInternalSetup(); + internalCoreSetup = coreInternalLifecycleMock.createInternalSetup(); + httpResourcesRegistrar = httpResourcesMock.createRegistrar(); internalCoreSetup.httpResources.createRegistrar.mockReturnValue(httpResourcesRegistrar); - coreApp = new CoreApp(coreContext); + coreApp = new CoreAppsService(coreContext); }); afterEach(() => { diff --git a/src/core/server/core_app/core_app.ts b/packages/core/apps/core-apps-server-internal/src/core_app.ts similarity index 95% rename from src/core/server/core_app/core_app.ts rename to packages/core/apps/core-apps-server-internal/src/core_app.ts index 83665512767e7..97c9eadd75707 100644 --- a/src/core/server/core_app/core_app.ts +++ b/packages/core/apps/core-apps-server-internal/src/core_app.ts @@ -22,9 +22,9 @@ import type { } from '@kbn/core-http-server'; import type { UiPlugins } from '@kbn/core-plugins-base-server-internal'; import type { HttpResources, HttpResourcesServiceToolkit } from '@kbn/core-http-resources-server'; -import { InternalCorePreboot, InternalCoreSetup } from '@kbn/core-lifecycle-server-internal'; +import type { InternalCorePreboot, InternalCoreSetup } from '@kbn/core-lifecycle-server-internal'; import { registerBundleRoutes } from './bundle_routes'; -import type { InternalCoreAppRequestHandlerContext } from './internal_types'; +import type { InternalCoreAppsServiceRequestHandlerContext } from './internal_types'; /** @internal */ interface CommonRoutesParams { @@ -39,7 +39,7 @@ interface CommonRoutesParams { } /** @internal */ -export class CoreApp { +export class CoreAppsService { private readonly logger: Logger; private readonly env: Env; @@ -91,7 +91,7 @@ export class CoreApp { private registerDefaultRoutes(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins) { const httpSetup = coreSetup.http; - const router = httpSetup.createRouter(''); + const router = httpSetup.createRouter(''); const resources = coreSetup.httpResources.createRegistrar(router); router.get({ path: '/', validate: false }, async (context, req, res) => { diff --git a/packages/core/apps/core-apps-server-internal/src/index.ts b/packages/core/apps/core-apps-server-internal/src/index.ts new file mode 100644 index 0000000000000..d2eca9036f40e --- /dev/null +++ b/packages/core/apps/core-apps-server-internal/src/index.ts @@ -0,0 +1,15 @@ +/* + * 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 { CoreAppsService } from './core_app'; +export type { + InternalCoreAppsServiceRequestHandlerContext, + InternalCoreAppsServiceRouter, +} from './internal_types'; +// only used by integration tests +export { registerRouteForBundle, FileHashCache } from './bundle_routes'; diff --git a/src/core/server/core_app/internal_types.ts b/packages/core/apps/core-apps-server-internal/src/internal_types.ts similarity index 72% rename from src/core/server/core_app/internal_types.ts rename to packages/core/apps/core-apps-server-internal/src/internal_types.ts index 49e73a67a11a8..f294c80664ae4 100644 --- a/src/core/server/core_app/internal_types.ts +++ b/packages/core/apps/core-apps-server-internal/src/internal_types.ts @@ -13,15 +13,15 @@ import type { UiSettingsRequestHandlerContext } from '@kbn/core-ui-settings-serv * Request handler context used by core's coreApp routes. * @internal */ -export interface InternalCoreAppRequestHandlerContext extends RequestHandlerContextBase { +export interface InternalCoreAppsServiceRequestHandlerContext extends RequestHandlerContextBase { core: Promise<{ uiSettings: UiSettingsRequestHandlerContext; }>; } /** - * Router bound to the {@link InternalCoreAppRequestHandlerContext}. + * Router bound to the {@link InternalCoreAppsServiceRequestHandlerContext}. * Used by core's coreApp routes. * @internal */ -export type InternalCoreAppRouter = IRouter; +export type InternalCoreAppsServiceRouter = IRouter; diff --git a/packages/core/apps/core-apps-server-internal/tsconfig.json b/packages/core/apps/core-apps-server-internal/tsconfig.json new file mode 100644 index 0000000000000..ff48529c6f303 --- /dev/null +++ b/packages/core/apps/core-apps-server-internal/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/src/core/server/integration_tests/core_app/bundle_routes.test.ts b/src/core/server/integration_tests/core_app/bundle_routes.test.ts index b3b02cef691d9..af3782b015912 100644 --- a/src/core/server/integration_tests/core_app/bundle_routes.test.ts +++ b/src/core/server/integration_tests/core_app/bundle_routes.test.ts @@ -15,8 +15,7 @@ import { contextServiceMock } from '@kbn/core-http-context-server-mocks'; import type { IRouter } from '@kbn/core-http-server'; import { HttpService } from '@kbn/core-http-server-internal'; import { createHttpServer } from '@kbn/core-http-server-mocks'; -import { registerRouteForBundle } from '../../core_app/bundle_routes/bundles_route'; -import { FileHashCache } from '../../core_app/bundle_routes/file_hash_cache'; +import { registerRouteForBundle, FileHashCache } from '@kbn/core-apps-server-internal'; const buildNum = 1234; const fooPluginFixture = resolve(__dirname, './__fixtures__/plugin/foo'); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 7bddb8da80da1..2b5c2ed5beb09 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -79,7 +79,7 @@ import { PluginsService, config as pluginsConfig, } from '@kbn/core-plugins-server-internal'; -import { CoreApp } from './core_app'; +import { CoreAppsService } from '@kbn/core-apps-server-internal'; import { elasticApmConfig } from './root/elastic_config'; const coreId = Symbol('core'); @@ -118,7 +118,7 @@ export class Server { private readonly httpResources: HttpResourcesService; private readonly status: StatusService; private readonly logging: LoggingService; - private readonly coreApp: CoreApp; + private readonly coreApp: CoreAppsService; private readonly coreUsageData: CoreUsageDataService; private readonly i18n: I18nService; private readonly deprecations: DeprecationsService; @@ -161,7 +161,7 @@ export class Server { this.node = new NodeService(core); this.metrics = new MetricsService(core); this.status = new StatusService(core); - this.coreApp = new CoreApp(core); + this.coreApp = new CoreAppsService(core); this.httpResources = new HttpResourcesService(core); this.logging = new LoggingService(core); this.coreUsageData = new CoreUsageDataService(core); diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index f79c53e115149..1289768bfbcd9 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -31,7 +31,7 @@ export const IGNORE_FILE_GLOBS = [ '**/.*', '**/__mocks__/**/*', 'x-pack/docs/**/*', - 'src/core/server/core_app/assets/fonts/**/*', + 'packages/core/apps/core-apps-server-internal/src/assets/fonts/**/*', 'src/dev/code_coverage/ingest_coverage/integration_tests/mocks/**/*', 'packages/kbn-utility-types/test-d/**/*', '**/Jenkinsfile*', diff --git a/tsconfig.base.json b/tsconfig.base.json index ef94f9bef56c2..95f620b6e4382 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -44,6 +44,8 @@ "@kbn/core-apps-browser-internal/*": ["packages/core/apps/core-apps-browser-internal/*"], "@kbn/core-apps-browser-mocks": ["packages/core/apps/core-apps-browser-mocks"], "@kbn/core-apps-browser-mocks/*": ["packages/core/apps/core-apps-browser-mocks/*"], + "@kbn/core-apps-server-internal": ["packages/core/apps/core-apps-server-internal"], + "@kbn/core-apps-server-internal/*": ["packages/core/apps/core-apps-server-internal/*"], "@kbn/core-base-browser-internal": ["packages/core/base/core-base-browser-internal"], "@kbn/core-base-browser-internal/*": ["packages/core/base/core-base-browser-internal/*"], "@kbn/core-base-browser-mocks": ["packages/core/base/core-base-browser-mocks"], diff --git a/yarn.lock b/yarn.lock index 5ce08cea40dff..ca156c451e3c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2788,6 +2788,10 @@ version "0.0.0" uid "" +"@kbn/core-apps-server-internal@link:bazel-bin/packages/core/apps/core-apps-server-internal": + version "0.0.0" + uid "" + "@kbn/core-base-browser-internal@link:bazel-bin/packages/core/base/core-base-browser-internal": version "0.0.0" uid "" From 82537934b5c7e10571e1dd26faeb888a6b345f07 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Wed, 2 Nov 2022 15:38:29 -0400 Subject: [PATCH 44/86] [Guided onboarding] Implement telemetry (#143896) --- .../public/components/guide_panel.test.tsx | 38 ++++---- .../public/components/guide_panel.tsx | 26 +++++- .../public/components/guide_panel_step.tsx | 5 +- .../public/components/quit_guide_modal.tsx | 86 +++++++++++++------ .../getting_started.test.tsx.snap | 2 +- .../getting_started.test.tsx | 2 +- .../guided_onboarding/getting_started.tsx | 2 +- 7 files changed, 113 insertions(+), 48 deletions(-) diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx index 3bf4ae8f91c8c..da1931120d371 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx @@ -232,15 +232,21 @@ describe('Guided setup', () => { expect(find('guideDescription').text()).toContain( `You've completed the Elastic Testing example guide` ); - expect(exists('useElasticButton')).toBe(true); + expect(exists('onboarding--completeGuideButton--testGuide')).toBe(true); }); describe('Steps', () => { - const clickActiveStepButton = async () => { + const clickStepButton = async ({ + telemetryGuideId, + stepNumber, + }: { + telemetryGuideId: string; + stepNumber: number; + }) => { const { component, find } = testBed; await act(async () => { - find('activeStepButton').simulate('click'); + find(`onboarding--stepButton--${telemetryGuideId}--step${stepNumber}`).simulate('click'); }); component.update(); @@ -251,9 +257,9 @@ describe('Guided setup', () => { await updateComponentWithState(component, mockActiveTestGuideState, true); - expect(find('activeStepButton').text()).toEqual('Start'); + expect(find('onboarding--stepButton--testGuide--step1').text()).toEqual('Start'); - await clickActiveStepButton(); + await clickStepButton({ telemetryGuideId: 'testGuide', stepNumber: 1 }); expect(exists('guidePanel')).toBe(false); }); @@ -263,9 +269,9 @@ describe('Guided setup', () => { await updateComponentWithState(component, mockInProgressTestGuideState, true); - expect(find('activeStepButton').text()).toEqual('Continue'); + expect(find('onboarding--stepButton--testGuide--step1').text()).toEqual('Continue'); - await clickActiveStepButton(); + await clickStepButton({ telemetryGuideId: 'testGuide', stepNumber: 1 }); expect(exists('guidePanel')).toBe(false); }); @@ -275,14 +281,14 @@ describe('Guided setup', () => { await updateComponentWithState(component, mockReadyToCompleteTestGuideState, true); - expect(find('activeStepButton').text()).toEqual('Mark done'); + expect(find('onboarding--stepButton--testGuide--step2').text()).toEqual('Mark done'); - await clickActiveStepButton(); + await clickStepButton({ telemetryGuideId: 'testGuide', stepNumber: 2 }); // The guide panel should remain open after marking a step done expect(exists('guidePanel')).toBe(true); - // Dependent on the Search guide config, which expects another step to start - expect(find('activeStepButton').text()).toEqual('Start'); + // Dependent on the Search guide config, which expects step 3 to start + expect(find('onboarding--stepButton--testGuide--step3').text()).toEqual('Start'); }); test('should render the step description as a paragraph if it is only one sentence', async () => { @@ -357,19 +363,19 @@ describe('Guided setup', () => { component.update(); - expect(exists('quitGuideModal')).toBe(true); + expect(exists('onboarding--quitGuideModal')).toBe(true); }); test('quit a guide', async () => { const { component, find, exists } = testBed; await act(async () => { - find('confirmModalConfirmButton').simulate('click'); + find('onboarding--quitGuideButton--testGuide').simulate('click'); }); component.update(); - expect(exists('quitGuideModal')).toBe(false); + expect(exists('onboarding--quitGuideModal')).toBe(false); // TODO check for the correct button behavior once https://github.com/elastic/kibana/issues/141129 is implemented }); @@ -378,12 +384,12 @@ describe('Guided setup', () => { const { component, find, exists } = testBed; await act(async () => { - find('confirmModalCancelButton').simulate('click'); + find('onboarding--cancelQuitGuideButton--testGuide').simulate('click'); }); component.update(); - expect(exists('quitGuideModal')).toBe(false); + expect(exists('onboarding--quitGuideModal')).toBe(false); expect(exists('guideButton')).toBe(true); }); }); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx index 823637b23247a..c81844bc0dbbf 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx @@ -31,6 +31,7 @@ import { i18n } from '@kbn/i18n'; import { ApplicationStart } from '@kbn/core/public'; import type { GuideState, GuideStep as GuideStepStatus } from '@kbn/guided-onboarding'; +import { GuideId } from '@kbn/guided-onboarding'; import type { GuideConfig, StepConfig } from '../types'; import type { ApiService } from '../services/api'; @@ -58,6 +59,20 @@ const getProgress = (state?: GuideState): number => { return 0; }; +// Temporarily provide a different guide ID for telemetry purposes +// Should not be necessary once https://github.com/elastic/kibana/issues/144452 is addressed +const getTelemetryGuideId = (guideId: GuideId) => { + switch (guideId) { + case 'security': + return 'siem'; + case 'observability': + return 'kubernetes'; + case 'search': + default: + return guideId; + } +}; + export const GuidePanel = ({ api, application }: GuidePanelProps) => { const { euiTheme } = useEuiTheme(); const [isGuideOpen, setIsGuideOpen] = useState(false); @@ -146,6 +161,7 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { const stepsCompleted = getProgress(guideState); const isGuideReadyToComplete = guideState?.status === 'ready_to_complete'; + const telemetryGuideId = getTelemetryGuideId(guideState.guideId); return ( <> @@ -265,6 +281,7 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { stepNumber={index + 1} handleButtonClick={() => handleStepButtonClick(stepState, step)} key={accordionId} + telemetryGuideId={telemetryGuideId} /> ); } @@ -276,7 +293,8 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { completeGuide(guideConfig.completedGuideRedirectLocation)} fill - data-test-subj="useElasticButton" + // data-test-subj used for FS tracking and testing + data-test-subj={`onboarding--completeGuideButton--${telemetryGuideId}`} > {i18n.translate('guidedOnboarding.dropdownPanel.elasticButtonLabel', { defaultMessage: 'Continue using Elastic', @@ -353,7 +371,11 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { )} {isQuitGuideModalOpen && ( - + )} ); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx index 1def8ce0a266a..eb153efca9102 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx @@ -31,6 +31,7 @@ interface GuideStepProps { stepConfig: StepConfig; stepNumber: number; handleButtonClick: () => void; + telemetryGuideId: string; } export const GuideStep = ({ @@ -39,6 +40,7 @@ export const GuideStep = ({ stepNumber, stepConfig, handleButtonClick, + telemetryGuideId, }: GuideStepProps) => { const { euiTheme } = useEuiTheme(); const styles = getGuidePanelStepStyles(euiTheme, stepStatus); @@ -111,7 +113,8 @@ export const GuideStep = ({ handleButtonClick()} fill - data-test-subj="activeStepButton" + // data-test-subj used for FS tracking and tests + data-test-subj={`onboarding--stepButton--${telemetryGuideId}--step${stepNumber}`} > {getStepButtonLabel()} diff --git a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx index 171750cd83db5..7325ffb6114d1 100644 --- a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx +++ b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx @@ -7,7 +7,16 @@ */ import React, { useState } from 'react'; -import { EuiText, EuiConfirmModal } from '@elastic/eui'; +import { + EuiText, + EuiButton, + EuiButtonEmpty, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { GuideState } from '@kbn/guided-onboarding'; import { apiService } from '../services/api'; @@ -15,9 +24,14 @@ import { apiService } from '../services/api'; interface QuitGuideModalProps { closeModal: () => void; currentGuide: GuideState; + telemetryGuideId: string; } -export const QuitGuideModal = ({ closeModal, currentGuide }: QuitGuideModalProps) => { +export const QuitGuideModal = ({ + closeModal, + currentGuide, + telemetryGuideId, +}: QuitGuideModalProps) => { const [isDeleting, setIsDeleting] = useState(false); const deleteGuide = async () => { @@ -27,32 +41,52 @@ export const QuitGuideModal = ({ closeModal, currentGuide }: QuitGuideModalProps }; return ( - - -

        - {i18n.translate('guidedOnboarding.quitGuideModal.modalDescription', { - defaultMessage: - 'You can restart anytime by opening the Setup guide from the Help menu.', + + + {i18n.translate('guidedOnboarding.quitGuideModal.modalTitle', { + defaultMessage: 'Quit this guide?', + })} + + + + +

        + {i18n.translate('guidedOnboarding.quitGuideModal.modalDescription', { + defaultMessage: + 'You can restart anytime by opening the Setup guide from the Help menu.', + })} +

        +
        + + + + {i18n.translate('guidedOnboarding.quitGuideModal.cancelButtonLabel', { + defaultMessage: 'Cancel', + })} + + + + {i18n.translate('guidedOnboarding.quitGuideModal.quitButtonLabel', { + defaultMessage: 'Quit guide', })} -

        - -
        +
        + + ); }; diff --git a/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap b/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap index 0886dd9fb0ad8..685e36e46810c 100644 --- a/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap +++ b/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap @@ -102,7 +102,7 @@ exports[`getting started should render getting started component 1`] = ` className="eui-textCenter" > I’d like to do something else (skip) diff --git a/src/plugins/home/public/application/components/guided_onboarding/getting_started.test.tsx b/src/plugins/home/public/application/components/guided_onboarding/getting_started.test.tsx index 45b643fd11433..624e56b85538e 100644 --- a/src/plugins/home/public/application/components/guided_onboarding/getting_started.test.tsx +++ b/src/plugins/home/public/application/components/guided_onboarding/getting_started.test.tsx @@ -58,7 +58,7 @@ describe('getting started', () => { test('skip button should disable home welcome screen', async () => { const component = mountWithIntl(); - const skipButton = findTestSubject(component, 'onboarding--skipUseCaseTourLink'); + const skipButton = findTestSubject(component, 'onboarding--skipGuideLink'); skipButton.simulate('click'); expect(localStorage.getItem(KEY_ENABLE_WELCOME)).toBe('false'); diff --git a/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx b/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx index c82b200dfb6bd..eb7f05ef7161a 100644 --- a/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx +++ b/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx @@ -145,7 +145,7 @@ export const GettingStarted = () => {
        {/* data-test-subj used for FS tracking */} - + {skipText}
        From 56e8555d8b73178f041edff67b98fa0d5cc5e2d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 2 Nov 2022 15:52:17 -0400 Subject: [PATCH 45/86] [APM] Metadata API does not filter by environment (#144472) --- .../app/metrics_details/service_node_metrics/index.tsx | 3 ++- .../apm/server/routes/services/get_service_node_metadata.ts | 5 +++-- x-pack/plugins/apm/server/routes/services/route.ts | 5 +++-- .../tests/services/get_service_node_metadata.spec.ts | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx index b78542f291f72..4a6dd62b46e7c 100644 --- a/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx @@ -93,13 +93,14 @@ export function ServiceNodeMetrics({ serviceNodeName }: Props) { kuery, start, end, + environment, }, }, } ); } }, - [kuery, serviceName, serviceNodeName, start, end] + [kuery, serviceName, serviceNodeName, start, end, environment] ); const { docLinks } = useApmPluginContext().core; diff --git a/x-pack/plugins/apm/server/routes/services/get_service_node_metadata.ts b/x-pack/plugins/apm/server/routes/services/get_service_node_metadata.ts index 227dcab3deb41..f85bed4b77bba 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_node_metadata.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_node_metadata.ts @@ -20,7 +20,6 @@ import { environmentQuery, serviceNodeNameQuery, } from '../../../common/utils/environment_query'; -import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceNodeMetadata({ @@ -30,6 +29,7 @@ export async function getServiceNodeMetadata({ apmEventClient, start, end, + environment, }: { kuery: string; serviceName: string; @@ -37,6 +37,7 @@ export async function getServiceNodeMetadata({ apmEventClient: APMEventClient; start: number; end: number; + environment: string; }) { const params = { apm: { @@ -50,7 +51,7 @@ export async function getServiceNodeMetadata({ filter: [ { term: { [SERVICE_NAME]: serviceName } }, ...rangeQuery(start, end), - ...environmentQuery(ENVIRONMENT_ALL.value), + ...environmentQuery(environment), ...kqlQuery(kuery), ...serviceNodeNameQuery(serviceNodeName), ], diff --git a/x-pack/plugins/apm/server/routes/services/route.ts b/x-pack/plugins/apm/server/routes/services/route.ts index 0dc2de2d68b45..d1b0e25fb4d05 100644 --- a/x-pack/plugins/apm/server/routes/services/route.ts +++ b/x-pack/plugins/apm/server/routes/services/route.ts @@ -426,7 +426,7 @@ const serviceNodeMetadataRoute = createApmServerRoute({ serviceName: t.string, serviceNodeName: t.string, }), - query: t.intersection([kueryRt, rangeRt]), + query: t.intersection([kueryRt, rangeRt, environmentRt]), }), options: { tags: ['access:apm'] }, handler: async ( @@ -435,7 +435,7 @@ const serviceNodeMetadataRoute = createApmServerRoute({ const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName, serviceNodeName } = params.path; - const { kuery, start, end } = params.query; + const { kuery, start, end, environment } = params.query; return getServiceNodeMetadata({ kuery, @@ -444,6 +444,7 @@ const serviceNodeMetadataRoute = createApmServerRoute({ serviceNodeName, start, end, + environment, }); }, }); diff --git a/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts b/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts index faa160063dfa1..9e64bc127db7c 100644 --- a/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts +++ b/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts @@ -28,6 +28,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { start: new Date(start).toISOString(), end: new Date(end).toISOString(), kuery: '', + environment: 'production', }, }, }); From f84b88bd583a318b98fddedb1f7b2b4ee85bad79 Mon Sep 17 00:00:00 2001 From: John Dorlus Date: Wed, 2 Nov 2022 17:47:51 -0400 Subject: [PATCH 46/86] CCS Console test for integration test framework (#144477) * Removed comment of the issue that was referenced for the skip. But the tests were already skipped. * Added a describe block for the tests that involve space b. There is an accessibility issue that causes one test to fail and then the subsequent test fails. The issue has been logged. The rest of the tests have been unskipped. * Added CCS Test for integration test. * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' Co-authored-by: cuffs Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../apps/ccs/ccs_console.js | 41 +++++++++++++++++++ .../apps/ccs/index.js | 1 + 2 files changed, 42 insertions(+) create mode 100644 x-pack/test/stack_functional_integration/apps/ccs/ccs_console.js diff --git a/x-pack/test/stack_functional_integration/apps/ccs/ccs_console.js b/x-pack/test/stack_functional_integration/apps/ccs/ccs_console.js new file mode 100644 index 0000000000000..8a86c5fc65f15 --- /dev/null +++ b/x-pack/test/stack_functional_integration/apps/ccs/ccs_console.js @@ -0,0 +1,41 @@ +/* + * 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 expect from '@kbn/expect'; + +export default function ({ getService, getPageObjects }) { + const retry = getService('retry'); + const log = getService('log'); + const PageObjects = getPageObjects(['common', 'console']); + + describe('Integration Tests - Console App CCS', function describeIndexTests() { + this.tags('includeFirefox'); + before(async () => { + log.debug('navigateTo console'); + await PageObjects.common.navigateToApp('console'); + await retry.try(async () => { + await PageObjects.console.collapseHelp(); + }); + }); + + describe('Perform CCS Search in Console', () => { + before(async () => { + await PageObjects.console.clearTextArea(); + }); + it('it should be able to access remote data', async () => { + await PageObjects.console.enterRequest( + '\nGET data:makelogs工程-*/_search\n {\n "query": {\n "bool": {\n "must": [\n {"match": {"extension" : "jpg"' + ); + await PageObjects.console.clickPlay(); + await retry.try(async () => { + const actualResponse = await PageObjects.console.getResponse(); + expect(actualResponse).to.contain('"_index": "data:makelogs工程-0"'); + }); + }); + }); + }); +} diff --git a/x-pack/test/stack_functional_integration/apps/ccs/index.js b/x-pack/test/stack_functional_integration/apps/ccs/index.js index ac82ca0dfda65..2f24a0e31d7dc 100644 --- a/x-pack/test/stack_functional_integration/apps/ccs/index.js +++ b/x-pack/test/stack_functional_integration/apps/ccs/index.js @@ -8,5 +8,6 @@ export default function ({ loadTestFile }) { describe('ccs test', function () { loadTestFile(require.resolve('./ccs_discover')); + loadTestFile(require.resolve('./ccs_console')); }); } From e41569b4a6f10cc0b03bd772d0f6094dde942f6e Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 3 Nov 2022 01:04:55 +0000 Subject: [PATCH 47/86] fix(NA): wrongly spread stripInternal and rootDir configs across packages (#144463) * chore(NA): remove overrides for rootDir on packages * chore(NA): replace './target_types' with 'target_types' on packages * chore(NA): removes stripInternal false configs * chore(NA): remove unused strip internals --- packages/analytics/client/tsconfig.json | 1 - packages/analytics/shippers/elastic_v3/browser/tsconfig.json | 1 - packages/analytics/shippers/elastic_v3/common/tsconfig.json | 1 - packages/analytics/shippers/elastic_v3/server/tsconfig.json | 1 - packages/analytics/shippers/fullstory/tsconfig.json | 1 - packages/analytics/shippers/gainsight/tsconfig.json | 1 - packages/content-management/table_list/tsconfig.json | 1 - .../analytics/core-analytics-browser-internal/tsconfig.json | 1 - .../core/analytics/core-analytics-browser-mocks/tsconfig.json | 1 - packages/core/analytics/core-analytics-browser/tsconfig.json | 1 - .../analytics/core-analytics-server-internal/tsconfig.json | 1 - .../core/analytics/core-analytics-server-mocks/tsconfig.json | 1 - packages/core/analytics/core-analytics-server/tsconfig.json | 1 - .../core-application-browser-internal/tsconfig.json | 2 -- .../application/core-application-browser-mocks/tsconfig.json | 2 -- .../core/application/core-application-browser/tsconfig.json | 2 -- .../core/application/core-application-common/tsconfig.json | 2 -- packages/core/apps/core-apps-browser-internal/tsconfig.json | 1 - packages/core/apps/core-apps-browser-mocks/tsconfig.json | 1 - packages/core/base/core-base-browser-internal/tsconfig.json | 1 - packages/core/base/core-base-browser-mocks/tsconfig.json | 1 - packages/core/base/core-base-common-internal/tsconfig.json | 1 - packages/core/base/core-base-common/tsconfig.json | 1 - packages/core/base/core-base-server-internal/tsconfig.json | 1 - packages/core/base/core-base-server-mocks/tsconfig.json | 1 - .../core-capabilities-browser-internal/tsconfig.json | 2 -- .../capabilities/core-capabilities-browser-mocks/tsconfig.json | 2 -- .../core/capabilities/core-capabilities-common/tsconfig.json | 1 - .../core-capabilities-server-internal/tsconfig.json | 1 - .../capabilities/core-capabilities-server-mocks/tsconfig.json | 1 - .../core/capabilities/core-capabilities-server/tsconfig.json | 1 - .../core/chrome/core-chrome-browser-internal/tsconfig.json | 1 - packages/core/chrome/core-chrome-browser-mocks/tsconfig.json | 1 - packages/core/chrome/core-chrome-browser/tsconfig.json | 1 - packages/core/config/core-config-server-internal/tsconfig.json | 1 - .../core-deprecations-browser-internal/tsconfig.json | 1 - .../deprecations/core-deprecations-browser-mocks/tsconfig.json | 1 - .../core/deprecations/core-deprecations-browser/tsconfig.json | 1 - .../core/deprecations/core-deprecations-common/tsconfig.json | 1 - .../core-deprecations-server-internal/tsconfig.json | 1 - .../deprecations/core-deprecations-server-mocks/tsconfig.json | 1 - .../core/deprecations/core-deprecations-server/tsconfig.json | 2 -- .../doc-links/core-doc-links-browser-internal/tsconfig.json | 1 - .../core/doc-links/core-doc-links-browser-mocks/tsconfig.json | 1 - packages/core/doc-links/core-doc-links-browser/tsconfig.json | 1 - .../doc-links/core-doc-links-server-internal/tsconfig.json | 1 - .../core/doc-links/core-doc-links-server-mocks/tsconfig.json | 1 - packages/core/doc-links/core-doc-links-server/tsconfig.json | 1 - .../core-elasticsearch-client-server-internal/tsconfig.json | 1 - .../core-elasticsearch-client-server-mocks/tsconfig.json | 1 - .../core-elasticsearch-server-internal/tsconfig.json | 1 - .../core-elasticsearch-server-mocks/tsconfig.json | 1 - .../core/elasticsearch/core-elasticsearch-server/tsconfig.json | 1 - .../environment/core-environment-server-internal/tsconfig.json | 1 - .../environment/core-environment-server-mocks/tsconfig.json | 1 - .../core-execution-context-browser-internal/tsconfig.json | 1 - .../core-execution-context-browser-mocks/tsconfig.json | 1 - .../core-execution-context-browser/tsconfig.json | 1 - .../core-execution-context-common/tsconfig.json | 1 - .../core-execution-context-server-internal/tsconfig.json | 1 - .../core-execution-context-server-mocks/tsconfig.json | 1 - .../core-execution-context-server/tsconfig.json | 1 - .../core-fatal-errors-browser-internal/tsconfig.json | 1 - .../fatal-errors/core-fatal-errors-browser-mocks/tsconfig.json | 1 - .../core/fatal-errors/core-fatal-errors-browser/tsconfig.json | 1 - packages/core/http/core-http-browser-internal/tsconfig.json | 1 - packages/core/http/core-http-browser-mocks/tsconfig.json | 1 - packages/core/http/core-http-browser/tsconfig.json | 1 - packages/core/http/core-http-common/tsconfig.json | 1 - .../core/http/core-http-context-server-internal/tsconfig.json | 1 - .../core/http/core-http-context-server-mocks/tsconfig.json | 1 - .../tsconfig.json | 1 - .../core-http-request-handler-context-server/tsconfig.json | 1 - .../http/core-http-resources-server-internal/tsconfig.json | 1 - .../core/http/core-http-resources-server-mocks/tsconfig.json | 1 - packages/core/http/core-http-resources-server/tsconfig.json | 1 - .../core/http/core-http-router-server-internal/tsconfig.json | 1 - packages/core/http/core-http-router-server-mocks/tsconfig.json | 1 - packages/core/http/core-http-server-internal/tsconfig.json | 1 - packages/core/http/core-http-server-mocks/tsconfig.json | 1 - packages/core/http/core-http-server/tsconfig.json | 1 - packages/core/i18n/core-i18n-browser-internal/tsconfig.json | 1 - packages/core/i18n/core-i18n-browser-mocks/tsconfig.json | 1 - packages/core/i18n/core-i18n-browser/tsconfig.json | 1 - packages/core/i18n/core-i18n-server-internal/tsconfig.json | 2 -- packages/core/i18n/core-i18n-server-mocks/tsconfig.json | 1 - packages/core/i18n/core-i18n-server/tsconfig.json | 1 - .../core-injected-metadata-browser-internal/tsconfig.json | 1 - .../core-injected-metadata-browser-mocks/tsconfig.json | 1 - .../core-injected-metadata-browser/tsconfig.json | 1 - .../core-injected-metadata-common-internal/tsconfig.json | 1 - .../core-integrations-browser-internal/tsconfig.json | 1 - .../integrations/core-integrations-browser-mocks/tsconfig.json | 1 - .../lifecycle/core-lifecycle-browser-internal/tsconfig.json | 1 - .../core/lifecycle/core-lifecycle-browser-mocks/tsconfig.json | 1 - packages/core/lifecycle/core-lifecycle-browser/tsconfig.json | 1 - .../lifecycle/core-lifecycle-server-internal/tsconfig.json | 1 - .../core/lifecycle/core-lifecycle-server-mocks/tsconfig.json | 1 - packages/core/lifecycle/core-lifecycle-server/tsconfig.json | 1 - .../core/logging/core-logging-browser-internal/tsconfig.json | 1 - packages/core/logging/core-logging-browser-mocks/tsconfig.json | 1 - .../core/logging/core-logging-common-internal/tsconfig.json | 1 - .../core/logging/core-logging-server-internal/tsconfig.json | 1 - packages/core/logging/core-logging-server-mocks/tsconfig.json | 1 - packages/core/logging/core-logging-server/tsconfig.json | 1 - .../core-metrics-collectors-server-internal/tsconfig.json | 1 - .../metrics/core-metrics-collectors-server-mocks/tsconfig.json | 1 - .../core/metrics/core-metrics-server-internal/tsconfig.json | 1 - packages/core/metrics/core-metrics-server-mocks/tsconfig.json | 1 - packages/core/metrics/core-metrics-server/tsconfig.json | 1 - .../core-mount-utils-browser-internal/tsconfig.json | 1 - .../core/mount-utils/core-mount-utils-browser/tsconfig.json | 1 - packages/core/node/core-node-server-internal/tsconfig.json | 1 - packages/core/node/core-node-server-mocks/tsconfig.json | 1 - packages/core/node/core-node-server/tsconfig.json | 1 - .../core-notifications-browser-internal/tsconfig.json | 1 - .../core-notifications-browser-mocks/tsconfig.json | 1 - .../notifications/core-notifications-browser/tsconfig.json | 1 - .../core/overlays/core-overlays-browser-internal/tsconfig.json | 1 - .../core/overlays/core-overlays-browser-mocks/tsconfig.json | 1 - packages/core/overlays/core-overlays-browser/tsconfig.json | 1 - .../plugins/core-plugins-base-server-internal/tsconfig.json | 1 - .../core/plugins/core-plugins-browser-internal/tsconfig.json | 1 - packages/core/plugins/core-plugins-browser-mocks/tsconfig.json | 1 - packages/core/plugins/core-plugins-browser/tsconfig.json | 1 - .../core/plugins/core-plugins-server-internal/tsconfig.json | 1 - packages/core/plugins/core-plugins-server-mocks/tsconfig.json | 1 - packages/core/plugins/core-plugins-server/tsconfig.json | 1 - .../core/preboot/core-preboot-server-internal/tsconfig.json | 1 - packages/core/preboot/core-preboot-server-mocks/tsconfig.json | 1 - packages/core/preboot/core-preboot-server/tsconfig.json | 1 - .../rendering/core-rendering-browser-internal/tsconfig.json | 1 - .../core/rendering/core-rendering-browser-mocks/tsconfig.json | 1 - .../rendering/core-rendering-server-internal/tsconfig.json | 1 - .../core/rendering/core-rendering-server-mocks/tsconfig.json | 1 - packages/core/root/core-root-browser-internal/tsconfig.json | 1 - .../saved-objects/core-saved-objects-api-browser/tsconfig.json | 1 - .../core-saved-objects-api-server-internal/tsconfig.json | 2 -- .../core-saved-objects-api-server-mocks/tsconfig.json | 2 -- .../saved-objects/core-saved-objects-api-server/tsconfig.json | 1 - .../core-saved-objects-base-server-internal/tsconfig.json | 1 - .../core-saved-objects-base-server-mocks/tsconfig.json | 1 - .../core-saved-objects-browser-internal/tsconfig.json | 1 - .../core-saved-objects-browser-mocks/tsconfig.json | 1 - .../saved-objects/core-saved-objects-browser/tsconfig.json | 1 - .../core/saved-objects/core-saved-objects-common/tsconfig.json | 1 - .../tsconfig.json | 2 -- .../tsconfig.json | 2 -- .../core-saved-objects-migration-server-internal/tsconfig.json | 2 -- .../core-saved-objects-migration-server-mocks/tsconfig.json | 2 -- .../core-saved-objects-server-internal/tsconfig.json | 2 -- .../core-saved-objects-server-mocks/tsconfig.json | 2 -- .../core/saved-objects/core-saved-objects-server/tsconfig.json | 1 - .../core-saved-objects-utils-server/tsconfig.json | 1 - packages/core/status/core-status-common-internal/tsconfig.json | 1 - packages/core/status/core-status-common/tsconfig.json | 1 - packages/core/status/core-status-server-internal/tsconfig.json | 1 - packages/core/status/core-status-server-mocks/tsconfig.json | 1 - packages/core/status/core-status-server/tsconfig.json | 1 - .../core-test-helpers-deprecations-getters/tsconfig.json | 1 - .../core-test-helpers-http-setup-browser/tsconfig.json | 1 - .../core-test-helpers-so-type-serializer/tsconfig.json | 1 - .../test-helpers/core-test-helpers-test-utils/tsconfig.json | 1 - packages/core/theme/core-theme-browser-internal/tsconfig.json | 1 - packages/core/theme/core-theme-browser-mocks/tsconfig.json | 1 - packages/core/theme/core-theme-browser/tsconfig.json | 1 - .../core-ui-settings-browser-internal/tsconfig.json | 1 - .../ui-settings/core-ui-settings-browser-mocks/tsconfig.json | 1 - .../core/ui-settings/core-ui-settings-browser/tsconfig.json | 1 - .../core/ui-settings/core-ui-settings-common/tsconfig.json | 1 - .../ui-settings/core-ui-settings-server-internal/tsconfig.json | 1 - .../ui-settings/core-ui-settings-server-mocks/tsconfig.json | 1 - .../core/ui-settings/core-ui-settings-server/tsconfig.json | 1 - .../core-usage-data-base-server-internal/tsconfig.json | 2 -- .../usage-data/core-usage-data-server-internal/tsconfig.json | 1 - .../core/usage-data/core-usage-data-server-mocks/tsconfig.json | 1 - packages/core/usage-data/core-usage-data-server/tsconfig.json | 2 -- packages/home/sample_data_card/tsconfig.json | 1 - packages/home/sample_data_tab/tsconfig.json | 1 - packages/home/sample_data_types/tsconfig.json | 1 - packages/kbn-ace/tsconfig.json | 2 +- packages/kbn-ambient-storybook-types/tsconfig.json | 2 -- packages/kbn-ambient-ui-types/tsconfig.json | 1 - packages/kbn-analytics/tsconfig.json | 3 +-- packages/kbn-apm-config-loader/tsconfig.json | 3 +-- packages/kbn-axe-config/tsconfig.json | 1 - packages/kbn-bazel-packages/tsconfig.json | 1 - packages/kbn-bazel-runner/tsconfig.json | 1 - packages/kbn-cases-components/tsconfig.json | 1 - packages/kbn-ci-stats-core/tsconfig.json | 1 - packages/kbn-ci-stats-performance-metrics/tsconfig.json | 1 - packages/kbn-ci-stats-reporter/tsconfig.json | 1 - packages/kbn-cli-dev-mode/tsconfig.json | 2 +- packages/kbn-coloring/tsconfig.json | 1 - packages/kbn-config-mocks/tsconfig.json | 1 - packages/kbn-config/tsconfig.json | 3 +-- packages/kbn-crypto-browser/tsconfig.json | 1 - packages/kbn-crypto/tsconfig.json | 2 +- packages/kbn-dev-cli-errors/tsconfig.json | 1 - packages/kbn-dev-cli-runner/tsconfig.json | 1 - packages/kbn-dev-proc-runner/tsconfig.json | 1 - packages/kbn-dev-utils/tsconfig.json | 1 - packages/kbn-doc-links/tsconfig.json | 3 +-- packages/kbn-es-archiver/tsconfig.json | 2 +- packages/kbn-es-errors/tsconfig.json | 1 - packages/kbn-es-query/tsconfig.json | 2 +- packages/kbn-es-types/tsconfig.json | 1 - packages/kbn-es/tsconfig.json | 2 +- packages/kbn-eslint-plugin-disable/tsconfig.json | 1 - packages/kbn-eslint-plugin-imports/tsconfig.json | 1 - packages/kbn-failed-test-reporter-cli/tsconfig.json | 1 - packages/kbn-field-types/tsconfig.json | 2 +- packages/kbn-find-used-node-modules/tsconfig.json | 1 - packages/kbn-ftr-common-functional-services/tsconfig.json | 1 - packages/kbn-ftr-screenshot-filename/tsconfig.json | 1 - packages/kbn-generate/templates/package/tsconfig.json.ejs | 1 - packages/kbn-generate/tsconfig.json | 1 - packages/kbn-get-repo-files/tsconfig.json | 1 - packages/kbn-guided-onboarding/tsconfig.json | 1 - packages/kbn-handlebars/tsconfig.json | 2 +- packages/kbn-hapi-mocks/tsconfig.json | 1 - packages/kbn-i18n-react/tsconfig.json | 2 +- packages/kbn-i18n/tsconfig.json | 2 +- packages/kbn-import-resolver/tsconfig.json | 1 - packages/kbn-interpreter/tsconfig.json | 3 +-- packages/kbn-io-ts-utils/tsconfig.json | 3 +-- packages/kbn-jest-serializers/tsconfig.json | 1 - packages/kbn-journeys/tsconfig.json | 1 - packages/kbn-kibana-manifest-schema/tsconfig.json | 1 - packages/kbn-language-documentation-popover/tsconfig.json | 2 +- packages/kbn-logging-mocks/tsconfig.json | 1 - packages/kbn-logging/tsconfig.json | 1 - packages/kbn-managed-vscode-config-cli/tsconfig.json | 1 - packages/kbn-managed-vscode-config/tsconfig.json | 1 - packages/kbn-monaco/tsconfig.json | 2 +- packages/kbn-optimizer-webpack-helpers/tsconfig.json | 1 - packages/kbn-optimizer/tsconfig.json | 2 +- .../kbn-performance-testing-dataset-extractor/tsconfig.json | 1 - packages/kbn-plugin-discovery/tsconfig.json | 1 - packages/kbn-react-field/tsconfig.json | 2 +- packages/kbn-repo-source-classifier-cli/tsconfig.json | 1 - packages/kbn-repo-source-classifier/tsconfig.json | 1 - packages/kbn-rule-data-utils/tsconfig.json | 3 +-- .../tsconfig.json | 1 - packages/kbn-server-http-tools/tsconfig.json | 2 +- packages/kbn-server-route-repository/tsconfig.json | 3 +-- packages/kbn-shared-svg/tsconfig.json | 1 - packages/kbn-shared-ux-utility/tsconfig.json | 1 - packages/kbn-some-dev-log/tsconfig.json | 1 - packages/kbn-sort-package-json/tsconfig.json | 1 - packages/kbn-std/tsconfig.json | 3 +-- packages/kbn-stdio-dev-helpers/tsconfig.json | 1 - packages/kbn-synthetic-package-map/tsconfig.json | 1 - packages/kbn-telemetry-tools/tsconfig.json | 2 +- packages/kbn-test-jest-helpers/tsconfig.json | 3 +-- packages/kbn-test-subj-selector/tsconfig.json | 1 - packages/kbn-test/tsconfig.json | 2 +- packages/kbn-tooling-log/tsconfig.json | 1 - packages/kbn-type-summarizer-cli/tsconfig.json | 1 - packages/kbn-type-summarizer-core/tsconfig.json | 1 - packages/kbn-type-summarizer/tsconfig.json | 1 - packages/kbn-typed-react-router-config/tsconfig.json | 3 +-- packages/kbn-ui-shared-deps-npm/tsconfig.json | 2 +- packages/kbn-ui-shared-deps-src/tsconfig.json | 2 +- packages/kbn-ui-theme/tsconfig.json | 3 +-- packages/kbn-user-profile-components/tsconfig.json | 3 +-- packages/kbn-utility-types-jest/tsconfig.json | 3 +-- packages/kbn-utility-types/tsconfig.json | 3 +-- packages/kbn-yarn-lock-validator/tsconfig.json | 1 - packages/shared-ux/avatar/solution/tsconfig.json | 1 - packages/shared-ux/avatar/user_profile/impl/tsconfig.json | 1 - packages/shared-ux/button/exit_full_screen/impl/tsconfig.json | 1 - packages/shared-ux/button/exit_full_screen/mocks/tsconfig.json | 1 - packages/shared-ux/button/exit_full_screen/types/tsconfig.json | 1 - packages/shared-ux/button_toolbar/tsconfig.json | 1 - packages/shared-ux/card/no_data/impl/tsconfig.json | 1 - packages/shared-ux/card/no_data/mocks/tsconfig.json | 1 - packages/shared-ux/card/no_data/types/tsconfig.json | 1 - packages/shared-ux/link/redirect_app/impl/tsconfig.json | 1 - packages/shared-ux/link/redirect_app/mocks/tsconfig.json | 1 - packages/shared-ux/link/redirect_app/types/tsconfig.json | 1 - packages/shared-ux/markdown/impl/tsconfig.json | 3 +-- packages/shared-ux/markdown/mocks/tsconfig.json | 1 - packages/shared-ux/markdown/types/tsconfig.json | 1 - packages/shared-ux/page/analytics_no_data/impl/tsconfig.json | 1 - packages/shared-ux/page/analytics_no_data/mocks/tsconfig.json | 1 - packages/shared-ux/page/analytics_no_data/types/tsconfig.json | 1 - packages/shared-ux/page/kibana_no_data/impl/tsconfig.json | 1 - packages/shared-ux/page/kibana_no_data/mocks/tsconfig.json | 1 - packages/shared-ux/page/kibana_no_data/types/tsconfig.json | 1 - packages/shared-ux/page/kibana_template/impl/tsconfig.json | 1 - packages/shared-ux/page/kibana_template/mocks/tsconfig.json | 1 - packages/shared-ux/page/kibana_template/types/tsconfig.json | 1 - packages/shared-ux/page/no_data/impl/tsconfig.json | 1 - packages/shared-ux/page/no_data/mocks/tsconfig.json | 1 - packages/shared-ux/page/no_data/types/tsconfig.json | 1 - packages/shared-ux/page/no_data_config/impl/tsconfig.json | 1 - packages/shared-ux/page/no_data_config/mocks/tsconfig.json | 1 - packages/shared-ux/page/no_data_config/types/tsconfig.json | 1 - packages/shared-ux/page/solution_nav/tsconfig.json | 1 - packages/shared-ux/prompt/no_data_views/impl/tsconfig.json | 1 - packages/shared-ux/prompt/no_data_views/mocks/tsconfig.json | 1 - packages/shared-ux/prompt/no_data_views/types/tsconfig.json | 1 - packages/shared-ux/router/impl/tsconfig.json | 1 - packages/shared-ux/router/mocks/tsconfig.json | 2 -- packages/shared-ux/router/types/tsconfig.json | 1 - packages/shared-ux/storybook/config/tsconfig.json | 1 - packages/shared-ux/storybook/mock/tsconfig.json | 1 - 308 files changed, 35 insertions(+), 344 deletions(-) diff --git a/packages/analytics/client/tsconfig.json b/packages/analytics/client/tsconfig.json index e543b7493c3b9..cc7ee1b2ebd68 100644 --- a/packages/analytics/client/tsconfig.json +++ b/packages/analytics/client/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/analytics/shippers/elastic_v3/browser/tsconfig.json b/packages/analytics/shippers/elastic_v3/browser/tsconfig.json index 2f35c0edbedd7..6d893e10d8cc9 100644 --- a/packages/analytics/shippers/elastic_v3/browser/tsconfig.json +++ b/packages/analytics/shippers/elastic_v3/browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/analytics/shippers/elastic_v3/common/tsconfig.json b/packages/analytics/shippers/elastic_v3/common/tsconfig.json index 2f35c0edbedd7..6d893e10d8cc9 100644 --- a/packages/analytics/shippers/elastic_v3/common/tsconfig.json +++ b/packages/analytics/shippers/elastic_v3/common/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/analytics/shippers/elastic_v3/server/tsconfig.json b/packages/analytics/shippers/elastic_v3/server/tsconfig.json index 2f35c0edbedd7..6d893e10d8cc9 100644 --- a/packages/analytics/shippers/elastic_v3/server/tsconfig.json +++ b/packages/analytics/shippers/elastic_v3/server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/analytics/shippers/fullstory/tsconfig.json b/packages/analytics/shippers/fullstory/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/analytics/shippers/fullstory/tsconfig.json +++ b/packages/analytics/shippers/fullstory/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/analytics/shippers/gainsight/tsconfig.json b/packages/analytics/shippers/gainsight/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/analytics/shippers/gainsight/tsconfig.json +++ b/packages/analytics/shippers/gainsight/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/content-management/table_list/tsconfig.json b/packages/content-management/table_list/tsconfig.json index a7520dbfb4fe2..695a24957687b 100644 --- a/packages/content-management/table_list/tsconfig.json +++ b/packages/content-management/table_list/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/analytics/core-analytics-browser-internal/tsconfig.json b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/analytics/core-analytics-browser-internal/tsconfig.json +++ b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/analytics/core-analytics-browser-mocks/tsconfig.json b/packages/core/analytics/core-analytics-browser-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/analytics/core-analytics-browser-mocks/tsconfig.json +++ b/packages/core/analytics/core-analytics-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/analytics/core-analytics-browser/tsconfig.json b/packages/core/analytics/core-analytics-browser/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/analytics/core-analytics-browser/tsconfig.json +++ b/packages/core/analytics/core-analytics-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/analytics/core-analytics-server-internal/tsconfig.json b/packages/core/analytics/core-analytics-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/analytics/core-analytics-server-internal/tsconfig.json +++ b/packages/core/analytics/core-analytics-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/analytics/core-analytics-server-mocks/tsconfig.json b/packages/core/analytics/core-analytics-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/analytics/core-analytics-server-mocks/tsconfig.json +++ b/packages/core/analytics/core-analytics-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/analytics/core-analytics-server/tsconfig.json b/packages/core/analytics/core-analytics-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/analytics/core-analytics-server/tsconfig.json +++ b/packages/core/analytics/core-analytics-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/application/core-application-browser-internal/tsconfig.json b/packages/core/application/core-application-browser-internal/tsconfig.json index bccdc4b2a95aa..c561d9f220124 100644 --- a/packages/core/application/core-application-browser-internal/tsconfig.json +++ b/packages/core/application/core-application-browser-internal/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/application/core-application-browser-mocks/tsconfig.json b/packages/core/application/core-application-browser-mocks/tsconfig.json index 6548f04ad9fd3..37f8e83d0d7a6 100644 --- a/packages/core/application/core-application-browser-mocks/tsconfig.json +++ b/packages/core/application/core-application-browser-mocks/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/application/core-application-browser/tsconfig.json b/packages/core/application/core-application-browser/tsconfig.json index 43a846448fc74..48df8f2957246 100644 --- a/packages/core/application/core-application-browser/tsconfig.json +++ b/packages/core/application/core-application-browser/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/application/core-application-common/tsconfig.json b/packages/core/application/core-application-common/tsconfig.json index 43a846448fc74..48df8f2957246 100644 --- a/packages/core/application/core-application-common/tsconfig.json +++ b/packages/core/application/core-application-common/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/apps/core-apps-browser-internal/tsconfig.json b/packages/core/apps/core-apps-browser-internal/tsconfig.json index 571fbfcd8ef25..37f8e83d0d7a6 100644 --- a/packages/core/apps/core-apps-browser-internal/tsconfig.json +++ b/packages/core/apps/core-apps-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/apps/core-apps-browser-mocks/tsconfig.json b/packages/core/apps/core-apps-browser-mocks/tsconfig.json index 8a9b8b46147f1..741519055e986 100644 --- a/packages/core/apps/core-apps-browser-mocks/tsconfig.json +++ b/packages/core/apps/core-apps-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/base/core-base-browser-internal/tsconfig.json b/packages/core/base/core-base-browser-internal/tsconfig.json index d5dece108de5d..3cdea36de9eac 100644 --- a/packages/core/base/core-base-browser-internal/tsconfig.json +++ b/packages/core/base/core-base-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/base/core-base-browser-mocks/tsconfig.json b/packages/core/base/core-base-browser-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/base/core-base-browser-mocks/tsconfig.json +++ b/packages/core/base/core-base-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/base/core-base-common-internal/tsconfig.json b/packages/core/base/core-base-common-internal/tsconfig.json index d5dece108de5d..3cdea36de9eac 100644 --- a/packages/core/base/core-base-common-internal/tsconfig.json +++ b/packages/core/base/core-base-common-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/base/core-base-common/tsconfig.json b/packages/core/base/core-base-common/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/base/core-base-common/tsconfig.json +++ b/packages/core/base/core-base-common/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/base/core-base-server-internal/tsconfig.json b/packages/core/base/core-base-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/base/core-base-server-internal/tsconfig.json +++ b/packages/core/base/core-base-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/base/core-base-server-mocks/tsconfig.json b/packages/core/base/core-base-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/base/core-base-server-mocks/tsconfig.json +++ b/packages/core/base/core-base-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/capabilities/core-capabilities-browser-internal/tsconfig.json b/packages/core/capabilities/core-capabilities-browser-internal/tsconfig.json index 43a846448fc74..48df8f2957246 100644 --- a/packages/core/capabilities/core-capabilities-browser-internal/tsconfig.json +++ b/packages/core/capabilities/core-capabilities-browser-internal/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/capabilities/core-capabilities-browser-mocks/tsconfig.json b/packages/core/capabilities/core-capabilities-browser-mocks/tsconfig.json index 43a846448fc74..48df8f2957246 100644 --- a/packages/core/capabilities/core-capabilities-browser-mocks/tsconfig.json +++ b/packages/core/capabilities/core-capabilities-browser-mocks/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/capabilities/core-capabilities-common/tsconfig.json b/packages/core/capabilities/core-capabilities-common/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/capabilities/core-capabilities-common/tsconfig.json +++ b/packages/core/capabilities/core-capabilities-common/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/capabilities/core-capabilities-server-internal/tsconfig.json b/packages/core/capabilities/core-capabilities-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/capabilities/core-capabilities-server-internal/tsconfig.json +++ b/packages/core/capabilities/core-capabilities-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/capabilities/core-capabilities-server-mocks/tsconfig.json b/packages/core/capabilities/core-capabilities-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/capabilities/core-capabilities-server-mocks/tsconfig.json +++ b/packages/core/capabilities/core-capabilities-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/capabilities/core-capabilities-server/tsconfig.json b/packages/core/capabilities/core-capabilities-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/capabilities/core-capabilities-server/tsconfig.json +++ b/packages/core/capabilities/core-capabilities-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/chrome/core-chrome-browser-internal/tsconfig.json b/packages/core/chrome/core-chrome-browser-internal/tsconfig.json index 4eb9855fa759d..c561d9f220124 100644 --- a/packages/core/chrome/core-chrome-browser-internal/tsconfig.json +++ b/packages/core/chrome/core-chrome-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/chrome/core-chrome-browser-mocks/tsconfig.json b/packages/core/chrome/core-chrome-browser-mocks/tsconfig.json index 8a9b8b46147f1..741519055e986 100644 --- a/packages/core/chrome/core-chrome-browser-mocks/tsconfig.json +++ b/packages/core/chrome/core-chrome-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/chrome/core-chrome-browser/tsconfig.json b/packages/core/chrome/core-chrome-browser/tsconfig.json index 8a9b8b46147f1..741519055e986 100644 --- a/packages/core/chrome/core-chrome-browser/tsconfig.json +++ b/packages/core/chrome/core-chrome-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/config/core-config-server-internal/tsconfig.json b/packages/core/config/core-config-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/config/core-config-server-internal/tsconfig.json +++ b/packages/core/config/core-config-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/deprecations/core-deprecations-browser-internal/tsconfig.json b/packages/core/deprecations/core-deprecations-browser-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/deprecations/core-deprecations-browser-internal/tsconfig.json +++ b/packages/core/deprecations/core-deprecations-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/deprecations/core-deprecations-browser-mocks/tsconfig.json b/packages/core/deprecations/core-deprecations-browser-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/deprecations/core-deprecations-browser-mocks/tsconfig.json +++ b/packages/core/deprecations/core-deprecations-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/deprecations/core-deprecations-browser/tsconfig.json b/packages/core/deprecations/core-deprecations-browser/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/deprecations/core-deprecations-browser/tsconfig.json +++ b/packages/core/deprecations/core-deprecations-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/deprecations/core-deprecations-common/tsconfig.json b/packages/core/deprecations/core-deprecations-common/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/deprecations/core-deprecations-common/tsconfig.json +++ b/packages/core/deprecations/core-deprecations-common/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/deprecations/core-deprecations-server-internal/tsconfig.json b/packages/core/deprecations/core-deprecations-server-internal/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/deprecations/core-deprecations-server-internal/tsconfig.json +++ b/packages/core/deprecations/core-deprecations-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/deprecations/core-deprecations-server-mocks/tsconfig.json b/packages/core/deprecations/core-deprecations-server-mocks/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/deprecations/core-deprecations-server-mocks/tsconfig.json +++ b/packages/core/deprecations/core-deprecations-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/deprecations/core-deprecations-server/tsconfig.json b/packages/core/deprecations/core-deprecations-server/tsconfig.json index 3fe98195b374a..4582562d6c9bb 100644 --- a/packages/core/deprecations/core-deprecations-server/tsconfig.json +++ b/packages/core/deprecations/core-deprecations-server/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/doc-links/core-doc-links-browser-internal/tsconfig.json b/packages/core/doc-links/core-doc-links-browser-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/doc-links/core-doc-links-browser-internal/tsconfig.json +++ b/packages/core/doc-links/core-doc-links-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/doc-links/core-doc-links-browser-mocks/tsconfig.json b/packages/core/doc-links/core-doc-links-browser-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/doc-links/core-doc-links-browser-mocks/tsconfig.json +++ b/packages/core/doc-links/core-doc-links-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/doc-links/core-doc-links-browser/tsconfig.json b/packages/core/doc-links/core-doc-links-browser/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/doc-links/core-doc-links-browser/tsconfig.json +++ b/packages/core/doc-links/core-doc-links-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/doc-links/core-doc-links-server-internal/tsconfig.json b/packages/core/doc-links/core-doc-links-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/doc-links/core-doc-links-server-internal/tsconfig.json +++ b/packages/core/doc-links/core-doc-links-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/doc-links/core-doc-links-server-mocks/tsconfig.json b/packages/core/doc-links/core-doc-links-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/doc-links/core-doc-links-server-mocks/tsconfig.json +++ b/packages/core/doc-links/core-doc-links-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/doc-links/core-doc-links-server/tsconfig.json b/packages/core/doc-links/core-doc-links-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/doc-links/core-doc-links-server/tsconfig.json +++ b/packages/core/doc-links/core-doc-links-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/tsconfig.json b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/tsconfig.json +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/tsconfig.json b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/tsconfig.json +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/tsconfig.json b/packages/core/elasticsearch/core-elasticsearch-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/tsconfig.json +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/elasticsearch/core-elasticsearch-server-mocks/tsconfig.json b/packages/core/elasticsearch/core-elasticsearch-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-mocks/tsconfig.json +++ b/packages/core/elasticsearch/core-elasticsearch-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/elasticsearch/core-elasticsearch-server/tsconfig.json b/packages/core/elasticsearch/core-elasticsearch-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server/tsconfig.json +++ b/packages/core/elasticsearch/core-elasticsearch-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/environment/core-environment-server-internal/tsconfig.json b/packages/core/environment/core-environment-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/environment/core-environment-server-internal/tsconfig.json +++ b/packages/core/environment/core-environment-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/environment/core-environment-server-mocks/tsconfig.json b/packages/core/environment/core-environment-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/environment/core-environment-server-mocks/tsconfig.json +++ b/packages/core/environment/core-environment-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/execution-context/core-execution-context-browser-internal/tsconfig.json b/packages/core/execution-context/core-execution-context-browser-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/execution-context/core-execution-context-browser-internal/tsconfig.json +++ b/packages/core/execution-context/core-execution-context-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/execution-context/core-execution-context-browser-mocks/tsconfig.json b/packages/core/execution-context/core-execution-context-browser-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/execution-context/core-execution-context-browser-mocks/tsconfig.json +++ b/packages/core/execution-context/core-execution-context-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/execution-context/core-execution-context-browser/tsconfig.json b/packages/core/execution-context/core-execution-context-browser/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/execution-context/core-execution-context-browser/tsconfig.json +++ b/packages/core/execution-context/core-execution-context-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/execution-context/core-execution-context-common/tsconfig.json b/packages/core/execution-context/core-execution-context-common/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/execution-context/core-execution-context-common/tsconfig.json +++ b/packages/core/execution-context/core-execution-context-common/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/execution-context/core-execution-context-server-internal/tsconfig.json b/packages/core/execution-context/core-execution-context-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/execution-context/core-execution-context-server-internal/tsconfig.json +++ b/packages/core/execution-context/core-execution-context-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/execution-context/core-execution-context-server-mocks/tsconfig.json b/packages/core/execution-context/core-execution-context-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/execution-context/core-execution-context-server-mocks/tsconfig.json +++ b/packages/core/execution-context/core-execution-context-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/execution-context/core-execution-context-server/tsconfig.json b/packages/core/execution-context/core-execution-context-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/execution-context/core-execution-context-server/tsconfig.json +++ b/packages/core/execution-context/core-execution-context-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/fatal-errors/core-fatal-errors-browser-internal/tsconfig.json b/packages/core/fatal-errors/core-fatal-errors-browser-internal/tsconfig.json index 4eb9855fa759d..c561d9f220124 100644 --- a/packages/core/fatal-errors/core-fatal-errors-browser-internal/tsconfig.json +++ b/packages/core/fatal-errors/core-fatal-errors-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/fatal-errors/core-fatal-errors-browser-mocks/tsconfig.json b/packages/core/fatal-errors/core-fatal-errors-browser-mocks/tsconfig.json index d5dece108de5d..3cdea36de9eac 100644 --- a/packages/core/fatal-errors/core-fatal-errors-browser-mocks/tsconfig.json +++ b/packages/core/fatal-errors/core-fatal-errors-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/fatal-errors/core-fatal-errors-browser/tsconfig.json b/packages/core/fatal-errors/core-fatal-errors-browser/tsconfig.json index d5dece108de5d..3cdea36de9eac 100644 --- a/packages/core/fatal-errors/core-fatal-errors-browser/tsconfig.json +++ b/packages/core/fatal-errors/core-fatal-errors-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/http/core-http-browser-internal/tsconfig.json b/packages/core/http/core-http-browser-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/http/core-http-browser-internal/tsconfig.json +++ b/packages/core/http/core-http-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-browser-mocks/tsconfig.json b/packages/core/http/core-http-browser-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/http/core-http-browser-mocks/tsconfig.json +++ b/packages/core/http/core-http-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-browser/tsconfig.json b/packages/core/http/core-http-browser/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/http/core-http-browser/tsconfig.json +++ b/packages/core/http/core-http-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-common/tsconfig.json b/packages/core/http/core-http-common/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/http/core-http-common/tsconfig.json +++ b/packages/core/http/core-http-common/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-context-server-internal/tsconfig.json b/packages/core/http/core-http-context-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/http/core-http-context-server-internal/tsconfig.json +++ b/packages/core/http/core-http-context-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-context-server-mocks/tsconfig.json b/packages/core/http/core-http-context-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/http/core-http-context-server-mocks/tsconfig.json +++ b/packages/core/http/core-http-context-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-request-handler-context-server-internal/tsconfig.json b/packages/core/http/core-http-request-handler-context-server-internal/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/http/core-http-request-handler-context-server-internal/tsconfig.json +++ b/packages/core/http/core-http-request-handler-context-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-request-handler-context-server/tsconfig.json b/packages/core/http/core-http-request-handler-context-server/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/http/core-http-request-handler-context-server/tsconfig.json +++ b/packages/core/http/core-http-request-handler-context-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-resources-server-internal/tsconfig.json b/packages/core/http/core-http-resources-server-internal/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/http/core-http-resources-server-internal/tsconfig.json +++ b/packages/core/http/core-http-resources-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-resources-server-mocks/tsconfig.json b/packages/core/http/core-http-resources-server-mocks/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/http/core-http-resources-server-mocks/tsconfig.json +++ b/packages/core/http/core-http-resources-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-resources-server/tsconfig.json b/packages/core/http/core-http-resources-server/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/http/core-http-resources-server/tsconfig.json +++ b/packages/core/http/core-http-resources-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-router-server-internal/tsconfig.json b/packages/core/http/core-http-router-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/http/core-http-router-server-internal/tsconfig.json +++ b/packages/core/http/core-http-router-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-router-server-mocks/tsconfig.json b/packages/core/http/core-http-router-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/http/core-http-router-server-mocks/tsconfig.json +++ b/packages/core/http/core-http-router-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-server-internal/tsconfig.json b/packages/core/http/core-http-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/http/core-http-server-internal/tsconfig.json +++ b/packages/core/http/core-http-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-server-mocks/tsconfig.json b/packages/core/http/core-http-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/http/core-http-server-mocks/tsconfig.json +++ b/packages/core/http/core-http-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/http/core-http-server/tsconfig.json b/packages/core/http/core-http-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/http/core-http-server/tsconfig.json +++ b/packages/core/http/core-http-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/i18n/core-i18n-browser-internal/tsconfig.json b/packages/core/i18n/core-i18n-browser-internal/tsconfig.json index 8a9b8b46147f1..741519055e986 100644 --- a/packages/core/i18n/core-i18n-browser-internal/tsconfig.json +++ b/packages/core/i18n/core-i18n-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/i18n/core-i18n-browser-mocks/tsconfig.json b/packages/core/i18n/core-i18n-browser-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/i18n/core-i18n-browser-mocks/tsconfig.json +++ b/packages/core/i18n/core-i18n-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/i18n/core-i18n-browser/tsconfig.json b/packages/core/i18n/core-i18n-browser/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/i18n/core-i18n-browser/tsconfig.json +++ b/packages/core/i18n/core-i18n-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/i18n/core-i18n-server-internal/tsconfig.json b/packages/core/i18n/core-i18n-server-internal/tsconfig.json index 3fe98195b374a..4582562d6c9bb 100644 --- a/packages/core/i18n/core-i18n-server-internal/tsconfig.json +++ b/packages/core/i18n/core-i18n-server-internal/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/i18n/core-i18n-server-mocks/tsconfig.json b/packages/core/i18n/core-i18n-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/i18n/core-i18n-server-mocks/tsconfig.json +++ b/packages/core/i18n/core-i18n-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/i18n/core-i18n-server/tsconfig.json b/packages/core/i18n/core-i18n-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/i18n/core-i18n-server/tsconfig.json +++ b/packages/core/i18n/core-i18n-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/injected-metadata/core-injected-metadata-browser-internal/tsconfig.json b/packages/core/injected-metadata/core-injected-metadata-browser-internal/tsconfig.json index d5dece108de5d..3cdea36de9eac 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser-internal/tsconfig.json +++ b/packages/core/injected-metadata/core-injected-metadata-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/injected-metadata/core-injected-metadata-browser-mocks/tsconfig.json b/packages/core/injected-metadata/core-injected-metadata-browser-mocks/tsconfig.json index d5dece108de5d..3cdea36de9eac 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser-mocks/tsconfig.json +++ b/packages/core/injected-metadata/core-injected-metadata-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/injected-metadata/core-injected-metadata-browser/tsconfig.json b/packages/core/injected-metadata/core-injected-metadata-browser/tsconfig.json index d5dece108de5d..3cdea36de9eac 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser/tsconfig.json +++ b/packages/core/injected-metadata/core-injected-metadata-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/injected-metadata/core-injected-metadata-common-internal/tsconfig.json b/packages/core/injected-metadata/core-injected-metadata-common-internal/tsconfig.json index d5dece108de5d..3cdea36de9eac 100644 --- a/packages/core/injected-metadata/core-injected-metadata-common-internal/tsconfig.json +++ b/packages/core/injected-metadata/core-injected-metadata-common-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/integrations/core-integrations-browser-internal/tsconfig.json b/packages/core/integrations/core-integrations-browser-internal/tsconfig.json index bb5b7ea01eb29..e1805086a07a5 100644 --- a/packages/core/integrations/core-integrations-browser-internal/tsconfig.json +++ b/packages/core/integrations/core-integrations-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/integrations/core-integrations-browser-mocks/tsconfig.json b/packages/core/integrations/core-integrations-browser-mocks/tsconfig.json index d5dece108de5d..3cdea36de9eac 100644 --- a/packages/core/integrations/core-integrations-browser-mocks/tsconfig.json +++ b/packages/core/integrations/core-integrations-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/lifecycle/core-lifecycle-browser-internal/tsconfig.json b/packages/core/lifecycle/core-lifecycle-browser-internal/tsconfig.json index 91c6502f925cc..61706db827bc4 100644 --- a/packages/core/lifecycle/core-lifecycle-browser-internal/tsconfig.json +++ b/packages/core/lifecycle/core-lifecycle-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/lifecycle/core-lifecycle-browser-mocks/tsconfig.json b/packages/core/lifecycle/core-lifecycle-browser-mocks/tsconfig.json index 1fc18ba8b1f50..47ad657279cbb 100644 --- a/packages/core/lifecycle/core-lifecycle-browser-mocks/tsconfig.json +++ b/packages/core/lifecycle/core-lifecycle-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/lifecycle/core-lifecycle-browser/tsconfig.json b/packages/core/lifecycle/core-lifecycle-browser/tsconfig.json index 288ccfcbced9e..48df8f2957246 100644 --- a/packages/core/lifecycle/core-lifecycle-browser/tsconfig.json +++ b/packages/core/lifecycle/core-lifecycle-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/lifecycle/core-lifecycle-server-internal/tsconfig.json b/packages/core/lifecycle/core-lifecycle-server-internal/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/lifecycle/core-lifecycle-server-internal/tsconfig.json +++ b/packages/core/lifecycle/core-lifecycle-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/tsconfig.json b/packages/core/lifecycle/core-lifecycle-server-mocks/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/lifecycle/core-lifecycle-server-mocks/tsconfig.json +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/lifecycle/core-lifecycle-server/tsconfig.json b/packages/core/lifecycle/core-lifecycle-server/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/lifecycle/core-lifecycle-server/tsconfig.json +++ b/packages/core/lifecycle/core-lifecycle-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/logging/core-logging-browser-internal/tsconfig.json b/packages/core/logging/core-logging-browser-internal/tsconfig.json index 25957cd665d11..fbd1249f66205 100644 --- a/packages/core/logging/core-logging-browser-internal/tsconfig.json +++ b/packages/core/logging/core-logging-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/logging/core-logging-browser-mocks/tsconfig.json b/packages/core/logging/core-logging-browser-mocks/tsconfig.json index 571fbfcd8ef25..37f8e83d0d7a6 100644 --- a/packages/core/logging/core-logging-browser-mocks/tsconfig.json +++ b/packages/core/logging/core-logging-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/logging/core-logging-common-internal/tsconfig.json b/packages/core/logging/core-logging-common-internal/tsconfig.json index 25957cd665d11..fbd1249f66205 100644 --- a/packages/core/logging/core-logging-common-internal/tsconfig.json +++ b/packages/core/logging/core-logging-common-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/logging/core-logging-server-internal/tsconfig.json b/packages/core/logging/core-logging-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/logging/core-logging-server-internal/tsconfig.json +++ b/packages/core/logging/core-logging-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/logging/core-logging-server-mocks/tsconfig.json b/packages/core/logging/core-logging-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/logging/core-logging-server-mocks/tsconfig.json +++ b/packages/core/logging/core-logging-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/logging/core-logging-server/tsconfig.json b/packages/core/logging/core-logging-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/logging/core-logging-server/tsconfig.json +++ b/packages/core/logging/core-logging-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/tsconfig.json b/packages/core/metrics/core-metrics-collectors-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/metrics/core-metrics-collectors-server-internal/tsconfig.json +++ b/packages/core/metrics/core-metrics-collectors-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/metrics/core-metrics-collectors-server-mocks/tsconfig.json b/packages/core/metrics/core-metrics-collectors-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/metrics/core-metrics-collectors-server-mocks/tsconfig.json +++ b/packages/core/metrics/core-metrics-collectors-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/metrics/core-metrics-server-internal/tsconfig.json b/packages/core/metrics/core-metrics-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/metrics/core-metrics-server-internal/tsconfig.json +++ b/packages/core/metrics/core-metrics-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/metrics/core-metrics-server-mocks/tsconfig.json b/packages/core/metrics/core-metrics-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/metrics/core-metrics-server-mocks/tsconfig.json +++ b/packages/core/metrics/core-metrics-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/metrics/core-metrics-server/tsconfig.json b/packages/core/metrics/core-metrics-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/metrics/core-metrics-server/tsconfig.json +++ b/packages/core/metrics/core-metrics-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/mount-utils/core-mount-utils-browser-internal/tsconfig.json b/packages/core/mount-utils/core-mount-utils-browser-internal/tsconfig.json index 4eb9855fa759d..c561d9f220124 100644 --- a/packages/core/mount-utils/core-mount-utils-browser-internal/tsconfig.json +++ b/packages/core/mount-utils/core-mount-utils-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/mount-utils/core-mount-utils-browser/tsconfig.json b/packages/core/mount-utils/core-mount-utils-browser/tsconfig.json index d5dece108de5d..3cdea36de9eac 100644 --- a/packages/core/mount-utils/core-mount-utils-browser/tsconfig.json +++ b/packages/core/mount-utils/core-mount-utils-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/node/core-node-server-internal/tsconfig.json b/packages/core/node/core-node-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/node/core-node-server-internal/tsconfig.json +++ b/packages/core/node/core-node-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/node/core-node-server-mocks/tsconfig.json b/packages/core/node/core-node-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/node/core-node-server-mocks/tsconfig.json +++ b/packages/core/node/core-node-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/node/core-node-server/tsconfig.json b/packages/core/node/core-node-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/node/core-node-server/tsconfig.json +++ b/packages/core/node/core-node-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/notifications/core-notifications-browser-internal/tsconfig.json b/packages/core/notifications/core-notifications-browser-internal/tsconfig.json index bb5b7ea01eb29..e1805086a07a5 100644 --- a/packages/core/notifications/core-notifications-browser-internal/tsconfig.json +++ b/packages/core/notifications/core-notifications-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/notifications/core-notifications-browser-mocks/tsconfig.json b/packages/core/notifications/core-notifications-browser-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/notifications/core-notifications-browser-mocks/tsconfig.json +++ b/packages/core/notifications/core-notifications-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/notifications/core-notifications-browser/tsconfig.json b/packages/core/notifications/core-notifications-browser/tsconfig.json index 84be4fe27d5d6..3faa31fe437a8 100644 --- a/packages/core/notifications/core-notifications-browser/tsconfig.json +++ b/packages/core/notifications/core-notifications-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/overlays/core-overlays-browser-internal/tsconfig.json b/packages/core/overlays/core-overlays-browser-internal/tsconfig.json index bb5b7ea01eb29..e1805086a07a5 100644 --- a/packages/core/overlays/core-overlays-browser-internal/tsconfig.json +++ b/packages/core/overlays/core-overlays-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/overlays/core-overlays-browser-mocks/tsconfig.json b/packages/core/overlays/core-overlays-browser-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/overlays/core-overlays-browser-mocks/tsconfig.json +++ b/packages/core/overlays/core-overlays-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/overlays/core-overlays-browser/tsconfig.json b/packages/core/overlays/core-overlays-browser/tsconfig.json index d5dece108de5d..3cdea36de9eac 100644 --- a/packages/core/overlays/core-overlays-browser/tsconfig.json +++ b/packages/core/overlays/core-overlays-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/plugins/core-plugins-base-server-internal/tsconfig.json b/packages/core/plugins/core-plugins-base-server-internal/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/plugins/core-plugins-base-server-internal/tsconfig.json +++ b/packages/core/plugins/core-plugins-base-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/plugins/core-plugins-browser-internal/tsconfig.json b/packages/core/plugins/core-plugins-browser-internal/tsconfig.json index 1fc18ba8b1f50..47ad657279cbb 100644 --- a/packages/core/plugins/core-plugins-browser-internal/tsconfig.json +++ b/packages/core/plugins/core-plugins-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/plugins/core-plugins-browser-mocks/tsconfig.json b/packages/core/plugins/core-plugins-browser-mocks/tsconfig.json index 1fc18ba8b1f50..47ad657279cbb 100644 --- a/packages/core/plugins/core-plugins-browser-mocks/tsconfig.json +++ b/packages/core/plugins/core-plugins-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/plugins/core-plugins-browser/tsconfig.json b/packages/core/plugins/core-plugins-browser/tsconfig.json index 1fc18ba8b1f50..47ad657279cbb 100644 --- a/packages/core/plugins/core-plugins-browser/tsconfig.json +++ b/packages/core/plugins/core-plugins-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/plugins/core-plugins-server-internal/tsconfig.json b/packages/core/plugins/core-plugins-server-internal/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/plugins/core-plugins-server-internal/tsconfig.json +++ b/packages/core/plugins/core-plugins-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/plugins/core-plugins-server-mocks/tsconfig.json b/packages/core/plugins/core-plugins-server-mocks/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/plugins/core-plugins-server-mocks/tsconfig.json +++ b/packages/core/plugins/core-plugins-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/plugins/core-plugins-server/tsconfig.json b/packages/core/plugins/core-plugins-server/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/plugins/core-plugins-server/tsconfig.json +++ b/packages/core/plugins/core-plugins-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/preboot/core-preboot-server-internal/tsconfig.json b/packages/core/preboot/core-preboot-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/preboot/core-preboot-server-internal/tsconfig.json +++ b/packages/core/preboot/core-preboot-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/preboot/core-preboot-server-mocks/tsconfig.json b/packages/core/preboot/core-preboot-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/preboot/core-preboot-server-mocks/tsconfig.json +++ b/packages/core/preboot/core-preboot-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/preboot/core-preboot-server/tsconfig.json b/packages/core/preboot/core-preboot-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/preboot/core-preboot-server/tsconfig.json +++ b/packages/core/preboot/core-preboot-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/rendering/core-rendering-browser-internal/tsconfig.json b/packages/core/rendering/core-rendering-browser-internal/tsconfig.json index 571fbfcd8ef25..37f8e83d0d7a6 100644 --- a/packages/core/rendering/core-rendering-browser-internal/tsconfig.json +++ b/packages/core/rendering/core-rendering-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/rendering/core-rendering-browser-mocks/tsconfig.json b/packages/core/rendering/core-rendering-browser-mocks/tsconfig.json index 8a9b8b46147f1..741519055e986 100644 --- a/packages/core/rendering/core-rendering-browser-mocks/tsconfig.json +++ b/packages/core/rendering/core-rendering-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/rendering/core-rendering-server-internal/tsconfig.json b/packages/core/rendering/core-rendering-server-internal/tsconfig.json index 495e6859a42d1..2279a16c99adf 100644 --- a/packages/core/rendering/core-rendering-server-internal/tsconfig.json +++ b/packages/core/rendering/core-rendering-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/rendering/core-rendering-server-mocks/tsconfig.json b/packages/core/rendering/core-rendering-server-mocks/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/rendering/core-rendering-server-mocks/tsconfig.json +++ b/packages/core/rendering/core-rendering-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/root/core-root-browser-internal/tsconfig.json b/packages/core/root/core-root-browser-internal/tsconfig.json index 1fc18ba8b1f50..47ad657279cbb 100644 --- a/packages/core/root/core-root-browser-internal/tsconfig.json +++ b/packages/core/root/core-root-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/tsconfig.json b/packages/core/saved-objects/core-saved-objects-api-browser/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-api-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/tsconfig.json b/packages/core/saved-objects/core-saved-objects-api-server-internal/tsconfig.json index 3fe98195b374a..4582562d6c9bb 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/tsconfig.json b/packages/core/saved-objects/core-saved-objects-api-server-mocks/tsconfig.json index 3fe98195b374a..4582562d6c9bb 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-api-server/tsconfig.json b/packages/core/saved-objects/core-saved-objects-api-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-api-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/tsconfig.json b/packages/core/saved-objects/core-saved-objects-base-server-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-base-server-mocks/tsconfig.json b/packages/core/saved-objects/core-saved-objects-base-server-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-mocks/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-base-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/tsconfig.json b/packages/core/saved-objects/core-saved-objects-browser-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-browser-mocks/tsconfig.json b/packages/core/saved-objects/core-saved-objects-browser-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-mocks/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-browser/tsconfig.json b/packages/core/saved-objects/core-saved-objects-browser/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/saved-objects/core-saved-objects-browser/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-common/tsconfig.json b/packages/core/saved-objects/core-saved-objects-common/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/saved-objects/core-saved-objects-common/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-common/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/tsconfig.json b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/tsconfig.json index 3fe98195b374a..4582562d6c9bb 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/tsconfig.json b/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/tsconfig.json index 3fe98195b374a..4582562d6c9bb 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/tsconfig.json b/packages/core/saved-objects/core-saved-objects-migration-server-internal/tsconfig.json index 3fe98195b374a..4582562d6c9bb 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/tsconfig.json b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/tsconfig.json index 3fe98195b374a..4582562d6c9bb 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/tsconfig.json b/packages/core/saved-objects/core-saved-objects-server-internal/tsconfig.json index 3fe98195b374a..4582562d6c9bb 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-server-internal/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-server-mocks/tsconfig.json b/packages/core/saved-objects/core-saved-objects-server-mocks/tsconfig.json index 3fe98195b374a..4582562d6c9bb 100644 --- a/packages/core/saved-objects/core-saved-objects-server-mocks/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-server-mocks/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-server/tsconfig.json b/packages/core/saved-objects/core-saved-objects-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/saved-objects/core-saved-objects-server/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-utils-server/tsconfig.json b/packages/core/saved-objects/core-saved-objects-utils-server/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/saved-objects/core-saved-objects-utils-server/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-utils-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/status/core-status-common-internal/tsconfig.json b/packages/core/status/core-status-common-internal/tsconfig.json index 8a9b8b46147f1..741519055e986 100644 --- a/packages/core/status/core-status-common-internal/tsconfig.json +++ b/packages/core/status/core-status-common-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/status/core-status-common/tsconfig.json b/packages/core/status/core-status-common/tsconfig.json index 8a9b8b46147f1..741519055e986 100644 --- a/packages/core/status/core-status-common/tsconfig.json +++ b/packages/core/status/core-status-common/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/status/core-status-server-internal/tsconfig.json b/packages/core/status/core-status-server-internal/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/status/core-status-server-internal/tsconfig.json +++ b/packages/core/status/core-status-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/status/core-status-server-mocks/tsconfig.json b/packages/core/status/core-status-server-mocks/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/status/core-status-server-mocks/tsconfig.json +++ b/packages/core/status/core-status-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/status/core-status-server/tsconfig.json b/packages/core/status/core-status-server/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/status/core-status-server/tsconfig.json +++ b/packages/core/status/core-status-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/test-helpers/core-test-helpers-deprecations-getters/tsconfig.json b/packages/core/test-helpers/core-test-helpers-deprecations-getters/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/test-helpers/core-test-helpers-deprecations-getters/tsconfig.json +++ b/packages/core/test-helpers/core-test-helpers-deprecations-getters/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/test-helpers/core-test-helpers-http-setup-browser/tsconfig.json b/packages/core/test-helpers/core-test-helpers-http-setup-browser/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/test-helpers/core-test-helpers-http-setup-browser/tsconfig.json +++ b/packages/core/test-helpers/core-test-helpers-http-setup-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/test-helpers/core-test-helpers-so-type-serializer/tsconfig.json b/packages/core/test-helpers/core-test-helpers-so-type-serializer/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/test-helpers/core-test-helpers-so-type-serializer/tsconfig.json +++ b/packages/core/test-helpers/core-test-helpers-so-type-serializer/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/test-helpers/core-test-helpers-test-utils/tsconfig.json b/packages/core/test-helpers/core-test-helpers-test-utils/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/test-helpers/core-test-helpers-test-utils/tsconfig.json +++ b/packages/core/test-helpers/core-test-helpers-test-utils/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/theme/core-theme-browser-internal/tsconfig.json b/packages/core/theme/core-theme-browser-internal/tsconfig.json index 4eb9855fa759d..c561d9f220124 100644 --- a/packages/core/theme/core-theme-browser-internal/tsconfig.json +++ b/packages/core/theme/core-theme-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/theme/core-theme-browser-mocks/tsconfig.json b/packages/core/theme/core-theme-browser-mocks/tsconfig.json index d5dece108de5d..3cdea36de9eac 100644 --- a/packages/core/theme/core-theme-browser-mocks/tsconfig.json +++ b/packages/core/theme/core-theme-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/theme/core-theme-browser/tsconfig.json b/packages/core/theme/core-theme-browser/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/theme/core-theme-browser/tsconfig.json +++ b/packages/core/theme/core-theme-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/ui-settings/core-ui-settings-browser-internal/tsconfig.json b/packages/core/ui-settings/core-ui-settings-browser-internal/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/ui-settings/core-ui-settings-browser-internal/tsconfig.json +++ b/packages/core/ui-settings/core-ui-settings-browser-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/ui-settings/core-ui-settings-browser-mocks/tsconfig.json b/packages/core/ui-settings/core-ui-settings-browser-mocks/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/ui-settings/core-ui-settings-browser-mocks/tsconfig.json +++ b/packages/core/ui-settings/core-ui-settings-browser-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/ui-settings/core-ui-settings-browser/tsconfig.json b/packages/core/ui-settings/core-ui-settings-browser/tsconfig.json index 84be4fe27d5d6..3faa31fe437a8 100644 --- a/packages/core/ui-settings/core-ui-settings-browser/tsconfig.json +++ b/packages/core/ui-settings/core-ui-settings-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/core/ui-settings/core-ui-settings-common/tsconfig.json b/packages/core/ui-settings/core-ui-settings-common/tsconfig.json index af8fdef592c43..ef521586433c9 100644 --- a/packages/core/ui-settings/core-ui-settings-common/tsconfig.json +++ b/packages/core/ui-settings/core-ui-settings-common/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/tsconfig.json b/packages/core/ui-settings/core-ui-settings-server-internal/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/tsconfig.json +++ b/packages/core/ui-settings/core-ui-settings-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/ui-settings/core-ui-settings-server-mocks/tsconfig.json b/packages/core/ui-settings/core-ui-settings-server-mocks/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/ui-settings/core-ui-settings-server-mocks/tsconfig.json +++ b/packages/core/ui-settings/core-ui-settings-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/ui-settings/core-ui-settings-server/tsconfig.json b/packages/core/ui-settings/core-ui-settings-server/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/ui-settings/core-ui-settings-server/tsconfig.json +++ b/packages/core/ui-settings/core-ui-settings-server/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/usage-data/core-usage-data-base-server-internal/tsconfig.json b/packages/core/usage-data/core-usage-data-base-server-internal/tsconfig.json index 3fe98195b374a..4582562d6c9bb 100644 --- a/packages/core/usage-data/core-usage-data-base-server-internal/tsconfig.json +++ b/packages/core/usage-data/core-usage-data-base-server-internal/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/usage-data/core-usage-data-server-internal/tsconfig.json b/packages/core/usage-data/core-usage-data-server-internal/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/usage-data/core-usage-data-server-internal/tsconfig.json +++ b/packages/core/usage-data/core-usage-data-server-internal/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/usage-data/core-usage-data-server-mocks/tsconfig.json b/packages/core/usage-data/core-usage-data-server-mocks/tsconfig.json index ff48529c6f303..4582562d6c9bb 100644 --- a/packages/core/usage-data/core-usage-data-server-mocks/tsconfig.json +++ b/packages/core/usage-data/core-usage-data-server-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/usage-data/core-usage-data-server/tsconfig.json b/packages/core/usage-data/core-usage-data-server/tsconfig.json index 3fe98195b374a..4582562d6c9bb 100644 --- a/packages/core/usage-data/core-usage-data-server/tsconfig.json +++ b/packages/core/usage-data/core-usage-data-server/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/home/sample_data_card/tsconfig.json b/packages/home/sample_data_card/tsconfig.json index 9fdd594692a28..158e1387bb883 100644 --- a/packages/home/sample_data_card/tsconfig.json +++ b/packages/home/sample_data_card/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/home/sample_data_tab/tsconfig.json b/packages/home/sample_data_tab/tsconfig.json index 9fdd594692a28..158e1387bb883 100644 --- a/packages/home/sample_data_tab/tsconfig.json +++ b/packages/home/sample_data_tab/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/home/sample_data_types/tsconfig.json b/packages/home/sample_data_types/tsconfig.json index 159c10aa98cec..2ff5d03a149c7 100644 --- a/packages/home/sample_data_types/tsconfig.json +++ b/packages/home/sample_data_types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [] }, "include": [ diff --git a/packages/kbn-ace/tsconfig.json b/packages/kbn-ace/tsconfig.json index 8fd7178521b53..1cf2f1e9ac9a0 100644 --- a/packages/kbn-ace/tsconfig.json +++ b/packages/kbn-ace/tsconfig.json @@ -4,7 +4,7 @@ "allowJs": false, "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "stripInternal": true, "types": ["node"] }, diff --git a/packages/kbn-ambient-storybook-types/tsconfig.json b/packages/kbn-ambient-storybook-types/tsconfig.json index b816729f8b354..c0217a1c1a013 100644 --- a/packages/kbn-ambient-storybook-types/tsconfig.json +++ b/packages/kbn-ambient-storybook-types/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": "src", - "stripInternal": false, "types": [] }, "include": [ diff --git a/packages/kbn-ambient-ui-types/tsconfig.json b/packages/kbn-ambient-ui-types/tsconfig.json index a86b5dfec75b9..6904725bb1b26 100644 --- a/packages/kbn-ambient-ui-types/tsconfig.json +++ b/packages/kbn-ambient-ui-types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "@types/react", ] diff --git a/packages/kbn-analytics/tsconfig.json b/packages/kbn-analytics/tsconfig.json index 40efa4b7c3830..c1b62ffb7d142 100644 --- a/packages/kbn-analytics/tsconfig.json +++ b/packages/kbn-analytics/tsconfig.json @@ -4,8 +4,7 @@ "declaration": true, "emitDeclarationOnly": true, "isolatedModules": true, - "outDir": "./target_types", - "stripInternal": true, + "outDir": "target_types", "types": [ "node" ] diff --git a/packages/kbn-apm-config-loader/tsconfig.json b/packages/kbn-apm-config-loader/tsconfig.json index 51d1f22922c47..57c1dd1c94e0f 100644 --- a/packages/kbn-apm-config-loader/tsconfig.json +++ b/packages/kbn-apm-config-loader/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", - "stripInternal": false, + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-axe-config/tsconfig.json b/packages/kbn-axe-config/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-axe-config/tsconfig.json +++ b/packages/kbn-axe-config/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-bazel-packages/tsconfig.json b/packages/kbn-bazel-packages/tsconfig.json index 54d35c4858e63..b58cd70b2c650 100644 --- a/packages/kbn-bazel-packages/tsconfig.json +++ b/packages/kbn-bazel-packages/tsconfig.json @@ -5,7 +5,6 @@ "emitDeclarationOnly": true, "checkJs": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-bazel-runner/tsconfig.json b/packages/kbn-bazel-runner/tsconfig.json index dbd1dff4ef9ea..6065463237d14 100644 --- a/packages/kbn-bazel-runner/tsconfig.json +++ b/packages/kbn-bazel-runner/tsconfig.json @@ -5,7 +5,6 @@ "emitDeclarationOnly": true, "checkJs": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-cases-components/tsconfig.json b/packages/kbn-cases-components/tsconfig.json index 7b8e63a49fcb6..f48d8e4a548bf 100644 --- a/packages/kbn-cases-components/tsconfig.json +++ b/packages/kbn-cases-components/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/kbn-ci-stats-core/tsconfig.json b/packages/kbn-ci-stats-core/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-ci-stats-core/tsconfig.json +++ b/packages/kbn-ci-stats-core/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-ci-stats-performance-metrics/tsconfig.json b/packages/kbn-ci-stats-performance-metrics/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-ci-stats-performance-metrics/tsconfig.json +++ b/packages/kbn-ci-stats-performance-metrics/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-ci-stats-reporter/tsconfig.json b/packages/kbn-ci-stats-reporter/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-ci-stats-reporter/tsconfig.json +++ b/packages/kbn-ci-stats-reporter/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-cli-dev-mode/tsconfig.json b/packages/kbn-cli-dev-mode/tsconfig.json index d59a0bbd408f1..60a261148e76b 100644 --- a/packages/kbn-cli-dev-mode/tsconfig.json +++ b/packages/kbn-cli-dev-mode/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-coloring/tsconfig.json b/packages/kbn-coloring/tsconfig.json index 3f9461054a2c1..e0b5f2c053305 100644 --- a/packages/kbn-coloring/tsconfig.json +++ b/packages/kbn-coloring/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/kbn-config-mocks/tsconfig.json b/packages/kbn-config-mocks/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-config-mocks/tsconfig.json +++ b/packages/kbn-config-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-config/tsconfig.json b/packages/kbn-config/tsconfig.json index 51d1f22922c47..57c1dd1c94e0f 100644 --- a/packages/kbn-config/tsconfig.json +++ b/packages/kbn-config/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", - "stripInternal": false, + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-crypto-browser/tsconfig.json b/packages/kbn-crypto-browser/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-crypto-browser/tsconfig.json +++ b/packages/kbn-crypto-browser/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-crypto/tsconfig.json b/packages/kbn-crypto/tsconfig.json index 3dcbf034e70bf..f40f9a4ee1cfb 100644 --- a/packages/kbn-crypto/tsconfig.json +++ b/packages/kbn-crypto/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-dev-cli-errors/tsconfig.json b/packages/kbn-dev-cli-errors/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-dev-cli-errors/tsconfig.json +++ b/packages/kbn-dev-cli-errors/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-dev-cli-runner/tsconfig.json b/packages/kbn-dev-cli-runner/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-dev-cli-runner/tsconfig.json +++ b/packages/kbn-dev-cli-runner/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-dev-proc-runner/tsconfig.json b/packages/kbn-dev-proc-runner/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-dev-proc-runner/tsconfig.json +++ b/packages/kbn-dev-proc-runner/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-dev-utils/tsconfig.json b/packages/kbn-dev-utils/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-dev-utils/tsconfig.json +++ b/packages/kbn-dev-utils/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-doc-links/tsconfig.json b/packages/kbn-doc-links/tsconfig.json index 1a036108f4579..60a261148e76b 100644 --- a/packages/kbn-doc-links/tsconfig.json +++ b/packages/kbn-doc-links/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", - "stripInternal": false, + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-es-archiver/tsconfig.json b/packages/kbn-es-archiver/tsconfig.json index d7a6decde32cd..57c1dd1c94e0f 100644 --- a/packages/kbn-es-archiver/tsconfig.json +++ b/packages/kbn-es-archiver/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-es-errors/tsconfig.json b/packages/kbn-es-errors/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-es-errors/tsconfig.json +++ b/packages/kbn-es-errors/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-es-query/tsconfig.json b/packages/kbn-es-query/tsconfig.json index 78afadbecae24..292157c18591a 100644 --- a/packages/kbn-es-query/tsconfig.json +++ b/packages/kbn-es-query/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-es-types/tsconfig.json b/packages/kbn-es-types/tsconfig.json index 98e6b09c1c81a..292157c18591a 100644 --- a/packages/kbn-es-types/tsconfig.json +++ b/packages/kbn-es-types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-es/tsconfig.json b/packages/kbn-es/tsconfig.json index bfa142ccc520c..a47c95fc504e8 100644 --- a/packages/kbn-es/tsconfig.json +++ b/packages/kbn-es/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.bazel.json", "compilerOptions": { - "outDir": "target/types" + "outDir": "target_types" }, "include": [ "**/*.ts", diff --git a/packages/kbn-eslint-plugin-disable/tsconfig.json b/packages/kbn-eslint-plugin-disable/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-eslint-plugin-disable/tsconfig.json +++ b/packages/kbn-eslint-plugin-disable/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-eslint-plugin-imports/tsconfig.json b/packages/kbn-eslint-plugin-imports/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-eslint-plugin-imports/tsconfig.json +++ b/packages/kbn-eslint-plugin-imports/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-failed-test-reporter-cli/tsconfig.json b/packages/kbn-failed-test-reporter-cli/tsconfig.json index 98e6b09c1c81a..292157c18591a 100644 --- a/packages/kbn-failed-test-reporter-cli/tsconfig.json +++ b/packages/kbn-failed-test-reporter-cli/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-field-types/tsconfig.json b/packages/kbn-field-types/tsconfig.json index 580a9759f9e76..1cc4616a7ee49 100644 --- a/packages/kbn-field-types/tsconfig.json +++ b/packages/kbn-field-types/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.bazel.json", "compilerOptions": { - "outDir": "./target_types", + "outDir": "target_types", "declaration": true, "emitDeclarationOnly": true, "types": [ diff --git a/packages/kbn-find-used-node-modules/tsconfig.json b/packages/kbn-find-used-node-modules/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-find-used-node-modules/tsconfig.json +++ b/packages/kbn-find-used-node-modules/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-ftr-common-functional-services/tsconfig.json b/packages/kbn-ftr-common-functional-services/tsconfig.json index 98e6b09c1c81a..292157c18591a 100644 --- a/packages/kbn-ftr-common-functional-services/tsconfig.json +++ b/packages/kbn-ftr-common-functional-services/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-ftr-screenshot-filename/tsconfig.json b/packages/kbn-ftr-screenshot-filename/tsconfig.json index 98e6b09c1c81a..292157c18591a 100644 --- a/packages/kbn-ftr-screenshot-filename/tsconfig.json +++ b/packages/kbn-ftr-screenshot-filename/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-generate/templates/package/tsconfig.json.ejs b/packages/kbn-generate/templates/package/tsconfig.json.ejs index 9ce192ed67b46..2b1e544d34239 100644 --- a/packages/kbn-generate/templates/package/tsconfig.json.ejs +++ b/packages/kbn-generate/templates/package/tsconfig.json.ejs @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ <%_ if (pkg.web) { _%> "jest", diff --git a/packages/kbn-generate/tsconfig.json b/packages/kbn-generate/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-generate/tsconfig.json +++ b/packages/kbn-generate/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-get-repo-files/tsconfig.json b/packages/kbn-get-repo-files/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-get-repo-files/tsconfig.json +++ b/packages/kbn-get-repo-files/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-guided-onboarding/tsconfig.json b/packages/kbn-guided-onboarding/tsconfig.json index d28fc4d40371b..16588a28c9bfb 100644 --- a/packages/kbn-guided-onboarding/tsconfig.json +++ b/packages/kbn-guided-onboarding/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/kbn-handlebars/tsconfig.json b/packages/kbn-handlebars/tsconfig.json index d7a6decde32cd..57c1dd1c94e0f 100644 --- a/packages/kbn-handlebars/tsconfig.json +++ b/packages/kbn-handlebars/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-hapi-mocks/tsconfig.json b/packages/kbn-hapi-mocks/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-hapi-mocks/tsconfig.json +++ b/packages/kbn-hapi-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-i18n-react/tsconfig.json b/packages/kbn-i18n-react/tsconfig.json index a673e39a05ac1..14bb804696bbd 100644 --- a/packages/kbn-i18n-react/tsconfig.json +++ b/packages/kbn-i18n-react/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-i18n/tsconfig.json b/packages/kbn-i18n/tsconfig.json index 7b1a7613f0f51..90a2bc53a9b1c 100644 --- a/packages/kbn-i18n/tsconfig.json +++ b/packages/kbn-i18n/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-import-resolver/tsconfig.json b/packages/kbn-import-resolver/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-import-resolver/tsconfig.json +++ b/packages/kbn-import-resolver/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-interpreter/tsconfig.json b/packages/kbn-interpreter/tsconfig.json index 3b64720265657..57eff16f422bb 100644 --- a/packages/kbn-interpreter/tsconfig.json +++ b/packages/kbn-interpreter/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", - "stripInternal": true, + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-io-ts-utils/tsconfig.json b/packages/kbn-io-ts-utils/tsconfig.json index 51d1f22922c47..57c1dd1c94e0f 100644 --- a/packages/kbn-io-ts-utils/tsconfig.json +++ b/packages/kbn-io-ts-utils/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", - "stripInternal": false, + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-jest-serializers/tsconfig.json b/packages/kbn-jest-serializers/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-jest-serializers/tsconfig.json +++ b/packages/kbn-jest-serializers/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-journeys/tsconfig.json b/packages/kbn-journeys/tsconfig.json index 73854b491efab..d625ae13bf409 100644 --- a/packages/kbn-journeys/tsconfig.json +++ b/packages/kbn-journeys/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "mocha", "node" diff --git a/packages/kbn-kibana-manifest-schema/tsconfig.json b/packages/kbn-kibana-manifest-schema/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-kibana-manifest-schema/tsconfig.json +++ b/packages/kbn-kibana-manifest-schema/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-language-documentation-popover/tsconfig.json b/packages/kbn-language-documentation-popover/tsconfig.json index 9420678ac59f2..ab59ea0429ac2 100644 --- a/packages/kbn-language-documentation-popover/tsconfig.json +++ b/packages/kbn-language-documentation-popover/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "jest", "node", diff --git a/packages/kbn-logging-mocks/tsconfig.json b/packages/kbn-logging-mocks/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-logging-mocks/tsconfig.json +++ b/packages/kbn-logging-mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-logging/tsconfig.json b/packages/kbn-logging/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-logging/tsconfig.json +++ b/packages/kbn-logging/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-managed-vscode-config-cli/tsconfig.json b/packages/kbn-managed-vscode-config-cli/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-managed-vscode-config-cli/tsconfig.json +++ b/packages/kbn-managed-vscode-config-cli/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-managed-vscode-config/tsconfig.json b/packages/kbn-managed-vscode-config/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-managed-vscode-config/tsconfig.json +++ b/packages/kbn-managed-vscode-config/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-monaco/tsconfig.json b/packages/kbn-monaco/tsconfig.json index e717ec6c3149f..abba6736b0c28 100644 --- a/packages/kbn-monaco/tsconfig.json +++ b/packages/kbn-monaco/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-optimizer-webpack-helpers/tsconfig.json b/packages/kbn-optimizer-webpack-helpers/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-optimizer-webpack-helpers/tsconfig.json +++ b/packages/kbn-optimizer-webpack-helpers/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-optimizer/tsconfig.json b/packages/kbn-optimizer/tsconfig.json index e2ce5c11570c7..93c0f5ba2a57f 100644 --- a/packages/kbn-optimizer/tsconfig.json +++ b/packages/kbn-optimizer/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-performance-testing-dataset-extractor/tsconfig.json b/packages/kbn-performance-testing-dataset-extractor/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-performance-testing-dataset-extractor/tsconfig.json +++ b/packages/kbn-performance-testing-dataset-extractor/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-plugin-discovery/tsconfig.json b/packages/kbn-plugin-discovery/tsconfig.json index 819cbb943e5f1..e5578a8b0eeac 100644 --- a/packages/kbn-plugin-discovery/tsconfig.json +++ b/packages/kbn-plugin-discovery/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "checkJs": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-react-field/tsconfig.json b/packages/kbn-react-field/tsconfig.json index 9420678ac59f2..ab59ea0429ac2 100644 --- a/packages/kbn-react-field/tsconfig.json +++ b/packages/kbn-react-field/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "jest", "node", diff --git a/packages/kbn-repo-source-classifier-cli/tsconfig.json b/packages/kbn-repo-source-classifier-cli/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-repo-source-classifier-cli/tsconfig.json +++ b/packages/kbn-repo-source-classifier-cli/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-repo-source-classifier/tsconfig.json b/packages/kbn-repo-source-classifier/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-repo-source-classifier/tsconfig.json +++ b/packages/kbn-repo-source-classifier/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-rule-data-utils/tsconfig.json b/packages/kbn-rule-data-utils/tsconfig.json index 51d1f22922c47..57c1dd1c94e0f 100644 --- a/packages/kbn-rule-data-utils/tsconfig.json +++ b/packages/kbn-rule-data-utils/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", - "stripInternal": false, + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-securitysolution-exception-list-components/tsconfig.json b/packages/kbn-securitysolution-exception-list-components/tsconfig.json index 29f59e3a040d3..e1a84c71fdfd1 100644 --- a/packages/kbn-securitysolution-exception-list-components/tsconfig.json +++ b/packages/kbn-securitysolution-exception-list-components/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/kbn-server-http-tools/tsconfig.json b/packages/kbn-server-http-tools/tsconfig.json index a220affbfc45a..57c1dd1c94e0f 100644 --- a/packages/kbn-server-http-tools/tsconfig.json +++ b/packages/kbn-server-http-tools/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target/types", + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-server-route-repository/tsconfig.json b/packages/kbn-server-route-repository/tsconfig.json index 825b15f4cb419..a1cd5336c6255 100644 --- a/packages/kbn-server-route-repository/tsconfig.json +++ b/packages/kbn-server-route-repository/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", - "stripInternal": false, + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-shared-svg/tsconfig.json b/packages/kbn-shared-svg/tsconfig.json index cd57547f72077..df76c43764ad6 100644 --- a/packages/kbn-shared-svg/tsconfig.json +++ b/packages/kbn-shared-svg/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/kbn-shared-ux-utility/tsconfig.json b/packages/kbn-shared-ux-utility/tsconfig.json index a79192e00175e..4990376ba9063 100644 --- a/packages/kbn-shared-ux-utility/tsconfig.json +++ b/packages/kbn-shared-ux-utility/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/kbn-some-dev-log/tsconfig.json b/packages/kbn-some-dev-log/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-some-dev-log/tsconfig.json +++ b/packages/kbn-some-dev-log/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-sort-package-json/tsconfig.json b/packages/kbn-sort-package-json/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-sort-package-json/tsconfig.json +++ b/packages/kbn-sort-package-json/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-std/tsconfig.json b/packages/kbn-std/tsconfig.json index ae16eba4505a9..292157c18591a 100644 --- a/packages/kbn-std/tsconfig.json +++ b/packages/kbn-std/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", - "stripInternal": true, + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-stdio-dev-helpers/tsconfig.json b/packages/kbn-stdio-dev-helpers/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-stdio-dev-helpers/tsconfig.json +++ b/packages/kbn-stdio-dev-helpers/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-synthetic-package-map/tsconfig.json b/packages/kbn-synthetic-package-map/tsconfig.json index 7b74a1e555c86..75177b5488a9b 100644 --- a/packages/kbn-synthetic-package-map/tsconfig.json +++ b/packages/kbn-synthetic-package-map/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "node" ] diff --git a/packages/kbn-telemetry-tools/tsconfig.json b/packages/kbn-telemetry-tools/tsconfig.json index f910e6b2f0bac..59c205335d6ab 100644 --- a/packages/kbn-telemetry-tools/tsconfig.json +++ b/packages/kbn-telemetry-tools/tsconfig.json @@ -4,7 +4,7 @@ "declaration": true, "emitDeclarationOnly": true, "isolatedModules": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-test-jest-helpers/tsconfig.json b/packages/kbn-test-jest-helpers/tsconfig.json index 4a70c2e13a6df..2b02a63db1d05 100644 --- a/packages/kbn-test-jest-helpers/tsconfig.json +++ b/packages/kbn-test-jest-helpers/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", - "stripInternal": true, + "outDir": "target_types", "types": ["jest", "node"] }, "include": [ diff --git a/packages/kbn-test-subj-selector/tsconfig.json b/packages/kbn-test-subj-selector/tsconfig.json index 98e6b09c1c81a..292157c18591a 100644 --- a/packages/kbn-test-subj-selector/tsconfig.json +++ b/packages/kbn-test-subj-selector/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-test/tsconfig.json b/packages/kbn-test/tsconfig.json index 8b4a1a0e713c0..282d23e8bcb12 100644 --- a/packages/kbn-test/tsconfig.json +++ b/packages/kbn-test/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "stripInternal": true, "types": ["jest", "node"] }, diff --git a/packages/kbn-tooling-log/tsconfig.json b/packages/kbn-tooling-log/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-tooling-log/tsconfig.json +++ b/packages/kbn-tooling-log/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-type-summarizer-cli/tsconfig.json b/packages/kbn-type-summarizer-cli/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-type-summarizer-cli/tsconfig.json +++ b/packages/kbn-type-summarizer-cli/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-type-summarizer-core/tsconfig.json b/packages/kbn-type-summarizer-core/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-type-summarizer-core/tsconfig.json +++ b/packages/kbn-type-summarizer-core/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-type-summarizer/tsconfig.json b/packages/kbn-type-summarizer/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-type-summarizer/tsconfig.json +++ b/packages/kbn-type-summarizer/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-typed-react-router-config/tsconfig.json b/packages/kbn-typed-react-router-config/tsconfig.json index 77747d770c2aa..4c1b75ae89f88 100644 --- a/packages/kbn-typed-react-router-config/tsconfig.json +++ b/packages/kbn-typed-react-router-config/tsconfig.json @@ -4,8 +4,7 @@ "declaration": true, "emitDeclarationOnly": true, "isolatedModules": true, - "outDir": "./target_types", - "stripInternal": true, + "outDir": "target_types", "types": [ "node", "jest" diff --git a/packages/kbn-ui-shared-deps-npm/tsconfig.json b/packages/kbn-ui-shared-deps-npm/tsconfig.json index 376457cce75ef..e142364eaa188 100644 --- a/packages/kbn-ui-shared-deps-npm/tsconfig.json +++ b/packages/kbn-ui-shared-deps-npm/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "node", ] diff --git a/packages/kbn-ui-shared-deps-src/tsconfig.json b/packages/kbn-ui-shared-deps-src/tsconfig.json index 376457cce75ef..e142364eaa188 100644 --- a/packages/kbn-ui-shared-deps-src/tsconfig.json +++ b/packages/kbn-ui-shared-deps-src/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", + "outDir": "target_types", "types": [ "node", ] diff --git a/packages/kbn-ui-theme/tsconfig.json b/packages/kbn-ui-theme/tsconfig.json index 05fa2c9e696b5..7eae44ef23245 100644 --- a/packages/kbn-ui-theme/tsconfig.json +++ b/packages/kbn-ui-theme/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", - "stripInternal": true, + "outDir": "target_types", "types": ["node"] }, "include": [ diff --git a/packages/kbn-user-profile-components/tsconfig.json b/packages/kbn-user-profile-components/tsconfig.json index c94005d674932..b9cc115dd1264 100644 --- a/packages/kbn-user-profile-components/tsconfig.json +++ b/packages/kbn-user-profile-components/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", - "stripInternal": true, + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-utility-types-jest/tsconfig.json b/packages/kbn-utility-types-jest/tsconfig.json index ae16eba4505a9..292157c18591a 100644 --- a/packages/kbn-utility-types-jest/tsconfig.json +++ b/packages/kbn-utility-types-jest/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", - "stripInternal": true, + "outDir": "target_types", "types": [ "jest", "node" diff --git a/packages/kbn-utility-types/tsconfig.json b/packages/kbn-utility-types/tsconfig.json index 19facb3c91aba..57347ee107af5 100644 --- a/packages/kbn-utility-types/tsconfig.json +++ b/packages/kbn-utility-types/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, - "outDir": "./target_types", - "stripInternal": true, + "outDir": "target_types", "types": [ "node" ] diff --git a/packages/kbn-yarn-lock-validator/tsconfig.json b/packages/kbn-yarn-lock-validator/tsconfig.json index 118bd3fb10818..57c1dd1c94e0f 100644 --- a/packages/kbn-yarn-lock-validator/tsconfig.json +++ b/packages/kbn-yarn-lock-validator/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/shared-ux/avatar/solution/tsconfig.json b/packages/shared-ux/avatar/solution/tsconfig.json index 21b85ae51cd13..5cb7bca42f195 100644 --- a/packages/shared-ux/avatar/solution/tsconfig.json +++ b/packages/shared-ux/avatar/solution/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/avatar/user_profile/impl/tsconfig.json b/packages/shared-ux/avatar/user_profile/impl/tsconfig.json index d1cc3a9c6e996..d68e5f7ddeffa 100644 --- a/packages/shared-ux/avatar/user_profile/impl/tsconfig.json +++ b/packages/shared-ux/avatar/user_profile/impl/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/button/exit_full_screen/impl/tsconfig.json b/packages/shared-ux/button/exit_full_screen/impl/tsconfig.json index 7d24ab6a036ba..10624c45bc7cb 100644 --- a/packages/shared-ux/button/exit_full_screen/impl/tsconfig.json +++ b/packages/shared-ux/button/exit_full_screen/impl/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/button/exit_full_screen/mocks/tsconfig.json b/packages/shared-ux/button/exit_full_screen/mocks/tsconfig.json index 56a703280be4e..a00de7fc73226 100644 --- a/packages/shared-ux/button/exit_full_screen/mocks/tsconfig.json +++ b/packages/shared-ux/button/exit_full_screen/mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/button/exit_full_screen/types/tsconfig.json b/packages/shared-ux/button/exit_full_screen/types/tsconfig.json index a109753c20458..b863eab85b68e 100644 --- a/packages/shared-ux/button/exit_full_screen/types/tsconfig.json +++ b/packages/shared-ux/button/exit_full_screen/types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [] }, "include": [ diff --git a/packages/shared-ux/button_toolbar/tsconfig.json b/packages/shared-ux/button_toolbar/tsconfig.json index 9fdd594692a28..158e1387bb883 100644 --- a/packages/shared-ux/button_toolbar/tsconfig.json +++ b/packages/shared-ux/button_toolbar/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/card/no_data/impl/tsconfig.json b/packages/shared-ux/card/no_data/impl/tsconfig.json index 608ee34a18e41..5735aa5eceaa8 100644 --- a/packages/shared-ux/card/no_data/impl/tsconfig.json +++ b/packages/shared-ux/card/no_data/impl/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/card/no_data/mocks/tsconfig.json b/packages/shared-ux/card/no_data/mocks/tsconfig.json index 307c421c355d7..4703a8ebf5e35 100644 --- a/packages/shared-ux/card/no_data/mocks/tsconfig.json +++ b/packages/shared-ux/card/no_data/mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/card/no_data/types/tsconfig.json b/packages/shared-ux/card/no_data/types/tsconfig.json index a109753c20458..b863eab85b68e 100644 --- a/packages/shared-ux/card/no_data/types/tsconfig.json +++ b/packages/shared-ux/card/no_data/types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [] }, "include": [ diff --git a/packages/shared-ux/link/redirect_app/impl/tsconfig.json b/packages/shared-ux/link/redirect_app/impl/tsconfig.json index 31fd602881744..e5cd0acc7e3fb 100644 --- a/packages/shared-ux/link/redirect_app/impl/tsconfig.json +++ b/packages/shared-ux/link/redirect_app/impl/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/link/redirect_app/mocks/tsconfig.json b/packages/shared-ux/link/redirect_app/mocks/tsconfig.json index 56a703280be4e..a00de7fc73226 100644 --- a/packages/shared-ux/link/redirect_app/mocks/tsconfig.json +++ b/packages/shared-ux/link/redirect_app/mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/link/redirect_app/types/tsconfig.json b/packages/shared-ux/link/redirect_app/types/tsconfig.json index e4aed6f220b10..f566d00dd2704 100644 --- a/packages/shared-ux/link/redirect_app/types/tsconfig.json +++ b/packages/shared-ux/link/redirect_app/types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "rxjs", "@types/react", diff --git a/packages/shared-ux/markdown/impl/tsconfig.json b/packages/shared-ux/markdown/impl/tsconfig.json index dbb261fbbc413..60680c404dcdc 100644 --- a/packages/shared-ux/markdown/impl/tsconfig.json +++ b/packages/shared-ux/markdown/impl/tsconfig.json @@ -4,11 +4,10 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", - "react", + "react", "@kbn/ambient-ui-types", ] }, diff --git a/packages/shared-ux/markdown/mocks/tsconfig.json b/packages/shared-ux/markdown/mocks/tsconfig.json index d087908a4bc00..c8559330de310 100644 --- a/packages/shared-ux/markdown/mocks/tsconfig.json +++ b/packages/shared-ux/markdown/mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "node", "react" diff --git a/packages/shared-ux/markdown/types/tsconfig.json b/packages/shared-ux/markdown/types/tsconfig.json index ad91a6945198f..3cffae3f70336 100644 --- a/packages/shared-ux/markdown/types/tsconfig.json +++ b/packages/shared-ux/markdown/types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "rxjs", "@types/react", diff --git a/packages/shared-ux/page/analytics_no_data/impl/tsconfig.json b/packages/shared-ux/page/analytics_no_data/impl/tsconfig.json index 0b9a552bee78c..7c5977f8a0840 100644 --- a/packages/shared-ux/page/analytics_no_data/impl/tsconfig.json +++ b/packages/shared-ux/page/analytics_no_data/impl/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/page/analytics_no_data/mocks/tsconfig.json b/packages/shared-ux/page/analytics_no_data/mocks/tsconfig.json index 307c421c355d7..4703a8ebf5e35 100644 --- a/packages/shared-ux/page/analytics_no_data/mocks/tsconfig.json +++ b/packages/shared-ux/page/analytics_no_data/mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/page/analytics_no_data/types/tsconfig.json b/packages/shared-ux/page/analytics_no_data/types/tsconfig.json index a109753c20458..b863eab85b68e 100644 --- a/packages/shared-ux/page/analytics_no_data/types/tsconfig.json +++ b/packages/shared-ux/page/analytics_no_data/types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [] }, "include": [ diff --git a/packages/shared-ux/page/kibana_no_data/impl/tsconfig.json b/packages/shared-ux/page/kibana_no_data/impl/tsconfig.json index 6e42e35ef76f6..1f377c27d0e2d 100644 --- a/packages/shared-ux/page/kibana_no_data/impl/tsconfig.json +++ b/packages/shared-ux/page/kibana_no_data/impl/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/page/kibana_no_data/mocks/tsconfig.json b/packages/shared-ux/page/kibana_no_data/mocks/tsconfig.json index 56a703280be4e..a00de7fc73226 100644 --- a/packages/shared-ux/page/kibana_no_data/mocks/tsconfig.json +++ b/packages/shared-ux/page/kibana_no_data/mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/page/kibana_no_data/types/tsconfig.json b/packages/shared-ux/page/kibana_no_data/types/tsconfig.json index a109753c20458..b863eab85b68e 100644 --- a/packages/shared-ux/page/kibana_no_data/types/tsconfig.json +++ b/packages/shared-ux/page/kibana_no_data/types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [] }, "include": [ diff --git a/packages/shared-ux/page/kibana_template/impl/tsconfig.json b/packages/shared-ux/page/kibana_template/impl/tsconfig.json index 4baaa9985adb5..cf7982b9ab1e6 100644 --- a/packages/shared-ux/page/kibana_template/impl/tsconfig.json +++ b/packages/shared-ux/page/kibana_template/impl/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/page/kibana_template/mocks/tsconfig.json b/packages/shared-ux/page/kibana_template/mocks/tsconfig.json index a6a4dabce03f1..4c2618f5b3a19 100644 --- a/packages/shared-ux/page/kibana_template/mocks/tsconfig.json +++ b/packages/shared-ux/page/kibana_template/mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/page/kibana_template/types/tsconfig.json b/packages/shared-ux/page/kibana_template/types/tsconfig.json index a109753c20458..b863eab85b68e 100644 --- a/packages/shared-ux/page/kibana_template/types/tsconfig.json +++ b/packages/shared-ux/page/kibana_template/types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [] }, "include": [ diff --git a/packages/shared-ux/page/no_data/impl/tsconfig.json b/packages/shared-ux/page/no_data/impl/tsconfig.json index f970a21467add..ce5320536e563 100644 --- a/packages/shared-ux/page/no_data/impl/tsconfig.json +++ b/packages/shared-ux/page/no_data/impl/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/page/no_data/mocks/tsconfig.json b/packages/shared-ux/page/no_data/mocks/tsconfig.json index 307c421c355d7..4703a8ebf5e35 100644 --- a/packages/shared-ux/page/no_data/mocks/tsconfig.json +++ b/packages/shared-ux/page/no_data/mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/page/no_data/types/tsconfig.json b/packages/shared-ux/page/no_data/types/tsconfig.json index a109753c20458..b863eab85b68e 100644 --- a/packages/shared-ux/page/no_data/types/tsconfig.json +++ b/packages/shared-ux/page/no_data/types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [] }, "include": [ diff --git a/packages/shared-ux/page/no_data_config/impl/tsconfig.json b/packages/shared-ux/page/no_data_config/impl/tsconfig.json index 0b9a552bee78c..7c5977f8a0840 100644 --- a/packages/shared-ux/page/no_data_config/impl/tsconfig.json +++ b/packages/shared-ux/page/no_data_config/impl/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/page/no_data_config/mocks/tsconfig.json b/packages/shared-ux/page/no_data_config/mocks/tsconfig.json index 307c421c355d7..4703a8ebf5e35 100644 --- a/packages/shared-ux/page/no_data_config/mocks/tsconfig.json +++ b/packages/shared-ux/page/no_data_config/mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/page/no_data_config/types/tsconfig.json b/packages/shared-ux/page/no_data_config/types/tsconfig.json index a109753c20458..b863eab85b68e 100644 --- a/packages/shared-ux/page/no_data_config/types/tsconfig.json +++ b/packages/shared-ux/page/no_data_config/types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [] }, "include": [ diff --git a/packages/shared-ux/page/solution_nav/tsconfig.json b/packages/shared-ux/page/solution_nav/tsconfig.json index 21b85ae51cd13..5cb7bca42f195 100644 --- a/packages/shared-ux/page/solution_nav/tsconfig.json +++ b/packages/shared-ux/page/solution_nav/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/prompt/no_data_views/impl/tsconfig.json b/packages/shared-ux/prompt/no_data_views/impl/tsconfig.json index 8a581e3760903..5893035fdd770 100644 --- a/packages/shared-ux/prompt/no_data_views/impl/tsconfig.json +++ b/packages/shared-ux/prompt/no_data_views/impl/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/prompt/no_data_views/mocks/tsconfig.json b/packages/shared-ux/prompt/no_data_views/mocks/tsconfig.json index 56a703280be4e..a00de7fc73226 100644 --- a/packages/shared-ux/prompt/no_data_views/mocks/tsconfig.json +++ b/packages/shared-ux/prompt/no_data_views/mocks/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/prompt/no_data_views/types/tsconfig.json b/packages/shared-ux/prompt/no_data_views/types/tsconfig.json index a109753c20458..b863eab85b68e 100644 --- a/packages/shared-ux/prompt/no_data_views/types/tsconfig.json +++ b/packages/shared-ux/prompt/no_data_views/types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [] }, "include": [ diff --git a/packages/shared-ux/router/impl/tsconfig.json b/packages/shared-ux/router/impl/tsconfig.json index b804dcf4531f6..475b363297f65 100644 --- a/packages/shared-ux/router/impl/tsconfig.json +++ b/packages/shared-ux/router/impl/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/router/mocks/tsconfig.json b/packages/shared-ux/router/mocks/tsconfig.json index 6548f04ad9fd3..37f8e83d0d7a6 100644 --- a/packages/shared-ux/router/mocks/tsconfig.json +++ b/packages/shared-ux/router/mocks/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/router/types/tsconfig.json b/packages/shared-ux/router/types/tsconfig.json index 8ad061f2a6e2b..9b572c50a398e 100644 --- a/packages/shared-ux/router/types/tsconfig.json +++ b/packages/shared-ux/router/types/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [] }, "include": [ diff --git a/packages/shared-ux/storybook/config/tsconfig.json b/packages/shared-ux/storybook/config/tsconfig.json index c19d100b90e40..8beb0a5da38ab 100644 --- a/packages/shared-ux/storybook/config/tsconfig.json +++ b/packages/shared-ux/storybook/config/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", diff --git a/packages/shared-ux/storybook/mock/tsconfig.json b/packages/shared-ux/storybook/mock/tsconfig.json index d5dece108de5d..3cdea36de9eac 100644 --- a/packages/shared-ux/storybook/mock/tsconfig.json +++ b/packages/shared-ux/storybook/mock/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "stripInternal": false, "types": [ "jest", "node", From 11c3856d27a117aeb5a350d2dbc049443f79d785 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 3 Nov 2022 00:52:17 -0400 Subject: [PATCH 48/86] [api-docs] Daily api_docs build (#144495) --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.mdx | 2 +- api_docs/apm.devdocs.json | 150 +- api_docs/apm.mdx | 9 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.devdocs.json | 10 +- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_chat.mdx | 2 +- api_docs/cloud_experiments.mdx | 2 +- api_docs/cloud_security_posture.mdx | 2 +- api_docs/console.mdx | 2 +- api_docs/controls.mdx | 2 +- api_docs/core.devdocs.json | 64 +- api_docs/core.mdx | 2 +- api_docs/custom_integrations.mdx | 2 +- api_docs/dashboard.mdx | 2 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.devdocs.json | 184 ++- api_docs/data.mdx | 2 +- api_docs/data_query.mdx | 2 +- api_docs/data_search.mdx | 2 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.devdocs.json | 276 ++-- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/deprecations_by_api.mdx | 10 +- api_docs/deprecations_by_plugin.mdx | 20 +- api_docs/deprecations_by_team.mdx | 2 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.mdx | 2 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_log.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.devdocs.json | 198 ++- api_docs/expression_heatmap.mdx | 4 +- api_docs/expression_image.mdx | 2 +- .../expression_legacy_metric_vis.devdocs.json | 30 + api_docs/expression_legacy_metric_vis.mdx | 4 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.mdx | 2 +- api_docs/features.devdocs.json | 24 +- api_docs/features.mdx | 2 +- api_docs/field_formats.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.mdx | 2 +- api_docs/fleet.devdocs.json | 20 +- api_docs/fleet.mdx | 4 +- api_docs/global_search.mdx | 2 +- api_docs/guided_onboarding.mdx | 2 +- api_docs/home.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_utils.mdx | 2 +- api_docs/kbn_alerts.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_client.devdocs.json | 16 +- api_docs/kbn_analytics_client.mdx | 2 +- ..._analytics_shippers_elastic_v3_browser.mdx | 2 +- ...n_analytics_shippers_elastic_v3_common.mdx | 2 +- ...n_analytics_shippers_elastic_v3_server.mdx | 2 +- api_docs/kbn_analytics_shippers_fullstory.mdx | 2 +- api_docs/kbn_analytics_shippers_gainsight.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_cases_components.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.mdx | 2 +- .../kbn_content_management_table_list.mdx | 2 +- api_docs/kbn_core_analytics_browser.mdx | 2 +- .../kbn_core_analytics_browser_internal.mdx | 2 +- api_docs/kbn_core_analytics_browser_mocks.mdx | 2 +- api_docs/kbn_core_analytics_server.mdx | 2 +- .../kbn_core_analytics_server_internal.mdx | 2 +- api_docs/kbn_core_analytics_server_mocks.mdx | 2 +- api_docs/kbn_core_application_browser.mdx | 2 +- .../kbn_core_application_browser_internal.mdx | 2 +- .../kbn_core_application_browser_mocks.mdx | 2 +- api_docs/kbn_core_application_common.mdx | 2 +- api_docs/kbn_core_apps_browser_internal.mdx | 2 +- api_docs/kbn_core_apps_browser_mocks.mdx | 2 +- ...kbn_core_apps_server_internal.devdocs.json | 165 ++ api_docs/kbn_core_apps_server_internal.mdx | 30 + api_docs/kbn_core_base_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_common.mdx | 2 +- api_docs/kbn_core_base_server_internal.mdx | 2 +- api_docs/kbn_core_base_server_mocks.mdx | 2 +- .../kbn_core_capabilities_browser_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_common.mdx | 2 +- api_docs/kbn_core_capabilities_server.mdx | 2 +- .../kbn_core_capabilities_server_mocks.mdx | 2 +- api_docs/kbn_core_chrome_browser.mdx | 2 +- api_docs/kbn_core_chrome_browser_mocks.mdx | 2 +- api_docs/kbn_core_config_server_internal.mdx | 2 +- api_docs/kbn_core_deprecations_browser.mdx | 2 +- ...kbn_core_deprecations_browser_internal.mdx | 2 +- .../kbn_core_deprecations_browser_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_common.mdx | 2 +- api_docs/kbn_core_deprecations_server.mdx | 2 +- .../kbn_core_deprecations_server_internal.mdx | 2 +- .../kbn_core_deprecations_server_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_browser.mdx | 2 +- api_docs/kbn_core_doc_links_browser_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_server.mdx | 2 +- api_docs/kbn_core_doc_links_server_mocks.mdx | 2 +- ...e_elasticsearch_client_server_internal.mdx | 2 +- ...core_elasticsearch_client_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_server.mdx | 2 +- ...kbn_core_elasticsearch_server_internal.mdx | 2 +- .../kbn_core_elasticsearch_server_mocks.mdx | 2 +- .../kbn_core_environment_server_internal.mdx | 2 +- .../kbn_core_environment_server_mocks.mdx | 2 +- .../kbn_core_execution_context_browser.mdx | 2 +- ...ore_execution_context_browser_internal.mdx | 2 +- ...n_core_execution_context_browser_mocks.mdx | 2 +- .../kbn_core_execution_context_common.mdx | 2 +- .../kbn_core_execution_context_server.mdx | 2 +- ...core_execution_context_server_internal.mdx | 2 +- ...bn_core_execution_context_server_mocks.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser.mdx | 2 +- .../kbn_core_fatal_errors_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_browser.mdx | 2 +- api_docs/kbn_core_http_browser_internal.mdx | 2 +- api_docs/kbn_core_http_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_common.mdx | 2 +- .../kbn_core_http_context_server_mocks.mdx | 2 +- ...re_http_request_handler_context_server.mdx | 2 +- api_docs/kbn_core_http_resources_server.mdx | 2 +- ...bn_core_http_resources_server_internal.mdx | 2 +- .../kbn_core_http_resources_server_mocks.mdx | 2 +- .../kbn_core_http_router_server_internal.mdx | 2 +- .../kbn_core_http_router_server_mocks.mdx | 2 +- api_docs/kbn_core_http_server.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- .../kbn_core_injected_metadata_browser.mdx | 2 +- ...n_core_injected_metadata_browser_mocks.mdx | 2 +- ...kbn_core_integrations_browser_internal.mdx | 2 +- .../kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_server.mdx | 2 +- api_docs/kbn_core_lifecycle_server_mocks.mdx | 2 +- api_docs/kbn_core_logging_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_common_internal.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- ...ore_metrics_collectors_server_internal.mdx | 2 +- ...n_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- ...kbn_core_node_server_internal.devdocs.json | 36 + api_docs/kbn_core_node_server_internal.mdx | 4 +- .../kbn_core_node_server_mocks.devdocs.json | 39 + api_docs/kbn_core_node_server_mocks.mdx | 4 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- ...bn_core_notifications_browser_internal.mdx | 2 +- .../kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- .../kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_server.mdx | 2 +- api_docs/kbn_core_plugins_server_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- .../kbn_core_rendering_server_internal.mdx | 2 +- api_docs/kbn_core_rendering_server_mocks.mdx | 2 +- .../kbn_core_saved_objects_api_browser.mdx | 2 +- .../kbn_core_saved_objects_api_server.mdx | 2 +- ...core_saved_objects_api_server_internal.mdx | 2 +- ...bn_core_saved_objects_api_server_mocks.mdx | 2 +- ...ore_saved_objects_base_server_internal.mdx | 2 +- ...n_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- ...bn_core_saved_objects_browser_internal.mdx | 2 +- .../kbn_core_saved_objects_browser_mocks.mdx | 2 +- ...kbn_core_saved_objects_common.devdocs.json | 16 +- api_docs/kbn_core_saved_objects_common.mdx | 2 +- ..._objects_import_export_server_internal.mdx | 2 +- ...ved_objects_import_export_server_mocks.mdx | 2 +- ...cts_migration_server_internal.devdocs.json | 13 +- ...aved_objects_migration_server_internal.mdx | 4 +- ...e_saved_objects_migration_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_server.mdx | 2 +- ...saved_objects_server_internal.devdocs.json | 4 +- ...kbn_core_saved_objects_server_internal.mdx | 2 +- .../kbn_core_saved_objects_server_mocks.mdx | 2 +- .../kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- ...core_test_helpers_deprecations_getters.mdx | 2 +- ...n_core_test_helpers_http_setup_browser.mdx | 2 +- ...n_core_test_helpers_so_type_serializer.mdx | 2 +- api_docs/kbn_core_test_helpers_test_utils.mdx | 2 +- api_docs/kbn_core_theme_browser.mdx | 2 +- api_docs/kbn_core_theme_browser_internal.mdx | 2 +- api_docs/kbn_core_theme_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_browser.mdx | 2 +- .../kbn_core_ui_settings_browser_internal.mdx | 2 +- .../kbn_core_ui_settings_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_common.mdx | 2 +- api_docs/kbn_core_ui_settings_server.mdx | 2 +- .../kbn_core_ui_settings_server_internal.mdx | 2 +- .../kbn_core_ui_settings_server_mocks.mdx | 2 +- api_docs/kbn_core_usage_data_server.mdx | 2 +- .../kbn_core_usage_data_server_internal.mdx | 2 +- api_docs/kbn_core_usage_data_server_mocks.mdx | 2 +- api_docs/kbn_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_datemath.mdx | 2 +- api_docs/kbn_dev_cli_errors.mdx | 2 +- api_docs/kbn_dev_cli_runner.mdx | 2 +- api_docs/kbn_dev_proc_runner.mdx | 2 +- api_docs/kbn_dev_utils.mdx | 2 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_es.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- .../kbn_ftr_common_functional_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_get_repo_files.mdx | 2 +- api_docs/kbn_guided_onboarding.devdocs.json | 21 +- api_docs/kbn_guided_onboarding.mdx | 4 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_i18n_react.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_io_ts_utils.mdx | 2 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.mdx | 2 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- .../kbn_language_documentation_popover.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_is_populated_object.mdx | 2 +- api_docs/kbn_ml_string_hash.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_react_field.mdx | 2 +- api_docs/kbn_repo_source_classifier.mdx | 2 +- api_docs/kbn_rule_data_utils.mdx | 2 +- .../kbn_securitysolution_autocomplete.mdx | 2 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- ...ritysolution_exception_list_components.mdx | 2 +- api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- ..._securitysolution_io_ts_alerting_types.mdx | 2 +- .../kbn_securitysolution_io_ts_list_types.mdx | 2 +- ..._securitysolution_io_ts_types.devdocs.json | 6 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- .../kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- api_docs/kbn_shared_ux_avatar_solution.mdx | 2 +- ...ared_ux_avatar_user_profile_components.mdx | 2 +- .../kbn_shared_ux_button_exit_full_screen.mdx | 2 +- ...hared_ux_button_exit_full_screen_mocks.mdx | 2 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_link_redirect_app.mdx | 2 +- .../kbn_shared_ux_link_redirect_app_mocks.mdx | 2 +- api_docs/kbn_shared_ux_markdown.mdx | 2 +- api_docs/kbn_shared_ux_markdown_mocks.mdx | 2 +- .../kbn_shared_ux_page_analytics_no_data.mdx | 2 +- ...shared_ux_page_analytics_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_no_data.mdx | 2 +- ...bn_shared_ux_page_kibana_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_template.mdx | 2 +- ...n_shared_ux_page_kibana_template_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data.mdx | 2 +- .../kbn_shared_ux_page_no_data_config.mdx | 2 +- ...bn_shared_ux_page_no_data_config_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_solution_nav.mdx | 2 +- .../kbn_shared_ux_prompt_no_data_views.mdx | 2 +- ...n_shared_ux_prompt_no_data_views_mocks.mdx | 2 +- api_docs/kbn_shared_ux_router.mdx | 2 +- api_docs/kbn_shared_ux_router_mocks.mdx | 2 +- api_docs/kbn_shared_ux_storybook_config.mdx | 2 +- api_docs/kbn_shared_ux_storybook_mock.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_sort_package_json.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_type_summarizer.mdx | 2 +- api_docs/kbn_type_summarizer_core.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_shared_deps_src.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- api_docs/kbn_user_profile_components.mdx | 2 +- api_docs/kbn_utility_types.mdx | 2 +- api_docs/kbn_utility_types_jest.mdx | 2 +- api_docs/kbn_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.devdocs.json | 55 +- api_docs/lens.mdx | 4 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/lists.devdocs.json | 4 +- api_docs/lists.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/observability.devdocs.json | 24 +- api_docs/observability.mdx | 4 +- api_docs/osquery.mdx | 2 +- api_docs/plugin_directory.mdx | 31 +- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.mdx | 2 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/security.mdx | 2 +- api_docs/security_solution.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.mdx | 2 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/threat_intelligence.mdx | 2 +- api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.mdx | 2 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_field_list.devdocs.json | 1401 ++++++++++++++++- api_docs/unified_field_list.mdx | 7 +- api_docs/unified_histogram.mdx | 2 +- api_docs/unified_search.devdocs.json | 2 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.mdx | 2 +- api_docs/url_forwarding.mdx | 2 +- api_docs/usage_collection.mdx | 2 +- api_docs/ux.mdx | 2 +- api_docs/vis_default_editor.mdx | 2 +- api_docs/vis_type_gauge.mdx | 2 +- api_docs/vis_type_heatmap.mdx | 2 +- api_docs/vis_type_pie.mdx | 2 +- api_docs/vis_type_table.mdx | 2 +- api_docs/vis_type_timelion.mdx | 2 +- api_docs/vis_type_timeseries.mdx | 2 +- api_docs/vis_type_vega.mdx | 2 +- api_docs/vis_type_vislib.mdx | 2 +- api_docs/vis_type_xy.mdx | 2 +- api_docs/visualizations.devdocs.json | 125 +- api_docs/visualizations.mdx | 4 +- 456 files changed, 2914 insertions(+), 948 deletions(-) create mode 100644 api_docs/kbn_core_apps_server_internal.devdocs.json create mode 100644 api_docs/kbn_core_apps_server_internal.mdx diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 52942544eb15f..45ae25e9b17c0 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index b6f4e9b984a96..bd29be2c00481 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 54cbebe0b6819..936c9a2367ae2 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 3715af8a1501f..f530f33d11f74 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index 28b91d38809c7..31dd709c19c75 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -795,7 +795,7 @@ "label": "APIEndpoint", "description": [], "signature": [ - "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/title\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/nodes\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/summary\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search\" | \"POST /api/apm/services/{serviceName}/annotation\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/sorted_and_filtered_services\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/service_groups/services_count\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/rule_types/transaction_error_rate/chart_preview\" | \"GET /internal/apm/rule_types/transaction_duration/chart_preview\" | \"GET /internal/apm/rule_types/error_count/chart_preview\" | \"GET /api/apm/settings/agent-configuration\" | \"GET /api/apm/settings/agent-configuration/view\" | \"DELETE /api/apm/settings/agent-configuration\" | \"PUT /api/apm/settings/agent-configuration\" | \"POST /api/apm/settings/agent-configuration/search\" | \"GET /api/apm/settings/agent-configuration/environments\" | \"GET /api/apm/settings/agent-configuration/agent_name\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps\" | \"POST /api/apm/sourcemaps\" | \"DELETE /api/apm/sourcemaps/{id}\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"POST /internal/apm/correlations/field_stats/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/storage_explorer/is_cross_cluster_search\" | \"GET /internal/apm/storage_explorer/get_services\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\" | \"GET /internal/apm/settings/labs\" | \"GET /internal/apm/services/{serviceName}/mobile/filters\"" + "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/title\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/nodes\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/summary\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search\" | \"POST /api/apm/services/{serviceName}/annotation\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/sorted_and_filtered_services\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/service_groups/services_count\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"POST /internal/apm/traces/aggregated_critical_path\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/rule_types/transaction_error_rate/chart_preview\" | \"GET /internal/apm/rule_types/transaction_duration/chart_preview\" | \"GET /internal/apm/rule_types/error_count/chart_preview\" | \"GET /api/apm/settings/agent-configuration\" | \"GET /api/apm/settings/agent-configuration/view\" | \"DELETE /api/apm/settings/agent-configuration\" | \"PUT /api/apm/settings/agent-configuration\" | \"POST /api/apm/settings/agent-configuration/search\" | \"GET /api/apm/settings/agent-configuration/environments\" | \"GET /api/apm/settings/agent-configuration/agent_name\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps\" | \"POST /api/apm/sourcemaps\" | \"DELETE /api/apm/sourcemaps/{id}\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"POST /internal/apm/correlations/field_stats/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/storage_explorer/is_cross_cluster_search\" | \"GET /internal/apm/storage_explorer/get_services\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\" | \"GET /internal/apm/settings/labs\" | \"GET /internal/apm/services/{serviceName}/mobile/filters\"" ], "path": "x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts", "deprecated": false, @@ -903,7 +903,11 @@ "section": "def-common.NonEmptyStringBrand", "text": "NonEmptyStringBrand" }, - ">]>; }>]>; }>, ", + ">]>; }>, ", + "PartialC", + "<{ transactionType: ", + "StringC", + "; }>]>; }>, ", { "pluginId": "apm", "scope": "server", @@ -4495,6 +4499,74 @@ }, ", { transactionGroups: { transactionType: string; name: string; latency: number | null; throughput: number; errorRate: number; impact: number; }[]; isAggregationAccurate: boolean; bucketSize: number; }, ", "APMRouteCreateOptions", + ">; \"POST /internal/apm/traces/aggregated_critical_path\": ", + { + "pluginId": "@kbn/server-route-repository", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /internal/apm/traces/aggregated_critical_path\", ", + "TypeC", + "<{ body: ", + "IntersectionC", + "<[", + "TypeC", + "<{ traceIds: ", + "ArrayC", + "<", + "StringC", + ">; serviceName: ", + "UnionC", + "<[", + "BrandC", + "<", + "StringC", + ", ", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.NonEmptyStringBrand", + "text": "NonEmptyStringBrand" + }, + ">, ", + "NullC", + "]>; transactionName: ", + "UnionC", + "<[", + "BrandC", + "<", + "StringC", + ", ", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.NonEmptyStringBrand", + "text": "NonEmptyStringBrand" + }, + ">, ", + "NullC", + "]>; }>, ", + "TypeC", + "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>]>; }>, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { criticalPath: ", + "CriticalPathResponse", + " | null; }, ", + "APMRouteCreateOptions", ">; \"GET /internal/apm/traces/find\": ", { "pluginId": "@kbn/server-route-repository", @@ -5611,7 +5683,27 @@ "Type", "; end: ", "Type", - "; }>]>; }>, ", + "; }>, ", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", + "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", + "<", + "StringC", + ", ", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.NonEmptyStringBrand", + "text": "NonEmptyStringBrand" + }, + ">]>; }>]>; }>, ", { "pluginId": "apm", "scope": "server", @@ -7263,7 +7355,57 @@ }, "common": { "classes": [], - "functions": [], + "functions": [ + { + "parentPluginId": "apm", + "id": "def-common.getAggregatedCriticalPathRootNodes", + "type": "Function", + "tags": [], + "label": "getAggregatedCriticalPathRootNodes", + "description": [], + "signature": [ + "(params: { criticalPath: ", + "CriticalPathResponse", + "; }) => { rootNodes: ", + "CriticalPathTreeNode", + "[]; maxDepth: number; numNodes: number; }" + ], + "path": "x-pack/plugins/apm/common/critical_path/get_aggregated_critical_path_root_nodes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apm", + "id": "def-common.getAggregatedCriticalPathRootNodes.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "path": "x-pack/plugins/apm/common/critical_path/get_aggregated_critical_path_root_nodes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apm", + "id": "def-common.getAggregatedCriticalPathRootNodes.$1.criticalPath", + "type": "Object", + "tags": [], + "label": "criticalPath", + "description": [], + "signature": [ + "CriticalPathResponse" + ], + "path": "x-pack/plugins/apm/common/critical_path/get_aggregated_critical_path_root_nodes.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], "interfaces": [], "enums": [], "misc": [], diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index d88beee3b29c0..d07ecf0a253c2 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; @@ -21,7 +21,7 @@ Contact [APM UI](https://github.com/orgs/elastic/teams/apm-ui) for questions reg | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 38 | 0 | 38 | 56 | +| 41 | 0 | 41 | 58 | ## Client @@ -48,3 +48,8 @@ Contact [APM UI](https://github.com/orgs/elastic/teams/apm-ui) for questions reg ### Consts, variables and types +## Common + +### Functions + + diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index c420cb692a837..0ad42c9b17321 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index a4e00c6aa05fc..3db90dea640f8 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 0509d1f1e15ca..9e0901d75f6b1 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.devdocs.json b/api_docs/cases.devdocs.json index 0ce653b22e2fb..9426f8ab0d016 100644 --- a/api_docs/cases.devdocs.json +++ b/api_docs/cases.devdocs.json @@ -838,7 +838,15 @@ "label": "api", "description": [], "signature": [ - "{ getRelatedCases: (alertId: string, query: { owner?: string | string[] | undefined; }) => Promise<{ id: string; title: string; }[]>; cases: { find: (query: { tags?: string | string[] | undefined; status?: ", + "{ getRelatedCases: (alertId: string, query: { owner?: string | string[] | undefined; }) => Promise<{ id: string; title: string; description: string; status: ", + { + "pluginId": "@kbn/cases-components", + "scope": "common", + "docId": "kibKbnCasesComponentsPluginApi", + "section": "def-common.CaseStatuses", + "text": "CaseStatuses" + }, + "; createdAt: string; totals: { alerts: number; userComments: number; }; }[]>; cases: { find: (query: { tags?: string | string[] | undefined; status?: ", { "pluginId": "@kbn/cases-components", "scope": "common", diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 3baa539c48eca..e7a74aacce4ae 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 96516fd25f46c..e8838d50b0eaa 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index fbcab061d1c7b..f8d1163733b79 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_chat.mdx b/api_docs/cloud_chat.mdx index 49c787bbf32f9..80ff13377bc26 100644 --- a/api_docs/cloud_chat.mdx +++ b/api_docs/cloud_chat.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudChat title: "cloudChat" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudChat plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChat'] --- import cloudChatObj from './cloud_chat.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 152b4355f04db..c1b95e245680a 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index b9aa8c60aa4f9..4ad371b257335 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 17cd0e6d70f4b..8fdf1ffb89ac0 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 32564a5ae6760..6599db29e930d 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/core.devdocs.json b/api_docs/core.devdocs.json index a672cb981f7e8..b01c00aebe9ec 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -7863,6 +7863,10 @@ "plugin": "security", "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" + }, { "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/analytics/types.ts" @@ -7871,10 +7875,6 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/analytics/analytics_service.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" - }, { "plugin": "osquery", "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" @@ -8501,6 +8501,10 @@ "plugin": "cloud", "path": "x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts" }, + { + "plugin": "telemetry", + "path": "src/plugins/telemetry/server/plugin.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/public/analytics/register_user_context.ts" @@ -8509,10 +8513,6 @@ "plugin": "telemetry", "path": "src/plugins/telemetry/public/plugin.ts" }, - { - "plugin": "telemetry", - "path": "src/plugins/telemetry/server/plugin.ts" - }, { "plugin": "@kbn/analytics-client", "path": "packages/analytics/client/src/analytics_client/analytics_client.ts" @@ -13274,6 +13274,14 @@ "plugin": "savedSearch", "path": "src/plugins/saved_search/server/saved_objects/search_migrations.ts" }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/common/types/modules.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/common/types/modules.ts" + }, { "plugin": "visualizations", "path": "src/plugins/visualizations/common/types.ts" @@ -13314,14 +13322,6 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/common/types/modules.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/common/types/modules.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.11/index.ts" @@ -31144,6 +31144,10 @@ "plugin": "security", "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" + }, { "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/analytics/types.ts" @@ -31152,10 +31156,6 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/analytics/analytics_service.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" - }, { "plugin": "osquery", "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" @@ -31782,6 +31782,10 @@ "plugin": "cloud", "path": "x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts" }, + { + "plugin": "telemetry", + "path": "src/plugins/telemetry/server/plugin.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/public/analytics/register_user_context.ts" @@ -31790,10 +31794,6 @@ "plugin": "telemetry", "path": "src/plugins/telemetry/public/plugin.ts" }, - { - "plugin": "telemetry", - "path": "src/plugins/telemetry/server/plugin.ts" - }, { "plugin": "@kbn/analytics-client", "path": "packages/analytics/client/src/analytics_client/analytics_client.ts" @@ -47603,6 +47603,14 @@ "plugin": "savedSearch", "path": "src/plugins/saved_search/server/saved_objects/search_migrations.ts" }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/common/types/modules.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/common/types/modules.ts" + }, { "plugin": "visualizations", "path": "src/plugins/visualizations/common/types.ts" @@ -47643,14 +47651,6 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/common/types/modules.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/common/types/modules.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.11/index.ts" diff --git a/api_docs/core.mdx b/api_docs/core.mdx index 65a90ab56320c..8c4220e8cf716 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github description: API docs for the core plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 97e124c1de8e8..c8cf92add3f3a 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 6ba5323d1c419..8ec36dacef78c 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index d5ac616dc701e..a02131a88a707 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.devdocs.json b/api_docs/data.devdocs.json index 497266ba3188e..553e1e7be39fc 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -13119,10 +13119,54 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/common/utils/field_existing_utils.ts" }, + { + "plugin": "unifiedFieldList", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" + }, + { + "plugin": "unifiedFieldList", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" + }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/lib/alerts/alerting_service.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" + }, { "plugin": "presentationUtil", "path": "src/plugins/presentation_util/public/services/storybook/data_views.ts" @@ -13159,10 +13203,6 @@ "plugin": "observability", "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" }, - { - "plugin": "infra", - "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -13363,38 +13403,6 @@ "plugin": "fleet", "path": "x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx" }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/lib/alerts/alerting_service.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" - }, - { - "plugin": "apm", - "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" - }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx" @@ -13771,18 +13779,6 @@ "plugin": "dataViews", "path": "src/plugins/data_views/common/data_views/data_views.ts" }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.ts" @@ -20825,10 +20821,54 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/common/utils/field_existing_utils.ts" }, + { + "plugin": "unifiedFieldList", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" + }, + { + "plugin": "unifiedFieldList", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" + }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/lib/alerts/alerting_service.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" + }, { "plugin": "presentationUtil", "path": "src/plugins/presentation_util/public/services/storybook/data_views.ts" @@ -20865,10 +20905,6 @@ "plugin": "observability", "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" }, - { - "plugin": "infra", - "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -21069,38 +21105,6 @@ "plugin": "fleet", "path": "x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx" }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/lib/alerts/alerting_service.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" - }, - { - "plugin": "apm", - "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" - }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx" @@ -21477,18 +21481,6 @@ "plugin": "dataViews", "path": "src/plugins/data_views/common/data_views/data_views.ts" }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.ts" diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 629edd64e8ebf..f6bf34c288d68 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 71843dd2ac43a..db9f22945d1ac 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 7c5131facfb03..b12119e1daa88 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 827841baa8e23..8591c7282e883 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 2ae135c2ce21d..c98f96208c9c4 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index 797103352dc51..94ee7348cf66e 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.devdocs.json b/api_docs/data_views.devdocs.json index 70e6e1d21f6ec..7c18de244ea8f 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -307,10 +307,54 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/common/utils/field_existing_utils.ts" }, + { + "plugin": "unifiedFieldList", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" + }, + { + "plugin": "unifiedFieldList", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" + }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/lib/alerts/alerting_service.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" + }, { "plugin": "presentationUtil", "path": "src/plugins/presentation_util/public/services/storybook/data_views.ts" @@ -347,10 +391,6 @@ "plugin": "observability", "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" }, - { - "plugin": "infra", - "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -551,38 +591,6 @@ "plugin": "fleet", "path": "x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx" }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/lib/alerts/alerting_service.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" - }, - { - "plugin": "apm", - "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" - }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx" @@ -987,18 +995,6 @@ "plugin": "visTypeTimeseries", "path": "src/plugins/vis_types/timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.test.ts" }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.ts" @@ -8612,10 +8608,54 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/common/utils/field_existing_utils.ts" }, + { + "plugin": "unifiedFieldList", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" + }, + { + "plugin": "unifiedFieldList", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" + }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/lib/alerts/alerting_service.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" + }, { "plugin": "presentationUtil", "path": "src/plugins/presentation_util/public/services/storybook/data_views.ts" @@ -8652,10 +8692,6 @@ "plugin": "observability", "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" }, - { - "plugin": "infra", - "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -8856,38 +8892,6 @@ "plugin": "fleet", "path": "x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx" }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/lib/alerts/alerting_service.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" - }, - { - "plugin": "apm", - "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" - }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx" @@ -9292,18 +9296,6 @@ "plugin": "visTypeTimeseries", "path": "src/plugins/vis_types/timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.test.ts" }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.ts" @@ -15998,10 +15990,54 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/common/utils/field_existing_utils.ts" }, + { + "plugin": "unifiedFieldList", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" + }, + { + "plugin": "unifiedFieldList", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" + }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/lib/alerts/alerting_service.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" + }, + { + "plugin": "visTypeTimeseries", + "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" + }, { "plugin": "presentationUtil", "path": "src/plugins/presentation_util/public/services/storybook/data_views.ts" @@ -16038,10 +16074,6 @@ "plugin": "observability", "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx" }, - { - "plugin": "infra", - "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -16242,38 +16274,6 @@ "plugin": "fleet", "path": "x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx" }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/lib/alerts/alerting_service.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/common/index_patterns_utils.ts" - }, - { - "plugin": "visTypeTimeseries", - "path": "src/plugins/vis_types/timeseries/server/lib/get_fields.ts" - }, - { - "plugin": "apm", - "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" - }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx" @@ -16678,18 +16678,6 @@ "plugin": "visTypeTimeseries", "path": "src/plugins/vis_types/timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.test.ts" }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.test.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/sourcerer/routes/index.ts" diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 9448431955ffe..725d6d1513885 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 512739a20029e..044f63182b7b7 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 2d37c966c2be9..4e3a8df5e4bdf 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -28,9 +28,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | @kbn/core-saved-objects-common, savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | | | core, savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | | | discover, maps, monitoring | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, dataVisualizer, ml, fleet, visTypeTimeseries, apm, canvas, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, dataVisualizer, ml, fleet, visTypeTimeseries, apm, canvas, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, data, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, dataVisualizer, ml, fleet, visTypeTimeseries, apm, canvas, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, ml, infra, visTypeTimeseries, apm, presentationUtil, controls, lens, observability, dataVisualizer, fleet, canvas, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, ml, infra, visTypeTimeseries, apm, presentationUtil, controls, lens, observability, dataVisualizer, fleet, canvas, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, data, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, ml, infra, visTypeTimeseries, apm, presentationUtil, controls, lens, observability, dataVisualizer, fleet, canvas, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover | - | | | data, discover, embeddable | - | | | advancedSettings, discover | - | | | advancedSettings, discover | - | @@ -87,12 +87,12 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | @kbn/core-apps-browser-internal, @kbn/core-metrics-server-internal, @kbn/core-status-server-internal, @kbn/core-usage-data-server-internal, monitoring, kibanaUsageCollection | 8.8.0 | | | savedObjectsTaggingOss, dashboard | 8.8.0 | | | security, licenseManagement, ml, apm, crossClusterReplication, logstash, painlessLab, searchprofiler, watcher | 8.8.0 | +| | apm | 8.8.0 | | | security, fleet | 8.8.0 | | | security, fleet | 8.8.0 | | | security, fleet | 8.8.0 | | | @kbn/core-application-browser-internal, @kbn/core-application-browser-mocks, management, fleet, security, kibanaOverview | 8.8.0 | | | @kbn/core-application-browser-internal, @kbn/core-application-browser-mocks, management, fleet, security, kibanaOverview, core | 8.8.0 | -| | apm | 8.8.0 | | | security | 8.8.0 | | | mapsEms | 8.8.0 | | | @kbn/core-plugins-server, @kbn/core-plugins-server-internal, core | 8.8.0 | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 5d88e55e3d32e..eb573d7b20274 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -583,9 +583,9 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [loader.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.ts#:~:text=title), [lens_top_nav.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title), [loader.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.ts#:~:text=title), [lens_top_nav.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title) | - | -| | [loader.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.ts#:~:text=title), [lens_top_nav.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title), [loader.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.ts#:~:text=title), [lens_top_nav.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title) | - | -| | [loader.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.ts#:~:text=title), [lens_top_nav.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title), [loader.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.test.ts#:~:text=title) | - | +| | [loader.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.ts#:~:text=title), [lens_top_nav.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx#:~:text=title), [loader.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.ts#:~:text=title), [lens_top_nav.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx#:~:text=title) | - | +| | [loader.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.ts#:~:text=title), [lens_top_nav.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx#:~:text=title), [loader.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.ts#:~:text=title), [lens_top_nav.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx#:~:text=title) | - | +| | [loader.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/data_views_service/loader.ts#:~:text=title), [lens_top_nav.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx#:~:text=title) | - | | | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [mounter.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/mounter.tsx#:~:text=onAppLeave) | 8.8.0 | | | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/types.ts#:~:text=onAppLeave), [mounter.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/lens/public/app_plugin/mounter.tsx#:~:text=onAppLeave) | 8.8.0 | @@ -657,9 +657,9 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [new_job_capabilities_service_analytics.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts#:~:text=title), [new_job_capabilities_service_analytics.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts#:~:text=title), [configuration_step_details.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx#:~:text=title), [data_loader.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/datavisualizer/index_based/data_loader/data_loader.ts#:~:text=title), [use_index_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts#:~:text=title), [use_index_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts#:~:text=title), [use_index_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts#:~:text=title)+ 76 more | - | -| | [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [new_job_capabilities_service_analytics.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts#:~:text=title), [new_job_capabilities_service_analytics.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts#:~:text=title), [configuration_step_details.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx#:~:text=title), [data_loader.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/datavisualizer/index_based/data_loader/data_loader.ts#:~:text=title), [use_index_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts#:~:text=title), [use_index_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts#:~:text=title), [use_index_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts#:~:text=title)+ 76 more | - | -| | [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [new_job_capabilities_service_analytics.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts#:~:text=title), [new_job_capabilities_service_analytics.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts#:~:text=title), [configuration_step_details.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx#:~:text=title), [data_loader.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/datavisualizer/index_based/data_loader/data_loader.ts#:~:text=title), [use_index_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts#:~:text=title), [use_index_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts#:~:text=title), [use_index_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts#:~:text=title)+ 33 more | - | +| | [index_patterns.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts#:~:text=title), [rollup.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts#:~:text=title), [alerting_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts#:~:text=title), [data_recognizer.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [new_job_capabilities_service_analytics.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts#:~:text=title), [new_job_capabilities_service_analytics.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts#:~:text=title), [configuration_step_details.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx#:~:text=title)+ 76 more | - | +| | [index_patterns.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts#:~:text=title), [rollup.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts#:~:text=title), [alerting_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts#:~:text=title), [data_recognizer.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [new_job_capabilities_service_analytics.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts#:~:text=title), [new_job_capabilities_service_analytics.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts#:~:text=title), [configuration_step_details.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx#:~:text=title)+ 76 more | - | +| | [index_patterns.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts#:~:text=title), [rollup.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts#:~:text=title), [alerting_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts#:~:text=title), [data_recognizer.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [index_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/util/index_utils.ts#:~:text=title), [new_job_capabilities_service_analytics.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts#:~:text=title), [new_job_capabilities_service_analytics.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts#:~:text=title), [configuration_step_details.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx#:~:text=title)+ 33 more | - | | | [ml_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/components/ml_page/ml_page.tsx#:~:text=KibanaPageTemplate), [ml_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/components/ml_page/ml_page.tsx#:~:text=KibanaPageTemplate), [ml_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/components/ml_page/ml_page.tsx#:~:text=KibanaPageTemplate) | - | | | [ml_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/components/ml_page/ml_page.tsx#:~:text=RedirectAppLinks), [ml_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/components/ml_page/ml_page.tsx#:~:text=RedirectAppLinks), [ml_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/components/ml_page/ml_page.tsx#:~:text=RedirectAppLinks), [jobs_list_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx#:~:text=RedirectAppLinks), [jobs_list_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx#:~:text=RedirectAppLinks), [jobs_list_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx#:~:text=RedirectAppLinks) | - | | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/ml/public/plugin.ts#:~:text=license%24) | 8.8.0 | @@ -976,9 +976,9 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [load_field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/services/field_stats/load_field_stats.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/server/routes/field_stats.ts#:~:text=title), [field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/server/routes/field_stats.ts#:~:text=title), [load_field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/services/field_stats/load_field_stats.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title)+ 2 more | - | -| | [load_field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/services/field_stats/load_field_stats.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/server/routes/field_stats.ts#:~:text=title), [field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/server/routes/field_stats.ts#:~:text=title), [load_field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/services/field_stats/load_field_stats.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title)+ 2 more | - | -| | [load_field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/services/field_stats/load_field_stats.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/server/routes/field_stats.ts#:~:text=title), [field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/server/routes/field_stats.ts#:~:text=title) | - | +| | [load_field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/services/field_stats/load_field_stats.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [use_existing_fields.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/hooks/use_existing_fields.ts#:~:text=title), [use_existing_fields.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/hooks/use_existing_fields.ts#:~:text=title), [field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/server/routes/field_stats.ts#:~:text=title), [field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/server/routes/field_stats.ts#:~:text=title), [load_field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/services/field_stats/load_field_stats.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title)+ 6 more | - | +| | [load_field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/services/field_stats/load_field_stats.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [use_existing_fields.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/hooks/use_existing_fields.ts#:~:text=title), [use_existing_fields.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/hooks/use_existing_fields.ts#:~:text=title), [field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/server/routes/field_stats.ts#:~:text=title), [field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/server/routes/field_stats.ts#:~:text=title), [load_field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/services/field_stats/load_field_stats.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title)+ 6 more | - | +| | [load_field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/services/field_stats/load_field_stats.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [field_existing_utils.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/common/utils/field_existing_utils.ts#:~:text=title), [use_existing_fields.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/hooks/use_existing_fields.ts#:~:text=title), [use_existing_fields.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/public/hooks/use_existing_fields.ts#:~:text=title), [field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/server/routes/field_stats.ts#:~:text=title), [field_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/server/routes/field_stats.ts#:~:text=title) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index dd241a8891f6e..818676792f91c 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index d94756c643010..3eccaa28d481d 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 3c6ba7f1c5b52..aa18ecbfecc3c 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 5bb5cafd12785..93d44e5df3b1b 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index b3a02d8ef2138..5fd3ab983e6c8 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 97393add31a5f..258f0710fafc2 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index eb3082cec84fb..1d34e7432e4a6 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 16f606d78f62b..eb7322768c918 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index c0c8af71d6ed6..6c210de4a3eb7 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index ea069faa07372..9b62890d42a8c 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index e86bd54cf5441..9251a09f4331d 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index d1b60e3ad3e8d..12c514f316fe7 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 75e9f903d313c..038248c69857f 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.devdocs.json b/api_docs/expression_heatmap.devdocs.json index 79c0b1665724a..492d3070eedde 100644 --- a/api_docs/expression_heatmap.devdocs.json +++ b/api_docs/expression_heatmap.devdocs.json @@ -28,7 +28,13 @@ "description": [], "signature": [ "() => ", - "HeatmapExpressionFunctionDefinition" + { + "pluginId": "expressionHeatmap", + "scope": "common", + "docId": "kibExpressionHeatmapPluginApi", + "section": "def-common.HeatmapExpressionFunctionDefinition", + "text": "HeatmapExpressionFunctionDefinition" + } ], "path": "src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_function.ts", "deprecated": false, @@ -570,6 +576,84 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "expressionHeatmap", + "id": "def-common.HeatmapExpressionFunctionDefinition", + "type": "Type", + "tags": [], + "label": "HeatmapExpressionFunctionDefinition", + "description": [], + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionFunctionDefinition", + "text": "ExpressionFunctionDefinition" + }, + "<\"heatmap\", ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.Datatable", + "text": "Datatable" + }, + ", ", + { + "pluginId": "expressionHeatmap", + "scope": "common", + "docId": "kibExpressionHeatmapPluginApi", + "section": "def-common.HeatmapArguments", + "text": "HeatmapArguments" + }, + ", ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueRender", + "text": "ExpressionValueRender" + }, + "<", + { + "pluginId": "expressionHeatmap", + "scope": "common", + "docId": "kibExpressionHeatmapPluginApi", + "section": "def-common.HeatmapExpressionProps", + "text": "HeatmapExpressionProps" + }, + ">, ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + { + "pluginId": "@kbn/utility-types", + "scope": "server", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-server.SerializableRecord", + "text": "SerializableRecord" + }, + ">>" + ], + "path": "src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "expressionHeatmap", "id": "def-common.HeatmapGridConfigResult", @@ -586,6 +670,62 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "expressionHeatmap", + "id": "def-common.HeatmapGridExpressionFunctionDefinition", + "type": "Type", + "tags": [], + "label": "HeatmapGridExpressionFunctionDefinition", + "description": [], + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionFunctionDefinition", + "text": "ExpressionFunctionDefinition" + }, + "<\"heatmap_grid\", null, ", + "HeatmapGridConfig", + ", ", + { + "pluginId": "expressionHeatmap", + "scope": "common", + "docId": "kibExpressionHeatmapPluginApi", + "section": "def-common.HeatmapGridConfigResult", + "text": "HeatmapGridConfigResult" + }, + ", ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + { + "pluginId": "@kbn/utility-types", + "scope": "server", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-server.SerializableRecord", + "text": "SerializableRecord" + }, + ">>" + ], + "path": "src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "expressionHeatmap", "id": "def-common.HeatmapLegendConfigResult", @@ -602,6 +742,62 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "expressionHeatmap", + "id": "def-common.HeatmapLegendExpressionFunctionDefinition", + "type": "Type", + "tags": [], + "label": "HeatmapLegendExpressionFunctionDefinition", + "description": [], + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionFunctionDefinition", + "text": "ExpressionFunctionDefinition" + }, + "<\"heatmap_legend\", null, ", + "HeatmapLegendConfig", + ", ", + { + "pluginId": "expressionHeatmap", + "scope": "common", + "docId": "kibExpressionHeatmapPluginApi", + "section": "def-common.HeatmapLegendConfigResult", + "text": "HeatmapLegendConfigResult" + }, + ", ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + { + "pluginId": "@kbn/utility-types", + "scope": "server", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-server.SerializableRecord", + "text": "SerializableRecord" + }, + ">>" + ], + "path": "src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "expressionHeatmap", "id": "def-common.HeatmapRenderProps", diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index e39189d92034b..0844b3e94a269 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 108 | 14 | 104 | 3 | +| 111 | 14 | 107 | 2 | ## Common diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 06888bb5990e2..d12e3d10d06cc 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.devdocs.json b/api_docs/expression_legacy_metric_vis.devdocs.json index c69921b83f054..7f330c19bfbde 100644 --- a/api_docs/expression_legacy_metric_vis.devdocs.json +++ b/api_docs/expression_legacy_metric_vis.devdocs.json @@ -114,6 +114,21 @@ "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "expressionLegacyMetricVis", + "id": "def-common.MetricArguments.autoScaleMetricAlignment", + "type": "CompoundType", + "tags": [], + "label": "autoScaleMetricAlignment", + "description": [], + "signature": [ + "MetricAlignment", + " | undefined" + ], + "path": "src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "expressionLegacyMetricVis", "id": "def-common.MetricArguments.percentageMode", @@ -413,6 +428,21 @@ "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "expressionLegacyMetricVis", + "id": "def-common.MetricVisParam.autoScaleMetricAlignment", + "type": "CompoundType", + "tags": [], + "label": "autoScaleMetricAlignment", + "description": [], + "signature": [ + "MetricAlignment", + " | undefined" + ], + "path": "src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_renderers.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "expressionLegacyMetricVis", "id": "def-common.MetricVisParam.percentageMode", diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index 0b73ced821826..06725c77e3b9a 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 49 | 0 | 49 | 1 | +| 51 | 0 | 51 | 2 | ## Common diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 4052bfcfe1991..aa5cc4b4c2b33 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index 2a00942f8da1f..82ce89f2e9b72 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index 0af56d6868591..0b5b547c7fa26 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 5822c959cc5bf..2a6c6bc3acc9e 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index 89f2a0ca0717b..e67e8d2f2adf9 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 6333d7795219b..43c6376b76d1a 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index ba05fc5625d14..a4f2b1c54a11b 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 9571e8de7da6a..c8399afdaac75 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 624e92d6930ec..e62db4abe9aba 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.devdocs.json b/api_docs/features.devdocs.json index c7982c8ae51ad..0305784bbed93 100644 --- a/api_docs/features.devdocs.json +++ b/api_docs/features.devdocs.json @@ -352,14 +352,14 @@ "plugin": "security", "path": "x-pack/plugins/security/server/lib/role_utils.ts" }, - { - "plugin": "security", - "path": "x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts" - }, { "plugin": "fleet", "path": "x-pack/plugins/fleet/server/plugin.ts" }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts" @@ -1738,14 +1738,14 @@ "plugin": "security", "path": "x-pack/plugins/security/server/lib/role_utils.ts" }, - { - "plugin": "security", - "path": "x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts" - }, { "plugin": "fleet", "path": "x-pack/plugins/fleet/server/plugin.ts" }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts" @@ -3595,14 +3595,14 @@ "plugin": "security", "path": "x-pack/plugins/security/server/lib/role_utils.ts" }, - { - "plugin": "security", - "path": "x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts" - }, { "plugin": "fleet", "path": "x-pack/plugins/fleet/server/plugin.ts" }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts" diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 638320aa814f2..684232c6bb419 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 582e8bf22caa0..78fc717977236 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 462edf68d2898..7d3ce796501b1 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index fc22a35428955..e9d3976bbf623 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index 4ab611613bfef..9b000a45b2696 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -5371,7 +5371,9 @@ "label": "fetchFindLatestPackage", "description": [], "signature": [ - "(packageName: string) => Promise<", + "(packageName: string, options?: ", + "FetchFindLatestPackageOptions", + " | undefined) => Promise<", { "pluginId": "fleet", "scope": "common", @@ -5407,6 +5409,22 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackageClient.fetchFindLatestPackage.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "FetchFindLatestPackageOptions", + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/epm/package_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [] diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 7e448c72cf351..f30580dda5f04 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Fleet](https://github.com/orgs/elastic/teams/fleet) for questions regar | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 999 | 3 | 896 | 17 | +| 1000 | 3 | 897 | 18 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index bf4839a971908..048aaec803587 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 2ab4d7f8ee5e3..2f9c1dfdac76b 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 06136cd4365c2..a32112e8123c1 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index c80f54dc77abb..510ff24e2c251 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 73d38e1f5ca83..3bbdb688692ea 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 010c8c45f4997..e1c9ff267d7dd 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 0da9d4d1c34a0..b3d3c815e2d2d 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index d83052d85bec5..27942d7369d3d 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 95952e9c2d3f1..4bfcff1578434 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index de14ba43d90ad..cefdbda3681fe 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 5ebf8344d0a79..41604e4bde28d 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index e5aed58ec14a6..d006f31f28b8f 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index 7f792065ed030..94c015954b749 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.devdocs.json b/api_docs/kbn_analytics_client.devdocs.json index 952f802a9c51c..fb7abdc803b1d 100644 --- a/api_docs/kbn_analytics_client.devdocs.json +++ b/api_docs/kbn_analytics_client.devdocs.json @@ -694,6 +694,10 @@ "plugin": "security", "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" + }, { "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/analytics/types.ts" @@ -702,10 +706,6 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/analytics/analytics_service.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" - }, { "plugin": "osquery", "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" @@ -1184,6 +1184,10 @@ "plugin": "cloud", "path": "x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts" }, + { + "plugin": "telemetry", + "path": "src/plugins/telemetry/server/plugin.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/public/analytics/register_user_context.ts" @@ -1192,10 +1196,6 @@ "plugin": "telemetry", "path": "src/plugins/telemetry/public/plugin.ts" }, - { - "plugin": "telemetry", - "path": "src/plugins/telemetry/server/plugin.ts" - }, { "plugin": "@kbn/core-analytics-browser-mocks", "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 766880ee3d246..7295871ca7197 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index b40bf3cf25516..b9071bf78dc00 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 9165fe030a5b3..6be0962a8bec1 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index 5232acee8a742..2a18ed135769b 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 4ce115a2b872d..ef030cc0cf79a 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_gainsight.mdx b/api_docs/kbn_analytics_shippers_gainsight.mdx index 1945154b53c26..03a74158fb62d 100644 --- a/api_docs/kbn_analytics_shippers_gainsight.mdx +++ b/api_docs/kbn_analytics_shippers_gainsight.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-gainsight title: "@kbn/analytics-shippers-gainsight" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-gainsight plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-gainsight'] --- import kbnAnalyticsShippersGainsightObj from './kbn_analytics_shippers_gainsight.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 167d2760e8d0a..ada09206af9f6 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index c0e119389b9bf..8290e7b16cae8 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index ffd42f570c8b5..d36b1fd4b2a1c 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index da7e8a6f0efca..c7ce93dd0cf0c 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 476f2aa86673e..ddd535d9e8c90 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 2281bf38d2c7a..2cd4a2ad70d8b 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index 4fe0139ffcef2..a083756345726 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index 6202bd18fa0f5..0311d32eef9f4 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index cda58f6984e3e..1ddbffaaf9256 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index f5191dc11ca80..14c9634cf1529 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index b613b83babc34..6559237d7c043 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index d5200d17e5d9c..41a9a67465b00 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 97390f677e9c3..f3e0fcd2bb6a6 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index a509e6e43650a..4c5e16cc878b4 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index d4e6c2f3bc48c..1f6a3bfaf876b 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 296b12d13bf6e..4c443f79b5200 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index cef19c4700c6c..780e2e63973b1 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index 50520d77668de..6c12d9f559765 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index aac0cfb74f591..7994ddbe86ab0 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 505b59444fecd..93c2233ce32a3 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index 4c392de1a5c34..f5abc3b0f925b 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index fb9bae1b22c16..623dfb256f7f6 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index 753937573ceb5..f6d9f4b8d08aa 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index 457ed85ff463e..9e617b857cfe8 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 9141e044c07ed..fb70ab512b203 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index 92807451ddf24..06c24e6dec2f3 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index 1225a4754126a..57cde218ed910 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_apps_server_internal.devdocs.json b/api_docs/kbn_core_apps_server_internal.devdocs.json new file mode 100644 index 0000000000000..20c8639b26b2b --- /dev/null +++ b/api_docs/kbn_core_apps_server_internal.devdocs.json @@ -0,0 +1,165 @@ +{ + "id": "@kbn/core-apps-server-internal", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/core-apps-server-internal", + "id": "def-server.registerRouteForBundle", + "type": "Function", + "tags": [], + "label": "registerRouteForBundle", + "description": [], + "signature": [ + "(router: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IRouter", + "text": "IRouter" + }, + "<", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RequestHandlerContextBase", + "text": "RequestHandlerContextBase" + }, + ">, {\n publicPath,\n routePath,\n bundlesPath,\n fileHashCache,\n isDist,\n }: { publicPath: string; routePath: string; bundlesPath: string; fileHashCache: ", + "FileHashCache", + "; isDist: boolean; }) => void" + ], + "path": "packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-apps-server-internal", + "id": "def-server.registerRouteForBundle.$1", + "type": "Object", + "tags": [], + "label": "router", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IRouter", + "text": "IRouter" + }, + "<", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RequestHandlerContextBase", + "text": "RequestHandlerContextBase" + }, + ">" + ], + "path": "packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-apps-server-internal", + "id": "def-server.registerRouteForBundle.$2", + "type": "Object", + "tags": [], + "label": "{\n publicPath,\n routePath,\n bundlesPath,\n fileHashCache,\n isDist,\n }", + "description": [], + "path": "packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-apps-server-internal", + "id": "def-server.registerRouteForBundle.$2.publicPath", + "type": "string", + "tags": [], + "label": "publicPath", + "description": [], + "path": "packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-apps-server-internal", + "id": "def-server.registerRouteForBundle.$2.routePath", + "type": "string", + "tags": [], + "label": "routePath", + "description": [], + "path": "packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-apps-server-internal", + "id": "def-server.registerRouteForBundle.$2.bundlesPath", + "type": "string", + "tags": [], + "label": "bundlesPath", + "description": [], + "path": "packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-apps-server-internal", + "id": "def-server.registerRouteForBundle.$2.fileHashCache", + "type": "Object", + "tags": [], + "label": "fileHashCache", + "description": [], + "signature": [ + "FileHashCache" + ], + "path": "packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-apps-server-internal", + "id": "def-server.registerRouteForBundle.$2.isDist", + "type": "boolean", + "tags": [], + "label": "isDist", + "description": [], + "path": "packages/core/apps/core-apps-server-internal/src/bundle_routes/bundles_route.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_core_apps_server_internal.mdx b/api_docs/kbn_core_apps_server_internal.mdx new file mode 100644 index 0000000000000..2f9e033f81f35 --- /dev/null +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnCoreAppsServerInternalPluginApi +slug: /kibana-dev-docs/api/kbn-core-apps-server-internal +title: "@kbn/core-apps-server-internal" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/core-apps-server-internal plugin +date: 2022-11-03 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-server-internal'] +--- +import kbnCoreAppsServerInternalObj from './kbn_core_apps_server_internal.devdocs.json'; + + + +Contact Kibana Core for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 8 | 0 | 8 | 1 | + +## Server + +### Functions + + diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 70f1aab98ee17..fd21d52c3f49f 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 8d20cf01e07c4..87dbae0e75558 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index 3c844d22be101..a80a78f266cef 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index e09c75f962d7d..4c9a5171e53c5 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index fdea8c4c3a09a..872e7b659bcec 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index 37e09a46d51c5..a8c1a06c9e724 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index 2d7aeea5d9b3b..f243b980ec621 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 29b635d8f4196..6828ca933412f 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index ca0d498985991..ce7ffad8d42b2 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index 271c9cf1cf235..333fe8565485e 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index 3dbac556cc0fa..ae2beb727c431 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 9bd25df3c950a..0fd0d145f9bd0 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index ac0ef00d5e17f..d39b2a261712c 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index f3ee361b6a074..b3fd6253f8010 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index 04b8b09b91be8..d1cceba47468d 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index f1ecabdba0e89..b4f8af3d056d0 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index 9d1dae9d5dfa8..cccd51bf32540 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index e783e87e17ebc..33c846e27475d 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index 7e1dfa6f526b0..68f68551a9ba4 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index a15285b952aea..7c4df83bc1215 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 5dd7a404f129b..0f6af09a74ca5 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index 4ca8054da938d..4f472412bdd8b 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index 4da3b5040bcf2..63744d087509d 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 455acf405e723..d3a648b7fdfd9 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index a36566ab8bf71..e746c5322c337 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index 635d4084f0bf6..92b2d6dde7365 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index 06e1df815b88c..2c5f9df73db81 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index d34c2e7c010d7..d905712e08c31 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index a7115bc7d28fb..8f7ae5491b91d 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index 454483bfbcb5d..d934b7bb79687 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index 9f6aff27e2ca3..bf240bdd9f054 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 6e1eeff6a50e4..d875d5253be7a 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index 6fbaf2ea1d37c..7554063a4cb50 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 7e8fc5a4a9b3a..bb741053c2df8 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index 3db9406ef51ab..ad67db44a5615 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 2b9a091b73fb4..50ee73e86694d 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 5cd10e1427b3f..7cd7156000f04 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index acfd67fd663f4..a38a0fce2de59 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index 9fab11d719d39..76a522d4eca03 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index 795ac1c1cf613..9118df8ff644e 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index 38a386f2d52ef..bab2376399243 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index ee9aa20c6090a..a33ce776e8e5b 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index 8c2e78d8db526..bd055bed28bbc 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index 946fa06e34db4..28fcd57a5f38d 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server.mdx b/api_docs/kbn_core_http_resources_server.mdx index 252165678a06d..52ff60dfaddfc 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server'] --- import kbnCoreHttpResourcesServerObj from './kbn_core_http_resources_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_internal.mdx b/api_docs/kbn_core_http_resources_server_internal.mdx index ede57703e6906..003723baf0962 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-internal'] --- import kbnCoreHttpResourcesServerInternalObj from './kbn_core_http_resources_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_mocks.mdx b/api_docs/kbn_core_http_resources_server_mocks.mdx index ee25e21b59703..76944528962cf 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-mocks'] --- import kbnCoreHttpResourcesServerMocksObj from './kbn_core_http_resources_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index a8abbf3b0cb2a..a072eca8454c9 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 5cd66509c067e..e00cb4d0c9e51 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 1fd9fd7a34ecd..42b0cb168a4bb 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index e27c392179bfd..f3823bc677458 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index 327db7c08d54d..25ee758ebf646 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index 7858aa69f33a6..dfc344ca4f136 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 75e8e212134f0..a7dc446696dad 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index e2909cf12e7ef..0348097dd34a6 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index b835eba9d7ae8..61b58a74d2b60 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index 91cd19ebdc901..cb75ed42018f8 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index 144177ad323a0..4bb70ca9d64a7 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] --- import kbnCoreInjectedMetadataBrowserObj from './kbn_core_injected_metadata_browser.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index 47219273472eb..c4bd5559fb542 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index ddbdad606df37..a80e801e1c56a 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index 904ee9393d593..8d633d69edbd8 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index b1b9f192d2fa6..d89918520e374 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index deca9bcb6dc8d..0e264ea94fd62 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index 64d68b923d0cd..c718afd217eb5 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server'] --- import kbnCoreLifecycleServerObj from './kbn_core_lifecycle_server.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server_mocks.mdx b/api_docs/kbn_core_lifecycle_server_mocks.mdx index f4625c3dc74fd..ded04f346c0e6 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server-mocks'] --- import kbnCoreLifecycleServerMocksObj from './kbn_core_lifecycle_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_browser_mocks.mdx b/api_docs/kbn_core_logging_browser_mocks.mdx index 5c822bb58bd68..4daa57807ebd0 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-browser-mocks'] --- import kbnCoreLoggingBrowserMocksObj from './kbn_core_logging_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_common_internal.mdx b/api_docs/kbn_core_logging_common_internal.mdx index 5c219e5a6909d..80fe581a042e1 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-common-internal'] --- import kbnCoreLoggingCommonInternalObj from './kbn_core_logging_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 090b59a985f09..a1c07c85165bb 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index 61ddd3725dbd8..01f15e5eaeb6a 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index 8e0f0242cbd70..c68b3be5767f0 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index d41195027ff91..2fdeaf591126d 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index 2ef5b2e209091..257f1a6d7b50a 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index 85a6b8a02f7fe..cca5e84f387c1 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index fa2ac048268b8..f243656c5170c 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index 5e1f7c8ac1fb2..ad61b8805ce20 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index a3b9cb8ff40b5..cc67c969aa74e 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 87b96e7fb5954..c70487b056f69 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.devdocs.json b/api_docs/kbn_core_node_server_internal.devdocs.json index 857092dbf36e0..619eb67ae097d 100644 --- a/api_docs/kbn_core_node_server_internal.devdocs.json +++ b/api_docs/kbn_core_node_server_internal.devdocs.json @@ -12,6 +12,42 @@ "classes": [], "functions": [], "interfaces": [ + { + "parentPluginId": "@kbn/core-node-server-internal", + "id": "def-server.InternalNodeServiceStart", + "type": "Interface", + "tags": [], + "label": "InternalNodeServiceStart", + "description": [], + "path": "packages/core/node/core-node-server-internal/src/node_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-node-server-internal", + "id": "def-server.InternalNodeServiceStart.roles", + "type": "Object", + "tags": [], + "label": "roles", + "description": [ + "\nThe Kibana process can take on specialised roles via the `node.roles` config.\n\nThe roles can be used by plugins to adjust their behavior based\non the way the Kibana process has been configured." + ], + "signature": [ + { + "pluginId": "@kbn/core-node-server", + "scope": "server", + "docId": "kibKbnCoreNodeServerPluginApi", + "section": "def-server.NodeRoles", + "text": "NodeRoles" + } + ], + "path": "packages/core/node/core-node-server-internal/src/node_service.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-node-server-internal", "id": "def-server.PrebootDeps", diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index bdf111d7df110..6e977bfa546f4 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 5 | 0 | 5 | 1 | +| 7 | 0 | 6 | 1 | ## Server diff --git a/api_docs/kbn_core_node_server_mocks.devdocs.json b/api_docs/kbn_core_node_server_mocks.devdocs.json index 1a763a4509417..58ff508cc61cd 100644 --- a/api_docs/kbn_core_node_server_mocks.devdocs.json +++ b/api_docs/kbn_core_node_server_mocks.devdocs.json @@ -59,6 +59,45 @@ "trackAdoption": false, "returnComment": [], "children": [] + }, + { + "parentPluginId": "@kbn/core-node-server-mocks", + "id": "def-server.nodeServiceMock.createInternalStartContract", + "type": "Function", + "tags": [], + "label": "createInternalStartContract", + "description": [], + "signature": [ + "({ ui, backgroundTasks, }?: { ui: boolean; backgroundTasks: boolean; }) => jest.Mocked<", + { + "pluginId": "@kbn/core-node-server-internal", + "scope": "server", + "docId": "kibKbnCoreNodeServerInternalPluginApi", + "section": "def-server.InternalNodeServiceStart", + "text": "InternalNodeServiceStart" + }, + ">" + ], + "path": "packages/core/node/core-node-server-mocks/src/node_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/core-node-server-mocks", + "id": "def-server.nodeServiceMock.createInternalStartContract.$1", + "type": "Object", + "tags": [], + "label": "__0", + "description": [], + "signature": [ + "{ ui: boolean; backgroundTasks: boolean; }" + ], + "path": "packages/core/node/core-node-server-mocks/src/node_service.mock.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ], "initialIsOpen": false diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index 3c473f01e101c..eb1dbabe16886 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3 | 0 | 3 | 0 | +| 5 | 0 | 5 | 0 | ## Server diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index f4c8bbd6e3840..b36920c3a7653 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index 90a223d1368c8..511d3b25e9197 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index ed2e67766b3c3..fba28978dd254 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index 42832b3429210..937c446f32083 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index c4845b99ae9a4..2ab8aeaaa4407 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 46f0268668f70..34f7e00350084 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 2ecce761680ec..aa6a513394639 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index cdb0140f9350c..2e00eb9e6a013 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index 57934b2ddb533..66fb1fcf765d3 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] --- import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx index f6d9f25eb1e40..9a89a05323a30 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server-mocks'] --- import kbnCorePluginsServerMocksObj from './kbn_core_plugins_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 7792f55cfda9b..b9508fd323f05 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 0357ed0242c16..54d52da2cf241 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index 3268933adab76..485dfbf9ab419 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx index 895fd760bbd9e..0f27b3a4760df 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] --- import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx index 165616c4502e8..51f8a59803f9e 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 1f9adb63b5783..aa11edf4360a2 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index a623b48be84be..cef9bedd2cf94 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index 5cce42286ffcb..ac6cfd8104896 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index a1f2405c7edf5..7e6eb3ec7e296 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index d61caa7e4e61c..03a440f60ec5c 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index 60abd6ff785cc..7028bf5e77a13 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index 1bca17afdaeb9..eed0d7d39f146 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index 9ca99660f01e7..7d11724080b87 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 9c9331d6e4052..fca87cd64e0de 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.devdocs.json b/api_docs/kbn_core_saved_objects_common.devdocs.json index 094d17f6639a0..23f18d9d755b5 100644 --- a/api_docs/kbn_core_saved_objects_common.devdocs.json +++ b/api_docs/kbn_core_saved_objects_common.devdocs.json @@ -800,6 +800,14 @@ "plugin": "core", "path": "src/core/types/index.ts" }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/common/types/modules.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/common/types/modules.ts" + }, { "plugin": "visualizations", "path": "src/plugins/visualizations/common/types.ts" @@ -840,14 +848,6 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/common/types/modules.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/common/types/modules.ts" - }, { "plugin": "core", "path": "src/core/server/types.ts" diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 3c2c7f24e4d9a..1a371683da5cc 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index a3cbb364cff40..66b97c8b6fcac 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index 635cf9e2d7e08..f5f702644b6be 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json b/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json index a5ff80fa83667..e041d67290f46 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json @@ -66,7 +66,7 @@ "id": "def-server.KibanaMigrator.Unnamed.$1", "type": "Object", "tags": [], - "label": "{\n client,\n typeRegistry,\n kibanaIndex,\n soMigrationsConfig,\n kibanaVersion,\n logger,\n docLinks,\n }", + "label": "{\n client,\n typeRegistry,\n kibanaIndex,\n soMigrationsConfig,\n kibanaVersion,\n logger,\n docLinks,\n waitForMigrationCompletion,\n }", "description": [], "signature": [ { @@ -3130,6 +3130,17 @@ "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-saved-objects-migration-server-internal", + "id": "def-server.KibanaMigratorOptions.waitForMigrationCompletion", + "type": "boolean", + "tags": [], + "label": "waitForMigrationCompletion", + "description": [], + "path": "packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index 35c88f23905c8..456f52f1ccc56 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 100 | 0 | 74 | 43 | +| 101 | 0 | 75 | 43 | ## Server diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index 8ca90c8ed4b3f..4dabda6f1bdfe 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index b4c8420dc58e8..6ec1e83b817a8 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.devdocs.json b/api_docs/kbn_core_saved_objects_server_internal.devdocs.json index 9dbb795e3e83c..7449f9750e918 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.devdocs.json +++ b/api_docs/kbn_core_saved_objects_server_internal.devdocs.json @@ -119,7 +119,7 @@ "label": "start", "description": [], "signature": [ - "({ elasticsearch, pluginsInitialized, docLinks, }: ", + "({ elasticsearch, pluginsInitialized, docLinks, node, }: ", "SavedObjectsStartDeps", ") => Promise<", { @@ -140,7 +140,7 @@ "id": "def-server.SavedObjectsService.start.$1", "type": "Object", "tags": [], - "label": "{\n elasticsearch,\n pluginsInitialized = true,\n docLinks,\n }", + "label": "{\n elasticsearch,\n pluginsInitialized = true,\n docLinks,\n node,\n }", "description": [], "signature": [ "SavedObjectsStartDeps" diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 83866f0569e4c..914dc4459a0cd 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index 6c3b32b7c4ffb..2884262be26d7 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index cbbbc65f3d57d..6588a9fd252b6 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 2b7c3b0e27295..c9fb44d77cddc 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index f3452e72880f3..2fddd2801e3f3 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index 8c2456d7c6793..f89bc8d977a58 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 4c0c53706e32b..3cb0b73fa179d 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index 7ccd7192c9feb..1fb9f5e772953 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index f2fd45cb9b5be..58a0e75f8d109 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 14a608f7996e0..6c319eeb2a8b0 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index d94ccebe2ba52..bde5ce63233e9 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-so-type-serializer'] --- import kbnCoreTestHelpersSoTypeSerializerObj from './kbn_core_test_helpers_so_type_serializer.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_test_utils.mdx b/api_docs/kbn_core_test_helpers_test_utils.mdx index 36a4bda87c8c6..187f1a45dfd8e 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-test-utils'] --- import kbnCoreTestHelpersTestUtilsObj from './kbn_core_test_helpers_test_utils.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 032e355ea64a1..9878491edc07b 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index 51c579e355158..908d5228ca595 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 1ce0f05e6b33c..0ef580f50db61 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index ca650cd0a8799..9f8c994b5290d 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index 5adf0b86a9d41..c0abf7f7707b0 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 2561eecfafd85..752659a90ebd5 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index f3cedc0acc0d8..bcaa05f381695 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 9e21d0b8ea1d0..82dbb79cc0e85 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index fed838781520b..0e8a45969c6dc 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index 3d59f98d37ab7..403a2f8e52bcc 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index b44d1cdc008a4..f5bb8dd8510bd 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index 36f7e6bf90b53..6c631a99e4bd0 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index 61c84ad936c43..aeaf777a777d3 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index b802f33992bb0..2617374bccad3 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index 221307c83cb82..fa6b09adc14b7 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 84025a50d1c74..564b5e8ed82f5 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index c287908c3c906..932b13eea4ea9 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 41f98c754b20b..4f1735d40ab4e 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index da5b0be2065d6..175292a832693 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index 0f60ff10b7681..ebc21ca0d3547 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 6ccf2090313f2..7da2cb5f75081 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index 55e84ff674dab..94d7c9f7536d7 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 02f561f2d7631..2ef5daaf2f4d0 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index a5b7da29069ad..8fcc7db656094 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 00341a825dfaa..eccdea020c017 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index 5f85c2565f7c4..e941bf44f19c4 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 2ace595cf94f2..5743a11f21ddf 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 0d864876ecc3c..3a5d9b06d5ad7 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 8b7473c67000a..1bdc8b6a3e58c 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 37440ce4eec7a..9ffc3daf34dd6 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 4ec668bbf8d1e..d861898bc30ef 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index b0dc5f6522a5a..92296916487ac 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 5585017f12a97..6e98224c55442 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_get_repo_files.mdx b/api_docs/kbn_get_repo_files.mdx index 0603218f2a8b5..ce429eb8b64ce 100644 --- a/api_docs/kbn_get_repo_files.mdx +++ b/api_docs/kbn_get_repo_files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-get-repo-files title: "@kbn/get-repo-files" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/get-repo-files plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.devdocs.json b/api_docs/kbn_guided_onboarding.devdocs.json index 408f531d6798c..5e713efb8ace7 100644 --- a/api_docs/kbn_guided_onboarding.devdocs.json +++ b/api_docs/kbn_guided_onboarding.devdocs.json @@ -296,7 +296,7 @@ "label": "id", "description": [], "signature": [ - "\"add_data\" | \"view_dashboard\" | \"tour_observability\" | \"rules\" | \"alertsCases\" | \"browse_docs\" | \"search_experience\" | \"step1\" | \"step2\" | \"step3\"" + "\"add_data\" | \"view_dashboard\" | \"tour_observability\" | \"rules\" | \"alertsCases\" | \"search_experience\" | \"step1\" | \"step2\" | \"step3\"" ], "path": "packages/kbn-guided-onboarding/src/types.ts", "deprecated": false, @@ -337,6 +337,23 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.GuideStatus", + "type": "Type", + "tags": [], + "label": "GuideStatus", + "description": [ + "\nAllowed states for a guide:\n not_started: Guide has not been started\n in_progress: At least one step in the guide has been started\n ready_to_complete: All steps have been completed, but the \"Continue using Elastic\" button has not been clicked\n complete: All steps and the guide have been completed" + ], + "signature": [ + "\"complete\" | \"not_started\" | \"in_progress\" | \"ready_to_complete\"" + ], + "path": "packages/kbn-guided-onboarding/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/guided-onboarding", "id": "def-common.GuideStepIds", @@ -345,7 +362,7 @@ "label": "GuideStepIds", "description": [], "signature": [ - "\"add_data\" | \"view_dashboard\" | \"tour_observability\" | \"rules\" | \"alertsCases\" | \"browse_docs\" | \"search_experience\" | \"step1\" | \"step2\" | \"step3\"" + "\"add_data\" | \"view_dashboard\" | \"tour_observability\" | \"rules\" | \"alertsCases\" | \"search_experience\" | \"step1\" | \"step2\" | \"step3\"" ], "path": "packages/kbn-guided-onboarding/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index f2a22cdb44ab0..0aa31bf438def 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 22 | 0 | 21 | 1 | +| 23 | 0 | 21 | 1 | ## Common diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 7ca6bb3f58f4a..dc05bc2ced4b5 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 51b46ec8f1de1..3df3e8485fce9 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 7e2d9a03595cf..12024c463b405 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index 111a7b659bf81..6d0bb66f99cd9 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 13f62b0e76519..f249672864a0e 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_i18n_react.mdx b/api_docs/kbn_i18n_react.mdx index 0f2778a82f018..ea94ff42d9076 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n-react'] --- import kbnI18nReactObj from './kbn_i18n_react.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index e277b84e69f95..90fba8660ebdf 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 33211d1739b13..e3c797fbab46b 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 8c47000eb3a8c..39b1fc5311cc3 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index d1c2a25bacfd6..065823d9e9644 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index 46dd4b7056c8e..09e3d0971ab93 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 4880e05024194..57f9fc396e597 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_language_documentation_popover.mdx b/api_docs/kbn_language_documentation_popover.mdx index ee284472abac4..5d3db245585c2 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation-popover'] --- import kbnLanguageDocumentationPopoverObj from './kbn_language_documentation_popover.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 6065921d5a302..23c6c1cd2263c 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index e2eec42393837..4f5150c82ef25 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 74e4f15b9d050..9006c6eb7e893 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 031b38efd6f76..f62ce92ea9ca7 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 3da3a8c938a73..07dc1327976ee 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 29677c17e3b27..47431b91ad968 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index afe16255f91e1..e7cfafa592ae0 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index d85ec0b7d4204..09eb21cde5b97 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index e4d8797ce82f5..afd7005408cf8 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index ac8467a850fae..546be539cfa0f 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 9a7e5ea33e81b..6f75eb70e0277 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 9e3d5150080dc..b7b19661b2cf9 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 5bd8abb6f6479..a8676e99d4294 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index 4e8016f41e53a..09677f9c6b65d 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index 9f3690ad29f27..4e080c19295dc 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 64cd086abca61..d01f493861762 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 022c5ecfb17e9..e6b923f480260 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 38d8ec0e01fe0..793a249d94c81 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 65b8f48a21dd6..4d65817a6674a 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 18ac96d992c14..f10cbd6b5da35 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index e02b0d0a67cac..6dc50be6e8fb9 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 18de786c20315..0623581817fbe 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 3f2d31848d80f..791921a3b2fa3 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.devdocs.json b/api_docs/kbn_securitysolution_io_ts_types.devdocs.json index ef934492d28ef..8a14e911325af 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.devdocs.json +++ b/api_docs/kbn_securitysolution_io_ts_types.devdocs.json @@ -514,7 +514,7 @@ "label": "ImportQuerySchema", "description": [], "signature": [ - "{ overwrite?: boolean | undefined; overwrite_exceptions?: boolean | undefined; }" + "{ overwrite?: boolean | undefined; overwrite_exceptions?: boolean | undefined; as_new_list?: boolean | undefined; }" ], "path": "packages/kbn-securitysolution-io-ts-types/src/import_query_schema/index.ts", "deprecated": false, @@ -529,7 +529,7 @@ "label": "ImportQuerySchemaDecoded", "description": [], "signature": [ - "Omit<{ overwrite?: boolean | undefined; overwrite_exceptions?: boolean | undefined; }, \"overwrite\" | \"overwrite_exceptions\"> & { overwrite: boolean; overwrite_exceptions: boolean; }" + "Omit<{ overwrite?: boolean | undefined; overwrite_exceptions?: boolean | undefined; as_new_list?: boolean | undefined; }, \"overwrite\" | \"overwrite_exceptions\" | \"as_new_list\"> & { overwrite: boolean; overwrite_exceptions: boolean; as_new_list: boolean; }" ], "path": "packages/kbn-securitysolution-io-ts-types/src/import_query_schema/index.ts", "deprecated": false, @@ -912,6 +912,8 @@ "Type", "; overwrite_exceptions: ", "Type", + "; as_new_list: ", + "Type", "; }>>" ], "path": "packages/kbn-securitysolution-io-ts-types/src/import_query_schema/index.ts", diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index daffd458535f9..0bd93ca61eac2 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index bb5524f52f1e6..f1247503fc91e 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index b6d2b2ff50623..5b7acd2f00534 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index bd50d1bb08f2a..4bfb0dfd8093f 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index c2673ad1c20ad..1c4f5e60fa838 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 3754b02e09eb1..aca2121cc54ee 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index e84ceba0c74e2..1ac856e5e506b 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index 088be7f384a6a..fa290646271f5 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 87eda2aefa80c..9ee1289f2552a 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 949a80a1daf36..44222c6294693 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index b24fa399ea03c..ff6532bbdc9e0 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 80ed4944edf95..a1a24268fc15f 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index 616843e7fc739..ef6911aec3b73 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-solution'] --- import kbnSharedUxAvatarSolutionObj from './kbn_shared_ux_avatar_solution.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index 7b5942840740e..905ed473f7c98 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index d2e15a8f4d5d6..60ab35bff1665 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen'] --- import kbnSharedUxButtonExitFullScreenObj from './kbn_shared_ux_button_exit_full_screen.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index f72ed83bd3509..97d6137bca9ed 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index aee5367e1cb17..f19aa0d197cdc 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 11d36200d01a2..007de1e1fd8b8 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index d51956ad7bee1..25b1b57d0fd9c 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app.mdx b/api_docs/kbn_shared_ux_link_redirect_app.mdx index 78ed4841b4f7f..70d9b703add44 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app'] --- import kbnSharedUxLinkRedirectAppObj from './kbn_shared_ux_link_redirect_app.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 57c4b6e4ae934..69926b3eae2e3 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown.mdx b/api_docs/kbn_shared_ux_markdown.mdx index 10ec32d2099a6..1fa7b11407c37 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown'] --- import kbnSharedUxMarkdownObj from './kbn_shared_ux_markdown.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown_mocks.mdx b/api_docs/kbn_shared_ux_markdown_mocks.mdx index ed0d7e21980be..08c8baa9c6c84 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown-mocks'] --- import kbnSharedUxMarkdownMocksObj from './kbn_shared_ux_markdown_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 98c783b4252e1..7c94ef35c4278 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index af6d0a52cc402..8523c994a8d49 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index 6f463aa6d5434..f2305434606e4 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index e948b6be920c2..a3b9cc9894d63 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index 69672643f308b..357a198f95c61 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index cc8b75129677e..bb170fe07ac4c 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index c8f92d6a78035..b885897726fa6 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index a742104eca3e8..f07e4efa4b812 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index f778a95ade353..de2e1fa97b4fd 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index 07934b29c2ef8..248a97c7d1c81 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index be6fa825778c8..825eee67874ed 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index d1bff331a6c51..90e4d90459a76 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index 81c0603dbedf7..a681271b2c435 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index c0f2e10672f04..97116d112b55a 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index 8098d1cb4781a..b2b19a1caebfa 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index 999f5e6729b81..a509c914f3353 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index 12ca85a447435..8be42b6dd64b4 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 2c42de415757d..97eaab20edc04 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index e06d59275a656..f5975faa298a3 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index 9a61932038f5f..c75725f555600 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-package-json plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] --- import kbnSortPackageJsonObj from './kbn_sort_package_json.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 0526e7dc1e575..7b382d339f228 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index 7c8a214d81c65..24a000f94beb5 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index a42169ca4a8d5..fad4ed3b27081 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 33fa4899c6c5c..0e363e370570f 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 07b2852efaea5..c5cbe8e6ba447 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 40878db2b1acb..470e108023f3e 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index b718ee2a0b11e..9f49f13e4e438 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 788004d27bae0..dbd461a7f2973 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index 7761b81bdf24b..7c099ced6fbd1 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] --- import kbnTypeSummarizerObj from './kbn_type_summarizer.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx index a2c4bd2e7cd86..5ade197f7c552 100644 --- a/api_docs/kbn_type_summarizer_core.mdx +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer-core title: "@kbn/type-summarizer-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer-core plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] --- import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index c2786b0e9c713..c2d182c4e1952 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index 2ae7c6c2626dc..cbffe401bb099 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index ba6993957e63f..78eebf1992c48 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 27d13e7ecff24..577466bf033a6 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index d73762312d067..2fb001354e0c0 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index 476c33f908492..698d5fc4f897b 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index e4fe008027db0..dfdf80678aa9c 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 3641c2efc4faa..61ec7ae48f9e3 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index a8ef8e516c114..710079b304ac2 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 8aa83e00d9fbe..41de441b14cb2 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index 37b1b88208861..a8bcbbe417a1f 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index 68aefc3e24955..6d912f3dd27d3 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.devdocs.json b/api_docs/lens.devdocs.json index 91b4360bb98c9..596675ec4abb6 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -2554,6 +2554,20 @@ "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "lens", + "id": "def-public.LegacyMetricState.autoScaleMetricAlignment", + "type": "CompoundType", + "tags": [], + "label": "autoScaleMetricAlignment", + "description": [], + "signature": [ + "\"left\" | \"right\" | \"center\" | undefined" + ], + "path": "x-pack/plugins/lens/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "lens", "id": "def-public.LegacyMetricState.layerId", @@ -8857,7 +8871,7 @@ "section": "def-public.Action", "text": "Action" }, - "[] | undefined; }" + "[] | undefined; showInspector?: boolean | undefined; }" ], "path": "x-pack/plugins/lens/public/embeddable/embeddable_component.tsx", "deprecated": false, @@ -11522,10 +11536,10 @@ }, { "parentPluginId": "lens", - "id": "def-common.ExistingFields", + "id": "def-common.LegacyMetricState", "type": "Interface", "tags": [], - "label": "ExistingFields", + "label": "LegacyMetricState", "description": [], "path": "x-pack/plugins/lens/common/types.ts", "deprecated": false, @@ -11533,43 +11547,18 @@ "children": [ { "parentPluginId": "lens", - "id": "def-common.ExistingFields.indexPatternTitle", - "type": "string", - "tags": [], - "label": "indexPatternTitle", - "description": [], - "path": "x-pack/plugins/lens/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "lens", - "id": "def-common.ExistingFields.existingFieldNames", - "type": "Array", + "id": "def-common.LegacyMetricState.autoScaleMetricAlignment", + "type": "CompoundType", "tags": [], - "label": "existingFieldNames", + "label": "autoScaleMetricAlignment", "description": [], "signature": [ - "string[]" + "\"left\" | \"right\" | \"center\" | undefined" ], "path": "x-pack/plugins/lens/common/types.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "lens", - "id": "def-common.LegacyMetricState", - "type": "Interface", - "tags": [], - "label": "LegacyMetricState", - "description": [], - "path": "x-pack/plugins/lens/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + }, { "parentPluginId": "lens", "id": "def-common.LegacyMetricState.layerId", diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 0efa5c24f2abd..dcf014e8c95f1 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 676 | 0 | 583 | 48 | +| 675 | 0 | 582 | 48 | ## Client diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 7a9fa00f72bd6..b8dddff891acc 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index dcf5b6520a692..6c46e65dbdda0 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index f317b23f34330..13842df636289 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.devdocs.json b/api_docs/lists.devdocs.json index a7536c98ad1ab..ea176fe9e1bf9 100644 --- a/api_docs/lists.devdocs.json +++ b/api_docs/lists.devdocs.json @@ -1372,7 +1372,7 @@ "\nImport exception lists parent containers and items as stream" ], "signature": [ - "({ exceptionsToImport, maxExceptionsImportSize, overwrite, }: ", + "({ exceptionsToImport, maxExceptionsImportSize, overwrite, generateNewListId, }: ", "ImportExceptionListAndItemsOptions", ") => Promise<{ errors: ({ error: { status_code: number; message: string; }; } & { id?: string | undefined; list_id?: string | undefined; item_id?: string | undefined; })[]; success: boolean; success_count: number; success_exception_lists: boolean; success_count_exception_lists: number; success_exception_list_items: boolean; success_count_exception_list_items: number; }>" ], @@ -1385,7 +1385,7 @@ "id": "def-server.ExceptionListClient.importExceptionListAndItems.$1", "type": "Object", "tags": [], - "label": "{\n exceptionsToImport,\n maxExceptionsImportSize,\n overwrite,\n }", + "label": "{\n exceptionsToImport,\n maxExceptionsImportSize,\n overwrite,\n generateNewListId,\n }", "description": [], "signature": [ "ImportExceptionListAndItemsOptions" diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 02101b2f7144e..500276696f06c 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 9fbf4d558e24d..78bd8739c2c11 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 5273ea05ba706..54b3ff5152633 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 8c77516187246..74fd473915c49 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 8e79e9cf50f77..5507b92045e15 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 2cca54d39b84d..784d80d7b1d19 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 8dbb317227744..8e68980d1a6fb 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index 2e5a89083d1e5..ceeada3f14bcf 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 26279c90ab3d7..1e216762b0084 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index df3bdc250f202..17f16cddf2916 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -129,7 +129,7 @@ "label": "reportType", "description": [], "signature": [ - "\"data-distribution\" | \"kpi-over-time\" | \"core-web-vitals\" | \"device-data-distribution\" | \"single-metric\"" + "\"heatmap\" | \"data-distribution\" | \"kpi-over-time\" | \"core-web-vitals\" | \"device-data-distribution\" | \"single-metric\"" ], "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/exploratory_view_url.ts", "deprecated": false, @@ -2306,7 +2306,7 @@ "label": "reportType", "description": [], "signature": [ - "\"data-distribution\" | \"kpi-over-time\" | \"core-web-vitals\" | \"device-data-distribution\" | \"single-metric\"" + "\"heatmap\" | \"data-distribution\" | \"kpi-over-time\" | \"core-web-vitals\" | \"device-data-distribution\" | \"single-metric\"" ], "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/embeddable.tsx", "deprecated": false, @@ -3778,6 +3778,26 @@ "path": "x-pack/plugins/observability/public/plugin.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityPublicPluginsStart.unifiedSearch", + "type": "Object", + "tags": [], + "label": "unifiedSearch", + "description": [], + "signature": [ + { + "pluginId": "unifiedSearch", + "scope": "public", + "docId": "kibUnifiedSearchPluginApi", + "section": "def-public.UnifiedSearchPublicPluginStart", + "text": "UnifiedSearchPublicPluginStart" + } + ], + "path": "x-pack/plugins/observability/public/plugin.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 88b6f5e3c9a06..b75b7d428fbec 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Observability UI](https://github.com/orgs/elastic/teams/observability-u | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 555 | 40 | 552 | 31 | +| 556 | 40 | 553 | 31 | ## Client diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 8d8ef3664f840..b30a8f54cd56f 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index ac8ddcce501f9..ac3b928e4a481 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,13 +15,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
        public API | Number of teams | |--------------|----------|------------------------| -| 506 | 424 | 38 | +| 507 | 425 | 38 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 33147 | 514 | 23480 | 1096 | +| 33241 | 514 | 23572 | 1102 | ## Plugin Directory @@ -31,7 +31,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 36 | 1 | 32 | 2 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 9 | 0 | 0 | 2 | | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 383 | 0 | 374 | 26 | -| | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 38 | 0 | 38 | 56 | +| | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 41 | 0 | 41 | 58 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 9 | 0 | 9 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 81 | 1 | 72 | 2 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Canvas application to Kibana | 9 | 0 | 8 | 3 | @@ -69,9 +69,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 115 | 0 | 115 | 11 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'error' renderer to expressions | 17 | 0 | 15 | 2 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression Gauge plugin adds a `gauge` renderer and function to the expression plugin. The renderer will display the `gauge` chart. | 58 | 0 | 58 | 2 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression Heatmap plugin adds a `heatmap` renderer and function to the expression plugin. The renderer will display the `heatmap` chart. | 108 | 14 | 104 | 3 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression Heatmap plugin adds a `heatmap` renderer and function to the expression plugin. The renderer will display the `heatmap` chart. | 111 | 14 | 107 | 2 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'image' function and renderer to expressions | 26 | 0 | 26 | 0 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Adds a `metric` renderer and function to the expression plugin. The renderer will display the `legacy metric` chart. | 49 | 0 | 49 | 1 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Adds a `metric` renderer and function to the expression plugin. The renderer will display the `legacy metric` chart. | 51 | 0 | 51 | 2 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'metric' function and renderer to expressions | 32 | 0 | 27 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Adds a `metric` renderer and function to the expression plugin. The renderer will display the `metric` chart. | 62 | 0 | 62 | 1 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression Partition Visualization plugin adds a `partitionVis` renderer and `pieVis`, `mosaicVis`, `treemapVis`, `waffleVis` functions to the expression plugin. The renderer will display the `pie`, `waffle`, `treemap` and `mosaic` charts. | 70 | 0 | 70 | 2 | @@ -85,7 +85,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Index pattern fields and ambiguous values formatters | 288 | 26 | 249 | 3 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 62 | 0 | 62 | 2 | | | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 275 | 0 | 19 | 3 | -| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 999 | 3 | 896 | 17 | +| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1000 | 3 | 897 | 18 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | globalSearchProviders | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | @@ -105,7 +105,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | kibanaUsageCollection | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 0 | 0 | 0 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 615 | 3 | 420 | 9 | | | [Security Team](https://github.com/orgs/elastic/teams/security-team) | - | 3 | 0 | 3 | 1 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 676 | 0 | 583 | 48 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 675 | 0 | 582 | 48 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 8 | 0 | 8 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 3 | 0 | 3 | 0 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | @@ -119,7 +119,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Stack Monitoring](https://github.com/orgs/elastic/teams/stack-monitoring-ui) | - | 9 | 0 | 9 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 34 | 0 | 34 | 2 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | -| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 555 | 40 | 552 | 31 | +| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 556 | 40 | 553 | 31 | | | [Security asset management](https://github.com/orgs/elastic/teams/security-asset-management) | - | 21 | 0 | 21 | 3 | | painlessLab | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). | 243 | 8 | 187 | 12 | @@ -159,7 +159,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 518 | 11 | 489 | 49 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds UI Actions service to Kibana | 133 | 2 | 92 | 11 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends UI Actions plugin with more functionality | 206 | 0 | 142 | 9 | -| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list which can be integrated into apps | 122 | 0 | 117 | 2 | +| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list which can be integrated into apps | 192 | 0 | 187 | 4 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | The `unifiedHistogram` plugin provides UI components to create a layout including a resizable histogram and a main display. | 56 | 0 | 29 | 0 | | | [Unified Search](https://github.com/orgs/elastic/teams/kibana-app-services) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 131 | 2 | 104 | 18 | | upgradeAssistant | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | @@ -180,7 +180,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Registers the vega visualization. Is the elastic version of vega and vega-lite libraries. | 2 | 0 | 2 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and heatmap charts. We want to replace them with elastic-charts. | 26 | 0 | 25 | 1 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the new xy-axis chart using the elastic-charts library, which will eventually replace the vislib xy-axis charts including bar, area, and line. | 53 | 0 | 50 | 5 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 797 | 12 | 767 | 18 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 798 | 12 | 768 | 18 | | watcher | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | ## Package Directory @@ -225,6 +225,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 8 | 0 | 1 | 0 | | | Kibana Core | - | 20 | 0 | 19 | 0 | | | Kibana Core | - | 2 | 0 | 2 | 0 | +| | Kibana Core | - | 8 | 0 | 8 | 1 | | | Kibana Core | - | 3 | 0 | 3 | 0 | | | Kibana Core | - | 12 | 0 | 3 | 0 | | | Kibana Core | - | 7 | 0 | 7 | 2 | @@ -302,8 +303,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 19 | 0 | 19 | 0 | | | Kibana Core | - | 6 | 0 | 0 | 0 | | | Kibana Core | - | 5 | 0 | 0 | 0 | -| | Kibana Core | - | 5 | 0 | 5 | 1 | -| | Kibana Core | - | 3 | 0 | 3 | 0 | +| | Kibana Core | - | 7 | 0 | 6 | 1 | +| | Kibana Core | - | 5 | 0 | 5 | 0 | | | Kibana Core | - | 35 | 4 | 23 | 0 | | | Kibana Core | - | 32 | 0 | 11 | 2 | | | Kibana Core | - | 4 | 0 | 4 | 0 | @@ -331,7 +332,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 83 | 0 | 41 | 0 | | | Kibana Core | - | 25 | 0 | 23 | 0 | | | Kibana Core | - | 4 | 0 | 4 | 0 | -| | Kibana Core | - | 100 | 0 | 74 | 43 | +| | Kibana Core | - | 101 | 0 | 75 | 43 | | | Kibana Core | - | 12 | 0 | 12 | 0 | | | Kibana Core | - | 226 | 0 | 83 | 0 | | | Kibana Core | - | 69 | 0 | 69 | 4 | @@ -380,7 +381,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 29 | 0 | 29 | 1 | | | [Owner missing] | - | 1 | 0 | 0 | 0 | | | [Owner missing] | - | 3 | 0 | 0 | 0 | -| | [Owner missing] | - | 22 | 0 | 21 | 1 | +| | [Owner missing] | - | 23 | 0 | 21 | 1 | | | [Owner missing] | - | 6 | 0 | 0 | 0 | | | [Owner missing] | - | 3 | 0 | 3 | 0 | | | [Owner missing] | - | 32 | 0 | 22 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 058a94c5a3f93..a98f465692920 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 298e722ccbe63..d4d668ead8192 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 0865ae33d3309..ede1b6feb6e18 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index 76e7b560c9a96..9495dcca0124f 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index db89e2660498e..110d44a51ef75 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index e5fe1948f4823..a1660aa9abe9e 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 479bd3fce3cef..dccebb4795730 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index c224ed4d90063..8c6738ee3c7da 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 48bde91f38227..058858737eee7 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 03713793cdf3b..12e1390ec718a 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 44168f7c0c43e..b7c52fcbfae39 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index 2108d67e0f989..d6dbfe2f6f7a6 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index 8ec1ea62bb80a..6d2b4637af95b 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index 8589ded53271c..f7c4ca4218cfc 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index ba55504bcc6d7..56ec697de0c72 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index ced32610c26b2..09be20c329d7d 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index bd7e9e0b4fd8b..1d97d865a33e6 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index e9ec0ef165f1f..6b8539eca6d44 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index b5ddb63e46224..b2b08a6ea0b29 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 7021d9bccf9b3..e792894a664e3 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 6b8e2801b2e25..197746b59628d 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 438e34717e709..db8248b4cbf7b 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 928e3ee04199c..d108918642497 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index a4d1bbedffd5e..a1c53f2292eac 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 64369e049dc76..a4376a1f864a9 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 55f08585d969d..62f1c72d7055c 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 68091e39086ac..a8eb63367994e 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 505ebfebf30b4..9a9158c4cdccc 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index c7eddbea50f9e..4dd6d24e1589d 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index c6686cac360c3..e41d21340b255 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index f081b363ea8ce..2a00d9e90f08d 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 4837a3168eb84..e2dd6b3aeda72 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index dade66136c791..700d352d35d3a 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index b40cd03bb80ba..5bdb0e0ad5251 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.devdocs.json b/api_docs/unified_field_list.devdocs.json index 59cba2f7f458a..2434cfac2a6d7 100644 --- a/api_docs/unified_field_list.devdocs.json +++ b/api_docs/unified_field_list.devdocs.json @@ -3,6 +3,63 @@ "client": { "classes": [], "functions": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldListGrouped", + "type": "Function", + "tags": [], + "label": "FieldListGrouped", + "description": [], + "signature": [ + "(props: ", + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.FieldListGroupedProps", + "text": "FieldListGroupedProps" + }, + ") => JSX.Element" + ], + "path": "src/plugins/unified_field_list/public/components/field_list/index.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldListGrouped.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.FieldListGroupedProps", + "text": "FieldListGroupedProps" + }, + "" + ], + "path": "src/plugins/unified_field_list/public/components/field_list/index.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "unifiedFieldList", "id": "def-public.FieldPopover", @@ -427,9 +484,9 @@ "label": "loadFieldExisting", "description": [], "signature": [ - "({\n data,\n dslQuery,\n fromDate,\n toDate,\n timeFieldName,\n dataViewsService,\n uiSettingsClient,\n dataView,\n}: FetchFieldExistenceParams) => Promise<{ indexPatternTitle: string; existingFieldNames: string[]; }>" + "(params: FetchFieldExistenceParams) => Promise<{ existingFieldNames: string[]; indexPatternTitle: string; }>" ], - "path": "src/plugins/unified_field_list/public/services/field_existing/load_field_existing.ts", + "path": "src/plugins/unified_field_list/public/services/field_existing/index.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -438,12 +495,12 @@ "id": "def-public.loadFieldExisting.$1", "type": "Object", "tags": [], - "label": "{\n data,\n dslQuery,\n fromDate,\n toDate,\n timeFieldName,\n dataViewsService,\n uiSettingsClient,\n dataView,\n}", + "label": "params", "description": [], "signature": [ "FetchFieldExistenceParams" ], - "path": "src/plugins/unified_field_list/public/services/field_existing/load_field_existing.ts", + "path": "src/plugins/unified_field_list/public/services/field_existing/index.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -487,6 +544,23 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.resetExistingFieldsCache", + "type": "Function", + "tags": [], + "label": "resetExistingFieldsCache", + "description": [], + "signature": [ + "() => void" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "unifiedFieldList", "id": "def-public.triggerVisualizeActions", @@ -759,6 +833,141 @@ ], "returnComment": [], "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.useExistingFieldsFetcher", + "type": "Function", + "tags": [], + "label": "useExistingFieldsFetcher", + "description": [], + "signature": [ + "(params: ", + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.ExistingFieldsFetcherParams", + "text": "ExistingFieldsFetcherParams" + }, + ") => ", + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.ExistingFieldsFetcher", + "text": "ExistingFieldsFetcher" + } + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.useExistingFieldsFetcher.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "signature": [ + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.ExistingFieldsFetcherParams", + "text": "ExistingFieldsFetcherParams" + } + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.useExistingFieldsReader", + "type": "Function", + "tags": [], + "label": "useExistingFieldsReader", + "description": [], + "signature": [ + "() => { hasFieldData: (dataViewId: string, fieldName: string) => boolean; getFieldsExistenceStatus: (dataViewId: string) => ", + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.ExistenceFetchStatus", + "text": "ExistenceFetchStatus" + }, + "; isFieldsExistenceInfoUnavailable: (dataViewId: string) => boolean; }" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.useGroupedFields", + "type": "Function", + "tags": [], + "label": "useGroupedFields", + "description": [], + "signature": [ + "({\n dataViewId,\n allFields,\n services,\n fieldsExistenceReader,\n onOverrideFieldGroupDetails,\n onSupportedFieldFilter,\n onSelectedFieldFilter,\n onFilterField,\n}: ", + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.GroupedFieldsParams", + "text": "GroupedFieldsParams" + }, + ") => ", + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.GroupedFieldsResult", + "text": "GroupedFieldsResult" + }, + "" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.useGroupedFields.$1", + "type": "Object", + "tags": [], + "label": "{\n dataViewId,\n allFields,\n services,\n fieldsExistenceReader,\n onOverrideFieldGroupDetails,\n onSupportedFieldFilter,\n onSelectedFieldFilter,\n onFilterField,\n}", + "description": [], + "signature": [ + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.GroupedFieldsParams", + "text": "GroupedFieldsParams" + }, + "" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false } ], "interfaces": [ @@ -796,112 +1005,688 @@ }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldPopoverHeaderProps", + "id": "def-public.ExistingFieldsFetcher", "type": "Interface", "tags": [], - "label": "FieldPopoverHeaderProps", + "label": "ExistingFieldsFetcher", "description": [], - "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldPopoverHeaderProps.field", - "type": "Object", + "id": "def-public.ExistingFieldsFetcher.refetchFieldsExistenceInfo", + "type": "Function", "tags": [], - "label": "field", + "label": "refetchFieldsExistenceInfo", "description": [], "signature": [ + "(dataViewId?: string | undefined) => Promise" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewField", - "text": "DataViewField" + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsFetcher.refetchFieldsExistenceInfo.$1", + "type": "string", + "tags": [], + "label": "dataViewId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], - "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", - "deprecated": false, - "trackAdoption": false + "returnComment": [] }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldPopoverHeaderProps.closePopover", - "type": "Function", + "id": "def-public.ExistingFieldsFetcher.isProcessing", + "type": "boolean", "tags": [], - "label": "closePopover", + "label": "isProcessing", "description": [], - "signature": [ - "() => void" - ], - "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [] - }, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsFetcherParams", + "type": "Interface", + "tags": [], + "label": "ExistingFieldsFetcherParams", + "description": [], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldPopoverHeaderProps.buttonAddFieldToWorkspaceProps", - "type": "Object", + "id": "def-public.ExistingFieldsFetcherParams.dataViews", + "type": "Array", "tags": [], - "label": "buttonAddFieldToWorkspaceProps", + "label": "dataViews", "description": [], "signature": [ - "Partial<", - "EuiButtonIconProps", - "> | undefined" + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + "[]" ], - "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldPopoverHeaderProps.buttonAddFilterProps", - "type": "Object", + "id": "def-public.ExistingFieldsFetcherParams.fromDate", + "type": "string", "tags": [], - "label": "buttonAddFilterProps", + "label": "fromDate", "description": [], - "signature": [ - "Partial<", - "EuiButtonIconProps", - "> | undefined" - ], - "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldPopoverHeaderProps.buttonEditFieldProps", - "type": "Object", + "id": "def-public.ExistingFieldsFetcherParams.toDate", + "type": "string", "tags": [], - "label": "buttonEditFieldProps", + "label": "toDate", "description": [], - "signature": [ - "Partial<", - "EuiButtonIconProps", - "> | undefined" - ], - "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldPopoverHeaderProps.buttonDeleteFieldProps", - "type": "Object", + "id": "def-public.ExistingFieldsFetcherParams.query", + "type": "CompoundType", "tags": [], - "label": "buttonDeleteFieldProps", + "label": "query", "description": [], "signature": [ - "Partial<", - "EuiButtonIconProps", - "> | undefined" - ], - "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + " | ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.AggregateQuery", + "text": "AggregateQuery" + } + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsFetcherParams.filters", + "type": "Array", + "tags": [], + "label": "filters", + "description": [], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsFetcherParams.services", + "type": "Object", + "tags": [], + "label": "services", + "description": [], + "signature": [ + "{ core: Pick<", + { + "pluginId": "@kbn/core-lifecycle-browser", + "scope": "common", + "docId": "kibKbnCoreLifecycleBrowserPluginApi", + "section": "def-common.CoreStart", + "text": "CoreStart" + }, + ", \"uiSettings\">; data: ", + { + "pluginId": "data", + "scope": "public", + "docId": "kibDataPluginApi", + "section": "def-public.DataPublicPluginStart", + "text": "DataPublicPluginStart" + }, + "; dataViews: ", + { + "pluginId": "dataViews", + "scope": "public", + "docId": "kibDataViewsPluginApi", + "section": "def-public.DataViewsServicePublic", + "text": "DataViewsServicePublic" + }, + "; }" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsFetcherParams.onNoData", + "type": "Function", + "tags": [], + "label": "onNoData", + "description": [], + "signature": [ + "((dataViewId: string) => unknown) | undefined" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsFetcherParams.onNoData.$1", + "type": "string", + "tags": [], + "label": "dataViewId", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsInfo", + "type": "Interface", + "tags": [], + "label": "ExistingFieldsInfo", + "description": [], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsInfo.fetchStatus", + "type": "Enum", + "tags": [], + "label": "fetchStatus", + "description": [], + "signature": [ + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.ExistenceFetchStatus", + "text": "ExistenceFetchStatus" + } + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsInfo.existingFieldsByFieldNameMap", + "type": "Object", + "tags": [], + "label": "existingFieldsByFieldNameMap", + "description": [], + "signature": [ + "{ [x: string]: boolean; }" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsInfo.numberOfFetches", + "type": "number", + "tags": [], + "label": "numberOfFetches", + "description": [], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsInfo.hasDataViewRestrictions", + "type": "CompoundType", + "tags": [], + "label": "hasDataViewRestrictions", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsReader", + "type": "Interface", + "tags": [], + "label": "ExistingFieldsReader", + "description": [], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsReader.hasFieldData", + "type": "Function", + "tags": [], + "label": "hasFieldData", + "description": [], + "signature": [ + "(dataViewId: string, fieldName: string) => boolean" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsReader.hasFieldData.$1", + "type": "string", + "tags": [], + "label": "dataViewId", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsReader.hasFieldData.$2", + "type": "string", + "tags": [], + "label": "fieldName", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsReader.getFieldsExistenceStatus", + "type": "Function", + "tags": [], + "label": "getFieldsExistenceStatus", + "description": [], + "signature": [ + "(dataViewId: string) => ", + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.ExistenceFetchStatus", + "text": "ExistenceFetchStatus" + } + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsReader.getFieldsExistenceStatus.$1", + "type": "string", + "tags": [], + "label": "dataViewId", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsReader.isFieldsExistenceInfoUnavailable", + "type": "Function", + "tags": [], + "label": "isFieldsExistenceInfoUnavailable", + "description": [], + "signature": [ + "(dataViewId: string) => boolean" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistingFieldsReader.isFieldsExistenceInfoUnavailable.$1", + "type": "string", + "tags": [], + "label": "dataViewId", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldListGroupedProps", + "type": "Interface", + "tags": [], + "label": "FieldListGroupedProps", + "description": [], + "signature": [ + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.FieldListGroupedProps", + "text": "FieldListGroupedProps" + }, + "" + ], + "path": "src/plugins/unified_field_list/public/components/field_list/field_list_grouped.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldListGroupedProps.fieldGroups", + "type": "Object", + "tags": [], + "label": "fieldGroups", + "description": [], + "signature": [ + "{ SpecialFields?: ", + "FieldsGroup", + " | undefined; SelectedFields?: ", + "FieldsGroup", + " | undefined; AvailableFields?: ", + "FieldsGroup", + " | undefined; EmptyFields?: ", + "FieldsGroup", + " | undefined; MetaFields?: ", + "FieldsGroup", + " | undefined; }" + ], + "path": "src/plugins/unified_field_list/public/components/field_list/field_list_grouped.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldListGroupedProps.fieldsExistenceStatus", + "type": "Enum", + "tags": [], + "label": "fieldsExistenceStatus", + "description": [], + "signature": [ + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.ExistenceFetchStatus", + "text": "ExistenceFetchStatus" + } + ], + "path": "src/plugins/unified_field_list/public/components/field_list/field_list_grouped.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldListGroupedProps.fieldsExistInIndex", + "type": "boolean", + "tags": [], + "label": "fieldsExistInIndex", + "description": [], + "path": "src/plugins/unified_field_list/public/components/field_list/field_list_grouped.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldListGroupedProps.renderFieldItem", + "type": "Function", + "tags": [], + "label": "renderFieldItem", + "description": [], + "signature": [ + "(params: { field: T; hideDetails?: boolean | undefined; itemIndex: number; groupIndex: number; }) => JSX.Element" + ], + "path": "src/plugins/unified_field_list/public/components/field_list/field_list_grouped.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldListGroupedProps.renderFieldItem.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "signature": [ + "{ field: T; hideDetails?: boolean | undefined; itemIndex: number; groupIndex: number; }" + ], + "path": "src/plugins/unified_field_list/public/components/field_list/fields_accordion.tsx", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldListGroupedProps.screenReaderDescriptionForSearchInputId", + "type": "string", + "tags": [], + "label": "screenReaderDescriptionForSearchInputId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/unified_field_list/public/components/field_list/field_list_grouped.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldListGroupedProps.datatestsubj", + "type": "string", + "tags": [], + "label": "'data-test-subj'", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/unified_field_list/public/components/field_list/field_list_grouped.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldPopoverHeaderProps", + "type": "Interface", + "tags": [], + "label": "FieldPopoverHeaderProps", + "description": [], + "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldPopoverHeaderProps.field", + "type": "Object", + "tags": [], + "label": "field", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" + } + ], + "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldPopoverHeaderProps.closePopover", + "type": "Function", + "tags": [], + "label": "closePopover", + "description": [], + "signature": [ + "() => void" + ], + "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldPopoverHeaderProps.buttonAddFieldToWorkspaceProps", + "type": "Object", + "tags": [], + "label": "buttonAddFieldToWorkspaceProps", + "description": [], + "signature": [ + "Partial<", + "EuiButtonIconProps", + "> | undefined" + ], + "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldPopoverHeaderProps.buttonAddFilterProps", + "type": "Object", + "tags": [], + "label": "buttonAddFilterProps", + "description": [], + "signature": [ + "Partial<", + "EuiButtonIconProps", + "> | undefined" + ], + "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldPopoverHeaderProps.buttonEditFieldProps", + "type": "Object", + "tags": [], + "label": "buttonEditFieldProps", + "description": [], + "signature": [ + "Partial<", + "EuiButtonIconProps", + "> | undefined" + ], + "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldPopoverHeaderProps.buttonDeleteFieldProps", + "type": "Object", + "tags": [], + "label": "buttonDeleteFieldProps", + "description": [], + "signature": [ + "Partial<", + "EuiButtonIconProps", + "> | undefined" + ], + "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover_header.tsx", "deprecated": false, "trackAdoption": false }, @@ -1073,25 +1858,150 @@ ], "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover.tsx", "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldPopoverProps.renderContent", + "type": "Function", + "tags": [], + "label": "renderContent", + "description": [], + "signature": [ + "(() => React.ReactNode) | undefined" + ], + "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldsGroupDetails", + "type": "Interface", + "tags": [], + "label": "FieldsGroupDetails", + "description": [], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldsGroupDetails.showInAccordion", + "type": "boolean", + "tags": [], + "label": "showInAccordion", + "description": [], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldsGroupDetails.isInitiallyOpen", + "type": "boolean", + "tags": [], + "label": "isInitiallyOpen", + "description": [], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldsGroupDetails.title", + "type": "string", + "tags": [], + "label": "title", + "description": [], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldsGroupDetails.helpText", + "type": "string", + "tags": [], + "label": "helpText", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldsGroupDetails.isAffectedByGlobalFilter", + "type": "boolean", + "tags": [], + "label": "isAffectedByGlobalFilter", + "description": [], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldsGroupDetails.isAffectedByTimeFilter", + "type": "boolean", + "tags": [], + "label": "isAffectedByTimeFilter", + "description": [], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldsGroupDetails.hideDetails", + "type": "CompoundType", + "tags": [], + "label": "hideDetails", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldsGroupDetails.defaultNoFieldsMessage", + "type": "string", + "tags": [], + "label": "defaultNoFieldsMessage", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldPopoverProps.renderContent", - "type": "Function", + "id": "def-public.FieldsGroupDetails.hideIfEmpty", + "type": "CompoundType", "tags": [], - "label": "renderContent", + "label": "hideIfEmpty", "description": [], "signature": [ - "(() => React.ReactNode) | undefined" + "boolean | undefined" ], - "path": "src/plugins/unified_field_list/public/components/field_popover/field_popover.tsx", + "path": "src/plugins/unified_field_list/public/types.ts", "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] + "trackAdoption": false } ], "initialIsOpen": false @@ -1922,6 +2832,299 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsParams", + "type": "Interface", + "tags": [], + "label": "GroupedFieldsParams", + "description": [], + "signature": [ + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.GroupedFieldsParams", + "text": "GroupedFieldsParams" + }, + "" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsParams.dataViewId", + "type": "CompoundType", + "tags": [], + "label": "dataViewId", + "description": [], + "signature": [ + "string | null" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsParams.allFields", + "type": "Array", + "tags": [], + "label": "allFields", + "description": [], + "signature": [ + "T[]" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsParams.services", + "type": "Object", + "tags": [], + "label": "services", + "description": [], + "signature": [ + "{ dataViews: ", + { + "pluginId": "dataViews", + "scope": "public", + "docId": "kibDataViewsPluginApi", + "section": "def-public.DataViewsServicePublic", + "text": "DataViewsServicePublic" + }, + "; }" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsParams.fieldsExistenceReader", + "type": "Object", + "tags": [], + "label": "fieldsExistenceReader", + "description": [], + "signature": [ + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.ExistingFieldsReader", + "text": "ExistingFieldsReader" + }, + " | undefined" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsParams.onOverrideFieldGroupDetails", + "type": "Function", + "tags": [], + "label": "onOverrideFieldGroupDetails", + "description": [], + "signature": [ + "((groupName: ", + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.FieldsGroupNames", + "text": "FieldsGroupNames" + }, + ") => Partial<", + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.FieldsGroupDetails", + "text": "FieldsGroupDetails" + }, + "> | null | undefined) | undefined" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsParams.onOverrideFieldGroupDetails.$1", + "type": "Enum", + "tags": [], + "label": "groupName", + "description": [], + "signature": [ + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.FieldsGroupNames", + "text": "FieldsGroupNames" + } + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsParams.onSupportedFieldFilter", + "type": "Function", + "tags": [], + "label": "onSupportedFieldFilter", + "description": [], + "signature": [ + "((field: T) => boolean) | undefined" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsParams.onSupportedFieldFilter.$1", + "type": "Uncategorized", + "tags": [], + "label": "field", + "description": [], + "signature": [ + "T" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsParams.onSelectedFieldFilter", + "type": "Function", + "tags": [], + "label": "onSelectedFieldFilter", + "description": [], + "signature": [ + "((field: T) => boolean) | undefined" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsParams.onSelectedFieldFilter.$1", + "type": "Uncategorized", + "tags": [], + "label": "field", + "description": [], + "signature": [ + "T" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsParams.onFilterField", + "type": "Function", + "tags": [], + "label": "onFilterField", + "description": [], + "signature": [ + "((field: T) => boolean) | undefined" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsParams.onFilterField.$1", + "type": "Uncategorized", + "tags": [], + "label": "field", + "description": [], + "signature": [ + "T" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsResult", + "type": "Interface", + "tags": [], + "label": "GroupedFieldsResult", + "description": [], + "signature": [ + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.GroupedFieldsResult", + "text": "GroupedFieldsResult" + }, + "" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.GroupedFieldsResult.fieldGroups", + "type": "Object", + "tags": [], + "label": "fieldGroups", + "description": [], + "signature": [ + "{ SpecialFields?: ", + "FieldsGroup", + " | undefined; SelectedFields?: ", + "FieldsGroup", + " | undefined; AvailableFields?: ", + "FieldsGroup", + " | undefined; EmptyFields?: ", + "FieldsGroup", + " | undefined; MetaFields?: ", + "FieldsGroup", + " | undefined; }" + ], + "path": "src/plugins/unified_field_list/public/hooks/use_grouped_fields.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "unifiedFieldList", "id": "def-public.NumberStatsResult", @@ -2066,7 +3269,32 @@ "initialIsOpen": false } ], - "enums": [], + "enums": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.ExistenceFetchStatus", + "type": "Enum", + "tags": [], + "label": "ExistenceFetchStatus", + "description": [], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldsGroupNames", + "type": "Enum", + "tags": [], + "label": "FieldsGroupNames", + "description": [], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], "misc": [ { "parentPluginId": "unifiedFieldList", @@ -2143,6 +3371,31 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldListGroups", + "type": "Type", + "tags": [], + "label": "FieldListGroups", + "description": [], + "signature": [ + "{ SpecialFields?: ", + "FieldsGroup", + " | undefined; SelectedFields?: ", + "FieldsGroup", + " | undefined; AvailableFields?: ", + "FieldsGroup", + " | undefined; EmptyFields?: ", + "FieldsGroup", + " | undefined; MetaFields?: ", + "FieldsGroup", + " | undefined; }" + ], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "unifiedFieldList", "id": "def-public.FieldPopoverVisualizeProps", diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index b97e6416bdcd7..6a505b8b7390d 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-disco | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 122 | 0 | 117 | 2 | +| 192 | 0 | 187 | 4 | ## Client @@ -37,6 +37,9 @@ Contact [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-disco ### Interfaces +### Enums + + ### Consts, variables and types diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index a62224baf6c26..45df0fff8b396 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.devdocs.json b/api_docs/unified_search.devdocs.json index 95caead9e4701..76b9b00cf8f5a 100644 --- a/api_docs/unified_search.devdocs.json +++ b/api_docs/unified_search.devdocs.json @@ -358,7 +358,7 @@ "section": "def-public.TimeHistoryContract", "text": "TimeHistoryContract" }, - " | undefined; customSubmitButton?: React.ReactNode; screenTitle?: string | undefined; showQueryInput?: boolean | undefined; showFilterBar?: boolean | undefined; showDatePicker?: boolean | undefined; showAutoRefreshOnly?: boolean | undefined; hiddenFilterPanelOptions?: ", + " | undefined; customSubmitButton?: React.ReactNode; screenTitle?: string | undefined; showQueryMenu?: boolean | undefined; showQueryInput?: boolean | undefined; showFilterBar?: boolean | undefined; showDatePicker?: boolean | undefined; showAutoRefreshOnly?: boolean | undefined; hiddenFilterPanelOptions?: ", "FilterPanelOption", "[] | undefined; isRefreshPaused?: boolean | undefined; dateRangeFrom?: string | undefined; dateRangeTo?: string | undefined; showSaveQuery?: boolean | undefined; onQueryChange?: ((payload: { dateRange: ", { diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 0e837f00498ec..a66f0aa97a7e0 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index b06b94767e58b..82e5e71f4aab0 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 14a726450eacd..6a42326b36507 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index b2d4989832fa1..c2d61e75f8b37 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 64953d2e5af9e..3e687d886a017 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index a091604ac718f..a84e314d48ed3 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index 41dd38651b235..9219c4f9a3f09 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index 616061bf3fc8d..84d6f3b8b8bff 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index 19dbab0aab26d..dc4eb99cbdce1 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 7173026231189..67eaa3732a954 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index e85866399ac3c..5f26ede150ecd 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 3ff5b5d63ad63..f1a6f30574612 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index e2d574dc60cb3..faab1f8013825 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index b6ef47d2cfc98..72bcc30d618a1 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 78c16f75d360e..b6533d6e425d6 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.devdocs.json b/api_docs/visualizations.devdocs.json index b00231a68a28d..8ec07af6904d0 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -8205,62 +8205,13 @@ "description": [], "signature": [ "() => ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExpressionFunctionDefinition", - "text": "ExpressionFunctionDefinition" - }, - "<\"visdimension\", ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.Datatable", - "text": "Datatable" - }, - ", ", { "pluginId": "visualizations", "scope": "common", "docId": "kibVisualizationsPluginApi", - "section": "def-common.Arguments", - "text": "Arguments" - }, - ", ", - { - "pluginId": "visualizations", - "scope": "common", - "docId": "kibVisualizationsPluginApi", - "section": "def-common.ExpressionValueVisDimension", - "text": "ExpressionValueVisDimension" - }, - ", ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ExecutionContext", - "text": "ExecutionContext" - }, - "<", - { - "pluginId": "inspector", - "scope": "common", - "docId": "kibInspectorPluginApi", - "section": "def-common.Adapters", - "text": "Adapters" - }, - ", ", - { - "pluginId": "@kbn/utility-types", - "scope": "server", - "docId": "kibKbnUtilityTypesPluginApi", - "section": "def-server.SerializableRecord", - "text": "SerializableRecord" - }, - ">>" + "section": "def-common.ExpressionFunctionVisDimension", + "text": "ExpressionFunctionVisDimension" + } ], "path": "src/plugins/visualizations/common/expression_functions/vis_dimension.ts", "deprecated": false, @@ -13951,6 +13902,76 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "visualizations", + "id": "def-common.ExpressionFunctionVisDimension", + "type": "Type", + "tags": [], + "label": "ExpressionFunctionVisDimension", + "description": [], + "signature": [ + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionFunctionDefinition", + "text": "ExpressionFunctionDefinition" + }, + "<\"visdimension\", ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.Datatable", + "text": "Datatable" + }, + ", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.Arguments", + "text": "Arguments" + }, + ", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ExpressionValueVisDimension", + "text": "ExpressionValueVisDimension" + }, + ", ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExecutionContext", + "text": "ExecutionContext" + }, + "<", + { + "pluginId": "inspector", + "scope": "common", + "docId": "kibInspectorPluginApi", + "section": "def-common.Adapters", + "text": "Adapters" + }, + ", ", + { + "pluginId": "@kbn/utility-types", + "scope": "server", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-server.SerializableRecord", + "text": "SerializableRecord" + }, + ">>" + ], + "path": "src/plugins/visualizations/common/expression_functions/vis_dimension.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.ExpressionValueVisDimension", diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 0cd6f9f52f8e9..8fcde27cb1605 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2022-11-02 +date: 2022-11-03 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 797 | 12 | 767 | 18 | +| 798 | 12 | 768 | 18 | ## Client From 9e58af54a8d151c676035a915523acd11ede3223 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Thu, 3 Nov 2022 08:44:08 +0200 Subject: [PATCH 49/86] [Lens] Exclude the reference layer from the cursor sync hook (#144384) --- .../expression_xy/public/components/xy_chart.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx index c03cb9a664f1a..d57dc86b36ba1 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx @@ -275,8 +275,10 @@ export function XYChart({ [uiState] ); + // Exclude the reference layers from the cursor update + const cursorSyncLayers = filteredLayers.filter(isDataLayer); const handleCursorUpdate = useActiveCursor(chartsActiveCursorService, chartRef, { - datatables: filteredLayers.map(({ table }) => table), + datatables: cursorSyncLayers.map(({ table }) => table), }); const onRenderChange = useCallback( From a6ab758e5fbf8e4223f9d4d36b8c21f0fee613ce Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Thu, 3 Nov 2022 08:44:53 +0200 Subject: [PATCH 50/86] [Lens] Text based languages fixes and UX improvements (#144306) * [Lens] Fixes wrong label color on field duplication * Update meta information on selected field update * Mark as incompatible all the non numeric fields for the metrics fields selection * Restrict dropping non mumeric fields on metric dimensions * Update functional tests --- .../text_based/field_select.test.tsx | 15 ++-- .../datasources/text_based/field_select.tsx | 34 ++++---- .../text_based/text_based_languages.test.ts | 84 ++++++++++++++++++- .../text_based/text_based_languages.tsx | 31 +++++-- .../editor_frame/config_panel/layer_panel.tsx | 2 + x-pack/plugins/lens/public/types.ts | 4 + .../datatable/visualization.tsx | 1 + .../gauge/visualization.test.ts | 16 ++++ .../visualizations/gauge/visualization.tsx | 4 + .../heatmap/visualization.test.ts | 3 + .../visualizations/heatmap/visualization.tsx | 1 + .../legacy_metric/visualization.tsx | 1 + .../__snapshots__/visualization.test.ts.snap | 2 + .../visualizations/metric/visualization.tsx | 2 + .../partition/visualization.tsx | 1 + .../visualizations/xy/visualization.tsx | 1 + 16 files changed, 173 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/lens/public/datasources/text_based/field_select.test.tsx b/x-pack/plugins/lens/public/datasources/text_based/field_select.test.tsx index b344131747b1b..2a8e63da1b0e1 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/field_select.test.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/field_select.test.tsx @@ -6,11 +6,9 @@ */ import React from 'react'; -import type { DatatableColumn } from '@kbn/expressions-plugin/public'; - import { FieldPicker, FieldOptionValue } from '../../shared_components/field_picker'; -import { FieldSelect, FieldSelectProps } from './field_select'; +import { type FieldOptionCompatible, FieldSelect, FieldSelectProps } from './field_select'; import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; const fields = [ @@ -20,6 +18,7 @@ const fields = [ meta: { type: 'date', }, + compatible: true, }, { name: 'bytes', @@ -27,6 +26,7 @@ const fields = [ meta: { type: 'number', }, + compatible: true, }, { name: 'memory', @@ -34,8 +34,9 @@ const fields = [ meta: { type: 'number', }, + compatible: true, }, -] as DatatableColumn[]; +] as FieldOptionCompatible[]; describe('Layer Data Panel', () => { let defaultProps: FieldSelectProps; @@ -75,7 +76,7 @@ describe('Layer Data Panel', () => { label: 'Available fields', options: [ { - compatible: true, + compatible: 1, exists: true, label: 'timestamp', value: { @@ -85,7 +86,7 @@ describe('Layer Data Panel', () => { }, }, { - compatible: true, + compatible: 1, exists: true, label: 'bytes', value: { @@ -95,7 +96,7 @@ describe('Layer Data Panel', () => { }, }, { - compatible: true, + compatible: 1, exists: true, label: 'memory', value: { diff --git a/x-pack/plugins/lens/public/datasources/text_based/field_select.tsx b/x-pack/plugins/lens/public/datasources/text_based/field_select.tsx index e7cb5451d31ab..5ca4f1e341ad9 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/field_select.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/field_select.tsx @@ -13,10 +13,14 @@ import { FieldPicker, FieldOptionValue, FieldOption } from '../../shared_compone import type { TextBasedLayerColumn } from './types'; import type { DataType } from '../../types'; +export interface FieldOptionCompatible extends DatatableColumn { + compatible: boolean; +} + export interface FieldSelectProps extends EuiComboBoxProps { selectedField?: TextBasedLayerColumn; onChoose: (choice: FieldOptionValue) => void; - existingFields: DatatableColumn[]; + existingFields: FieldOptionCompatible[]; } export function FieldSelect({ @@ -26,19 +30,21 @@ export function FieldSelect({ ['data-test-subj']: dataTestSub, }: FieldSelectProps) { const memoizedFieldOptions = useMemo(() => { - const availableFields = existingFields.map((field) => { - const dataType = field?.meta?.type as DataType; - return { - compatible: true, - exists: true, - label: field.name, - value: { - type: 'field' as FieldOptionValue['type'], - field: field.name, - dataType, - }, - }; - }); + const availableFields = existingFields + .map((field) => { + const dataType = field?.meta?.type as DataType; + return { + compatible: field.compatible ? 1 : 0, + exists: true, + label: field.name, + value: { + type: 'field' as FieldOptionValue['type'], + field: field.name, + dataType, + }, + }; + }) + .sort((a, b) => b.compatible - a.compatible); return [ { label: i18n.translate('xpack.lens.indexPattern.availableFieldsLabel', { diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts index 25bab766558e3..b7a99c41cf72a 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts +++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts @@ -77,7 +77,7 @@ const expectedIndexPatterns = { const indexPatterns = expectedIndexPatterns; -describe('IndexPattern Data Source', () => { +describe('Textbased Data Source', () => { let baseState: TextBasedPrivateState; let TextBasedDatasource: Datasource; @@ -189,6 +189,88 @@ describe('IndexPattern Data Source', () => { }); }); + describe('#getDropProps', () => { + it('should return undefined if source is not present', () => { + const props = { + target: { + layerId: 'a', + groupId: 'groupId', + columnId: 'col1', + filterOperations: jest.fn(), + }, + state: baseState, + indexPatterns, + }; + expect(TextBasedDatasource.getDropProps(props)).toBeUndefined(); + }); + + it('should return undefined if target group not allows non numeric fields', () => { + const newState = { + ...baseState, + layers: { + a: { + columns: [], + allColumns: [ + { + columnId: 'col1', + fieldName: 'Test 1', + meta: { + type: 'string', + }, + }, + ], + query: { sql: 'SELECT * FROM foo' }, + index: 'foo', + }, + }, + } as unknown as TextBasedPrivateState; + const props = { + target: { + layerId: 'a', + groupId: 'groupId', + columnId: 'col1', + filterOperations: jest.fn(), + isMetricDimension: true, + }, + source: { + id: 'col1', + field: 'Test 1', + humanData: { + label: 'Test 1', + }, + }, + state: newState, + indexPatterns, + }; + expect(TextBasedDatasource.getDropProps(props)).toBeUndefined(); + }); + + it('should return props if field is allowed to be dropped', () => { + const props = { + target: { + layerId: 'a', + groupId: 'groupId', + columnId: 'col1', + filterOperations: jest.fn(), + isMetricDimension: true, + }, + source: { + id: 'col1', + field: 'Test 1', + humanData: { + label: 'Test 1', + }, + }, + state: baseState, + indexPatterns, + }; + expect(TextBasedDatasource.getDropProps(props)).toStrictEqual({ + dropTypes: ['field_add'], + nextLabel: 'Test 1', + }); + }); + }); + describe('#insertLayer', () => { it('should insert an empty layer into the previous state', () => { expect(TextBasedDatasource.insertLayer(baseState, 'newLayer')).toEqual({ diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx index 368036e3ebd50..ed8931d75f54d 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx @@ -383,11 +383,9 @@ export function getTextBasedDatasource({ customLabel = selectedField?.fieldName; } - const columnExists = props.state.fieldList.some((f) => f.name === customLabel); - render( {}} data-test-subj="lns-dimensionTrigger-textBased" > @@ -412,6 +410,19 @@ export function getTextBasedDatasource({ const selectedField = props.state.layers[props.layerId]?.allColumns?.find( (column) => column.columnId === props.columnId ); + + const updatedFields = fields.map((f) => { + return { + ...f, + compatible: props.isMetricDimension + ? props.filterOperations({ + dataType: f.meta.type as DataType, + isBucketed: Boolean(f?.meta?.type !== 'number'), + scale: 'ordinal', + }) + : true, + }; + }); render( { const meta = fields.find((f) => f.name === choice.field)?.meta; @@ -457,12 +468,12 @@ export function getTextBasedDatasource({ columns: props.state.layers[props.layerId].columns.map((col) => col.columnId !== props.columnId ? col - : { ...col, fieldName: choice.field } + : { ...col, fieldName: choice.field, meta } ), allColumns: props.state.layers[props.layerId].allColumns.map((col) => col.columnId !== props.columnId ? col - : { ...col, fieldName: choice.field } + : { ...col, fieldName: choice.field, meta } ), }, }, @@ -522,10 +533,16 @@ export function getTextBasedDatasource({ }, getDropProps: (props) => { - const { source } = props; + const { source, target, state } = props; if (!source) { return; } + if (target && target.isMetricDimension) { + const layerId = target.layerId; + const currentLayer = state.layers[layerId]; + const field = currentLayer.allColumns.find((f) => f.columnId === source.id); + if (field?.meta?.type !== 'number') return; + } const label = source.field as string; return { dropTypes: ['field_add'], nextLabel: label }; }, 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 13939fa276b31..69f661e92fb43 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 @@ -605,6 +605,7 @@ export function LayerPanel( filterOperations: group.filterOperations, prioritizedOperation: group.prioritizedOperation, isNewColumn: true, + isMetricDimension: group?.isMetricDimension, indexPatternId: layerDatasource ? layerDatasource.getUsedDataView(layerDatasourceState, layerId) : activeVisualization.getUsedDataView?.(visualizationState, layerId), @@ -717,6 +718,7 @@ export function LayerPanel( groupId: activeGroup.groupId, hideGrouping: activeGroup.hideGrouping, filterOperations: activeGroup.filterOperations, + isMetricDimension: activeGroup?.isMetricDimension, dimensionGroups, toggleFullscreen, isFullscreen, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 628afa8d61276..b947c43dd46de 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -612,6 +612,7 @@ export type DatasourceDimensionEditorProps = DatasourceDimensionPro dimensionGroups: VisualizationDimensionGroupConfig[]; toggleFullscreen: () => void; isFullscreen: boolean; + isMetricDimension?: boolean; layerType: LayerType | undefined; supportStaticValue: boolean; paramEditorCustomProps?: ParamEditorCustomProps; @@ -636,6 +637,7 @@ export interface DragDropOperation { filterOperations: (operation: OperationMetadata) => boolean; indexPatternId?: string; isNewColumn?: boolean; + isMetricDimension?: boolean; prioritizedOperation?: string; } @@ -793,6 +795,8 @@ export type VisualizationDimensionGroupConfig = SharedDimensionProps & { // need a special flag to know when to pass the previous column on duplicating requiresPreviousColumnOnDuplicate?: boolean; supportStaticValue?: boolean; + // used by text based datasource to restrict the field selection only to number fields for the metric dimensions + isMetricDimension?: boolean; paramEditorCustomProps?: ParamEditorCustomProps; enableFormatSelector?: boolean; formatSelectorOptions?: FormatSelectorOptions; // only relevant if supportFieldFormat is true diff --git a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx index ccfaff17a8ecb..88bd970b49bb7 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx @@ -299,6 +299,7 @@ export const getDatatableVisualization = ({ }), supportsMoreColumns: true, filterOperations: (op) => !op.isBucketed, + isMetricDimension: true, requiredMinDimensionCount: 1, dataTestSubj: 'lnsDatatable_metrics', enableDimensionEditor: true, diff --git a/x-pack/plugins/lens/public/visualizations/gauge/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/gauge/visualization.test.ts index 38580d33163ca..1041a7bb36078 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/gauge/visualization.test.ts @@ -102,6 +102,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.METRIC, groupLabel: 'Metric', + isMetricDimension: true, accessors: [{ columnId: 'metric-accessor', triggerIcon: 'none' }], filterOperations: isNumericDynamicMetric, supportsMoreColumns: false, @@ -118,6 +119,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.MIN, groupLabel: 'Minimum value', + isMetricDimension: true, accessors: [{ columnId: 'min-accessor' }], filterOperations: isNumericMetric, supportsMoreColumns: false, @@ -135,6 +137,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.MAX, groupLabel: 'Maximum value', + isMetricDimension: true, accessors: [{ columnId: 'max-accessor' }], filterOperations: isNumericMetric, supportsMoreColumns: false, @@ -152,6 +155,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.GOAL, groupLabel: 'Goal value', + isMetricDimension: true, accessors: [{ columnId: 'goal-accessor' }], filterOperations: isNumericMetric, supportsMoreColumns: false, @@ -184,6 +188,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.METRIC, groupLabel: 'Metric', + isMetricDimension: true, accessors: [], filterOperations: isNumericDynamicMetric, supportsMoreColumns: true, @@ -200,6 +205,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.MIN, groupLabel: 'Minimum value', + isMetricDimension: true, accessors: [{ columnId: 'min-accessor' }], filterOperations: isNumericMetric, supportsMoreColumns: false, @@ -217,6 +223,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.MAX, groupLabel: 'Maximum value', + isMetricDimension: true, accessors: [], filterOperations: isNumericMetric, supportsMoreColumns: true, @@ -234,6 +241,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.GOAL, groupLabel: 'Goal value', + isMetricDimension: true, accessors: [], filterOperations: isNumericMetric, supportsMoreColumns: true, @@ -272,6 +280,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.METRIC, groupLabel: 'Metric', + isMetricDimension: true, accessors: [{ columnId: 'metric-accessor', triggerIcon: 'none' }], filterOperations: isNumericDynamicMetric, supportsMoreColumns: false, @@ -288,6 +297,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.MIN, groupLabel: 'Minimum value', + isMetricDimension: true, accessors: [{ columnId: 'min-accessor' }], filterOperations: isNumericMetric, supportsMoreColumns: false, @@ -305,6 +315,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.MAX, groupLabel: 'Maximum value', + isMetricDimension: true, accessors: [{ columnId: 'max-accessor' }], filterOperations: isNumericMetric, supportsMoreColumns: false, @@ -322,6 +333,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.GOAL, groupLabel: 'Goal value', + isMetricDimension: true, accessors: [{ columnId: 'goal-accessor' }], filterOperations: isNumericMetric, supportsMoreColumns: false, @@ -365,6 +377,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.METRIC, groupLabel: 'Metric', + isMetricDimension: true, accessors: [{ columnId: 'metric-accessor', triggerIcon: 'none' }], filterOperations: isNumericDynamicMetric, supportsMoreColumns: false, @@ -381,6 +394,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.MIN, groupLabel: 'Minimum value', + isMetricDimension: true, accessors: [{ columnId: 'min-accessor' }], filterOperations: isNumericMetric, supportsMoreColumns: false, @@ -400,6 +414,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.MAX, groupLabel: 'Maximum value', + isMetricDimension: true, accessors: [{ columnId: 'max-accessor' }], filterOperations: isNumericMetric, supportsMoreColumns: false, @@ -419,6 +434,7 @@ describe('gauge', () => { }, groupId: GROUP_ID.GOAL, groupLabel: 'Goal value', + isMetricDimension: true, accessors: [{ columnId: 'goal-accessor' }], filterOperations: isNumericMetric, supportsMoreColumns: false, diff --git a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx index 684703a7e0011..7010615dcd8d6 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx @@ -244,6 +244,7 @@ export const getGaugeVisualization = ({ defaultMessage: 'Value', }), }, + isMetricDimension: true, accessors: metricAccessor ? [ palette @@ -282,6 +283,7 @@ export const getGaugeVisualization = ({ defaultMessage: 'Value', }), }, + isMetricDimension: true, accessors: state.minAccessor ? [{ columnId: state.minAccessor }] : [], filterOperations: isNumericMetric, supportsMoreColumns: !state.minAccessor, @@ -308,6 +310,7 @@ export const getGaugeVisualization = ({ defaultMessage: 'Value', }), }, + isMetricDimension: true, accessors: state.maxAccessor ? [{ columnId: state.maxAccessor }] : [], filterOperations: isNumericMetric, supportsMoreColumns: !state.maxAccessor, @@ -334,6 +337,7 @@ export const getGaugeVisualization = ({ defaultMessage: 'Value', }), }, + isMetricDimension: true, accessors: state.goalAccessor ? [{ columnId: state.goalAccessor }] : [], filterOperations: isNumericMetric, supportsMoreColumns: !state.goalAccessor, diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts index fd73a652b53e0..e26fe130e8da9 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.test.ts @@ -156,6 +156,7 @@ describe('heatmap', () => { }, groupId: GROUP_ID.CELL, groupLabel: 'Cell value', + isMetricDimension: true, accessors: [ { columnId: 'v-accessor', @@ -214,6 +215,7 @@ describe('heatmap', () => { }, groupId: GROUP_ID.CELL, groupLabel: 'Cell value', + isMetricDimension: true, accessors: [], filterOperations: isCellValueSupported, supportsMoreColumns: true, @@ -270,6 +272,7 @@ describe('heatmap', () => { }, groupId: GROUP_ID.CELL, groupLabel: 'Cell value', + isMetricDimension: true, accessors: [ { columnId: 'v-accessor', diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx index 9750cc89c55e7..82c455077e7cf 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx @@ -229,6 +229,7 @@ export const getHeatmapVisualization = ({ ] : [], filterOperations: isCellValueSupported, + isMetricDimension: true, supportsMoreColumns: !state.valueAccessor, enableDimensionEditor: true, requiredMinDimensionCount: 1, diff --git a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx index c98aa37dfd641..8299cf3c3a36b 100644 --- a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx @@ -223,6 +223,7 @@ export const getLegacyMetricVisualization = ({ defaultMessage: 'Value', }), }, + isMetricDimension: true, groupLabel: i18n.translate('xpack.lens.metric.label', { defaultMessage: 'Metric', }), diff --git a/x-pack/plugins/lens/public/visualizations/metric/__snapshots__/visualization.test.ts.snap b/x-pack/plugins/lens/public/visualizations/metric/__snapshots__/visualization.test.ts.snap index 6114628b3adf4..64b509e5f7a8c 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/__snapshots__/visualization.test.ts.snap +++ b/x-pack/plugins/lens/public/visualizations/metric/__snapshots__/visualization.test.ts.snap @@ -20,6 +20,7 @@ Object { }, "groupId": "metric", "groupLabel": "Primary metric", + "isMetricDimension": true, "paramEditorCustomProps": Object { "headingLabel": "Value", }, @@ -41,6 +42,7 @@ Object { }, "groupId": "secondaryMetric", "groupLabel": "Secondary metric", + "isMetricDimension": true, "paramEditorCustomProps": Object { "headingLabel": "Value", }, diff --git a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx index eac08b22c9ea4..3b3546655dce3 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx @@ -141,6 +141,7 @@ const getMetricLayerConfiguration = ( : [], supportsMoreColumns: !props.state.metricAccessor, filterOperations: isSupportedDynamicMetric, + isMetricDimension: true, enableDimensionEditor: true, enableFormatSelector: true, formatSelectorOptions: formatterOptions, @@ -166,6 +167,7 @@ const getMetricLayerConfiguration = ( : [], supportsMoreColumns: !props.state.secondaryMetricAccessor, filterOperations: isSupportedDynamicMetric, + isMetricDimension: true, enableDimensionEditor: true, enableFormatSelector: true, formatSelectorOptions: formatterOptions, diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx index 6bff536794e89..57118c04ed721 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx @@ -272,6 +272,7 @@ export const getPieVisualization = ({ groupLabel: i18n.translate('xpack.lens.pie.groupsizeLabel', { defaultMessage: 'Size by', }), + isMetricDimension: true, dimensionEditorGroupLabel: i18n.translate('xpack.lens.pie.groupSizeLabel', { defaultMessage: 'Size', }), diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx index 6622dd40615c8..47966ea309e3e 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx @@ -347,6 +347,7 @@ export const getXyVisualization = ({ groupLabel: getAxisName('y', { isHorizontal }), accessors: mappedAccessors, filterOperations: isNumericDynamicMetric, + isMetricDimension: true, supportsMoreColumns: true, requiredMinDimensionCount: 1, dataTestSubj: 'lnsXY_yDimensionPanel', From 5abc674b06028ff9d33eb94df582345a1a1c3c6f Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Thu, 3 Nov 2022 08:49:10 +0200 Subject: [PATCH 51/86] [Lens] Metric expression types improvement. (#144239) * Added metricTrendlineFn. * Added metricVis type safety. --- .../expression_metric/common/index.ts | 1 + .../visualizations/metric/to_expression.ts | 138 +++++++++--------- .../metric/visualization.test.ts | 10 +- 3 files changed, 69 insertions(+), 80 deletions(-) diff --git a/src/plugins/chart_expressions/expression_metric/common/index.ts b/src/plugins/chart_expressions/expression_metric/common/index.ts index 163c153efa9ee..ae8f3b9fae7a2 100755 --- a/src/plugins/chart_expressions/expression_metric/common/index.ts +++ b/src/plugins/chart_expressions/expression_metric/common/index.ts @@ -14,6 +14,7 @@ export type { MetricInput, MetricVisRenderConfig, MetricVisExpressionFunctionDefinition, + TrendlineExpressionFunctionDefinition, DimensionsVisParam, MetricVisParam, VisParams, diff --git a/x-pack/plugins/lens/public/visualizations/metric/to_expression.ts b/x-pack/plugins/lens/public/visualizations/metric/to_expression.ts index 97c8a3c995aab..1326f5369cbd2 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/to_expression.ts +++ b/x-pack/plugins/lens/public/visualizations/metric/to_expression.ts @@ -5,12 +5,13 @@ * 2.0. */ +import { LayoutDirection } from '@elastic/charts'; import { CustomPaletteParams, CUSTOM_PALETTE, PaletteRegistry } from '@kbn/coloring'; -import { - EXPRESSION_METRIC_NAME, - EXPRESSION_METRIC_TRENDLINE_NAME, -} from '@kbn/expression-metric-vis-plugin/public'; -import { buildExpressionFunction } from '@kbn/expressions-plugin/common'; +import type { + TrendlineExpressionFunctionDefinition, + MetricVisExpressionFunctionDefinition, +} from '@kbn/expression-metric-vis-plugin/common'; +import { buildExpression, buildExpressionFunction } from '@kbn/expressions-plugin/common'; import { Ast } from '@kbn/interpreter'; import { CollapseArgs, CollapseFunction } from '../../../common/expressions'; import { CollapseExpressionFunction } from '../../../common/expressions/collapse/types'; @@ -33,51 +34,47 @@ const getTrendlineExpression = ( state: MetricVisualizationState, datasourceExpressionsByLayers: Record ): Ast | undefined => { - if (!state.trendlineLayerId || !state.trendlineMetricAccessor || !state.trendlineTimeAccessor) { + const { trendlineLayerId, trendlineMetricAccessor, trendlineTimeAccessor } = state; + if (!trendlineLayerId || !trendlineMetricAccessor || !trendlineTimeAccessor) { return; } - const datasourceExpression = datasourceExpressionsByLayers[state.trendlineLayerId]; + const datasourceExpression = datasourceExpressionsByLayers[trendlineLayerId]; - return { - type: 'expression', - chain: [ - { - type: 'function', - function: EXPRESSION_METRIC_TRENDLINE_NAME, - arguments: { - metric: [state.trendlineMetricAccessor], - timeField: [state.trendlineTimeAccessor], - breakdownBy: - state.trendlineBreakdownByAccessor && !state.collapseFn - ? [state.trendlineBreakdownByAccessor] - : [], - inspectorTableId: [state.trendlineLayerId], - ...(datasourceExpression - ? { - table: [ - { - ...datasourceExpression, - chain: [ - ...datasourceExpression.chain, - ...(state.collapseFn - ? [ - buildExpressionFunction('lens_collapse', { - by: [state.trendlineTimeAccessor], - metric: [state.trendlineMetricAccessor], - fn: [state.collapseFn], - }).toAst(), - ] - : []), - ], - }, - ], - } - : {}), + if (!datasourceExpression) { + return; + } + + const metricTrendlineFn = buildExpressionFunction( + 'metricTrendline', + { + metric: trendlineMetricAccessor, + timeField: trendlineTimeAccessor, + breakdownBy: + state.trendlineBreakdownByAccessor && !state.collapseFn + ? state.trendlineBreakdownByAccessor + : undefined, + inspectorTableId: trendlineLayerId, + table: [ + { + ...datasourceExpression, + chain: [ + ...datasourceExpression.chain, + ...(state.collapseFn + ? [ + buildExpressionFunction('lens_collapse', { + by: [trendlineTimeAccessor], + metric: [trendlineMetricAccessor], + fn: [state.collapseFn], + }).toAst(), + ] + : []), + ], }, - }, - ], - }; + ], + } + ); + return buildExpression([metricTrendlineFn]).toAst(); }; export const toExpression = ( @@ -135,38 +132,35 @@ export const toExpression = ( const trendlineExpression = getTrendlineExpression(state, datasourceExpressionsByLayers); + const metricFn = buildExpressionFunction('metricVis', { + metric: state.metricAccessor, + secondaryMetric: state.secondaryMetricAccessor, + secondaryPrefix: state.secondaryPrefix, + max: showingBar(state) ? state.maxAccessor : undefined, + breakdownBy: + state.breakdownByAccessor && !state.collapseFn ? state.breakdownByAccessor : undefined, + trendline: trendlineExpression ? [trendlineExpression] : [], + subtitle: state.subtitle ?? undefined, + progressDirection: state.progressDirection as LayoutDirection, + color: state.color || getDefaultColor(state), + palette: state.palette?.params + ? [ + paletteService + .get(CUSTOM_PALETTE) + .toExpression(computePaletteParams(state.palette.params as CustomPaletteParams)), + ] + : [], + maxCols: state.maxCols ?? DEFAULT_MAX_COLUMNS, + minTiles: maxPossibleTiles ?? undefined, + inspectorTableId: state.layerId, + }); + return { type: 'expression', chain: [ ...(datasourceExpression?.chain ?? []), ...(collapseExpressionFunction ? [collapseExpressionFunction] : []), - { - type: 'function', - function: EXPRESSION_METRIC_NAME, - arguments: { - metric: state.metricAccessor ? [state.metricAccessor] : [], - secondaryMetric: state.secondaryMetricAccessor ? [state.secondaryMetricAccessor] : [], - secondaryPrefix: - typeof state.secondaryPrefix !== 'undefined' ? [state.secondaryPrefix] : [], - max: showingBar(state) ? [state.maxAccessor] : [], - breakdownBy: - state.breakdownByAccessor && !state.collapseFn ? [state.breakdownByAccessor] : [], - trendline: trendlineExpression ? [trendlineExpression] : [], - subtitle: state.subtitle ? [state.subtitle] : [], - progressDirection: state.progressDirection ? [state.progressDirection] : [], - color: [state.color || getDefaultColor(state)], - palette: state.palette?.params - ? [ - paletteService - .get(CUSTOM_PALETTE) - .toExpression(computePaletteParams(state.palette.params as CustomPaletteParams)), - ] - : [], - maxCols: [state.maxCols ?? DEFAULT_MAX_COLUMNS], - minTiles: maxPossibleTiles ? [maxPossibleTiles] : [], - inspectorTableId: [state.layerId], - }, - }, + metricFn.toAst(), ], }; }; diff --git a/x-pack/plugins/lens/public/visualizations/metric/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/metric/visualization.test.ts index 2a89ef784492d..41e9fab67d4d6 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/metric/visualization.test.ts @@ -300,21 +300,18 @@ describe('metric visualization', () => { "chain": Array [ Object { "arguments": Object { - "breakdownBy": Array [], "color": Array [ "static-color", ], "inspectorTableId": Array [ "first", ], - "max": Array [], "maxCols": Array [ 5, ], "metric": Array [ "metric-col-id", ], - "minTiles": Array [], "palette": Array [ Object { "chain": Array [ @@ -370,7 +367,6 @@ describe('metric visualization', () => { "inspectorTableId": Array [ "first", ], - "max": Array [], "maxCols": Array [ 5, ], @@ -473,7 +469,6 @@ describe('metric visualization', () => { "chain": Array [ Object { "arguments": Object { - "breakdownBy": Array [], "inspectorTableId": Array [ "second", ], @@ -520,7 +515,6 @@ describe('metric visualization', () => { "chain": Array [ Object { "arguments": Object { - "breakdownBy": Array [], "inspectorTableId": Array [ "second", ], @@ -608,8 +602,8 @@ describe('metric visualization', () => { "type": "function", } `); - expect(ast.chain[1].arguments.minTiles).toHaveLength(0); - expect(ast.chain[1].arguments.breakdownBy).toHaveLength(0); + expect(ast.chain[1].arguments.minTiles).toBeUndefined(); + expect(ast.chain[1].arguments.breakdownBy).toBeUndefined(); }); it('always applies max function to static max dimensions', () => { From 9e19d09e37735307ef469751552e8b9a83ccc9ba Mon Sep 17 00:00:00 2001 From: Shahzad Date: Thu, 3 Nov 2022 08:52:09 +0100 Subject: [PATCH 52/86] [Synthetics] Step details page object count breakdown (#144409) --- .../components/color_palette.tsx | 9 ++- .../components/object_count_list.tsx | 57 +++++++++++++++++++ .../step_details_page/step_detail_page.tsx | 5 +- .../public/apps/synthetics/routes.tsx | 2 +- 4 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/object_count_list.tsx diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/color_palette.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/color_palette.tsx index bafd0de5aa48a..be6bc97a1fcae 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/color_palette.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/components/color_palette.tsx @@ -10,22 +10,27 @@ import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingContent } from '@elastic/ import { useTheme } from '@kbn/observability-plugin/public'; import styled from 'styled-components'; import { colourPalette } from './network_waterfall/step_detail/waterfall/data_formatting'; + export const ColorPalette = ({ label, mimeType, percent, value, loading, + labelWidth = 40, + valueWidth = 60, }: { label: string; mimeType: string; percent: number; value: string; loading: boolean; + labelWidth?: number; + valueWidth?: number; }) => { return ( - + {label} @@ -35,7 +40,7 @@ export const ColorPalette = ({ loading={loading} /> - + { + const objectMetrics = useObjectMetrics(); + + return ( + <> + + + +

        {OBJECT_COUNT_LABEL}

        +
        +
        + + + {TOTAL_LABEL}: {objectMetrics.totalObjects} + + +
        + +
        + {objectMetrics.items.map(({ label, mimeType, percent, count }) => ( + <> + + + + ))} +
        + + ); +}; + +const OBJECT_COUNT_LABEL = i18n.translate('xpack.synthetics.stepDetails.objectCount', { + defaultMessage: 'Object count', +}); + +const TOTAL_LABEL = i18n.translate('xpack.synthetics.stepDetails.total', { + defaultMessage: 'Total', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx index f0bb1b91d5a8e..999d805d6167c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_detail_page.tsx @@ -19,6 +19,7 @@ import { import { WaterfallChartContainer } from './components/network_waterfall/step_detail/waterfall/waterfall_chart_container'; import { ObjectWeightList } from './components/object_weight_list'; import { NetworkTimingsBreakdown } from './network_timings_breakdown'; +import { ObjectCountList } from './components/object_count_list'; import { StepImage } from './components/step_image'; import { useJourneySteps } from '../monitor_details/hooks/use_journey_steps'; import { MonitorDetailsLinkPortal } from '../monitor_add_edit/monitor_details_portal'; @@ -94,7 +95,9 @@ export const StepDetailPage = () => { - {/* TODO: Add breakdown of object weight*/} + + +
        diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx index 06b684dd30719..ebc7bd197e0f9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx @@ -47,8 +47,8 @@ import { MONITOR_ERRORS_ROUTE, MONITOR_HISTORY_ROUTE, MONITOR_ROUTE, - STEP_DETAIL_ROUTE, ERROR_DETAILS_ROUTE, + STEP_DETAIL_ROUTE, OVERVIEW_ROUTE, } from '../../../common/constants'; import { PLUGIN } from '../../../common/constants/plugin'; From cf152eae232f56cf295ce493a0061f1d0d6fa3be Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Thu, 3 Nov 2022 09:41:57 +0100 Subject: [PATCH 53/86] [Osquery] [PoC] Add alert attachment along with Osquery Results to Cases (#143674) --- .../public/cases/add_to_cases_button.tsx | 24 +++++++++++++++++-- .../osquery/public/common/contexts.tsx | 15 ++++++++++++ .../shared_components/lazy_osquery_action.tsx | 24 +++++++++++++++---- .../osquery_results/index.tsx | 2 ++ .../osquery_results/osquery_result.test.tsx | 3 +++ .../osquery_results/osquery_result.tsx | 7 +++--- .../osquery_results/osquery_results.test.tsx | 3 +++ .../osquery_results/test_utils.tsx | 1 + .../osquery_results/types.ts | 3 +++ .../event_details/event_details.tsx | 1 + .../components/event_details/osquery_tab.tsx | 18 +++++++++++--- .../timeline_actions/alert_context_menu.tsx | 1 + .../components/osquery/osquery_flyout.tsx | 7 +++++- .../event_details/flyout/footer.tsx | 1 + 14 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/osquery/public/common/contexts.tsx diff --git a/x-pack/plugins/osquery/public/cases/add_to_cases_button.tsx b/x-pack/plugins/osquery/public/cases/add_to_cases_button.tsx index 03b5bcebfed40..cbc3589df597c 100644 --- a/x-pack/plugins/osquery/public/cases/add_to_cases_button.tsx +++ b/x-pack/plugins/osquery/public/cases/add_to_cases_button.tsx @@ -5,12 +5,13 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useContext, useMemo } from 'react'; import { CommentType, ExternalReferenceStorageType } from '@kbn/cases-plugin/common'; import { EuiButtonEmpty, EuiButtonIcon, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public'; import { useKibana } from '../common/lib/kibana'; +import { AlertAttachmentContext } from '../common/contexts'; const ADD_TO_CASE = i18n.translate( 'xpack.osquery.pack.queriesTable.addToCaseResultsActionAriaLabel', @@ -37,6 +38,24 @@ export const AddToCaseButton: React.FC = ({ iconProps, }) => { const { cases } = useKibana().services; + const ecsData = useContext(AlertAttachmentContext); + const alertAttachments = useMemo( + () => + ecsData?._id + ? [ + { + alertId: ecsData?._id ?? '', + index: ecsData?._index ?? '', + rule: cases.helpers.getRuleIdFromEvent({ + ecs: ecsData, + data: [], + }), + type: CommentType.alert as const, + }, + ] + : [], + [cases.helpers, ecsData] + ); const casePermissions = cases.helpers.canUseCases(); const hasCasesPermissions = @@ -45,6 +64,7 @@ export const AddToCaseButton: React.FC = ({ const handleClick = useCallback(() => { const attachments: CaseAttachmentsWithoutOwner = [ + ...alertAttachments, { type: CommentType.externalReference, externalReferenceId: actionId, @@ -58,7 +78,7 @@ export const AddToCaseButton: React.FC = ({ if (hasCasesPermissions) { selectCaseModal.open({ attachments }); } - }, [actionId, agentIds, hasCasesPermissions, queryId, selectCaseModal]); + }, [actionId, agentIds, alertAttachments, hasCasesPermissions, queryId, selectCaseModal]); if (isIcon) { return ( diff --git a/x-pack/plugins/osquery/public/common/contexts.tsx b/x-pack/plugins/osquery/public/common/contexts.tsx new file mode 100644 index 0000000000000..e035e08710a91 --- /dev/null +++ b/x-pack/plugins/osquery/public/common/contexts.tsx @@ -0,0 +1,15 @@ +/* + * 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'; + +export interface AlertEcsData { + _id: string; + _index?: string; +} + +export const AlertAttachmentContext = React.createContext(null); diff --git a/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action.tsx b/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action.tsx index ff464e7782bb7..15ffbdee14c4a 100644 --- a/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action.tsx +++ b/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action.tsx @@ -5,21 +5,35 @@ * 2.0. */ -import React, { lazy, Suspense } from 'react'; +import React, { lazy, Suspense, useMemo } from 'react'; import ServicesWrapper from './services_wrapper'; import type { ServicesWrapperProps } from './services_wrapper'; import type { OsqueryActionProps } from './osquery_action'; +import type { AlertEcsData } from '../common/contexts'; +import { AlertAttachmentContext } from '../common/contexts'; export const getLazyOsqueryAction = + (services: ServicesWrapperProps['services']) => // eslint-disable-next-line react/display-name - (services: ServicesWrapperProps['services']) => (props: OsqueryActionProps) => { + (props: OsqueryActionProps & { ecsData?: AlertEcsData }) => { const OsqueryAction = lazy(() => import('./osquery_action')); + const { ecsData, ...restProps } = props; + const renderAction = useMemo(() => { + if (ecsData && ecsData?._id) { + return ( + + + + ); + } + + return ; + }, [OsqueryAction, ecsData, restProps]); + return ( - - - + {renderAction} ); }; diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_results/index.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_results/index.tsx index 84dc4203b2924..36c09a2bce188 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_results/index.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_results/index.tsx @@ -24,6 +24,7 @@ const OsqueryActionResultsComponent: React.FC = ({ agentIds, ruleName, alertId, + ecsData, }) => { const { data: actionsData } = useAllLiveQueries({ filterQuery: { term: { alert_ids: alertId } }, @@ -49,6 +50,7 @@ const OsqueryActionResultsComponent: React.FC = ({ startDate={startDate} ruleName={ruleName} agentIds={agentIds} + ecsData={ecsData} /> ); })} diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.test.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.test.tsx index 772df753c8978..d4573eb291d89 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.test.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.test.tsx @@ -41,6 +41,9 @@ const defaultProps = { actionId: 'test-action-id', startDate: DETAILS_TIMESTAMP, queryId: '', + ecsData: { + _id: 'test', + }, }; const mockKibana = (permissionType: unknown = defaultPermissions) => { const mockedKibana = getMockedKibanaConfig(permissionType); diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.tsx index 333e09efce1fb..c48e75c44ba82 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_result.tsx @@ -13,6 +13,7 @@ import type { OsqueryActionResultsProps } from './types'; import { useLiveQueryDetails } from '../../actions/use_live_query_details'; import { ATTACHED_QUERY } from '../../agents/translations'; import { PackQueriesStatusTable } from '../../live_queries/form/pack_queries_status_table'; +import { AlertAttachmentContext } from '../../common/contexts'; interface OsqueryResultProps extends Omit { actionId: string; @@ -21,13 +22,13 @@ interface OsqueryResultProps extends Omit } export const OsqueryResult = React.memo( - ({ actionId, ruleName, agentIds, startDate }) => { + ({ actionId, ruleName, agentIds, startDate, ecsData }) => { const { data } = useLiveQueryDetails({ actionId, }); return ( -
        + ( /> -
        + ); } ); diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_results.test.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_results.test.tsx index 5190d10dc1c7e..fcfa6b1a720a3 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_results.test.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_results/osquery_results.test.tsx @@ -36,6 +36,9 @@ const defaultProps = { ruleName: ['Test-rule'], ruleActions: [{ action_type_id: 'action1' }, { action_type_id: 'action2' }], alertId: 'test-alert-id', + ecsData: { + _id: 'test', + }, }; const defaultPermissions = { diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_results/test_utils.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_results/test_utils.tsx index c1c991becc8ac..262a68a0b259c 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_results/test_utils.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_results/test_utils.tsx @@ -52,6 +52,7 @@ export const getMockedKibanaConfig = (permissionType: unknown) => update: true, push: true, })), + getRuleIdFromEvent: jest.fn(), }, ui: { getCasesContext: jest.fn().mockImplementation(() => mockCasesContext), diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_results/types.ts b/x-pack/plugins/osquery/public/shared_components/osquery_results/types.ts index 7d8d268be2830..cb4b11651ee45 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_results/types.ts +++ b/x-pack/plugins/osquery/public/shared_components/osquery_results/types.ts @@ -5,8 +5,11 @@ * 2.0. */ +import type { AlertEcsData } from '../../common/contexts'; + export interface OsqueryActionResultsProps { agentIds?: string[]; ruleName?: string[]; alertId: string; + ecsData: AlertEcsData; } diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index edfe3e70e6ca0..38cdaf583fa13 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -393,6 +393,7 @@ const EventDetailsComponent: React.FC = ({ const osqueryTab = useOsqueryTab({ rawEventData: rawEventData as AlertRawEventData, + ...(detailsEcsData !== null ? { ecsData: detailsEcsData } : {}), }); const tabs = useMemo(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx index ed2551b94422a..acadbd6746d70 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx @@ -15,6 +15,7 @@ import { import React, { useMemo } from 'react'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n-react'; +import type { Ecs } from '../../../../common/ecs'; import { PERMISSION_DENIED } from '../../../detection_engine/rule_response_actions/osquery/translations'; import { expandDottedObject } from '../../../../common/utils/expand_dotted'; import { RESPONSE_ACTION_TYPES } from '../../../../common/detection_engine/rule_response_actions/schemas'; @@ -59,7 +60,13 @@ interface ExpandedEventFieldsObject { }; } -export const useOsqueryTab = ({ rawEventData }: { rawEventData?: AlertRawEventData }) => { +export const useOsqueryTab = ({ + rawEventData, + ecsData, +}: { + rawEventData?: AlertRawEventData; + ecsData?: Ecs; +}) => { const { services: { osquery, application }, } = useKibana(); @@ -87,7 +94,7 @@ export const useOsqueryTab = ({ rawEventData }: { rawEventData?: AlertRawEventDa [] ); - if (!osquery || !rawEventData || !responseActionsEnabled) { + if (!osquery || !rawEventData || !responseActionsEnabled || !ecsData) { return; } @@ -137,7 +144,12 @@ export const useOsqueryTab = ({ rawEventData }: { rawEventData?: AlertRawEventDa {!application?.capabilities?.osquery?.read ? ( emptyPrompt ) : ( - + )} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 40d6feda3b925..31a548e2ced9c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -283,6 +283,7 @@ const AlertContextMenuComponent: React.FC )} diff --git a/x-pack/plugins/security_solution/public/detections/components/osquery/osquery_flyout.tsx b/x-pack/plugins/security_solution/public/detections/components/osquery/osquery_flyout.tsx index cd35752d56310..00dbe210e7026 100644 --- a/x-pack/plugins/security_solution/public/detections/components/osquery/osquery_flyout.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/osquery/osquery_flyout.tsx @@ -8,6 +8,7 @@ import React from 'react'; import styled from 'styled-components'; import { EuiFlyout, EuiFlyoutFooter, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; +import type { Ecs } from '../../../../common/ecs'; import { useKibana } from '../../../common/lib/kibana'; import { OsqueryEventDetailsFooter } from './osquery_flyout_footer'; import { ACTION_OSQUERY } from './translations'; @@ -20,11 +21,14 @@ export interface OsqueryFlyoutProps { agentId?: string; defaultValues?: {}; onClose: () => void; + ecsData?: Ecs; } -export const OsqueryFlyoutComponent: React.FC = ({ + +const OsqueryFlyoutComponent: React.FC = ({ agentId, defaultValues, onClose, + ecsData, }) => { const { services: { osquery }, @@ -49,6 +53,7 @@ export const OsqueryFlyoutComponent: React.FC = ({ agentId={agentId} formType="steps" defaultValues={defaultValues} + ecsData={ecsData} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx index d372c8bca2de5..5a7b0c9530213 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx @@ -195,6 +195,7 @@ export const FlyoutFooterComponent = React.memo( agentId={isOsqueryFlyoutOpenWithAgentId} defaultValues={alertId ? { alertIds: [alertId] } : undefined} onClose={closeOsqueryFlyout} + ecsData={detailsEcsData} /> )} From 4a44fd31bb6f5f300c4f9e9bc67764803f3b21f3 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Thu, 3 Nov 2022 10:11:43 +0100 Subject: [PATCH 54/86] [Files] Copy updates and file picker single select (#144398) * implement copy updates * move rename file and update empty state copy * update title to respect multi select vs single select * added test for single select behaviour * implement single select behaviour * introduce and use multiple prop, default true * pass multiple flag to state * pass multiple upload flag to UI * added single select story * update files example to still support multiple select * uploadMultiple -> selectMultiple * remove use of non-existent i18n * update filepicker react component tests --- .../public/components/file_picker.tsx | 1 + .../{upload_files.tsx => empty_prompt.tsx} | 11 +++---- .../file_picker/components/modal_footer.tsx | 5 +-- .../file_picker/components/title.tsx | 8 +++-- .../public/components/file_picker/context.tsx | 6 ++-- .../file_picker/file_picker.stories.tsx | 23 +++++++++++++ .../file_picker/file_picker.test.tsx | 2 +- .../components/file_picker/file_picker.tsx | 32 ++++++++++++++----- .../file_picker/file_picker_state.test.ts | 17 ++++++++++ .../file_picker/file_picker_state.ts | 19 +++++++++-- .../components/file_picker/i18n_texts.ts | 12 +++---- 11 files changed, 105 insertions(+), 31 deletions(-) rename src/plugins/files/public/components/file_picker/components/{upload_files.tsx => empty_prompt.tsx} (80%) diff --git a/examples/files_example/public/components/file_picker.tsx b/examples/files_example/public/components/file_picker.tsx index 72a2057755742..220ca917b78b6 100644 --- a/examples/files_example/public/components/file_picker.tsx +++ b/examples/files_example/public/components/file_picker.tsx @@ -27,6 +27,7 @@ export const MyFilePicker: FunctionComponent = ({ onClose, onDone, onUplo onDone={onDone} onUpload={(n) => onUpload(n.map(({ id }) => id))} pageSize={50} + multiple /> ); }; diff --git a/src/plugins/files/public/components/file_picker/components/upload_files.tsx b/src/plugins/files/public/components/file_picker/components/empty_prompt.tsx similarity index 80% rename from src/plugins/files/public/components/file_picker/components/upload_files.tsx rename to src/plugins/files/public/components/file_picker/components/empty_prompt.tsx index a33f458bdf9d2..84b52281805ad 100644 --- a/src/plugins/files/public/components/file_picker/components/upload_files.tsx +++ b/src/plugins/files/public/components/file_picker/components/empty_prompt.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { EuiEmptyPrompt, EuiText } from '@elastic/eui'; +import { EuiEmptyPrompt } from '@elastic/eui'; import type { FunctionComponent } from 'react'; import { UploadFile } from '../../upload_file'; import { useFilePickerContext } from '../context'; @@ -15,25 +15,22 @@ import { i18nTexts } from '../i18n_texts'; interface Props { kind: string; + multiple: boolean; } -export const UploadFilesPrompt: FunctionComponent = ({ kind }) => { +export const EmptyPrompt: FunctionComponent = ({ kind, multiple }) => { const { state } = useFilePickerContext(); return ( {i18nTexts.emptyStatePrompt}} - body={ - -

        {i18nTexts.emptyStatePromptSubtitle}

        -
        - } titleSize="s" actions={[ // TODO: We can remove this once the entire modal is an upload area { state.selectFile(file.map(({ id }) => id)); state.retry(); diff --git a/src/plugins/files/public/components/file_picker/components/modal_footer.tsx b/src/plugins/files/public/components/file_picker/components/modal_footer.tsx index 0a9ad3b3dcafa..d5d60fbd7cc8b 100644 --- a/src/plugins/files/public/components/file_picker/components/modal_footer.tsx +++ b/src/plugins/files/public/components/file_picker/components/modal_footer.tsx @@ -22,9 +22,10 @@ interface Props { kind: string; onDone: SelectButtonProps['onClick']; onUpload?: FilePickerProps['onUpload']; + multiple: boolean; } -export const ModalFooter: FunctionComponent = ({ kind, onDone, onUpload }) => { +export const ModalFooter: FunctionComponent = ({ kind, onDone, onUpload, multiple }) => { const { state } = useFilePickerContext(); const onUploadStart = useCallback(() => state.setIsUploading(true), [state]); const onUploadEnd = useCallback(() => state.setIsUploading(false), [state]); @@ -53,7 +54,7 @@ export const ModalFooter: FunctionComponent = ({ kind, onDone, onUpload } onUploadEnd={onUploadEnd} kind={kind} initialPromptText={i18nTexts.uploadFilePlaceholderText} - multiple + multiple={multiple} compressed /> diff --git a/src/plugins/files/public/components/file_picker/components/title.tsx b/src/plugins/files/public/components/file_picker/components/title.tsx index 2d4de8881fa76..98ce9b0bc93d1 100644 --- a/src/plugins/files/public/components/file_picker/components/title.tsx +++ b/src/plugins/files/public/components/file_picker/components/title.tsx @@ -11,8 +11,12 @@ import type { FunctionComponent } from 'react'; import { EuiTitle } from '@elastic/eui'; import { i18nTexts } from '../i18n_texts'; -export const Title: FunctionComponent = () => ( +interface Props { + multiple: boolean; +} + +export const Title: FunctionComponent = ({ multiple }) => ( -

        {i18nTexts.title}

        +

        {multiple ? i18nTexts.titleMultiple : i18nTexts.title}

        ); diff --git a/src/plugins/files/public/components/file_picker/context.tsx b/src/plugins/files/public/components/file_picker/context.tsx index 48d0ea3986253..c17fe601e487a 100644 --- a/src/plugins/files/public/components/file_picker/context.tsx +++ b/src/plugins/files/public/components/file_picker/context.tsx @@ -23,17 +23,19 @@ const FilePickerCtx = createContext( interface FilePickerContextProps { kind: string; pageSize: number; + multiple: boolean; } export const FilePickerContext: FunctionComponent = ({ kind, pageSize, + multiple, children, }) => { const filesContext = useFilesContext(); const { client } = filesContext; const state = useMemo( - () => createFilePickerState({ pageSize, client, kind }), - [pageSize, client, kind] + () => createFilePickerState({ pageSize, client, kind, selectMultiple: multiple }), + [pageSize, client, kind, multiple] ); useEffect(() => state.dispose, [state]); return ( diff --git a/src/plugins/files/public/components/file_picker/file_picker.stories.tsx b/src/plugins/files/public/components/file_picker/file_picker.stories.tsx index 517663ebbbbbe..1d38a9893ed57 100644 --- a/src/plugins/files/public/components/file_picker/file_picker.stories.tsx +++ b/src/plugins/files/public/components/file_picker/file_picker.stories.tsx @@ -27,6 +27,7 @@ const defaultProps: FilePickerProps = { kind, onDone: action('done!'), onClose: action('close!'), + multiple: true, }; export default { @@ -198,3 +199,25 @@ TryFilter.decorators = [ ); }, ]; + +export const SingleSelect = Template.bind({}); +SingleSelect.decorators = [ + (Story) => ( + `data:image/png;base64,${base64dLogo}`, + list: async (): Promise => ({ + files: [createFileJSON(), createFileJSON(), createFileJSON()], + total: 1, + }), + } as unknown as FilesClient + } + > + + + ), +]; +SingleSelect.args = { + multiple: undefined, +}; diff --git a/src/plugins/files/public/components/file_picker/file_picker.test.tsx b/src/plugins/files/public/components/file_picker/file_picker.test.tsx index 58c86739755a8..055eea0f4d5ab 100644 --- a/src/plugins/files/public/components/file_picker/file_picker.test.tsx +++ b/src/plugins/files/public/components/file_picker/file_picker.test.tsx @@ -30,7 +30,7 @@ describe('FilePicker', () => { async function initTestBed(props?: Partial) { const createTestBed = registerTestBed((p: Props) => ( - + )); diff --git a/src/plugins/files/public/components/file_picker/file_picker.tsx b/src/plugins/files/public/components/file_picker/file_picker.tsx index 9472f87f7b912..94616a67a85cb 100644 --- a/src/plugins/files/public/components/file_picker/file_picker.tsx +++ b/src/plugins/files/public/components/file_picker/file_picker.tsx @@ -24,7 +24,7 @@ import { useFilePickerContext, FilePickerContext } from './context'; import { Title } from './components/title'; import { ErrorContent } from './components/error_content'; -import { UploadFilesPrompt } from './components/upload_files'; +import { EmptyPrompt } from './components/empty_prompt'; import { FileGrid } from './components/file_grid'; import { SearchField } from './components/search_field'; import { ModalFooter } from './components/modal_footer'; @@ -53,9 +53,17 @@ export interface Props { * The number of results to show per page. */ pageSize?: number; + /** + * Whether you can select one or more files + * + * @default false + */ + multiple?: boolean; } -const Component: FunctionComponent = ({ onClose, onDone, onUpload }) => { +type InnerProps = Required>; + +const Component: FunctionComponent = ({ onClose, onDone, onUpload, multiple }) => { const { state, kind } = useFilePickerContext(); const hasFiles = useBehaviorSubject(state.hasFiles$); @@ -65,7 +73,9 @@ const Component: FunctionComponent = ({ onClose, onDone, onUpload }) => { useObservable(state.files$); - const renderFooter = () => ; + const renderFooter = () => ( + + ); return ( = ({ onClose, onDone, onUpload }) => { onClose={onClose} > - + <Title multiple={multiple} /> <SearchField /> </EuiModalHeader> {isLoading ? ( @@ -93,7 +103,7 @@ const Component: FunctionComponent<Props> = ({ onClose, onDone, onUpload }) => { </EuiModalBody> ) : !hasFiles && !hasQuery ? ( <EuiModalBody> - <UploadFilesPrompt kind={kind} /> + <EmptyPrompt multiple={multiple} kind={kind} /> </EuiModalBody> ) : ( <> @@ -109,9 +119,15 @@ const Component: FunctionComponent<Props> = ({ onClose, onDone, onUpload }) => { ); }; -export const FilePicker: FunctionComponent<Props> = (props) => ( - <FilePickerContext pageSize={props.pageSize ?? 20} kind={props.kind}> - <Component {...props} /> +export const FilePicker: FunctionComponent<Props> = ({ + pageSize = 20, + kind, + multiple = false, + onUpload = () => {}, + ...rest +}) => ( + <FilePickerContext pageSize={pageSize} kind={kind} multiple={multiple}> + <Component {...rest} {...{ pageSize, kind, multiple, onUpload }} /> </FilePickerContext> ); diff --git a/src/plugins/files/public/components/file_picker/file_picker_state.test.ts b/src/plugins/files/public/components/file_picker/file_picker_state.test.ts index 0e7971e60bdcc..62881fa042a84 100644 --- a/src/plugins/files/public/components/file_picker/file_picker_state.test.ts +++ b/src/plugins/files/public/components/file_picker/file_picker_state.test.ts @@ -32,6 +32,7 @@ describe('FilePickerState', () => { client: filesClient, pageSize: 20, kind: 'test', + selectMultiple: true, }); }); it('starts off empty', () => { @@ -181,4 +182,20 @@ describe('FilePickerState', () => { expectObservable(filePickerState.files$).toBe('a------', { a: [] }); }); }); + describe('single selection', () => { + beforeEach(() => { + filePickerState = createFilePickerState({ + client: filesClient, + pageSize: 20, + kind: 'test', + selectMultiple: false, + }); + }); + it('allows only one file to be selected', () => { + filePickerState.selectFile('a'); + expect(filePickerState.getSelectedFileIds()).toEqual(['a']); + filePickerState.selectFile(['b', 'a', 'c']); + expect(filePickerState.getSelectedFileIds()).toEqual(['b']); + }); + }); }); diff --git a/src/plugins/files/public/components/file_picker/file_picker_state.ts b/src/plugins/files/public/components/file_picker/file_picker_state.ts index 42214f77c9cf2..a47268585a420 100644 --- a/src/plugins/files/public/components/file_picker/file_picker_state.ts +++ b/src/plugins/files/public/components/file_picker/file_picker_state.ts @@ -56,7 +56,8 @@ export class FilePickerState { constructor( private readonly client: FilesClient, private readonly kind: string, - public readonly pageSize: number + public readonly pageSize: number, + private selectMultiple: boolean ) { this.subscriptions = [ this.query$ @@ -105,8 +106,18 @@ export class FilePickerState { this.internalIsLoading$.next(value); } + /** + * If multiple selection is not configured, this will take the first file id + * if an array of file ids was provided. + */ public selectFile = (fileId: string | string[]): void => { - (Array.isArray(fileId) ? fileId : [fileId]).forEach((id) => this.fileSet.add(id)); + const fileIds = Array.isArray(fileId) ? fileId : [fileId]; + if (!this.selectMultiple) { + this.fileSet.clear(); + this.fileSet.add(fileIds[0]); + } else { + for (const id of fileIds) this.fileSet.add(id); + } this.sendNextSelectedFiles(); }; @@ -216,11 +227,13 @@ interface CreateFilePickerArgs { client: FilesClient; kind: string; pageSize: number; + selectMultiple: boolean; } export const createFilePickerState = ({ pageSize, client, kind, + selectMultiple, }: CreateFilePickerArgs): FilePickerState => { - return new FilePickerState(client, kind, pageSize); + return new FilePickerState(client, kind, pageSize, selectMultiple); }; diff --git a/src/plugins/files/public/components/file_picker/i18n_texts.ts b/src/plugins/files/public/components/file_picker/i18n_texts.ts index 0958e99cdffe1..9bc4b4642cd68 100644 --- a/src/plugins/files/public/components/file_picker/i18n_texts.ts +++ b/src/plugins/files/public/components/file_picker/i18n_texts.ts @@ -12,17 +12,17 @@ export const i18nTexts = { title: i18n.translate('files.filePicker.title', { defaultMessage: 'Select a file', }), + titleMultiple: i18n.translate('files.filePicker.titleMultiple', { + defaultMessage: 'Select files', + }), loadingFilesErrorTitle: i18n.translate('files.filePicker.error.loadingTitle', { defaultMessage: 'Could not load files', }), retryButtonLabel: i18n.translate('files.filePicker.error.retryButtonLabel', { defaultMessage: 'Retry', }), - emptyStatePrompt: i18n.translate('files.filePicker.emptyStatePrompt', { - defaultMessage: 'No files found', - }), - emptyStatePromptSubtitle: i18n.translate('files.filePicker.emptyStatePromptSubtitle', { - defaultMessage: 'Upload your first file.', + emptyStatePrompt: i18n.translate('files.filePicker.emptyStatePromptTitle', { + defaultMessage: 'Upload your first file', }), selectFileLabel: i18n.translate('files.filePicker.selectFileButtonLable', { defaultMessage: 'Select file', @@ -36,7 +36,7 @@ export const i18nTexts = { defaultMessage: 'my-file-*', }), emptyFileGridPrompt: i18n.translate('files.filePicker.emptyGridPrompt', { - defaultMessage: 'No files matched filter', + defaultMessage: 'No files match your filter', }), loadMoreButtonLabel: i18n.translate('files.filePicker.loadMoreButtonLabel', { defaultMessage: 'Load more', From 6482b22d1e3969865352eba15eb0797097654d91 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko <dzmitry.lemechko@elastic.co> Date: Thu, 3 Nov 2022 10:30:14 +0100 Subject: [PATCH 55/86] Performance journeys: log how many visualisations were loaded / rendered out of expected count (#144422) * [performance/utils] extend error message for waitForVisualizations * revert test change * remove new line Co-authored-by: Liza Katz <liza.katz@elastic.co> --- .../journeys/data_stress_test_lens.ts | 4 +-- .../journeys/ecommerce_dashboard.ts | 4 +-- .../performance/journeys/flight_dashboard.ts | 4 +-- .../journeys/many_fields_discover.ts | 1 - .../journeys/promotion_tracking_dashboard.ts | 4 +-- .../journeys/web_logs_dashboard.ts | 4 +-- x-pack/performance/utils.ts | 25 +++++++++++++------ 7 files changed, 27 insertions(+), 19 deletions(-) diff --git a/x-pack/performance/journeys/data_stress_test_lens.ts b/x-pack/performance/journeys/data_stress_test_lens.ts index bc27506afabb2..cf9a4c44e930b 100644 --- a/x-pack/performance/journeys/data_stress_test_lens.ts +++ b/x-pack/performance/journeys/data_stress_test_lens.ts @@ -11,8 +11,8 @@ import { waitForVisualizations } from '../utils'; export const journey = new Journey({ kbnArchives: ['test/functional/fixtures/kbn_archiver/stress_test'], esArchives: ['test/functional/fixtures/es_archiver/stress_test'], -}).step('Go to dashboard', async ({ page, kbnUrl, kibanaServer }) => { +}).step('Go to dashboard', async ({ page, kbnUrl, kibanaServer, log }) => { await kibanaServer.uiSettings.update({ 'histogram:maxBars': 100 }); await page.goto(kbnUrl.get(`/app/dashboards#/view/92b143a0-2e9c-11ed-b1b6-a504560b392c`)); - await waitForVisualizations(page, 1); + await waitForVisualizations(page, log, 1); }); diff --git a/x-pack/performance/journeys/ecommerce_dashboard.ts b/x-pack/performance/journeys/ecommerce_dashboard.ts index b9c107cd12cbd..45a54fffd7620 100644 --- a/x-pack/performance/journeys/ecommerce_dashboard.ts +++ b/x-pack/performance/journeys/ecommerce_dashboard.ts @@ -19,7 +19,7 @@ export const journey = new Journey({ await page.waitForSelector('#dashboardListingHeading'); }) - .step('Go to Ecommerce Dashboard', async ({ page }) => { + .step('Go to Ecommerce Dashboard', async ({ page, log }) => { await page.click(subj('dashboardListingTitleLink-[eCommerce]-Revenue-Dashboard')); - await waitForVisualizations(page, 13); + await waitForVisualizations(page, log, 13); }); diff --git a/x-pack/performance/journeys/flight_dashboard.ts b/x-pack/performance/journeys/flight_dashboard.ts index 46030dd47d2fb..d6cfa0d8acdeb 100644 --- a/x-pack/performance/journeys/flight_dashboard.ts +++ b/x-pack/performance/journeys/flight_dashboard.ts @@ -19,7 +19,7 @@ export const journey = new Journey({ await page.waitForSelector('#dashboardListingHeading'); }) - .step('Go to Flights Dashboard', async ({ page }) => { + .step('Go to Flights Dashboard', async ({ page, log }) => { await page.click(subj('dashboardListingTitleLink-[Flights]-Global-Flight-Dashboard')); - await waitForVisualizations(page, 14); + await waitForVisualizations(page, log, 14); }); diff --git a/x-pack/performance/journeys/many_fields_discover.ts b/x-pack/performance/journeys/many_fields_discover.ts index 0e57ed69ead00..7fc2ef20a2ab5 100644 --- a/x-pack/performance/journeys/many_fields_discover.ts +++ b/x-pack/performance/journeys/many_fields_discover.ts @@ -17,7 +17,6 @@ export const journey = new Journey({ }) .step('Go to Discover Page', async ({ page, kbnUrl }) => { await page.goto(kbnUrl.get(`/app/discover`)); - await waitForChrome(page); await page.waitForSelector(subj('discoverDocTable')); }) diff --git a/x-pack/performance/journeys/promotion_tracking_dashboard.ts b/x-pack/performance/journeys/promotion_tracking_dashboard.ts index 6b7872f76c00a..792a4dab88176 100644 --- a/x-pack/performance/journeys/promotion_tracking_dashboard.ts +++ b/x-pack/performance/journeys/promotion_tracking_dashboard.ts @@ -50,6 +50,6 @@ export const journey = new Journey({ await page.click(subj('superDatePickerCommonlyUsed_Last_30 days')); }) - .step('Wait for visualization animations to finish', async ({ page }) => { - await waitForVisualizations(page, 1); + .step('Wait for visualization animations to finish', async ({ page, log }) => { + await waitForVisualizations(page, log, 1); }); diff --git a/x-pack/performance/journeys/web_logs_dashboard.ts b/x-pack/performance/journeys/web_logs_dashboard.ts index c86d23b4fd9c6..e4d4c66d272f6 100644 --- a/x-pack/performance/journeys/web_logs_dashboard.ts +++ b/x-pack/performance/journeys/web_logs_dashboard.ts @@ -19,7 +19,7 @@ export const journey = new Journey({ await page.waitForSelector('#dashboardListingHeading'); }) - .step('Go to Web Logs Dashboard', async ({ page }) => { + .step('Go to Web Logs Dashboard', async ({ page, log }) => { await page.click(subj('dashboardListingTitleLink-[Logs]-Web-Traffic')); - await waitForVisualizations(page, 11); + await waitForVisualizations(page, log, 11); }); diff --git a/x-pack/performance/utils.ts b/x-pack/performance/utils.ts index 1ec26cf5706cb..16c997c2166fa 100644 --- a/x-pack/performance/utils.ts +++ b/x-pack/performance/utils.ts @@ -5,19 +5,28 @@ * 2.0. */ +import { ToolingLog } from '@kbn/tooling-log'; import { Page } from 'playwright'; export async function waitForChrome(page: Page) { return page.waitForSelector('.headerGlobalNav', { state: 'attached' }); } -export async function waitForVisualizations(page: Page, visCount: number) { - return await page.waitForFunction(function renderCompleted(cnt) { - const visualizations = Array.from(document.querySelectorAll('[data-rendering-count]')); - const visualizationElementsLoaded = visualizations.length === cnt; - const visualizationAnimationsFinished = visualizations.every( - (e) => e.getAttribute('data-render-complete') === 'true' +export async function waitForVisualizations(page: Page, log: ToolingLog, visCount: number) { + try { + await page.waitForFunction(function renderCompleted(cnt) { + const visualizations = Array.from(document.querySelectorAll('[data-rendering-count]')); + const allVisLoaded = visualizations.length === cnt; + return allVisLoaded + ? visualizations.every((e) => e.getAttribute('data-render-complete') === 'true') + : false; + }, visCount); + } catch (err) { + const loadedVis = await page.$$('[data-rendering-count]'); + const renderedVis = await page.$$('[data-rendering-count][data-render-complete="true"]'); + log.error( + `'waitForVisualizations' failed: loaded - ${loadedVis.length}, rendered - ${renderedVis.length}, expected - ${visCount}` ); - return visualizationElementsLoaded && visualizationAnimationsFinished; - }, visCount); + throw err; + } } From 2594349660fbad8340db9fa83fbf633754a037c4 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger <walter.rafelsberger@elastic.co> Date: Thu, 3 Nov 2022 10:43:44 +0100 Subject: [PATCH 56/86] [ML] Explain Log Rate Spikes: Fix applying overall params to histogram queries. (#144219) Applying the overall params like the time range to the histogram queries was missing. This fixes it by creating getHistogramQuery that can be applied when fetching the histogram for overall data, individual field/value histograms and group histograms. --- .../server/routes/explain_log_rate_spikes.ts | 31 ++++---- .../queries/get_histogram_query.test.ts | 72 +++++++++++++++++++ .../routes/queries/get_histogram_query.ts | 41 +++++++++++ 3 files changed, 128 insertions(+), 16 deletions(-) create mode 100644 x-pack/plugins/aiops/server/routes/queries/get_histogram_query.test.ts create mode 100644 x-pack/plugins/aiops/server/routes/queries/get_histogram_query.ts diff --git a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts index 320fd557bfb02..1e1c85d5e4b30 100644 --- a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts +++ b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts @@ -45,6 +45,7 @@ import { groupDuplicates, } from './queries/fetch_frequent_items'; import type { ItemsetResult } from './queries/fetch_frequent_items'; +import { getHistogramQuery } from './queries/get_histogram_query'; import { getFieldValuePairCounts, getSimpleHierarchicalTree, @@ -321,12 +322,15 @@ export const defineExplainLogRateSpikesRoute = ( logDebugMessage('Fetch overall histogram.'); let overallTimeSeries: NumericChartData | undefined; + + const overallHistogramQuery = getHistogramQuery(request.body); + try { overallTimeSeries = ( (await fetchHistogramsForFields( client, request.body.index, - { match_all: {} }, + overallHistogramQuery, // fields histogramFields, // samplerShardSize @@ -579,13 +583,12 @@ export const defineExplainLogRateSpikesRoute = ( await asyncForEach(changePointGroupsChunk, async (cpg) => { if (overallTimeSeries !== undefined) { - const histogramQuery = { - bool: { - filter: cpg.group.map((d) => ({ - term: { [d.fieldName]: d.fieldValue }, - })), - }, - }; + const histogramQuery = getHistogramQuery( + request.body, + cpg.group.map((d) => ({ + term: { [d.fieldName]: d.fieldValue }, + })) + ); let cpgTimeSeries: NumericChartData; try { @@ -675,15 +678,11 @@ export const defineExplainLogRateSpikesRoute = ( await asyncForEach(changePointsChunk, async (cp) => { if (overallTimeSeries !== undefined) { - const histogramQuery = { - bool: { - filter: [ - { - term: { [cp.fieldName]: cp.fieldValue }, - }, - ], + const histogramQuery = getHistogramQuery(request.body, [ + { + term: { [cp.fieldName]: cp.fieldValue }, }, - }; + ]); let cpTimeSeries: NumericChartData; diff --git a/x-pack/plugins/aiops/server/routes/queries/get_histogram_query.test.ts b/x-pack/plugins/aiops/server/routes/queries/get_histogram_query.test.ts new file mode 100644 index 0000000000000..95113d39f41c8 --- /dev/null +++ b/x-pack/plugins/aiops/server/routes/queries/get_histogram_query.test.ts @@ -0,0 +1,72 @@ +/* + * 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 { getHistogramQuery } from './get_histogram_query'; + +const paramsMock = { + index: 'the-index', + timeFieldName: 'the-time-field-name', + start: 1577836800000, + end: 1609459200000, + baselineMin: 10, + baselineMax: 20, + deviationMin: 30, + deviationMax: 40, + includeFrozen: false, + searchQuery: '{"bool":{"filter":[],"must":[{"match_all":{}}],"must_not":[]}}', +}; + +describe('getHistogramQuery', () => { + it('returns histogram query without additional filters', () => { + const query = getHistogramQuery(paramsMock); + expect(query).toEqual({ + bool: { + filter: [ + { bool: { filter: [], must: [{ match_all: {} }], must_not: [] } }, + { + range: { + 'the-time-field-name': { + format: 'epoch_millis', + gte: 1577836800000, + lte: 1609459200000, + }, + }, + }, + ], + }, + }); + }); + + it('returns histogram query with additional filters', () => { + const query = getHistogramQuery(paramsMock, [ + { + term: { ['the-filter-fieldName']: 'the-filter-fieldValue' }, + }, + ]); + expect(query).toEqual({ + bool: { + filter: [ + { bool: { filter: [], must: [{ match_all: {} }], must_not: [] } }, + { + term: { + 'the-filter-fieldName': 'the-filter-fieldValue', + }, + }, + { + range: { + 'the-time-field-name': { + format: 'epoch_millis', + gte: 1577836800000, + lte: 1609459200000, + }, + }, + }, + ], + }, + }); + }); +}); diff --git a/x-pack/plugins/aiops/server/routes/queries/get_histogram_query.ts b/x-pack/plugins/aiops/server/routes/queries/get_histogram_query.ts new file mode 100644 index 0000000000000..f8e7138699fc4 --- /dev/null +++ b/x-pack/plugins/aiops/server/routes/queries/get_histogram_query.ts @@ -0,0 +1,41 @@ +/* + * 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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import type { AiopsExplainLogRateSpikesSchema } from '../../../common/api/explain_log_rate_spikes'; + +import { getQueryWithParams } from './get_query_with_params'; + +export function getHistogramQuery( + params: AiopsExplainLogRateSpikesSchema, + filter: estypes.QueryDslQueryContainer[] = [] +) { + const histogramQuery = getQueryWithParams({ + params, + }); + + if (Array.isArray(histogramQuery.bool.filter)) { + const existingFilter = histogramQuery.bool.filter.filter((d) => Object.keys(d)[0] !== 'range'); + + histogramQuery.bool.filter = [ + ...existingFilter, + ...filter, + { + range: { + [params.timeFieldName]: { + gte: params.start, + lte: params.end, + format: 'epoch_millis', + }, + }, + }, + ]; + } + + return histogramQuery; +} From 992a69c0c5d27b9907464ed9495f5d5c6948d03a Mon Sep 17 00:00:00 2001 From: Cristina Amico <criamico@users.noreply.github.com> Date: Thu, 3 Nov 2022 10:57:00 +0100 Subject: [PATCH 57/86] [Fleet] Update landing page for add fleet server (#144179) * [Fleet] Update landing page for add fleet server * Fix cypress tests * Fix test and add max-width to text Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../fleet/cypress/e2e/a11y/home_page.cy.ts | 16 +++- .../fleet/cypress/e2e/fleet_settings.cy.ts | 32 +++++-- .../fleet/cypress/e2e/fleet_startup.cy.ts | 8 +- x-pack/plugins/fleet/cypress/screens/fleet.ts | 3 + .../hooks/use_fleet_server_host.ts | 8 ++ .../hooks/use_quick_start_form.ts | 6 +- .../fleet_server_instructions/index.tsx | 84 ++++++++++++++++--- .../instructions.stories.tsx | 4 +- .../steps/get_started.tsx | 14 ++++ .../fleet_server_requirement_page.tsx | 4 +- .../fleet_server_hosts_section.tsx | 12 ++- 11 files changed, 162 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/fleet/cypress/e2e/a11y/home_page.cy.ts b/x-pack/plugins/fleet/cypress/e2e/a11y/home_page.cy.ts index 6d1f714bda0d5..12a362ec7581a 100644 --- a/x-pack/plugins/fleet/cypress/e2e/a11y/home_page.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/a11y/home_page.cy.ts @@ -25,16 +25,20 @@ import { SETTINGS_TAB, SETTINGS_FLEET_SERVER_HOST_HEADING, FLEET_SERVER_SETUP, + LANDING_PAGE_ADD_FLEET_SERVER_BUTTON, } from '../../screens/fleet'; import { AGENT_POLICY_NAME_LINK } from '../../screens/integrations'; import { cleanupAgentPolicies, unenrollAgent } from '../../tasks/cleanup'; describe('Home page', () => { before(() => { navigateTo(FLEET); - cy.getBySel(AGENT_FLYOUT.QUICK_START_TAB_BUTTON, { timeout: 15000 }).should('be.visible'); + cy.getBySel(LANDING_PAGE_ADD_FLEET_SERVER_BUTTON).click(); }); describe('Agents', () => { + before(() => { + cy.getBySel(AGENT_FLYOUT.QUICK_START_TAB_BUTTON, { timeout: 15000 }).should('be.visible'); + }); const fleetServerHost = 'https://localhost:8220'; describe('Quick Start', () => { @@ -46,7 +50,9 @@ describe('Home page', () => { cy.get('[placeholder="Specify host URL"', { timeout: 15000 }).should('be.visible'); cy.get('[placeholder="Specify host URL"').type(fleetServerHost); cy.getBySel(GENERATE_FLEET_SERVER_POLICY_BUTTON).click(); - cy.getBySel(PLATFORM_TYPE_LINUX_BUTTON, { timeout: 15000 }).should('be.visible'); + cy.getBySel(PLATFORM_TYPE_LINUX_BUTTON, { timeout: 15000 }) + .scrollIntoView() + .should('be.visible'); checkA11y({ skipFailures: false }); }); }); @@ -67,7 +73,9 @@ describe('Home page', () => { it('Generate service token', () => { cy.getBySel(ADVANCED_FLEET_SERVER_ADD_HOST_BUTTON, { timeout: 15000 }).should('be.visible'); cy.getBySel(ADVANCED_FLEET_SERVER_GENERATE_SERVICE_TOKEN_BUTTON).click(); - cy.getBySel(PLATFORM_TYPE_LINUX_BUTTON, { timeout: 15000 }).should('be.visible'); + cy.getBySel(PLATFORM_TYPE_LINUX_BUTTON, { timeout: 15000 }) + .scrollIntoView() + .should('be.visible'); checkA11y({ skipFailures: false }); }); }); @@ -75,6 +83,7 @@ describe('Home page', () => { describe('Agent Policies', () => { before(() => { + navigateTo(FLEET); cy.getBySel(AGENT_POLICIES_TAB).click(); cy.getBySel(AGENT_POLICIES_CREATE_AGENT_POLICY_FLYOUT.CREATE_BUTTON, { timeout: 15000, @@ -104,6 +113,7 @@ describe('Home page', () => { describe('Enrollment Tokens', () => { before(() => { + navigateTo(FLEET); cy.getBySel(ENROLLMENT_TOKENS_TAB).click(); }); it('Enrollment Tokens Table', () => { diff --git a/x-pack/plugins/fleet/cypress/e2e/fleet_settings.cy.ts b/x-pack/plugins/fleet/cypress/e2e/fleet_settings.cy.ts index 8e213532ce7b4..0c45603a4b2ae 100644 --- a/x-pack/plugins/fleet/cypress/e2e/fleet_settings.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/fleet_settings.cy.ts @@ -11,6 +11,8 @@ import { SETTINGS_OUTPUTS, SETTINGS_FLEET_SERVER_HOSTS, FLEET_SERVER_HOST_FLYOUT, + FLEET_SERVER_SETUP, + GENERATE_FLEET_SERVER_POLICY_BUTTON, } from '../screens/fleet'; describe('Edit settings', () => { @@ -45,19 +47,39 @@ describe('Edit settings', () => { }); it('should allow to update Fleet server hosts', () => { + cy.getBySel(SETTINGS_FLEET_SERVER_HOSTS.EDIT_BUTTON).click(); + + cy.getBySel(FLEET_SERVER_HOST_FLYOUT.NAME_INPUT).clear().type('Edited Host'); + + cy.get('[placeholder="Specify host URL"').clear().type('https://localhost:8221'); + + cy.intercept('PUT', '/api/fleet/fleet_server_hosts/fleet-default-settings', { + name: 'Edited Host', + host_urls: ['https://localhost:8221'], + is_default: false, + }).as('updateFleetServerHosts'); + + cy.getBySel(SETTINGS_SAVE_BTN).click(); + cy.getBySel(CONFIRM_MODAL.CONFIRM_BUTTON).click(); + + cy.wait('@updateFleetServerHosts').then((interception) => { + expect(interception.request.body.host_urls[0]).to.equal('https://localhost:8221'); + }); + }); + + it('should allow to create new Fleet server hosts', () => { cy.getBySel(SETTINGS_FLEET_SERVER_HOSTS.ADD_BUTTON).click(); - cy.getBySel(FLEET_SERVER_HOST_FLYOUT.NAME_INPUT).type('Host edited'); - cy.getBySel(FLEET_SERVER_HOST_FLYOUT.DEFAULT_SWITCH).click(); + cy.getBySel(FLEET_SERVER_SETUP.NAME_INPUT).type('New Host'); + cy.getBySel(FLEET_SERVER_SETUP.DEFAULT_SWITCH).click(); cy.get('[placeholder="Specify host URL"').type('https://localhost:8221'); cy.intercept('POST', '/api/fleet/fleet_server_hosts', { - name: 'Host edited', + name: 'New Host', host_urls: ['https://localhost:8221'], is_default: true, }).as('updateFleetServerHosts'); - cy.getBySel(SETTINGS_SAVE_BTN).click(); - cy.getBySel(CONFIRM_MODAL.CONFIRM_BUTTON).click(); + cy.getBySel(GENERATE_FLEET_SERVER_POLICY_BUTTON).click(); cy.wait('@updateFleetServerHosts').then((interception) => { expect(interception.request.body.host_urls[0]).to.equal('https://localhost:8221'); diff --git a/x-pack/plugins/fleet/cypress/e2e/fleet_startup.cy.ts b/x-pack/plugins/fleet/cypress/e2e/fleet_startup.cy.ts index d79793d10d6bb..3960ea060fcc1 100644 --- a/x-pack/plugins/fleet/cypress/e2e/fleet_startup.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/fleet_startup.cy.ts @@ -14,6 +14,7 @@ import { ADVANCED_FLEET_SERVER_ADD_HOST_BUTTON, ADVANCED_FLEET_SERVER_GENERATE_SERVICE_TOKEN_BUTTON, FLEET_SERVER_SETUP, + LANDING_PAGE_ADD_FLEET_SERVER_BUTTON, } from '../screens/fleet'; import { cleanupAgentPolicies, unenrollAgent } from '../tasks/cleanup'; import { verifyPolicy, verifyAgentPackage, navigateToTab } from '../tasks/fleet'; @@ -80,18 +81,23 @@ describe('Fleet startup', () => { }); it('should create Fleet Server policy', () => { + cy.getBySel(LANDING_PAGE_ADD_FLEET_SERVER_BUTTON).click(); + cy.getBySel(AGENT_FLYOUT.ADVANCED_TAB_BUTTON).click(); - cy.getBySel(CREATE_FLEET_SERVER_POLICY_BTN).click(); + cy.getBySel(CREATE_FLEET_SERVER_POLICY_BTN, { timeout: 180000 }).click(); // Wait until the success callout is shown before navigating away cy.getBySel(AGENT_POLICY_CREATE_STATUS_CALLOUT) .should('exist') .and('have.class', 'euiCallOut--success'); + cy.getBySel(AGENT_FLYOUT.CLOSE_BUTTON).click(); // verify policy is created and has fleet server and system package verifyPolicy('Fleet Server policy 1', ['Fleet Server', 'System']); + // Reopen Flyout navigateToTab(AGENTS_TAB); + cy.getBySel(LANDING_PAGE_ADD_FLEET_SERVER_BUTTON).click(); cy.getBySel(AGENT_FLYOUT.ADVANCED_TAB_BUTTON).click(); // verify create button changed to dropdown diff --git a/x-pack/plugins/fleet/cypress/screens/fleet.ts b/x-pack/plugins/fleet/cypress/screens/fleet.ts index 7bd8c6293e97d..1248a47778ccb 100644 --- a/x-pack/plugins/fleet/cypress/screens/fleet.ts +++ b/x-pack/plugins/fleet/cypress/screens/fleet.ts @@ -7,6 +7,7 @@ export const ADD_AGENT_BUTTON = 'addAgentButton'; export const ADD_AGENT_BUTTON_TOP = 'addAgentBtnTop'; +export const LANDING_PAGE_ADD_FLEET_SERVER_BUTTON = 'fleetServerLanding.addFleetServerButton'; export const AGENTS_TAB = 'fleet-agents-tab'; export const AGENT_POLICIES_TAB = 'fleet-agent-policies-tab'; @@ -102,6 +103,7 @@ export const SETTINGS_OUTPUTS = { export const SETTINGS_FLEET_SERVER_HOSTS = { ADD_BUTTON: 'settings.fleetServerHosts.addFleetServerHostBtn', + EDIT_BUTTON: 'fleetServerHostsTable.edit.btn', }; export const AGENT_POLICY_FORM = { @@ -126,4 +128,5 @@ export const FLEET_SERVER_HOST_FLYOUT = { export const FLEET_SERVER_SETUP = { NAME_INPUT: 'fleetServerSetup.nameInput', HOST_INPUT: 'fleetServerSetup.multiRowInput', + DEFAULT_SWITCH: 'fleetServerHostsFlyout.isDefaultSwitch', }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts index 23f8942454b17..34df4465358e4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts @@ -13,6 +13,7 @@ import { useGetFleetServerHosts, useComboInput, useInput, + useSwitchInput, } from '../../../hooks'; import type { FleetServerHost } from '../../../types'; @@ -31,6 +32,7 @@ export interface FleetServerHostForm { inputs: { hostUrlsInput: ReturnType<typeof useComboInput>; nameInput: ReturnType<typeof useInput>; + isDefaultInput: ReturnType<typeof useSwitchInput>; }; } @@ -41,6 +43,11 @@ export const useFleetServerHost = (): FleetServerHostForm => { const isPreconfigured = fleetServerHost?.is_preconfigured ?? false; const nameInput = useInput(fleetServerHost?.name ?? '', validateName, isPreconfigured); + const isDefaultInput = useSwitchInput( + fleetServerHost?.is_default ?? false, + isPreconfigured || fleetServerHost?.is_default + ); + const hostUrlsInput = useComboInput( 'hostUrls', fleetServerHost?.host_urls || [], @@ -105,6 +112,7 @@ export const useFleetServerHost = (): FleetServerHostForm => { inputs: { hostUrlsInput, nameInput, + isDefaultInput, }, }; }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts index f3167b977d312..ed893e5d06d65 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts @@ -8,7 +8,7 @@ import { useState, useCallback, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; -import type { useComboInput, useInput } from '../../../hooks'; +import type { useComboInput, useInput, useSwitchInput } from '../../../hooks'; import { sendCreateAgentPolicy, sendGetOneAgentPolicy, useStartServices } from '../../../hooks'; import type { NewAgentPolicy } from '../../../types'; @@ -42,6 +42,7 @@ export interface QuickStartCreateForm { inputs: { hostUrlsInput: ReturnType<typeof useComboInput>; nameInput: ReturnType<typeof useInput>; + isDefaultInput: ReturnType<typeof useSwitchInput>; }; } @@ -84,7 +85,7 @@ export const useQuickStartCreateForm = (): QuickStartCreateForm => { const newFleetServerHost = { name: inputs.nameInput.value, host_urls: inputs.hostUrlsInput.value, - is_default: true, + is_default: inputs.isDefaultInput.value, id: 'fleet-server-host', is_preconfigured: false, }; @@ -125,6 +126,7 @@ export const useQuickStartCreateForm = (): QuickStartCreateForm => { validate, inputs.nameInput.value, inputs.hostUrlsInput.value, + inputs.isDefaultInput.value, setFleetServerHost, saveFleetServerHost, generateServiceToken, diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx index bb0c2607cfe1a..5f609e1155519 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useState, useCallback } from 'react'; import { EuiButtonGroup, EuiFlexGroup, @@ -16,12 +16,15 @@ import { EuiSpacer, EuiText, EuiTitle, + EuiToolTip, + EuiFlexItem, + EuiButton, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import styled from 'styled-components'; -import { useStartServices } from '../../hooks'; +import { useStartServices, useFlyoutContext } from '../../hooks'; import { QuickStartTab } from './quick_start_tab'; import { AdvancedTab } from './advanced_tab'; @@ -134,17 +137,78 @@ export const FleetServerFlyout: React.FunctionComponent<Props> = ({ onClose }) = ); }; -// Renders instructions directly -export const FleetServerInstructions: React.FunctionComponent = () => { - const { tabs, currentTab, setCurrentTab, currentTabContent } = useFleetServerTabs(); +export const AddFleetServerLanding: React.FunctionComponent = () => { + const { docLinks } = useStartServices(); + const flyoutContext = useFlyoutContext(); + + const onClickAddFleetServer = useCallback(() => { + flyoutContext.openFleetServerFlyout(); + }, [flyoutContext]); return ( <ContentWrapper gutterSize="none" justifyContent="center" direction="column"> - <Header tabs={tabs} currentTab={currentTab} onTabClick={(id) => setCurrentTab(id)} /> - - <EuiSpacer size="m" /> - - {currentTabContent} + <EuiFlexGroup alignItems="center" direction="column"> + <EuiFlexItem> + <EuiTitle size="m"> + <h2 data-test-subj="addFleetServerHeader"> + <FormattedMessage + id="xpack.fleet.fleetServerLanding.title" + defaultMessage="Add a Fleet Server" + /> + </h2> + </EuiTitle> + </EuiFlexItem> + + <EuiFlexItem> + <EuiText + css={` + max-width: 500px; + text-align: center; + `} + > + <FormattedMessage + id="xpack.fleet.fleetServerLanding.instructions" + defaultMessage="A Fleet Server is required before you can enroll agents with Fleet. Follow the instructions below to set up a Fleet Server. For more information, see the {userGuideLink}" + values={{ + userGuideLink: ( + <EuiLink + href={docLinks.links.fleet.fleetServerAddFleetServer} + external + target="_blank" + > + <FormattedMessage + id="xpack.fleet.fleetServerSetup.setupGuideLink" + defaultMessage="Fleet and Elastic Agent Guide" + /> + </EuiLink> + ), + }} + /> + </EuiText> + </EuiFlexItem> + <EuiSpacer size="s" /> + <EuiFlexItem> + <EuiToolTip + content={ + <FormattedMessage + id="xpack.fleet.fleetServerLanding.addFleetServerButton.tooltip" + defaultMessage="Fleet Server is a component of the Elastic Stack used to centrally manage Elastic Agents" + /> + } + > + <EuiButton + onClick={onClickAddFleetServer} + fill + data-test-subj="fleetServerLanding.addFleetServerButton" + > + <FormattedMessage + id="xpack.fleet.fleetServerLanding.addFleetServerButton" + defaultMessage="Add Fleet Server" + /> + </EuiButton> + </EuiToolTip> + </EuiFlexItem> + </EuiFlexGroup> </ContentWrapper> ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/instructions.stories.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/instructions.stories.tsx index 9993fab723a44..cc930783f73c5 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/instructions.stories.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/instructions.stories.tsx @@ -7,10 +7,10 @@ import React from 'react'; -import { FleetServerInstructions as FleetServerInstructionsComponent } from '.'; +import { AddFleetServerLanding } from '.'; export const FleetServerInstructions = () => { - return <FleetServerInstructionsComponent />; + return <AddFleetServerLanding />; }; FleetServerInstructions.args = { diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx index 07a34be899796..3224874ea0b95 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx @@ -19,6 +19,7 @@ import { EuiText, EuiFormRow, EuiFieldText, + EuiSwitch, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -133,6 +134,19 @@ const GettingStartedStepContent: React.FunctionComponent<QuickStartCreateForm> = {status === 'error' && <EuiFormErrorText>{error}</EuiFormErrorText>} </> </EuiFormRow> + <EuiFormRow fullWidth {...inputs.isDefaultInput.formRowProps}> + <EuiSwitch + data-test-subj="fleetServerHostsFlyout.isDefaultSwitch" + {...inputs.isDefaultInput.props} + disabled={false} + label={ + <FormattedMessage + id="xpack.fleet.settings.fleetServerHostsFlyout.defaultOutputSwitchLabel" + defaultMessage="Make this Fleet server the default one." + /> + } + /> + </EuiFormRow> <EuiSpacer size="m" /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx index b4818a6908adf..fe089a6669c27 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx @@ -15,7 +15,7 @@ import { FleetServerMissingPrivileges } from '../components/fleet_server_callout import { Loading } from '../components'; -import { FleetServerInstructions } from '../../../components'; +import { AddFleetServerLanding } from '../../../components'; import { CloudInstructions, EnrollmentRecommendation } from './components'; @@ -78,7 +78,7 @@ export const FleetServerRequirementPage: React.FunctionComponent< ) : showEnrollmentRecommendation ? ( <EnrollmentRecommendation showStandaloneTab={showStandaloneTab} /> ) : ( - <FleetServerInstructions /> + <AddFleetServerLanding /> )} </FlexItemWithMinWidth> </ContentWrapper> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/fleet_server_hosts_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/fleet_server_hosts_section.tsx index 1114efb814a21..806df98d517b3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/fleet_server_hosts_section.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/fleet_server_hosts_section.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiTitle, EuiLink, EuiText, EuiSpacer, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { FleetServerHost } from '../../../../types'; -import { useLink, useStartServices } from '../../../../hooks'; +import { useFlyoutContext, useStartServices } from '../../../../hooks'; import { FleetServerHostsTable } from '../fleet_server_hosts_table'; export interface FleetServerHostsSectionProps { @@ -24,7 +24,11 @@ export const FleetServerHostsSection: React.FunctionComponent<FleetServerHostsSe deleteFleetServerHost, }) => { const { docLinks } = useStartServices(); - const { getHref } = useLink(); + const flyoutContext = useFlyoutContext(); + + const onClickAddFleetServer = useCallback(() => { + flyoutContext.openFleetServerFlyout(); + }, [flyoutContext]); return ( <> @@ -61,7 +65,7 @@ export const FleetServerHostsSection: React.FunctionComponent<FleetServerHostsSe <EuiSpacer size="s" /> <EuiButtonEmpty iconType="plusInCircle" - href={getHref('settings_create_fleet_server_hosts')} + onClick={onClickAddFleetServer} data-test-subj="settings.fleetServerHosts.addFleetServerHostBtn" > <FormattedMessage From 69c3244606ff20546608f7b9dff5dd448f35ad36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= <alejandro.haro@elastic.co> Date: Thu, 3 Nov 2022 10:57:31 +0100 Subject: [PATCH 58/86] [Telemetry] Smarter next attempt timer to avoid skipping days (#144132) --- src/plugins/telemetry/common/constants.ts | 6 + .../common/is_report_interval_expired.test.ts | 6 +- .../common/is_report_interval_expired.ts | 8 +- .../telemetry/server/fetcher.test.mock.ts | 15 ++ src/plugins/telemetry/server/fetcher.test.ts | 212 +++++++++++++++++- src/plugins/telemetry/server/fetcher.ts | 137 ++++++++--- .../server/get_next_attempt_date.test.ts | 48 ++++ .../telemetry/server/get_next_attempt_date.ts | 39 ++++ 8 files changed, 429 insertions(+), 42 deletions(-) create mode 100644 src/plugins/telemetry/server/fetcher.test.mock.ts create mode 100644 src/plugins/telemetry/server/get_next_attempt_date.test.ts create mode 100644 src/plugins/telemetry/server/get_next_attempt_date.ts diff --git a/src/plugins/telemetry/common/constants.ts b/src/plugins/telemetry/common/constants.ts index 4987dc0b512ab..6ec7bebd642b9 100644 --- a/src/plugins/telemetry/common/constants.ts +++ b/src/plugins/telemetry/common/constants.ts @@ -12,6 +12,12 @@ */ export const REPORT_INTERVAL_MS = 86400000; +/** + * The buffer time, in milliseconds, to consider the {@link REPORT_INTERVAL_MS} as expired. + * Currently, 2 minutes. + */ +export const REPORT_INTERVAL_BUFFER_MS = 120000; + /** * How often we poll for the opt-in status. * Currently, 10 seconds. diff --git a/src/plugins/telemetry/common/is_report_interval_expired.test.ts b/src/plugins/telemetry/common/is_report_interval_expired.test.ts index 68c252a959c0e..81ea513098773 100644 --- a/src/plugins/telemetry/common/is_report_interval_expired.test.ts +++ b/src/plugins/telemetry/common/is_report_interval_expired.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { REPORT_INTERVAL_MS } from './constants'; +import { REPORT_INTERVAL_BUFFER_MS, REPORT_INTERVAL_MS } from './constants'; import { isReportIntervalExpired } from './is_report_interval_expired'; describe('isReportIntervalExpired', () => { @@ -54,7 +54,9 @@ describe('isReportIntervalExpired', () => { }); test('false when close but not yet', () => { - expect(isReportIntervalExpired(Date.now() - REPORT_INTERVAL_MS + 1000)).toBe(false); + expect( + isReportIntervalExpired(Date.now() - REPORT_INTERVAL_MS + REPORT_INTERVAL_BUFFER_MS + 1000) + ).toBe(false); }); test('false when date in the future', () => { diff --git a/src/plugins/telemetry/common/is_report_interval_expired.ts b/src/plugins/telemetry/common/is_report_interval_expired.ts index d91916c12c449..ee379ddbf0abb 100644 --- a/src/plugins/telemetry/common/is_report_interval_expired.ts +++ b/src/plugins/telemetry/common/is_report_interval_expired.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { REPORT_INTERVAL_MS } from './constants'; +import { REPORT_INTERVAL_BUFFER_MS, REPORT_INTERVAL_MS } from './constants'; /** * The report is considered expired if: @@ -15,5 +15,9 @@ import { REPORT_INTERVAL_MS } from './constants'; * @returns `true` if the report interval is considered expired */ export function isReportIntervalExpired(lastReportAt: number | undefined) { - return !lastReportAt || isNaN(lastReportAt) || Date.now() - lastReportAt > REPORT_INTERVAL_MS; + return ( + !lastReportAt || + isNaN(lastReportAt) || + Date.now() - lastReportAt > REPORT_INTERVAL_MS - REPORT_INTERVAL_BUFFER_MS + ); } diff --git a/src/plugins/telemetry/server/fetcher.test.mock.ts b/src/plugins/telemetry/server/fetcher.test.mock.ts new file mode 100644 index 0000000000000..7a77b6452fc09 --- /dev/null +++ b/src/plugins/telemetry/server/fetcher.test.mock.ts @@ -0,0 +1,15 @@ +/* + * 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 fetchMock = jest.fn(); + +jest.doMock('node-fetch', () => fetchMock); + +export const getNextAttemptDateMock = jest.fn(); + +jest.doMock('./get_next_attempt_date', () => ({ getNextAttemptDate: getNextAttemptDateMock })); diff --git a/src/plugins/telemetry/server/fetcher.test.ts b/src/plugins/telemetry/server/fetcher.test.ts index 0bbaaefde4416..1a6a9d8d0f0c8 100644 --- a/src/plugins/telemetry/server/fetcher.test.ts +++ b/src/plugins/telemetry/server/fetcher.test.ts @@ -7,14 +7,29 @@ */ /* eslint-disable dot-notation */ -import { FetcherTask } from './fetcher'; +import { fakeSchedulers } from 'rxjs-marbles/jest'; import { coreMock } from '@kbn/core/server/mocks'; import { telemetryCollectionManagerPluginMock, Setup, } from '@kbn/telemetry-collection-manager-plugin/server/mocks'; +jest.mock('rxjs', () => { + const RxJs = jest.requireActual('rxjs'); + return { + ...RxJs, + // Redefining timer as a merge of timer and interval because `fakeSchedulers` fails to advance on the intervals + timer: (dueTime: number, interval: number) => + RxJs.merge(RxJs.timer(dueTime), RxJs.interval(interval)), + }; +}); + +import { fetchMock, getNextAttemptDateMock } from './fetcher.test.mock'; +import { FetcherTask } from './fetcher'; + describe('FetcherTask', () => { + beforeEach(() => jest.useFakeTimers('legacy')); + describe('sendIfDue', () => { let getCurrentConfigs: jest.Mock; let shouldSendReport: jest.Mock; @@ -95,4 +110,199 @@ describe('FetcherTask', () => { expect(updateReportFailure).toBeCalledTimes(0); }); }); + + describe('Validate connectivity', () => { + let fetcherTask: FetcherTask; + let getCurrentConfigs: jest.Mock; + let updateReportFailure: jest.Mock; + + beforeEach(() => { + getCurrentConfigs = jest.fn(); + updateReportFailure = jest.fn(); + fetcherTask = new FetcherTask(coreMock.createPluginInitializerContext({})); + Object.assign(fetcherTask, { getCurrentConfigs, updateReportFailure }); + }); + + afterEach(() => { + fetchMock.mockReset(); + }); + + test( + 'Validates connectivity and sets as online when the OPTIONS request succeeds', + fakeSchedulers(async (advance) => { + expect(fetcherTask['isOnline$'].value).toBe(false); + getCurrentConfigs.mockResolvedValue({ + telemetryOptIn: true, + telemetrySendUsageFrom: 'server', + failureCount: 0, + telemetryUrl: 'test-url', + }); + fetchMock.mockResolvedValue({}); + const subscription = fetcherTask['validateConnectivity'](); + advance(5 * 60 * 1000); + await new Promise((resolve) => process.nextTick(resolve)); // Wait for the promise to fulfill + expect(getCurrentConfigs).toHaveBeenCalledTimes(1); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetchMock).toHaveBeenCalledWith('test-url', { method: 'options' }); + expect(fetcherTask['isOnline$'].value).toBe(true); + subscription.unsubscribe(); + }) + ); + + test( + 'Skips validation when already set as online', + fakeSchedulers(async (advance) => { + fetcherTask['isOnline$'].next(true); + const subscription = fetcherTask['validateConnectivity'](); + advance(5 * 60 * 1000); + await new Promise((resolve) => process.nextTick(resolve)); // Wait for the promise to fulfill + expect(getCurrentConfigs).toHaveBeenCalledTimes(0); + expect(fetchMock).toHaveBeenCalledTimes(0); + expect(fetcherTask['isOnline$'].value).toBe(true); + subscription.unsubscribe(); + }) + ); + + test( + 'Retries on errors', + fakeSchedulers(async (advance) => { + expect(fetcherTask['isOnline$'].value).toBe(false); + getCurrentConfigs.mockResolvedValue({ + telemetryOptIn: true, + telemetrySendUsageFrom: 'server', + failureCount: 0, + telemetryUrl: 'test-url', + }); + fetchMock.mockRejectedValue(new Error('Something went terribly wrong')); + const subscription = fetcherTask['validateConnectivity'](); + advance(5 * 60 * 1000); + await new Promise((resolve) => process.nextTick(resolve)); // Wait for the promise to fulfill + expect(getCurrentConfigs).toHaveBeenCalledTimes(1); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(updateReportFailure).toHaveBeenCalledTimes(1); + expect(fetcherTask['isOnline$'].value).toBe(false); + + // Try again after 12 hours + fetchMock.mockResolvedValue({}); + advance(12 * 60 * 60 * 1000); + await new Promise((resolve) => process.nextTick(resolve)); // Wait for the promise to fulfill + expect(getCurrentConfigs).toHaveBeenCalledTimes(2); + expect(fetchMock).toHaveBeenCalledTimes(2); + expect(updateReportFailure).toHaveBeenCalledTimes(1); + expect(fetcherTask['isOnline$'].value).toBe(true); + + subscription.unsubscribe(); + }) + ); + + test( + 'Should not retry if it hit the max number of failures for this version', + fakeSchedulers(async (advance) => { + expect(fetcherTask['isOnline$'].value).toBe(false); + getCurrentConfigs.mockResolvedValue({ + telemetryOptIn: true, + telemetrySendUsageFrom: 'server', + failureCount: 3, + failureVersion: 'version', + currentVersion: 'version', + telemetryUrl: 'test-url', + }); + const subscription = fetcherTask['validateConnectivity'](); + advance(5 * 60 * 1000); + await new Promise((resolve) => process.nextTick(resolve)); // Wait for the promise to fulfill + expect(getCurrentConfigs).toHaveBeenCalledTimes(1); + expect(fetchMock).toHaveBeenCalledTimes(0); + expect(fetcherTask['isOnline$'].value).toBe(false); + + subscription.unsubscribe(); + }) + ); + + test( + 'Should retry if it hit the max number of failures for a different version', + fakeSchedulers(async (advance) => { + expect(fetcherTask['isOnline$'].value).toBe(false); + getCurrentConfigs.mockResolvedValue({ + telemetryOptIn: true, + telemetrySendUsageFrom: 'server', + failureCount: 3, + failureVersion: 'version', + currentVersion: 'another_version', + telemetryUrl: 'test-url', + }); + const subscription = fetcherTask['validateConnectivity'](); + advance(5 * 60 * 1000); + await new Promise((resolve) => process.nextTick(resolve)); // Wait for the promise to fulfill + expect(getCurrentConfigs).toHaveBeenCalledTimes(1); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(fetcherTask['isOnline$'].value).toBe(true); + + subscription.unsubscribe(); + }) + ); + }); + + describe('startSendIfDueSubscription', () => { + let fetcherTask: FetcherTask; + let sendIfDue: jest.Mock; + + beforeEach(() => { + sendIfDue = jest.fn().mockResolvedValue({}); + fetcherTask = new FetcherTask(coreMock.createPluginInitializerContext({})); + Object.assign(fetcherTask, { sendIfDue }); + }); + + afterEach(() => { + getNextAttemptDateMock.mockReset(); + }); + + test('Tries to send telemetry when it is online', () => { + const subscription = fetcherTask['startSendIfDueSubscription'](); + fetcherTask['isOnline$'].next(true); + expect(sendIfDue).toHaveBeenCalledTimes(1); + subscription.unsubscribe(); + }); + + test('Does not send telemetry when it is offline', () => { + const subscription = fetcherTask['startSendIfDueSubscription'](); + fetcherTask['isOnline$'].next(false); + expect(sendIfDue).toHaveBeenCalledTimes(0); + subscription.unsubscribe(); + }); + + test( + 'Sends telemetry when the next attempt date kicks in', + fakeSchedulers((advance) => { + fetcherTask['isOnline$'].next(true); + const subscription = fetcherTask['startSendIfDueSubscription'](); + const lastReported = Date.now(); + getNextAttemptDateMock.mockReturnValue(new Date(lastReported + 1000)); + fetcherTask['lastReported$'].next(lastReported); + advance(1000); + expect(sendIfDue).toHaveBeenCalledTimes(1); + subscription.unsubscribe(); + }) + ); + + test( + 'Keeps retrying every 1 minute after the next attempt date until a new emission of lastReported occurs', + fakeSchedulers(async (advance) => { + fetcherTask['isOnline$'].next(true); + const subscription = fetcherTask['startSendIfDueSubscription'](); + const lastReported = Date.now(); + getNextAttemptDateMock.mockReturnValue(new Date(lastReported + 1000)); + fetcherTask['lastReported$'].next(lastReported); + advance(1000); + await new Promise((resolve) => process.nextTick(resolve)); + expect(sendIfDue).toHaveBeenCalledTimes(1); + advance(60 * 1000); + await new Promise((resolve) => process.nextTick(resolve)); + expect(sendIfDue).toHaveBeenCalledTimes(2); + advance(60 * 1000); + await new Promise((resolve) => process.nextTick(resolve)); + expect(sendIfDue).toHaveBeenCalledTimes(3); + subscription.unsubscribe(); + }) + ); + }); }); diff --git a/src/plugins/telemetry/server/fetcher.ts b/src/plugins/telemetry/server/fetcher.ts index 29c3dd44e1984..210e0a37d6c1c 100644 --- a/src/plugins/telemetry/server/fetcher.ts +++ b/src/plugins/telemetry/server/fetcher.ts @@ -6,7 +6,19 @@ * Side Public License, v 1. */ -import { firstValueFrom, Observable, Subscription, timer } from 'rxjs'; +import { + BehaviorSubject, + exhaustMap, + filter, + firstValueFrom, + merge, + mergeMap, + Observable, + skip, + Subscription, + takeUntil, + timer, +} from 'rxjs'; import fetch from 'node-fetch'; import type { TelemetryCollectionManagerPluginStart } from '@kbn/telemetry-collection-manager-plugin/server'; import { @@ -16,6 +28,7 @@ import { SavedObjectsClient, CoreStart, } from '@kbn/core/server'; +import { getNextAttemptDate } from './get_next_attempt_date'; import { getTelemetryChannelEndpoint, getTelemetryOptIn, @@ -42,15 +55,19 @@ interface TelemetryConfig { lastReported: number | undefined; } +const SECOND = 1000; +const MINUTE = 60 * SECOND; +const HOUR = 60 * MINUTE; + export class FetcherTask { - private readonly initialCheckDelayMs = 60 * 1000 * 5; - private readonly checkIntervalMs = 60 * 1000 * 60 * 12; + private readonly initialCheckDelayMs = 5 * MINUTE; + private readonly connectivityCheckIntervalMs = 12 * HOUR; private readonly config$: Observable<TelemetryConfigType>; private readonly currentKibanaVersion: string; private readonly logger: Logger; - private intervalId?: Subscription; - private lastReported?: number; - private isSending = false; + private readonly subscriptions = new Subscription(); + private readonly isOnline$ = new BehaviorSubject<boolean>(false); // Let's initially assume we are not online + private readonly lastReported$ = new BehaviorSubject<number>(0); private internalRepository?: SavedObjectsClientContract; private telemetryCollectionManager?: TelemetryCollectionManagerPluginStart; @@ -64,22 +81,74 @@ export class FetcherTask { this.internalRepository = new SavedObjectsClient(savedObjects.createInternalRepository()); this.telemetryCollectionManager = telemetryCollectionManager; - this.intervalId = timer(this.initialCheckDelayMs, this.checkIntervalMs).subscribe(() => - this.sendIfDue() - ); + this.subscriptions.add(this.validateConnectivity()); + this.subscriptions.add(this.startSendIfDueSubscription()); } public stop() { - if (this.intervalId) { - this.intervalId.unsubscribe(); - } + this.subscriptions.unsubscribe(); } - private async sendIfDue() { - if (this.isSending) { - return; - } + /** + * Periodically validates the connectivity from the server to our remote telemetry URL. + * OPTIONS is less intrusive as it does not contain any payload and is used here to check if the endpoint is reachable. + */ + private validateConnectivity(): Subscription { + return timer(this.initialCheckDelayMs, this.connectivityCheckIntervalMs) + .pipe( + // Skip any further processing if already online + filter(() => !this.isOnline$.value), + // Fetch current state and configs + exhaustMap(async () => await this.getCurrentConfigs()), + // Skip if opted-out, or should only send from the browser + filter( + ({ telemetryOptIn, telemetrySendUsageFrom }) => + telemetryOptIn === true && telemetrySendUsageFrom === 'server' + ), + // Skip if already failed three times for this version + filter( + ({ failureCount, failureVersion, currentVersion }) => + !(failureCount > 2 && failureVersion === currentVersion) + ), + exhaustMap(async ({ telemetryUrl, failureCount }) => { + try { + await fetch(telemetryUrl, { method: 'options' }); + this.isOnline$.next(true); + } catch (err) { + this.logger.error(`Cannot reach the remote telemetry endpoint ${telemetryUrl}`); + await this.updateReportFailure({ failureCount }); + } + }) + ) + .subscribe(); + } + private startSendIfDueSubscription() { + return merge( + // Attempt to send telemetry... + // ... whenever connectivity changes + this.isOnline$, + // ... when lastReported$ has a new value... + this.lastReported$.pipe( + filter(Boolean), + mergeMap((lastReported) => + // ... set a timer of 24h from there (+ a random seed to avoid concurrent emissions from multiple Kibana instances). + // Emitting again every 1 minute after the next attempt date in case we reach a deadlock in further checks (like Kibana is not healthy at the moment of sending). + timer(getNextAttemptDate(lastReported), MINUTE).pipe( + // Cancel this observable if lastReported$ emits again + takeUntil(this.lastReported$.pipe(skip(1))) + ) + ) + ) + ) + .pipe( + filter(() => this.isOnline$.value), + exhaustMap(() => this.sendIfDue()) + ) + .subscribe(); + } + + private async sendIfDue() { // Skip this run if Kibana is not in a healthy state to fetch telemetry. if (!(await this.telemetryCollectionManager?.shouldGetTelemetry())) { return; @@ -99,13 +168,11 @@ export class FetcherTask { } let clusters: EncryptedTelemetryPayload = []; - this.isSending = true; try { clusters = await this.fetchTelemetry(); } catch (err) { this.logger.warn(`Error fetching usage. (${err})`); - this.isSending = false; return; } @@ -119,7 +186,6 @@ export class FetcherTask { this.logger.warn(`Error sending telemetry usage data. (${err})`); } - this.isSending = false; } private async getCurrentConfigs(): Promise<TelemetryConfig> { @@ -137,6 +203,13 @@ export class FetcherTask { telemetrySavedObject, }); + const lastReported = telemetrySavedObject ? telemetrySavedObject.lastReported : void 0; + + // If the lastReported value in the SO is more recent than the in-memory one, refresh the memory (another instance or the browser may have reported it) + if (lastReported && lastReported > this.lastReported$.value) { + this.lastReported$.next(lastReported); + } + return { telemetryOptIn: getTelemetryOptIn({ currentKibanaVersion, @@ -152,15 +225,15 @@ export class FetcherTask { failureCount, failureVersion, currentVersion: currentKibanaVersion, - lastReported: telemetrySavedObject ? telemetrySavedObject.lastReported : void 0, + lastReported, }; } private async updateLastReported() { - this.lastReported = Date.now(); + this.lastReported$.next(Date.now()); updateTelemetrySavedObject(this.internalRepository!, { reportFailureCount: 0, - lastReported: this.lastReported, + lastReported: this.lastReported$.value, }).catch((err) => { err.message = `Failed to update the telemetry saved object: ${err.message}`; this.logger.debug(err); @@ -168,6 +241,7 @@ export class FetcherTask { } private async updateReportFailure({ failureCount }: { failureCount: number }) { + this.isOnline$.next(false); updateTelemetrySavedObject(this.internalRepository!, { reportFailureCount: failureCount + 1, reportFailureVersion: this.currentKibanaVersion, @@ -180,19 +254,15 @@ export class FetcherTask { private shouldSendReport({ telemetryOptIn, telemetrySendUsageFrom, - failureCount, - failureVersion, - currentVersion, lastReported, }: TelemetryConfig) { - if (failureCount > 2 && failureVersion === currentVersion) { - return false; - } - if (telemetryOptIn && telemetrySendUsageFrom === 'server') { // Check both: in-memory and SO-driven value. // This will avoid the server retrying over and over when it has issues with storing the state in the SO. - if (isReportIntervalExpired(this.lastReported) && isReportIntervalExpired(lastReported)) { + if ( + isReportIntervalExpired(this.lastReported$.value) && + isReportIntervalExpired(lastReported) + ) { return true; } } @@ -210,13 +280,6 @@ export class FetcherTask { payload: EncryptedTelemetryPayload ): Promise<void> { this.logger.debug(`Sending usage stats.`); - /** - * send OPTIONS before sending usage data. - * OPTIONS is less intrusive as it does not contain any payload and is used here to check if the endpoint is reachable. - */ - await fetch(telemetryUrl, { - method: 'options', - }); await Promise.all( payload.map(async ({ clusterUuid, stats }) => { diff --git a/src/plugins/telemetry/server/get_next_attempt_date.test.ts b/src/plugins/telemetry/server/get_next_attempt_date.test.ts new file mode 100644 index 0000000000000..63efcd7796654 --- /dev/null +++ b/src/plugins/telemetry/server/get_next_attempt_date.test.ts @@ -0,0 +1,48 @@ +/* + * 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 crypto from 'crypto'; +import { getNextAttemptDate } from './get_next_attempt_date'; + +describe('getNextAttemptDate', () => { + // The casting is needed because `randomInt` has multiple call signatures and typescript is taking the callback one. + const randomIntSpy = jest.spyOn(crypto, 'randomInt') as unknown as jest.SpyInstance< + number, + [min: number, max: number] + >; + + afterEach(() => { + randomIntSpy.mockReset(); + }); + + test('returns the date + 24h + random seed (2 minutes)', () => { + randomIntSpy.mockReturnValue(120); + + expect(getNextAttemptDate(new Date('2022-10-27T12:00:00Z').getTime())).toStrictEqual( + new Date('2022-10-28T12:02:00Z') + ); + }); + + test('returns the start of the next day if the random addition stays in the same day', () => { + randomIntSpy.mockReturnValue(120); + randomIntSpy.mockReturnValueOnce(-120); + expect(getNextAttemptDate(new Date('2022-10-27T00:01:00Z').getTime())).toStrictEqual( + new Date('2022-10-28T00:03:00Z') + ); + expect(randomIntSpy).toHaveBeenCalledTimes(2); + }); + + test('returns the end of the next day minus 1 minute if the random addition goes to the following day', () => { + randomIntSpy.mockReturnValue(-120); + randomIntSpy.mockReturnValueOnce(120); + expect(getNextAttemptDate(new Date('2022-10-27T23:58:30Z').getTime())).toStrictEqual( + new Date('2022-10-28T23:56:30Z') + ); + expect(randomIntSpy).toHaveBeenCalledTimes(2); + }); +}); diff --git a/src/plugins/telemetry/server/get_next_attempt_date.ts b/src/plugins/telemetry/server/get_next_attempt_date.ts new file mode 100644 index 0000000000000..507491ef5450e --- /dev/null +++ b/src/plugins/telemetry/server/get_next_attempt_date.ts @@ -0,0 +1,39 @@ +/* + * 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 { randomInt } from 'crypto'; +import moment from 'moment'; +import { REPORT_INTERVAL_BUFFER_MS, REPORT_INTERVAL_MS } from '../common/constants'; + +const REPORT_INTERVAL_BUFFER_S = REPORT_INTERVAL_BUFFER_MS / 1000; + +/** + * Returns the next attempt to send telemetry + * @param fromMs The last attempt in ms from epoch + */ +export function getNextAttemptDate(fromMs: number): Date { + const lastAttempt = moment(fromMs).utcOffset(0); + const endOfLastAttemptDay = lastAttempt.clone().endOf('day'); + const dayPlusReportInterval = lastAttempt.clone().add(REPORT_INTERVAL_MS, 'milliseconds'); + const endOfNextDay = dayPlusReportInterval.clone().endOf('day'); + const nextAttemptDate = dayPlusReportInterval + .clone() + .add(randomInt(-REPORT_INTERVAL_BUFFER_S, REPORT_INTERVAL_BUFFER_S), 'seconds'); + + // Edge case: If the random seed makes the next attempt to be in the same day of the last attempt, generate it again + if (nextAttemptDate.isBefore(endOfLastAttemptDay)) { + return getNextAttemptDate(fromMs); + } + + // Edge case: If the random seed goes to the following day, generate it again + if (nextAttemptDate.isAfter(endOfNextDay)) { + return getNextAttemptDate(fromMs); + } + + return nextAttemptDate.toDate(); +} From 11ec1c5e0456978a104ce1b31462ca0e8beec971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= <alejandro.haro@elastic.co> Date: Thu, 3 Nov 2022 11:09:57 +0100 Subject: [PATCH 59/86] [LaunchDarkly] Match deployment metadata format to Cloud UI convention (#144478) --- .../metadata_service/metadata_service.test.ts | 12 +++++----- .../metadata_service/metadata_service.ts | 24 +++++++++---------- .../cloud_experiments/public/plugin.test.ts | 8 +++---- .../cloud_experiments/public/plugin.ts | 6 ++--- .../cloud_experiments/server/plugin.test.ts | 8 +++---- .../cloud_experiments/server/plugin.ts | 8 +++---- 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.test.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.test.ts index 6e79b5e4ebf03..fd955a44ee9cc 100644 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.test.ts @@ -43,12 +43,12 @@ describe('MetadataService', () => { }); test( - 'emits in_trial when trial_end_date is provided', + 'emits inTrial when trialEndDate is provided', fakeSchedulers(async (advance) => { const initialMetadata = { userId: 'fake-user-id', kibanaVersion: 'version', - trial_end_date: new Date(0).toISOString(), + trialEndDate: new Date(0).toISOString(), }; metadataService.setup(initialMetadata); @@ -62,7 +62,7 @@ describe('MetadataService', () => { await new Promise((resolve) => process.nextTick(resolve)); // The timer triggers a promise, so we need to skip to the next tick await expect(firstValueFrom(metadataService.userMetadata$)).resolves.toStrictEqual({ ...initialMetadata, - in_trial: false, + inTrial: false, }); }) ); @@ -75,9 +75,9 @@ describe('MetadataService', () => { }); test( - 'emits has_data after resolving the `hasUserDataView`', + 'emits hasData after resolving the `hasUserDataView`', fakeSchedulers(async (advance) => { - metadataService.start({ hasDataFetcher: async () => ({ has_data: true }) }); + metadataService.start({ hasDataFetcher: async () => ({ hasData: true }) }); // Still equals initialMetadata await expect(firstValueFrom(metadataService.userMetadata$)).resolves.toStrictEqual( @@ -89,7 +89,7 @@ describe('MetadataService', () => { await new Promise((resolve) => process.nextTick(resolve)); // The timer triggers a promise, so we need to skip to the next tick await expect(firstValueFrom(metadataService.userMetadata$)).resolves.toStrictEqual({ ...initialMetadata, - has_data: true, + hasData: true, }); }) ); diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.ts index 6691211b7b01c..dacdd7d3b4799 100644 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.ts @@ -21,18 +21,18 @@ import { import { type Duration } from 'moment'; export interface MetadataServiceStartContract { - hasDataFetcher: () => Promise<{ has_data: boolean }>; + hasDataFetcher: () => Promise<{ hasData: boolean }>; } export interface UserMetadata extends Record<string, string | boolean | number | undefined> { // Static values userId: string; kibanaVersion: string; - trial_end_date?: string; - is_elastic_staff_owned?: boolean; + trialEndDate?: string; + isElasticStaff?: boolean; // Dynamic/calculated values - in_trial?: boolean; - has_data?: boolean; + inTrial?: boolean; + hasData?: boolean; } export interface MetadataServiceConfig { @@ -48,14 +48,14 @@ export class MetadataService { public setup(initialUserMetadata: UserMetadata) { this._userMetadata$.next(initialUserMetadata); - // Calculate `in_trial` based on the `trial_end_date`. + // Calculate `inTrial` based on the `trialEndDate`. // Elastic Cloud allows customers to end their trials earlier or even extend it in some cases, but this is a good compromise for now. - const trialEndDate = initialUserMetadata.trial_end_date; + const trialEndDate = initialUserMetadata.trialEndDate; if (trialEndDate) { this.scheduleUntil( - () => ({ in_trial: Date.now() <= new Date(trialEndDate).getTime() }), - // Stop recalculating in_trial when the user is no-longer in trial - (metadata) => metadata.in_trial === false + () => ({ inTrial: Date.now() <= new Date(trialEndDate).getTime() }), + // Stop recalculating inTrial when the user is no-longer in trial + (metadata) => metadata.inTrial === false ); } } @@ -64,7 +64,7 @@ export class MetadataService { return this._userMetadata$.pipe( filter(Boolean), // Ensure we don't return undefined debounceTime(100), // Swallows multiple emissions that may occur during bootstrap - distinct((meta) => [meta.in_trial, meta.has_data].join('-')), // Checks if any of the dynamic fields have changed + distinct((meta) => [meta.inTrial, meta.hasData].join('-')), // Checks if any of the dynamic fields have changed shareReplay(1) ); } @@ -76,7 +76,7 @@ export class MetadataService { this.scheduleUntil( async () => hasDataFetcher(), // Stop checking the moment the user has any data - (metadata) => metadata.has_data === true + (metadata) => metadata.hasData === true ); } diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.ts index 9c1e3d25537fd..05ce6dddf3615 100644 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.ts @@ -128,9 +128,9 @@ describe('Cloud Experiments public plugin', () => { }); expect(metadataServiceSetupSpy).toHaveBeenCalledWith({ - is_elastic_staff_owned: true, + isElasticStaff: true, kibanaVersion: 'version', - trial_end_date: '2020-10-01T14:13:12.000Z', + trialEndDate: '2020-10-01T14:13:12.000Z', userId: '1c2412b751f056aef6e340efa5637d137442d489a4b1e3117071e7c87f8523f2', }); }); @@ -170,7 +170,7 @@ describe('Cloud Experiments public plugin', () => { ); }); - test('triggers a userMetadataUpdate for `has_data`', async () => { + test('triggers a userMetadataUpdate for `hasData`', async () => { plugin.setup(coreMock.createSetup(), { cloud: { ...cloudMock.createSetup(), isCloudEnabled: true }, }); @@ -184,7 +184,7 @@ describe('Cloud Experiments public plugin', () => { // For some reason, fakeSchedulers is not working on browser-side tests :shrug: expect(launchDarklyInstanceMock.updateUserMetadata).toHaveBeenCalledWith( expect.objectContaining({ - has_data: true, + hasData: true, }) ); }); diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.ts index 164d6e45c5294..90c43fb475a60 100755 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.ts @@ -82,8 +82,8 @@ export class CloudExperimentsPlugin this.metadataService.setup({ userId: sha256(deps.cloud.cloudId), kibanaVersion: this.kibanaVersion, - trial_end_date: deps.cloud.trialEndDate?.toISOString(), - is_elastic_staff_owned: deps.cloud.isElasticStaffOwned, + trialEndDate: deps.cloud.trialEndDate?.toISOString(), + isElasticStaff: deps.cloud.isElasticStaffOwned, }); } } @@ -98,7 +98,7 @@ export class CloudExperimentsPlugin ): CloudExperimentsPluginStart { if (cloud.isCloudEnabled) { this.metadataService.start({ - hasDataFetcher: async () => ({ has_data: await dataViews.hasData.hasUserDataView() }), + hasDataFetcher: async () => ({ hasData: await dataViews.hasData.hasUserDataView() }), }); // We only subscribe to the user metadata updates if Cloud is enabled. diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.ts index 4d33e0ce65a7d..d01c085dacd75 100644 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.ts @@ -118,8 +118,8 @@ describe('Cloud Experiments server plugin', () => { expect(launchDarklyInstanceMock.updateUserMetadata).toHaveBeenCalledWith({ userId: '1c2412b751f056aef6e340efa5637d137442d489a4b1e3117071e7c87f8523f2', kibanaVersion: coreMock.createPluginInitializerContext().env.packageInfo.version, - is_elastic_staff_owned: true, - trial_end_date: expect.any(String), + isElasticStaff: true, + trialEndDate: expect.any(String), }); }) ); @@ -165,7 +165,7 @@ describe('Cloud Experiments server plugin', () => { ); }); - test('triggers a userMetadataUpdate for `has_data`', async () => { + test('triggers a userMetadataUpdate for `hasData`', async () => { plugin.setup(coreMock.createSetup(), { cloud: { ...cloudMock.createSetup(), isCloudEnabled: true }, }); @@ -177,7 +177,7 @@ describe('Cloud Experiments server plugin', () => { await new Promise((resolve) => setTimeout(resolve, 200)); // Waiting for scheduler and debounceTime to complete (don't know why fakeScheduler didn't work here). expect(launchDarklyInstanceMock.updateUserMetadata).toHaveBeenCalledWith( expect.objectContaining({ - has_data: true, + hasData: true, }) ); }); diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.ts index 093b17934b686..7c7b04fcee3fe 100755 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.ts @@ -89,8 +89,8 @@ export class CloudExperimentsPlugin // We use the Cloud ID as the userId in the Cloud Experiments userId: createSHA256Hash(deps.cloud.cloudId), kibanaVersion: this.initializerContext.env.packageInfo.version, - trial_end_date: deps.cloud.trialEndDate?.toISOString(), - is_elastic_staff_owned: deps.cloud.isElasticStaffOwned, + trialEndDate: deps.cloud.trialEndDate?.toISOString(), + isElasticStaff: deps.cloud.isElasticStaffOwned, }); // We only subscribe to the user metadata updates if Cloud is enabled. @@ -146,7 +146,7 @@ export class CloudExperimentsPlugin private async addHasDataMetadata( core: CoreStart, dataViews: DataViewsServerPluginStart - ): Promise<{ has_data: boolean }> { + ): Promise<{ hasData: boolean }> { const dataViewsService = await dataViews.dataViewsServiceFactory( core.savedObjects.createInternalRepository(), core.elasticsearch.client.asInternalUser, @@ -154,7 +154,7 @@ export class CloudExperimentsPlugin true // Ignore capabilities checks ); return { - has_data: await dataViewsService.hasUserDataView(), + hasData: await dataViewsService.hasUserDataView(), }; } } From 06ad564bf9bc1b5a892a49e5b48fe1d4aa2da10d Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Thu, 3 Nov 2022 11:14:33 +0100 Subject: [PATCH 60/86] fix autocomplete for fleet (#144450) * fix autocomplete for fleet * changed implementation, fixed tests * changed implementation to fix autocomplete on fleet side --- .../fleet/components/search_bar.tsx | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx index 663ebf1aaeb49..14ff7e820b388 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx @@ -55,7 +55,7 @@ export const SearchBar: React.FunctionComponent<Props> = ({ usageCollection, } = useStartServices(); - const [indexPatternFields, setIndexPatternFields] = useState<FieldSpec[]>(); + const [dataView, setDataView] = useState<DataView | undefined>(); const isQueryValid = useMemo(() => { if (!value || value === '') { @@ -86,9 +86,14 @@ export const SearchBar: React.FunctionComponent<Props> = ({ return true; } }); - setIndexPatternFields(fields); + const fieldsMap = fields.reduce((acc: Record<string, FieldSpec>, curr: FieldSpec) => { + acc[curr.name] = curr; + return acc; + }, {}); + const newDataView = await data.dataViews.create({ title: indexPattern, fields: fieldsMap }); + setDataView(newDataView); } catch (err) { - setIndexPatternFields(undefined); + setDataView(undefined); } }; fetchFields(); @@ -98,16 +103,7 @@ export const SearchBar: React.FunctionComponent<Props> = ({ <NoWrapQueryStringInput iconType="search" disableLanguageSwitcher={true} - indexPatterns={ - indexPatternFields - ? ([ - { - title: indexPattern, - fields: indexPatternFields, - }, - ] as DataView[]) - : [] - } + indexPatterns={dataView ? [dataView] : []} query={{ query: value, language: 'kuery', From 7245346d7a831afdf0894fcc35a5fa37332d57a2 Mon Sep 17 00:00:00 2001 From: Muhammad Ibragimov <53621505+mibragimov@users.noreply.github.com> Date: Thu, 3 Nov 2022 15:20:32 +0500 Subject: [PATCH 61/86] [Guided onboarding] [Security] Add EUI tour to Integrations pages (#143917) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add EUI tour to Integrations pages * Fix popever remaining open when the user changes the category of the integration list * Add tour offset Co-authored-by: Yulia Čech <6585477+yuliacech@users.noreply.github.com> Co-authored-by: Muhammad Ibragimov <muhammad.ibragimov@elastic.co> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Yulia Čech <6585477+yuliacech@users.noreply.github.com> --- .../epm/components/package_card.test.tsx | 10 +++ .../sections/epm/components/package_card.tsx | 62 +++++++++++-------- .../with_guided_onboarding_tour.tsx | 17 ++++- 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.test.tsx index fa76b0e7932fb..2abfb3168cdb1 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.test.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.test.tsx @@ -25,6 +25,16 @@ jest.mock('../../../hooks', () => { navigateToUrl: jest.fn(), }, }), + useIsGuidedOnboardingActive: jest.fn().mockReturnValue(false), + }; +}); + +jest.mock('../../../components', () => { + return { + ...jest.requireActual('../../../components'), + WithGuidedOnboardingTour: ({ children }: { children: React.ReactNode }) => { + return <>{children}</>; + }, }; }); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx index a37fdcf5533c8..553731bb3fd36 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx @@ -16,8 +16,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { CardIcon } from '../../../../../components/package_icon'; import type { IntegrationCardItem } from '../../../../../../common/types/models/epm'; -import { InlineReleaseBadge } from '../../../components'; -import { useStartServices } from '../../../hooks'; +import { InlineReleaseBadge, WithGuidedOnboardingTour } from '../../../components'; +import { useStartServices, useIsGuidedOnboardingActive } from '../../../hooks'; import { INTEGRATIONS_BASE_PATH, INTEGRATIONS_PLUGIN_ID } from '../../../constants'; export type PackageCardProps = IntegrationCardItem; @@ -74,6 +74,7 @@ export function PackageCard({ } const { application } = useStartServices(); + const isGuidedOnboardingActive = useIsGuidedOnboardingActive(name); const onCardClick = () => { if (url.startsWith(INTEGRATIONS_BASE_PATH)) { @@ -90,30 +91,37 @@ export function PackageCard({ const testid = `integration-card:${id}`; return ( - <TrackApplicationView viewId={testid}> - <Card - data-test-subj={testid} - layout="horizontal" - title={title || ''} - titleSize="xs" - description={description} - hasBorder - icon={ - <CardIcon - icons={icons} - packageName={name} - integrationName={integration} - version={version} - size="xl" - /> - } - onClick={onCardClick} - > - <EuiFlexGroup gutterSize="xs"> - {verifiedBadge} - {releaseBadge} - </EuiFlexGroup> - </Card> - </TrackApplicationView> + <WithGuidedOnboardingTour + packageKey={name} + isTourVisible={isGuidedOnboardingActive} + tourType={'integrationCard'} + tourOffset={10} + > + <TrackApplicationView viewId={testid}> + <Card + data-test-subj={testid} + layout="horizontal" + title={title || ''} + titleSize="xs" + description={description} + hasBorder + icon={ + <CardIcon + icons={icons} + packageName={name} + integrationName={integration} + version={version} + size="xl" + /> + } + onClick={onCardClick} + > + <EuiFlexGroup gutterSize="xs"> + {verifiedBadge} + {releaseBadge} + </EuiFlexGroup> + </Card> + </TrackApplicationView> + </WithGuidedOnboardingTour> ); } diff --git a/x-pack/plugins/fleet/public/components/with_guided_onboarding_tour.tsx b/x-pack/plugins/fleet/public/components/with_guided_onboarding_tour.tsx index 5ed0256e7f881..9065cb723d8b5 100644 --- a/x-pack/plugins/fleet/public/components/with_guided_onboarding_tour.tsx +++ b/x-pack/plugins/fleet/public/components/with_guided_onboarding_tour.tsx @@ -11,7 +11,7 @@ import type { EuiTourStepProps } from '@elastic/eui'; import { EuiButton, EuiText, EuiTourStep } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -type TourType = 'addIntegrationButton' | 'integrationsList' | 'agentModalButton'; +type TourType = 'addIntegrationButton' | 'integrationCard' | 'agentModalButton'; const getTourConfig = (packageKey: string, tourType: TourType) => { if (packageKey.startsWith('endpoint') && tourType === 'addIntegrationButton') { return { @@ -25,6 +25,17 @@ const getTourConfig = (packageKey: string, tourType: TourType) => { }; } + if (packageKey.startsWith('endpoint') && tourType === 'integrationCard') { + return { + title: i18n.translate('xpack.fleet.guidedOnboardingTour.endpointCard.title', { + defaultMessage: 'Select Elastic Defend', + }), + description: i18n.translate('xpack.fleet.guidedOnboardingTour.endpointCard.description', { + defaultMessage: 'The best way to get data quickly in to your SIEM.', + }), + }; + } + if (packageKey.startsWith('kubernetes') && tourType === 'addIntegrationButton') { return { title: i18n.translate('xpack.fleet.guidedOnboardingTour.kubernetesButton.tourTitle', { @@ -90,6 +101,10 @@ export const WithGuidedOnboardingTour: FunctionComponent<{ })} </EuiButton> } + isOpen={isGuidedOnboardingTourOpen} + // Close the tour when the user clicks outside of the tour. This is a workaround for + // popover remaining open when the user changes the category of the integration list + closePopover={() => setIsGuidedOnboardingTourOpen(false)} > {children} </EuiTourStep> From 5ceda2b237a28b2a7697c36d14483eea2ac0c406 Mon Sep 17 00:00:00 2001 From: Shahzad <shahzad.muhammad@elastic.co> Date: Thu, 3 Nov 2022 11:28:26 +0100 Subject: [PATCH 62/86] [Synthetics] Getting started use url as monitor name (#143062) Co-authored-by: Abdul Zahid <awahab07@yahoo.com> --- .../getting_started/simple_monitor_form.tsx | 11 ++--------- .../getting_started/use_simple_monitor.ts | 14 +++++++++----- .../monitors_page/overview/overview_page.tsx | 4 ++-- .../plugins/translations/translations/fr-FR.json | 1 - .../plugins/translations/translations/ja-JP.json | 1 - .../plugins/translations/translations/zh-CN.json | 1 - 6 files changed, 13 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx index ce849f586a93c..68499e7612652 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.tsx @@ -45,7 +45,7 @@ export const SimpleMonitorForm = () => { setMonitorData(data); }; - const { loading } = useSimpleMonitor({ monitorData }); + const { loading, data: newMonitor } = useSimpleMonitor({ monitorData }); const hasURLError = !!errors?.[ConfigKey.URLS]; @@ -53,7 +53,7 @@ export const SimpleMonitorForm = () => { <EuiForm onSubmit={handleSubmit(onSubmit)} component="form" - isInvalid={isSubmitted && !isValid && !loading} + isInvalid={isSubmitted && !isValid && !loading && !newMonitor?.id} noValidate > <EuiFormRow @@ -92,13 +92,6 @@ export const SimpleMonitorForm = () => { ); }; -export const MY_FIRST_MONITOR = i18n.translate( - 'xpack.synthetics.monitorManagement.myFirstMonitor', - { - defaultMessage: 'My first monitor', - } -); - export const WEBSITE_URL_LABEL = i18n.translate( 'xpack.synthetics.monitorManagement.websiteUrlLabel', { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts index ec4c4e6507358..5a7d4a186fbaa 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts @@ -9,6 +9,7 @@ import { useFetcher } from '@kbn/observability-plugin/public'; import { useEffect } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useSelector } from 'react-redux'; +import { useSyntheticsRefreshContext } from '../../contexts'; import { selectServiceLocationsState } from '../../state'; import { showSyncErrors } from '../monitors_page/management/show_sync_errors'; import { fetchCreateMonitor } from '../../state'; @@ -19,13 +20,15 @@ import { ServiceLocationErrors, SyntheticsMonitorWithId, } from '../../../../../common/runtime_types'; -import { MONITOR_SUCCESS_LABEL, MY_FIRST_MONITOR, SimpleFormData } from './simple_monitor_form'; +import { MONITOR_SUCCESS_LABEL, SimpleFormData } from './simple_monitor_form'; import { kibanaService } from '../../../../utils/kibana_service'; export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData }) => { const { application } = useKibana().services; const { locations: serviceLocations } = useSelector(selectServiceLocationsState); + const { refreshApp } = useSyntheticsRefreshContext(); + const { data, loading } = useFetcher(() => { if (!monitorData) { return new Promise<undefined>((resolve) => resolve(undefined)); @@ -39,7 +42,7 @@ export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData await page.goto('${urls}'); });`, [ConfigKey.MONITOR_TYPE]: DataStream.BROWSER, - [ConfigKey.NAME]: MY_FIRST_MONITOR, + [ConfigKey.NAME]: urls, [ConfigKey.LOCATIONS]: locations, [ConfigKey.URLS]: urls, }, @@ -62,9 +65,10 @@ export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData title: MONITOR_SUCCESS_LABEL, toastLifeTimeMs: 3000, }); - application?.navigateToApp('synthetics', { path: `` }); + refreshApp(); + application?.navigateToApp('synthetics', { path: 'monitors' }); } - }, [application, data, loading, serviceLocations]); + }, [application, data, loading, refreshApp, serviceLocations]); - return { data, loading }; + return { data: data as SyntheticsMonitorWithId, loading }; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx index 4eb864e7a2a3c..23997199c5b60 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx @@ -32,7 +32,7 @@ export const OverviewPage: React.FC = () => { const dispatch = useDispatch(); - const { refreshApp } = useSyntheticsRefreshContext(); + const { refreshApp, lastRefresh } = useSyntheticsRefreshContext(); const { loading, pageState } = useSelector(selectOverviewState); const { loading: locationsLoading, locationsLoaded } = useSelector(selectServiceLocationsState); @@ -52,7 +52,7 @@ export const OverviewPage: React.FC = () => { useEffect(() => { dispatch(fetchMonitorOverviewAction.get(pageState)); - }, [dispatch, pageState]); + }, [dispatch, pageState, lastRefresh]); const { enablement: { isEnabled }, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index faacde782d509..39f0f53e959af 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -31139,7 +31139,6 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.reasonLabel": "Raison", "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "Statut", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "Impossible de synchroniser les moniteurs avec le service Synthetics", - "xpack.synthetics.monitorManagement.myFirstMonitor": "Mon premier moniteur", "xpack.synthetics.monitorManagement.nameRequired": "Le nom de l’emplacement est requis", "xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermission": "Vous n'êtes pas autorisé à accéder à Fleet. Des autorisations Fleet sont nécessaires pour créer de nouveaux emplacements privés.", "xpack.synthetics.monitorManagement.needPermissions": "Permissions requises", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 59e637a55f71c..3be793b6ebd42 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -31115,7 +31115,6 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.reasonLabel": "理由", "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "ステータス", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "モニターをSyntheticsサービスと同期できませんでした", - "xpack.synthetics.monitorManagement.myFirstMonitor": "最初のモニター", "xpack.synthetics.monitorManagement.nameRequired": "場所名は必須です", "xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermission": "Fleet へのアクセスが許可されていません。新しい非公開の場所を作成するには、Fleet権限が必要です。", "xpack.synthetics.monitorManagement.needPermissions": "権限が必要です", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 921639d7cdab4..064f959adf2e6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -31150,7 +31150,6 @@ "xpack.synthetics.monitorManagement.monitorSync.failure.reasonLabel": "原因", "xpack.synthetics.monitorManagement.monitorSync.failure.statusLabel": "状态", "xpack.synthetics.monitorManagement.monitorSync.failure.title": "监测无法与 Synthetics 服务同步", - "xpack.synthetics.monitorManagement.myFirstMonitor": "我的第一个监测", "xpack.synthetics.monitorManagement.nameRequired": "“位置名称”必填", "xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermission": "您无权访问 Fleet。需要 Fleet 权限才能创建新的专用位置。", "xpack.synthetics.monitorManagement.needPermissions": "需要权限", From cc4be7e612c1ab66905e6ad30b7b561b402fbb8c Mon Sep 17 00:00:00 2001 From: Joe Reuter <johannes.reuter@elastic.co> Date: Thu, 3 Nov 2022 11:35:46 +0100 Subject: [PATCH 63/86] [Lens] Allow date functions in formula (#143632) * allow date functions in formula * fix tests * fix test * fix review comments * fix test Co-authored-by: Marco Liberati <dej611@users.noreply.github.com> --- .../expression_functions/specs/math_column.ts | 31 ++++++++++++++++++- .../formula/editor/math_completion.test.ts | 4 +-- .../formula/editor/math_completion.ts | 10 +++--- .../definitions/formula/formula.test.tsx | 2 +- .../definitions/formula/formula.tsx | 5 +++ .../definitions/formula/generate.ts | 6 +++- .../definitions/formula/math.test.ts | 5 +++ .../operations/definitions/formula/math.tsx | 2 ++ .../definitions/last_value.test.tsx | 2 +- .../operations/definitions/last_value.tsx | 16 ++++++++-- 10 files changed, 70 insertions(+), 13 deletions(-) diff --git a/src/plugins/expressions/common/expression_functions/specs/math_column.ts b/src/plugins/expressions/common/expression_functions/specs/math_column.ts index b513ef5d27409..e056bc6b876e1 100644 --- a/src/plugins/expressions/common/expression_functions/specs/math_column.ts +++ b/src/plugins/expressions/common/expression_functions/specs/math_column.ts @@ -14,6 +14,7 @@ import { Datatable, DatatableColumn, DatatableColumnType, getType } from '../../ export type MathColumnArguments = MathArguments & { id: string; name?: string; + castColumns?: string[]; copyMetaFrom?: string | null; }; @@ -52,6 +53,14 @@ export const mathColumn: ExpressionFunctionDefinition< }), required: true, }, + castColumns: { + types: ['string'], + multi: true, + help: i18n.translate('expressions.functions.mathColumn.args.castColumnsHelpText', { + defaultMessage: 'The ids of columns to cast to numbers before applying the formula', + }), + required: false, + }, copyMetaFrom: { types: ['string', 'null'], help: i18n.translate('expressions.functions.mathColumn.args.copyMetaFromHelpText', { @@ -77,11 +86,31 @@ export const mathColumn: ExpressionFunctionDefinition< const newRows = await Promise.all( input.rows.map(async (row) => { + let preparedRow = row; + if (args.castColumns) { + preparedRow = { ...row }; + args.castColumns.forEach((columnId) => { + switch (typeof row[columnId]) { + case 'string': + const parsedAsDate = Number(new Date(preparedRow[columnId])); + if (!isNaN(parsedAsDate)) { + preparedRow[columnId] = parsedAsDate; + return; + } else { + preparedRow[columnId] = Number(preparedRow[columnId]); + return; + } + case 'boolean': + preparedRow[columnId] = Number(preparedRow[columnId]); + return; + } + }); + } const result = await math.fn( { ...input, columns: input.columns, - rows: [row], + rows: [preparedRow], }, { expression: args.expression, diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.test.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.test.ts index 76b70ba9a471c..d2237bea0f39e 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.test.ts @@ -353,7 +353,7 @@ describe('math completion', () => { expect(results.list).toEqual(['bytes', 'memory']); }); - it('should autocomplete only operations that provide numeric output', async () => { + it('should autocomplete only operations that provide numeric or date output', async () => { const results = await suggest({ expression: 'last_value()', zeroIndexedOffset: 11, @@ -366,7 +366,7 @@ describe('math completion', () => { unifiedSearch: unifiedSearchPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), }); - expect(results.list).toEqual(['bytes', 'memory']); + expect(results.list).toEqual(['bytes', 'memory', 'timestamp', 'start_date']); }); }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts index 8d0c9fd4d6b6b..de23e8c4f1bbe 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts @@ -306,12 +306,14 @@ function getArgumentSuggestions( operationDefinitionMap ); // TODO: This only allow numeric functions, will reject last_value(string) for example. - const validOperation = available.find( + const validOperation = available.filter( ({ operationMetaData }) => - operationMetaData.dataType === 'number' && !operationMetaData.isBucketed + (operationMetaData.dataType === 'number' || operationMetaData.dataType === 'date') && + !operationMetaData.isBucketed ); - if (validOperation) { - const fields = validOperation.operations + if (validOperation.length) { + const fields = validOperation + .flatMap((op) => op.operations) .filter((op) => op.operationType === operation.type) .map((op) => ('field' in op ? op.field : undefined)) .filter(nonNullable); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.test.tsx index 90ece71627f42..0925b5cbcb0d2 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.test.tsx @@ -157,7 +157,7 @@ describe('formula', () => { formulaOperation.buildColumn({ previousColumn: { ...layer.columns.col1, - dataType: 'date', + dataType: 'boolean', filter: { language: 'kuery', query: 'ABC: DEF' }, }, layer, diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.tsx index 9746a25ebf907..577b68b98675d 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.tsx @@ -139,6 +139,11 @@ export const formulaOperation: OperationDefinition<FormulaIndexPatternColumn, 'm arguments: { id: [columnId], name: [label || defaultLabel], + ...(currentColumn.references.length + ? { + castColumns: [currentColumn.references[0]], + } + : {}), expression: [currentColumn.references.length ? `"${currentColumn.references[0]}"` : ''], }, }, diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/generate.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/generate.ts index 2315d6d164fc8..d860cd11fd594 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/generate.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/generate.ts @@ -54,7 +54,11 @@ export function generateFormula( previousFormula += `${previousColumn.operationType}(${metric.operationType}(${fieldName})`; } } else { - if (previousColumn && 'sourceField' in previousColumn && previousColumn.dataType === 'number') { + if ( + previousColumn && + 'sourceField' in previousColumn && + (previousColumn.dataType === 'number' || previousColumn.dataType === 'date') + ) { previousFormula += `${previousColumn.operationType}(${getSafeFieldName(previousColumn)}`; } else { // couldn't find formula function to call, exit early because adding args is going to fail anyway diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.test.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.test.ts index c96bff33b5267..5ca43cb125cc8 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.test.ts @@ -163,6 +163,7 @@ describe('math operation', () => { arguments: { id: ['myColumnId'], name: ['Math'], + castColumns: [], expression: [ '(((((((((((((((("columnX0" + "columnX1") + "columnX2") + "columnX3") + "columnX4") + "columnX5") + "columnX6") + "columnX7") + "columnX8") + "columnX9") + "columnX10") + "columnX11") + "columnX12") + "columnX13") + "columnX14") + "columnX15") + "columnX16")', ], @@ -243,6 +244,7 @@ describe('math operation', () => { arguments: { id: ['myColumnId'], name: ['Math'], + castColumns: [], expression: [ `("columnX0" + (("columnX1" - "columnX2") / ("columnX3" - ("columnX4" * "columnX5"))))`, ], @@ -298,6 +300,7 @@ describe('math operation', () => { arguments: { id: ['myColumnId'], name: ['Math'], + castColumns: [], expression: [`max(min("columnX0","columnX1"),abs("columnX2"))`], onError: ['null'], }, @@ -342,6 +345,7 @@ describe('math operation', () => { arguments: { id: ['myColumnId'], name: ['Math'], + castColumns: [], expression: [`(5 + (3 / 8))`], onError: ['null'], }, @@ -425,6 +429,7 @@ describe('math operation', () => { arguments: { id: ['myColumnId'], name: ['Math'], + castColumns: [], expression: [ 'ifelse(("columnX0" == 0),ifelse(("columnX1" < 0),ifelse(("columnX2" <= 0),"columnX3","columnX4"),"columnX5"),ifelse(("columnX6" > 0),ifelse(("columnX7" >= 0),"columnX8","columnX9"),"columnX10"))', ], diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.tsx index 4215d727a4c42..4f396cb8ccf5a 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/math.tsx @@ -46,6 +46,8 @@ export const mathOperation: OperationDefinition<MathIndexPatternColumn, 'managed name: [column.label], expression: [astToString(column.params.tinymathAst)], onError: ['null'], + // cast everything into number + castColumns: column.references, }, }, ]; diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx index cf5babde1feb6..104f6da48041f 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx @@ -319,7 +319,7 @@ describe('last_value', () => { ).toEqual({ dataType: 'ip', isBucketed: false, - scale: 'ratio', + scale: 'ordinal', }); }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/last_value.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/last_value.tsx index 59b77cd73fc9d..698cdba8da13e 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/last_value.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/last_value.tsx @@ -124,6 +124,16 @@ function getExistsFilter(field: string) { }; } +function getScale(type: string) { + return type === 'string' || + type === 'ip' || + type === 'ip_range' || + type === 'date_range' || + type === 'number_range' + ? 'ordinal' + : 'ratio'; +} + export const lastValueOperation: OperationDefinition< LastValueIndexPatternColumn, 'field', @@ -155,7 +165,7 @@ export const lastValueOperation: OperationDefinition< label: ofName(field.displayName, oldColumn.timeShift, oldColumn.reducedTimeRange), sourceField: field.name, params: newParams, - scale: field.type === 'string' ? 'ordinal' : 'ratio', + scale: getScale(field.type), filter: oldColumn.filter && isEqual(oldColumn.filter, getExistsFilter(oldColumn.sourceField)) ? getExistsFilter(field.name) @@ -167,7 +177,7 @@ export const lastValueOperation: OperationDefinition< return { dataType: type as DataType, isBucketed: false, - scale: type === 'string' ? 'ordinal' : 'ratio', + scale: getScale(type), }; } }, @@ -218,7 +228,7 @@ export const lastValueOperation: OperationDefinition< dataType: field.type as DataType, operationType: 'last_value', isBucketed: false, - scale: field.type === 'string' ? 'ordinal' : 'ratio', + scale: getScale(field.type), sourceField: field.name, filter: getFilter(previousColumn, columnParams) || getExistsFilter(field.name), timeShift: columnParams?.shift || previousColumn?.timeShift, From ac7f549d1de09c060eb567a9c081958152917fdb Mon Sep 17 00:00:00 2001 From: Dario Gieselaar <dario.gieselaar@elastic.co> Date: Thu, 3 Nov 2022 11:56:12 +0100 Subject: [PATCH 64/86] [APM] Use FNV1 for hashing critical path data (#144429) --- .../traces/get_aggregated_critical_path.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/apm/server/routes/traces/get_aggregated_critical_path.ts b/x-pack/plugins/apm/server/routes/traces/get_aggregated_critical_path.ts index 8acf458f46e22..612039ed0914b 100644 --- a/x-pack/plugins/apm/server/routes/traces/get_aggregated_critical_path.ts +++ b/x-pack/plugins/apm/server/routes/traces/get_aggregated_critical_path.ts @@ -106,7 +106,17 @@ export async function getAggregatedCriticalPath({ map_script: { source: ` String toHash (def item) { - return item.toString(); + long FNV_32_INIT = 0x811c9dc5L; + long FNV_32_PRIME = 0x01000193L; + char[] chars = item.toString().toCharArray(); + long rv = FNV_32_INIT; + int len = chars.length; + for(int i = 0; i < len; i++) { + byte bt = (byte) chars[i]; + rv ^= bt; + rv *= FNV_32_PRIME; + } + return rv.toString(); } def id; @@ -160,7 +170,17 @@ export async function getAggregatedCriticalPath({ reduce_script: { source: ` String toHash (def item) { - return item.toString(); + long FNV_32_INIT = 0x811c9dc5L; + long FNV_32_PRIME = 0x01000193L; + char[] chars = item.toString().toCharArray(); + long rv = FNV_32_INIT; + int len = chars.length; + for(int i = 0; i < len; i++) { + byte bt = (byte) chars[i]; + rv ^= bt; + rv *= FNV_32_PRIME; + } + return rv.toString(); } def processEvent (def context, def event) { From 46fd8e373b75c9e0deb150df21b2c33c10ad9e98 Mon Sep 17 00:00:00 2001 From: Dima Arnautov <dmitrii.arnautov@elastic.co> Date: Thu, 3 Nov 2022 12:23:09 +0100 Subject: [PATCH 65/86] [ML] Remove the CPU autoscale warning (#144442) --- .../start_deployment_setup.tsx | 25 ------------------- .../translations/translations/fr-FR.json | 2 -- .../translations/translations/ja-JP.json | 2 -- .../translations/translations/zh-CN.json | 2 -- 4 files changed, 31 deletions(-) diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/start_deployment_setup.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/start_deployment_setup.tsx index a315da1ca9f8f..ea3f1c7a5705a 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/start_deployment_setup.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/start_deployment_setup.tsx @@ -33,7 +33,6 @@ import type { Observable } from 'rxjs'; import type { CoreTheme, OverlayStart } from '@kbn/core/public'; import { css } from '@emotion/react'; import { numberValidator } from '@kbn/ml-agg-utils'; -import { isCloud } from '../../services/ml_server_info'; import { composeValidators, requiredValidator } from '../../../../common/util/validators'; interface StartDeploymentSetup { @@ -224,30 +223,6 @@ export const StartDeploymentModal: FC<StartDeploymentModalProps> = ({ </EuiModalHeader> <EuiModalBody> - {isCloud() ? ( - <> - <EuiCallOut - size={'s'} - title={ - <FormattedMessage - id="xpack.ml.trainedModels.modelsList.startDeployment.cloudWarningHeader" - defaultMessage="In the future Cloud deployments will autoscale to have the required number of processors." - /> - } - iconType="iInCircle" - color={'warning'} - > - <p> - <FormattedMessage - id="xpack.ml.trainedModels.modelsList.startDeployment.cloudWarningText" - defaultMessage="However, in this release you must increase the size of your ML nodes manually in the Cloud console to get more processors." - /> - </p> - </EuiCallOut> - <EuiSpacer size={'m'} /> - </> - ) : null} - <EuiCallOut size={'s'} title={ diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 39f0f53e959af..315081311e48d 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -21583,8 +21583,6 @@ "xpack.ml.trainedModels.modelsList.pipelines.processorStats.typeHeader": "Type de processeur", "xpack.ml.trainedModels.modelsList.selectableMessage": "Sélectionner un modèle", "xpack.ml.trainedModels.modelsList.startDeployment.cancelButton": "Annuler", - "xpack.ml.trainedModels.modelsList.startDeployment.cloudWarningHeader": "À l'avenir, les déploiements cloud seront mis à l'échelle automatiquement afin de disposer du nombre requis de processeurs.", - "xpack.ml.trainedModels.modelsList.startDeployment.cloudWarningText": "Dans cette version, cependant, vous devez augmenter la taille de vos nœuds ML manuellement dans la console cloud pour obtenir plus de processeurs.", "xpack.ml.trainedModels.modelsList.startDeployment.docLinkTitle": "En savoir plus", "xpack.ml.trainedModels.modelsList.startDeployment.maxNumOfProcessorsWarning": "Le produit du nombre d'allocations et de threads par allocation doit être inférieur au nombre total de processeurs sur vos nœuds ML.", "xpack.ml.trainedModels.modelsList.startDeployment.numbersOfAllocationsHelp": "Augmentez pour améliorer le débit de toutes les requêtes.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3be793b6ebd42..a0044c2613305 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -21564,8 +21564,6 @@ "xpack.ml.trainedModels.modelsList.pipelines.processorStats.typeHeader": "プロセッサータイプ", "xpack.ml.trainedModels.modelsList.selectableMessage": "モデルを選択", "xpack.ml.trainedModels.modelsList.startDeployment.cancelButton": "キャンセル", - "xpack.ml.trainedModels.modelsList.startDeployment.cloudWarningHeader": "将来は、必要な数のプロセッサーになるように、クラウドデプロイが自動スケーリングされる予定です。", - "xpack.ml.trainedModels.modelsList.startDeployment.cloudWarningText": "ただし、このリリースでは、クラウドコンソールで手動でMLノードのサイズを増やし、プロセッサーを増やす必要があります。", "xpack.ml.trainedModels.modelsList.startDeployment.docLinkTitle": "詳細", "xpack.ml.trainedModels.modelsList.startDeployment.maxNumOfProcessorsWarning": "割り当て数と割り当てごとのスレッドの積は、MLノードのプロセッサーの合計数未満でなければなりません。", "xpack.ml.trainedModels.modelsList.startDeployment.numbersOfAllocationsHelp": "増やすと、すべてのリクエストのスループットを改善します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 064f959adf2e6..42668ff33f42e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -21594,8 +21594,6 @@ "xpack.ml.trainedModels.modelsList.pipelines.processorStats.typeHeader": "处理器类型", "xpack.ml.trainedModels.modelsList.selectableMessage": "选择模型", "xpack.ml.trainedModels.modelsList.startDeployment.cancelButton": "取消", - "xpack.ml.trainedModels.modelsList.startDeployment.cloudWarningHeader": "将来,云部署会自动扩展以提供所需数量的处理器。", - "xpack.ml.trainedModels.modelsList.startDeployment.cloudWarningText": "但是,在本版本中,您必须在云控制台中手动增加 ML 节点的大小,以获得更多处理器。", "xpack.ml.trainedModels.modelsList.startDeployment.docLinkTitle": "了解详情", "xpack.ml.trainedModels.modelsList.startDeployment.maxNumOfProcessorsWarning": "产品的分配次数和每次分配的线程数应小于 ML 节点上处理器的总数。", "xpack.ml.trainedModels.modelsList.startDeployment.numbersOfAllocationsHelp": "增加以提高所有请求的吞吐量。", From 48ea0297d535f39bffdc3cb108a4555348b002d0 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko <dzmitry.lemechko@elastic.co> Date: Thu, 3 Nov 2022 12:49:16 +0100 Subject: [PATCH 66/86] [performance/journeys] fix journeys (#144507) --- .../journeys/ecommerce_dashboard_saved_search_only.ts | 4 ++-- .../journeys/ecommerce_dashboard_tsvb_gauge_only.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/performance/journeys/ecommerce_dashboard_saved_search_only.ts b/x-pack/performance/journeys/ecommerce_dashboard_saved_search_only.ts index 9c454e890af29..c63f239c5a491 100644 --- a/x-pack/performance/journeys/ecommerce_dashboard_saved_search_only.ts +++ b/x-pack/performance/journeys/ecommerce_dashboard_saved_search_only.ts @@ -19,7 +19,7 @@ export const journey = new Journey({ await page.waitForSelector('#dashboardListingHeading'); }) - .step('Go to Ecommerce Dashboard with Saved Search only', async ({ page }) => { + .step('Go to Ecommerce Dashboard with Saved Search only', async ({ page, log }) => { await page.click(subj('dashboardListingTitleLink-[eCommerce]-Saved-Search-Dashboard')); - await waitForVisualizations(page, 1); + await waitForVisualizations(page, log, 1); }); diff --git a/x-pack/performance/journeys/ecommerce_dashboard_tsvb_gauge_only.ts b/x-pack/performance/journeys/ecommerce_dashboard_tsvb_gauge_only.ts index 1988112b9a397..ad252cfb16e88 100644 --- a/x-pack/performance/journeys/ecommerce_dashboard_tsvb_gauge_only.ts +++ b/x-pack/performance/journeys/ecommerce_dashboard_tsvb_gauge_only.ts @@ -19,7 +19,7 @@ export const journey = new Journey({ await page.waitForSelector('#dashboardListingHeading'); }) - .step('Go to Ecommerce Dashboard with TSVB Gauge only', async ({ page }) => { + .step('Go to Ecommerce Dashboard with TSVB Gauge only', async ({ page, log }) => { await page.click(subj('dashboardListingTitleLink-[eCommerce]-TSVB-Gauge-Only-Dashboard')); - await waitForVisualizations(page, 1); + await waitForVisualizations(page, log, 1); }); From 1934b595f85fa482be588ff7c4edeb9404ad2f5e Mon Sep 17 00:00:00 2001 From: doakalexi <109488926+doakalexi@users.noreply.github.com> Date: Thu, 3 Nov 2022 08:12:01 -0400 Subject: [PATCH 67/86] [ResponseOps][Alerting] Provide scaling metrics for Kibana Alerting (#143586) * Adding scaling metrics * Adding utilization tests * Changing the key name * Updating to use new api * Updating task created counter * Fixing tests * Fixing tests * Adding telemetry * Changing telemetry field * Updating telemetry schema * Fixing failing test * Fixed typos * Update x-pack/plugins/task_manager/server/routes/background_task_utilization.test.ts Co-authored-by: Ying Mao <ying.mao@elastic.co> * Addressing pr feedback * Updating to use configurable interval * Updating metrics to be counts Co-authored-by: Ying Mao <ying.mao@elastic.co> --- .../server/lib/adhoc_task_counter.test.ts | 27 + .../server/lib/adhoc_task_counter.ts | 36 ++ .../task_manager/server/lib/intervals.test.ts | 39 ++ .../task_manager/server/lib/intervals.ts | 4 + ...ground_task_utilization_statistics.test.ts | 542 ++++++++++++++++++ .../background_task_utilization_statistics.ts | 276 +++++++++ .../task_manager/server/monitoring/index.ts | 3 + .../monitoring/monitoring_stats_stream.ts | 20 +- x-pack/plugins/task_manager/server/plugin.ts | 20 +- .../background_task_utilization.test.ts | 206 +++++++ .../routes/background_task_utilization.ts | 149 +++++ .../task_manager/server/routes/index.ts | 1 + .../task_manager/server/task_store.test.ts | 72 +++ .../plugins/task_manager/server/task_store.ts | 14 +- .../task_manager_usage_collector.test.ts | 152 ++++- .../usage/task_manager_usage_collector.ts | 40 +- .../task_manager/server/usage/types.ts | 9 + .../schema/xpack_plugins.json | 23 + .../background_task_utilization_route.ts | 107 ++++ .../test_suites/task_manager/index.ts | 1 + 20 files changed, 1728 insertions(+), 13 deletions(-) create mode 100644 x-pack/plugins/task_manager/server/lib/adhoc_task_counter.test.ts create mode 100644 x-pack/plugins/task_manager/server/lib/adhoc_task_counter.ts create mode 100644 x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.test.ts create mode 100644 x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.ts create mode 100644 x-pack/plugins/task_manager/server/routes/background_task_utilization.test.ts create mode 100644 x-pack/plugins/task_manager/server/routes/background_task_utilization.ts create mode 100644 x-pack/test/plugin_api_integration/test_suites/task_manager/background_task_utilization_route.ts diff --git a/x-pack/plugins/task_manager/server/lib/adhoc_task_counter.test.ts b/x-pack/plugins/task_manager/server/lib/adhoc_task_counter.test.ts new file mode 100644 index 0000000000000..d8cb08127b592 --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/adhoc_task_counter.test.ts @@ -0,0 +1,27 @@ +/* + * 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 { AdHocTaskCounter } from './adhoc_task_counter'; + +describe('AdHocTaskCounter', () => { + const counter = new AdHocTaskCounter(); + + afterAll(() => { + counter.reset(); + }); + + it('increments counter', async () => { + counter.increment(10); + await expect(counter.count).toEqual(10); + }); + + it('resets counter', async () => { + counter.increment(10); + counter.reset(); + await expect(counter.count).toEqual(0); + }); +}); diff --git a/x-pack/plugins/task_manager/server/lib/adhoc_task_counter.ts b/x-pack/plugins/task_manager/server/lib/adhoc_task_counter.ts new file mode 100644 index 0000000000000..dc035ba890175 --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/adhoc_task_counter.ts @@ -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. + */ + +/** + * Keeps track of how many tasks have been created. + * + * @export + * @class AdHocTaskCounter + * + */ +export class AdHocTaskCounter { + /** + * Gets the number of created tasks. + */ + public get count() { + return this._count; + } + + private _count: number; + + constructor() { + this._count = 0; + } + + public increment(by: number = 1) { + this._count += by; + } + + public reset() { + this._count = 0; + } +} diff --git a/x-pack/plugins/task_manager/server/lib/intervals.test.ts b/x-pack/plugins/task_manager/server/lib/intervals.test.ts index 416be4c1946e5..0d139fe5dfccf 100644 --- a/x-pack/plugins/task_manager/server/lib/intervals.test.ts +++ b/x-pack/plugins/task_manager/server/lib/intervals.test.ts @@ -16,6 +16,7 @@ import { secondsFromDate, asInterval, maxIntervalFromDate, + parseIntervalAsMinute, } from './intervals'; let fakeTimer: sinon.SinonFakeTimers; @@ -65,6 +66,44 @@ describe('taskIntervals', () => { }); }); + describe('parseIntervalAsMinute', () => { + test('it accepts intervals in the form `Nm`', () => { + expect(() => parseIntervalAsMinute(`${_.random(1, 1000)}m`)).not.toThrow(); + }); + + test('it accepts intervals in the form `Ns`', () => { + expect(() => parseIntervalAsMinute(`${_.random(1, 1000)}s`)).not.toThrow(); + }); + + test('it rejects 0 based intervals', () => { + expect(() => parseIntervalAsMinute('0m')).toThrow( + /Invalid interval "0m"\. Intervals must be of the form {number}m. Example: 5m/ + ); + expect(() => parseIntervalAsMinute('0s')).toThrow( + /Invalid interval "0s"\. Intervals must be of the form {number}m. Example: 5m/ + ); + }); + + test('it rejects intervals are not of the form `Nm` or `Ns`', () => { + expect(() => parseIntervalAsMinute(`5m 2s`)).toThrow( + /Invalid interval "5m 2s"\. Intervals must be of the form {number}m. Example: 5m/ + ); + expect(() => parseIntervalAsMinute(`hello`)).toThrow( + /Invalid interval "hello"\. Intervals must be of the form {number}m. Example: 5m/ + ); + }); + + test('returns an interval as m', () => { + expect(parseIntervalAsMinute('5s')).toEqual(5 / 60); + expect(parseIntervalAsMinute('15s')).toEqual(15 / 60); + expect(parseIntervalAsMinute('20m')).toEqual(20); + expect(parseIntervalAsMinute('61m')).toEqual(61); + expect(parseIntervalAsMinute('90m')).toEqual(90); + expect(parseIntervalAsMinute('2h')).toEqual(2 * 60); + expect(parseIntervalAsMinute('9d')).toEqual(9 * 60 * 24); + }); + }); + describe('parseIntervalAsMillisecond', () => { test('it accepts intervals in the form `Nm`', () => { expect(() => parseIntervalAsMillisecond(`${_.random(1, 1000)}m`)).not.toThrow(); diff --git a/x-pack/plugins/task_manager/server/lib/intervals.ts b/x-pack/plugins/task_manager/server/lib/intervals.ts index f876c60fe5435..4ebb1e70a18bf 100644 --- a/x-pack/plugins/task_manager/server/lib/intervals.ts +++ b/x-pack/plugins/task_manager/server/lib/intervals.ts @@ -114,6 +114,10 @@ export const parseIntervalAsSecond = memoize((interval: Interval): number => { return Math.round(parseIntervalAsMillisecond(interval) / 1000); }); +export const parseIntervalAsMinute = memoize((interval: Interval): number => { + return parseIntervalAsMillisecond(interval) / (1000 * 60); +}); + export const parseIntervalAsMillisecond = memoize((interval: Interval): number => { const numericAsStr: string = interval.slice(0, -1); const numeric: number = parseInt(numericAsStr, 10); diff --git a/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.test.ts new file mode 100644 index 0000000000000..875c7cf735836 --- /dev/null +++ b/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.test.ts @@ -0,0 +1,542 @@ +/* + * 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 uuid from 'uuid'; +import { Subject, Observable } from 'rxjs'; +import { take, bufferCount, skip, map } from 'rxjs/operators'; +import { ConcreteTaskInstance, TaskStatus } from '../task'; +import { asTaskRunEvent, TaskTiming, TaskPersistence } from '../task_events'; +import { asOk } from '../lib/result_type'; +import { TaskLifecycleEvent } from '../polling_lifecycle'; +import { TaskRunResult } from '../task_running'; +import { AggregatedStat } from './runtime_statistics_aggregator'; +import { taskPollingLifecycleMock } from '../polling_lifecycle.mock'; +import { + BackgroundTaskUtilizationStat, + createBackgroundTaskUtilizationAggregator, + SummarizedBackgroundTaskUtilizationStat, + summarizeUtilizationStat, +} from './background_task_utilization_statistics'; +import { parseIntervalAsMinute } from '../lib/intervals'; +import { AdHocTaskCounter } from '../lib/adhoc_task_counter'; +import { sum } from 'lodash'; + +describe('Task Run Statistics', () => { + const pollInterval = 3000; + + beforeAll(() => { + jest.resetAllMocks(); + }); + + test('returns a running count of adhoc actual service_time', async () => { + const serviceTimes = [1000, 2000, 500, 300, 400, 15000, 20000, 200]; + const events$ = new Subject<TaskLifecycleEvent>(); + const taskPollingLifecycle = taskPollingLifecycleMock.create({ + events$: events$ as Observable<TaskLifecycleEvent>, + }); + const adHocTaskCounter = new AdHocTaskCounter(); + + const runningAverageWindowSize = 5; + const BackgroundTaskUtilizationAggregator = createBackgroundTaskUtilizationAggregator( + taskPollingLifecycle, + runningAverageWindowSize, + adHocTaskCounter, + pollInterval + ); + + function expectWindowEqualsUpdate( + taskStat: AggregatedStat<SummarizedBackgroundTaskUtilizationStat>, + window: number[] + ) { + expect(taskStat.value.adhoc.ran.service_time.actual).toEqual(sum(window)); + } + + return new Promise<void>((resolve) => { + const events = []; + const now = Date.now(); + for (const time of serviceTimes) { + events.push({ start: runAtMillisecondsAgo(now, time).getTime(), stop: now }); + } + BackgroundTaskUtilizationAggregator.pipe( + // skip initial stat which is just initialized data which + // ensures we don't stall on combineLatest + skip(1), + // Use 'summarizeUtilizationStat' to receive summarize stats + map(({ key, value }: AggregatedStat<BackgroundTaskUtilizationStat>) => ({ + key, + value: summarizeUtilizationStat(value).value, + })), + take(serviceTimes.length), + bufferCount(serviceTimes.length) + ).subscribe((taskStats: Array<AggregatedStat<SummarizedBackgroundTaskUtilizationStat>>) => { + expectWindowEqualsUpdate(taskStats[0], serviceTimes.slice(0, 1)); + expectWindowEqualsUpdate(taskStats[1], serviceTimes.slice(0, 2)); + expectWindowEqualsUpdate(taskStats[2], serviceTimes.slice(0, 3)); + expectWindowEqualsUpdate(taskStats[3], serviceTimes.slice(0, 4)); + expectWindowEqualsUpdate(taskStats[4], serviceTimes.slice(0, 5)); + // from the 6th value, begin to drop old values as out window is 5 + expectWindowEqualsUpdate(taskStats[5], serviceTimes.slice(1, 6)); + expectWindowEqualsUpdate(taskStats[6], serviceTimes.slice(2, 7)); + expectWindowEqualsUpdate(taskStats[7], serviceTimes.slice(3, 8)); + resolve(); + }); + + for (const event of events) { + events$.next(mockTaskRunEvent({}, event)); + } + }); + }); + + test('returns a running count of adhoc adjusted service_time', async () => { + const serviceTimes = [1000, 2000, 500, 300, 400, 15000, 20000, 200]; + const events$ = new Subject<TaskLifecycleEvent>(); + const taskPollingLifecycle = taskPollingLifecycleMock.create({ + events$: events$ as Observable<TaskLifecycleEvent>, + }); + const adHocTaskCounter = new AdHocTaskCounter(); + + const runningAverageWindowSize = 5; + const BackgroundTaskUtilizationAggregator = createBackgroundTaskUtilizationAggregator( + taskPollingLifecycle, + runningAverageWindowSize, + adHocTaskCounter, + pollInterval + ); + + function expectWindowEqualsUpdate( + taskStat: AggregatedStat<SummarizedBackgroundTaskUtilizationStat>, + window: number[] + ) { + expect(taskStat.value.adhoc.ran.service_time.adjusted).toEqual(sum(window)); + } + + return new Promise<void>((resolve) => { + const events = []; + const now = Date.now(); + for (const time of serviceTimes) { + events.push({ start: runAtMillisecondsAgo(now, time).getTime(), stop: now }); + } + BackgroundTaskUtilizationAggregator.pipe( + // skip initial stat which is just initialized data which + // ensures we don't stall on combineLatest + skip(1), + // Use 'summarizeUtilizationStat' to receive summarize stats + map(({ key, value }: AggregatedStat<BackgroundTaskUtilizationStat>) => ({ + key, + value: summarizeUtilizationStat(value).value, + })), + take(serviceTimes.length), + bufferCount(serviceTimes.length) + ).subscribe((taskStats: Array<AggregatedStat<SummarizedBackgroundTaskUtilizationStat>>) => { + expectWindowEqualsUpdate(taskStats[0], roundUpToNearestSec(serviceTimes.slice(0, 1), 3)); + expectWindowEqualsUpdate(taskStats[1], roundUpToNearestSec(serviceTimes.slice(0, 2), 3)); + expectWindowEqualsUpdate(taskStats[2], roundUpToNearestSec(serviceTimes.slice(0, 3), 3)); + expectWindowEqualsUpdate(taskStats[3], roundUpToNearestSec(serviceTimes.slice(0, 4), 3)); + expectWindowEqualsUpdate(taskStats[4], roundUpToNearestSec(serviceTimes.slice(0, 5), 3)); + // from the 6th value, begin to drop old values as out window is 5 + expectWindowEqualsUpdate(taskStats[5], roundUpToNearestSec(serviceTimes.slice(1, 6), 3)); + expectWindowEqualsUpdate(taskStats[6], roundUpToNearestSec(serviceTimes.slice(2, 7), 3)); + expectWindowEqualsUpdate(taskStats[7], roundUpToNearestSec(serviceTimes.slice(3, 8), 3)); + resolve(); + }); + + for (const event of events) { + events$.next(mockTaskRunEvent({}, event)); + } + }); + }); + + test('returns a running count of adhoc task_counter', async () => { + const tasks = [0, 0, 0, 0, 0, 0, 0, 0]; + const events$ = new Subject<TaskLifecycleEvent>(); + const taskPollingLifecycle = taskPollingLifecycleMock.create({ + events$: events$ as Observable<TaskLifecycleEvent>, + }); + const adHocTaskCounter = new AdHocTaskCounter(); + + const runningAverageWindowSize = 5; + const BackgroundTaskUtilizationAggregator = createBackgroundTaskUtilizationAggregator( + taskPollingLifecycle, + runningAverageWindowSize, + adHocTaskCounter, + pollInterval + ); + + function expectWindowEqualsUpdate( + taskStat: AggregatedStat<SummarizedBackgroundTaskUtilizationStat>, + window: number[] + ) { + expect(taskStat.value.adhoc.ran.service_time.task_counter).toEqual(window.length); + } + + return new Promise<void>((resolve) => { + BackgroundTaskUtilizationAggregator.pipe( + // skip initial stat which is just initialized data which + // ensures we don't stall on combineLatest + skip(1), + // Use 'summarizeUtilizationStat' to receive summarize stats + map(({ key, value }: AggregatedStat<BackgroundTaskUtilizationStat>) => ({ + key, + value: summarizeUtilizationStat(value).value, + })), + take(tasks.length), + bufferCount(tasks.length) + ).subscribe((taskStats: Array<AggregatedStat<SummarizedBackgroundTaskUtilizationStat>>) => { + expectWindowEqualsUpdate(taskStats[0], tasks.slice(0, 1)); + expectWindowEqualsUpdate(taskStats[1], tasks.slice(0, 2)); + expectWindowEqualsUpdate(taskStats[2], tasks.slice(0, 3)); + expectWindowEqualsUpdate(taskStats[3], tasks.slice(0, 4)); + expectWindowEqualsUpdate(taskStats[4], tasks.slice(0, 5)); + // from the 6th value, begin to drop old values as out window is 5 + expectWindowEqualsUpdate(taskStats[5], tasks.slice(1, 6)); + expectWindowEqualsUpdate(taskStats[6], tasks.slice(2, 7)); + expectWindowEqualsUpdate(taskStats[7], tasks.slice(3, 8)); + resolve(); + }); + + for (const task of tasks) { + events$.next(mockTaskRunEvent({}, { start: task, stop: task })); + } + }); + }); + + test('returns a running count of adhoc created counter', async () => { + const tasks = [1000, 2000, 500, 300, 400, 15000, 20000, 200]; + const events$ = new Subject<TaskLifecycleEvent>(); + const taskPollingLifecycle = taskPollingLifecycleMock.create({ + events$: events$ as Observable<TaskLifecycleEvent>, + }); + const adHocTaskCounter = new AdHocTaskCounter(); + + const runningAverageWindowSize = 5; + const BackgroundTaskUtilizationAggregator = createBackgroundTaskUtilizationAggregator( + taskPollingLifecycle, + runningAverageWindowSize, + adHocTaskCounter, + pollInterval + ); + + function expectWindowEqualsUpdate( + taskStat: AggregatedStat<SummarizedBackgroundTaskUtilizationStat>, + window: number[] + ) { + expect(taskStat.value.adhoc.created.counter).toEqual(sum(window)); + } + + return new Promise<void>((resolve) => { + BackgroundTaskUtilizationAggregator.pipe( + // skip initial stat which is just initialized data which + // ensures we don't stall on combineLatest + skip(1), + // Use 'summarizeUtilizationStat' to receive summarize stats + map(({ key, value }: AggregatedStat<BackgroundTaskUtilizationStat>) => ({ + key, + value: summarizeUtilizationStat(value).value, + })), + take(tasks.length), + bufferCount(tasks.length) + ).subscribe((taskStats: Array<AggregatedStat<SummarizedBackgroundTaskUtilizationStat>>) => { + expectWindowEqualsUpdate(taskStats[0], tasks.slice(0, 1)); + expectWindowEqualsUpdate(taskStats[1], tasks.slice(0, 2)); + expectWindowEqualsUpdate(taskStats[2], tasks.slice(0, 3)); + expectWindowEqualsUpdate(taskStats[3], tasks.slice(0, 4)); + expectWindowEqualsUpdate(taskStats[4], tasks.slice(0, 5)); + // from the 6th value, begin to drop old values as out window is 5 + expectWindowEqualsUpdate(taskStats[5], tasks.slice(1, 6)); + expectWindowEqualsUpdate(taskStats[6], tasks.slice(2, 7)); + expectWindowEqualsUpdate(taskStats[7], tasks.slice(3, 8)); + resolve(); + }); + + for (const task of tasks) { + adHocTaskCounter.increment(task); + events$.next(mockTaskRunEvent({}, { start: 0, stop: 0 })); + } + }); + }); + + test('returns a running count of recurring actual service_time', async () => { + const serviceTimes = [1000, 2000, 500, 300, 400, 15000, 20000, 200]; + const events$ = new Subject<TaskLifecycleEvent>(); + const taskPollingLifecycle = taskPollingLifecycleMock.create({ + events$: events$ as Observable<TaskLifecycleEvent>, + }); + const adHocTaskCounter = new AdHocTaskCounter(); + + const runningAverageWindowSize = 5; + const BackgroundTaskUtilizationAggregator = createBackgroundTaskUtilizationAggregator( + taskPollingLifecycle, + runningAverageWindowSize, + adHocTaskCounter, + pollInterval + ); + + function expectWindowEqualsUpdate( + taskStat: AggregatedStat<SummarizedBackgroundTaskUtilizationStat>, + window: number[] + ) { + expect(taskStat.value.recurring.ran.service_time.actual).toEqual(sum(window)); + } + + return new Promise<void>((resolve) => { + const events = []; + const now = Date.now(); + for (const time of serviceTimes) { + events.push({ start: runAtMillisecondsAgo(now, time).getTime(), stop: now }); + } + BackgroundTaskUtilizationAggregator.pipe( + // skip initial stat which is just initialized data which + // ensures we don't stall on combineLatest + skip(1), + // Use 'summarizeUtilizationStat' to receive summarize stats + map(({ key, value }: AggregatedStat<BackgroundTaskUtilizationStat>) => ({ + key, + value: summarizeUtilizationStat(value).value, + })), + take(serviceTimes.length), + bufferCount(serviceTimes.length) + ).subscribe((taskStats: Array<AggregatedStat<SummarizedBackgroundTaskUtilizationStat>>) => { + expectWindowEqualsUpdate(taskStats[0], serviceTimes.slice(0, 1)); + expectWindowEqualsUpdate(taskStats[1], serviceTimes.slice(0, 2)); + expectWindowEqualsUpdate(taskStats[2], serviceTimes.slice(0, 3)); + expectWindowEqualsUpdate(taskStats[3], serviceTimes.slice(0, 4)); + expectWindowEqualsUpdate(taskStats[4], serviceTimes.slice(0, 5)); + // from the 6th value, begin to drop old values as out window is 5 + expectWindowEqualsUpdate(taskStats[5], serviceTimes.slice(1, 6)); + expectWindowEqualsUpdate(taskStats[6], serviceTimes.slice(2, 7)); + expectWindowEqualsUpdate(taskStats[7], serviceTimes.slice(3, 8)); + resolve(); + }); + + for (const event of events) { + events$.next(mockTaskRunEvent({ schedule: { interval: '1h' } }, event)); + } + }); + }); + + test('returns a running count of recurring adjusted service_time', async () => { + const serviceTimes = [1000, 2000, 500, 300, 400, 15000, 20000, 200]; + const events$ = new Subject<TaskLifecycleEvent>(); + const taskPollingLifecycle = taskPollingLifecycleMock.create({ + events$: events$ as Observable<TaskLifecycleEvent>, + }); + const adHocTaskCounter = new AdHocTaskCounter(); + + const runningAverageWindowSize = 5; + const BackgroundTaskUtilizationAggregator = createBackgroundTaskUtilizationAggregator( + taskPollingLifecycle, + runningAverageWindowSize, + adHocTaskCounter, + pollInterval + ); + + function expectWindowEqualsUpdate( + taskStat: AggregatedStat<SummarizedBackgroundTaskUtilizationStat>, + window: number[] + ) { + expect(taskStat.value.recurring.ran.service_time.adjusted).toEqual(sum(window)); + } + + return new Promise<void>((resolve) => { + const events = []; + const now = Date.now(); + for (const time of serviceTimes) { + events.push({ start: runAtMillisecondsAgo(now, time).getTime(), stop: now }); + } + BackgroundTaskUtilizationAggregator.pipe( + // skip initial stat which is just initialized data which + // ensures we don't stall on combineLatest + skip(1), + // Use 'summarizeUtilizationStat' to receive summarize stats + map(({ key, value }: AggregatedStat<BackgroundTaskUtilizationStat>) => ({ + key, + value: summarizeUtilizationStat(value).value, + })), + take(serviceTimes.length), + bufferCount(serviceTimes.length) + ).subscribe((taskStats: Array<AggregatedStat<SummarizedBackgroundTaskUtilizationStat>>) => { + expectWindowEqualsUpdate(taskStats[0], roundUpToNearestSec(serviceTimes.slice(0, 1), 3)); + expectWindowEqualsUpdate(taskStats[1], roundUpToNearestSec(serviceTimes.slice(0, 2), 3)); + expectWindowEqualsUpdate(taskStats[2], roundUpToNearestSec(serviceTimes.slice(0, 3), 3)); + expectWindowEqualsUpdate(taskStats[3], roundUpToNearestSec(serviceTimes.slice(0, 4), 3)); + expectWindowEqualsUpdate(taskStats[4], roundUpToNearestSec(serviceTimes.slice(0, 5), 3)); + // from the 6th value, begin to drop old values as out window is 5 + expectWindowEqualsUpdate(taskStats[5], roundUpToNearestSec(serviceTimes.slice(1, 6), 3)); + expectWindowEqualsUpdate(taskStats[6], roundUpToNearestSec(serviceTimes.slice(2, 7), 3)); + expectWindowEqualsUpdate(taskStats[7], roundUpToNearestSec(serviceTimes.slice(3, 8), 3)); + resolve(); + }); + + for (const event of events) { + events$.next(mockTaskRunEvent({ schedule: { interval: '1h' } }, event)); + } + }); + }); + + test('returns a running count of recurring task_counter', async () => { + const tasks = [0, 0, 0, 0, 0, 0, 0, 0]; + const events$ = new Subject<TaskLifecycleEvent>(); + const taskPollingLifecycle = taskPollingLifecycleMock.create({ + events$: events$ as Observable<TaskLifecycleEvent>, + }); + const adHocTaskCounter = new AdHocTaskCounter(); + + const runningAverageWindowSize = 5; + const BackgroundTaskUtilizationAggregator = createBackgroundTaskUtilizationAggregator( + taskPollingLifecycle, + runningAverageWindowSize, + adHocTaskCounter, + pollInterval + ); + + function expectWindowEqualsUpdate( + taskStat: AggregatedStat<SummarizedBackgroundTaskUtilizationStat>, + window: number[] + ) { + expect(taskStat.value.recurring.ran.service_time.task_counter).toEqual(window.length); + } + + return new Promise<void>((resolve) => { + BackgroundTaskUtilizationAggregator.pipe( + // skip initial stat which is just initialized data which + // ensures we don't stall on combineLatest + skip(1), + // Use 'summarizeUtilizationStat' to receive summarize stats + map(({ key, value }: AggregatedStat<BackgroundTaskUtilizationStat>) => ({ + key, + value: summarizeUtilizationStat(value).value, + })), + take(tasks.length), + bufferCount(tasks.length) + ).subscribe((taskStats: Array<AggregatedStat<SummarizedBackgroundTaskUtilizationStat>>) => { + expectWindowEqualsUpdate(taskStats[0], tasks.slice(0, 1)); + expectWindowEqualsUpdate(taskStats[1], tasks.slice(0, 2)); + expectWindowEqualsUpdate(taskStats[2], tasks.slice(0, 3)); + expectWindowEqualsUpdate(taskStats[3], tasks.slice(0, 4)); + expectWindowEqualsUpdate(taskStats[4], tasks.slice(0, 5)); + // from the 6th value, begin to drop old values as out window is 5 + expectWindowEqualsUpdate(taskStats[5], tasks.slice(1, 6)); + expectWindowEqualsUpdate(taskStats[6], tasks.slice(2, 7)); + expectWindowEqualsUpdate(taskStats[7], tasks.slice(3, 8)); + resolve(); + }); + + for (const task of tasks) { + events$.next( + mockTaskRunEvent({ schedule: { interval: '1h' } }, { start: task, stop: task }) + ); + } + }); + }); + + test('returns a running count of recurring tasks_per_min', async () => { + const intervals = ['1h', '5m', '2h', '30m', '10m', '1m', '5h', '120m']; + const events$ = new Subject<TaskLifecycleEvent>(); + const taskPollingLifecycle = taskPollingLifecycleMock.create({ + events$: events$ as Observable<TaskLifecycleEvent>, + }); + const adHocTaskCounter = new AdHocTaskCounter(); + + const runningAverageWindowSize = 5; + const BackgroundTaskUtilizationAggregator = createBackgroundTaskUtilizationAggregator( + taskPollingLifecycle, + runningAverageWindowSize, + adHocTaskCounter, + pollInterval + ); + + function expectWindowEqualsUpdate( + taskStat: AggregatedStat<SummarizedBackgroundTaskUtilizationStat>, + window: number[] + ) { + expect(taskStat.value.recurring.tasks_per_min).toEqual(sum(window)); + } + + return new Promise<void>((resolve) => { + BackgroundTaskUtilizationAggregator.pipe( + // skip initial stat which is just initialized data which + // ensures we don't stall on combineLatest + skip(1), + // Use 'summarizeUtilizationStat' to receive summarize stats + map(({ key, value }: AggregatedStat<BackgroundTaskUtilizationStat>) => ({ + key, + value: summarizeUtilizationStat(value).value, + })), + take(intervals.length), + bufferCount(intervals.length) + ).subscribe((taskStats: Array<AggregatedStat<SummarizedBackgroundTaskUtilizationStat>>) => { + expectWindowEqualsUpdate(taskStats[0], mapInterval(intervals.slice(0, 1))); + expectWindowEqualsUpdate(taskStats[1], mapInterval(intervals.slice(0, 2))); + expectWindowEqualsUpdate(taskStats[2], mapInterval(intervals.slice(0, 3))); + expectWindowEqualsUpdate(taskStats[3], mapInterval(intervals.slice(0, 4))); + expectWindowEqualsUpdate(taskStats[4], mapInterval(intervals.slice(0, 5))); + // from the 6th value, begin to drop old values as out window is 5 + expectWindowEqualsUpdate(taskStats[5], mapInterval(intervals.slice(1, 6))); + expectWindowEqualsUpdate(taskStats[6], mapInterval(intervals.slice(2, 7))); + expectWindowEqualsUpdate(taskStats[7], mapInterval(intervals.slice(3, 8))); + resolve(); + }); + + for (const i of intervals) { + events$.next(mockTaskRunEvent({ schedule: { interval: i } }, { start: 0, stop: 0 })); + } + }); + }); +}); + +function runAtMillisecondsAgo(now: number, ms: number): Date { + return new Date(now - ms); +} + +function roundUpToNearestSec(duration: number[], s: number): number[] { + const pollInterval = s * 1000; + return duration.map((d) => Math.ceil(d / pollInterval) * pollInterval); +} + +function mapInterval(intervals: string[]): number[] { + return intervals.map((i) => { + const interval = parseIntervalAsMinute(i); + return 1 / interval; + }); +} + +const mockTaskRunEvent = ( + overrides: Partial<ConcreteTaskInstance> = {}, + timing: TaskTiming, + result: TaskRunResult = TaskRunResult.Success, + persistence?: TaskPersistence +) => { + const task = mockTaskInstance(overrides); + return asTaskRunEvent( + task.id, + asOk({ + task, + persistence: + persistence ?? (task.schedule ? TaskPersistence.Recurring : TaskPersistence.NonRecurring), + result, + }), + timing + ); +}; + +const mockTaskInstance = (overrides: Partial<ConcreteTaskInstance> = {}): ConcreteTaskInstance => ({ + id: uuid.v4(), + attempts: 0, + status: TaskStatus.Running, + version: '123', + runAt: new Date(), + scheduledAt: new Date(), + startedAt: new Date(), + retryAt: new Date(Date.now() + 5 * 60 * 1000), + state: {}, + taskType: 'alerting:test', + params: { + alertId: '1', + }, + ownerId: null, + ...overrides, +}); diff --git a/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.ts new file mode 100644 index 0000000000000..3d096cd366e72 --- /dev/null +++ b/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.ts @@ -0,0 +1,276 @@ +/* + * 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 { JsonObject } from '@kbn/utility-types'; +import { get } from 'lodash'; +import { combineLatest, filter, map, Observable, startWith } from 'rxjs'; +import { AdHocTaskCounter } from '../lib/adhoc_task_counter'; +import { parseIntervalAsMinute } from '../lib/intervals'; +import { unwrap } from '../lib/result_type'; +import { TaskLifecycleEvent, TaskPollingLifecycle } from '../polling_lifecycle'; +import { ConcreteTaskInstance } from '../task'; +import { isTaskRunEvent, TaskRun, TaskTiming } from '../task_events'; +import { MonitoredStat } from './monitoring_stats_stream'; +import { AggregatedStat, AggregatedStatProvider } from './runtime_statistics_aggregator'; +import { createRunningAveragedStat } from './task_run_calcultors'; + +export interface BackgroundTaskUtilizationStat extends JsonObject { + adhoc: AdhocTaskStat; + recurring: RecurringTaskStat; +} + +interface TaskStat extends JsonObject { + ran: { + service_time: { + actual: number[]; // total service time for running recurring tasks + adjusted: number[]; // total service time adjusted for polling interval + task_counter: number[]; // recurring tasks counter, only increases for the lifetime of the process + }; + }; +} + +interface AdhocTaskStat extends TaskStat { + created: { + counter: number[]; // counter for number of ad hoc tasks created + }; +} + +interface RecurringTaskStat extends TaskStat { + tasks_per_min: number[]; +} + +export interface SummarizedBackgroundTaskUtilizationStat extends JsonObject { + adhoc: { + created: { + counter: number; + }; + ran: { + service_time: { + actual: number; + adjusted: number; + task_counter: number; + }; + }; + }; + recurring: { + tasks_per_min: number; + ran: { + service_time: { + actual: number; + adjusted: number; + task_counter: number; + }; + }; + }; +} + +export function createBackgroundTaskUtilizationAggregator( + taskPollingLifecycle: TaskPollingLifecycle, + runningAverageWindowSize: number, + adHocTaskCounter: AdHocTaskCounter, + pollInterval: number +): AggregatedStatProvider<BackgroundTaskUtilizationStat> { + const taskRunEventToAdhocStat = createTaskRunEventToAdhocStat(runningAverageWindowSize); + const taskRunAdhocEvents$: Observable<Pick<BackgroundTaskUtilizationStat, 'adhoc'>> = + taskPollingLifecycle.events.pipe( + filter((taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent) && hasTiming(taskEvent)), + map((taskEvent: TaskLifecycleEvent) => ({ + taskEvent, + ...unwrap((taskEvent as TaskRun).event), + })), + filter(({ task }) => get(task, 'schedule.interval', null) == null), + map(({ taskEvent }) => { + return taskRunEventToAdhocStat(taskEvent.timing!, adHocTaskCounter, pollInterval); + }) + ); + + const taskRunEventToRecurringStat = createTaskRunEventToRecurringStat(runningAverageWindowSize); + const taskRunRecurringEvents$: Observable<Pick<BackgroundTaskUtilizationStat, 'recurring'>> = + taskPollingLifecycle.events.pipe( + filter((taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent) && hasTiming(taskEvent)), + map((taskEvent: TaskLifecycleEvent) => ({ + taskEvent, + ...unwrap((taskEvent as TaskRun).event), + })), + filter(({ task }) => get(task, 'schedule.interval', null) != null), + map(({ taskEvent, task }) => { + return taskRunEventToRecurringStat(taskEvent.timing!, task, pollInterval); + }) + ); + + return combineLatest([ + taskRunAdhocEvents$.pipe( + startWith({ + adhoc: { + created: { + counter: [], + }, + ran: { + service_time: { + actual: [], + adjusted: [], + task_counter: [], + }, + }, + }, + }) + ), + taskRunRecurringEvents$.pipe( + startWith({ + recurring: { + tasks_per_min: [], + ran: { + service_time: { + actual: [], + adjusted: [], + task_counter: [], + }, + }, + }, + }) + ), + ]).pipe( + map( + ([adhoc, recurring]: [ + Pick<BackgroundTaskUtilizationStat, 'adhoc'>, + Pick<BackgroundTaskUtilizationStat, 'recurring'> + ]) => { + return { + key: 'utilization', + value: { + ...adhoc, + ...recurring, + }, + } as AggregatedStat<BackgroundTaskUtilizationStat>; + } + ) + ); +} + +function hasTiming(taskEvent: TaskLifecycleEvent) { + return !!taskEvent?.timing; +} + +export function summarizeUtilizationStat({ adhoc, recurring }: BackgroundTaskUtilizationStat): { + value: SummarizedBackgroundTaskUtilizationStat; +} { + return { + value: { + adhoc: { + created: { + counter: calculateSum(adhoc.created.counter), + }, + ran: { + service_time: { + actual: calculateSum(adhoc.ran.service_time.actual), + adjusted: calculateSum(adhoc.ran.service_time.adjusted), + task_counter: calculateSum(adhoc.ran.service_time.task_counter), + }, + }, + }, + recurring: { + tasks_per_min: calculateSum(recurring.tasks_per_min), + ran: { + service_time: { + actual: calculateSum(recurring.ran.service_time.actual), + adjusted: calculateSum(recurring.ran.service_time.adjusted), + task_counter: calculateSum(recurring.ran.service_time.task_counter), + }, + }, + }, + }, + }; +} + +export function summarizeUtilizationStats({ + // eslint-disable-next-line @typescript-eslint/naming-convention + last_update, + stats, +}: { + last_update: string; + stats: MonitoredStat<BackgroundTaskUtilizationStat> | undefined; +}): { + last_update: string; + stats: MonitoredStat<SummarizedBackgroundTaskUtilizationStat> | null; +} { + return { + last_update, + stats: stats + ? { + timestamp: stats.timestamp, + ...summarizeUtilizationStat(stats.value), + } + : null, + }; +} + +function createTaskRunEventToAdhocStat(runningAverageWindowSize: number) { + const createdCounterQueue = createRunningAveragedStat<number>(runningAverageWindowSize); + const actualQueue = createRunningAveragedStat<number>(runningAverageWindowSize); + const adjustedQueue = createRunningAveragedStat<number>(runningAverageWindowSize); + const taskCounterQueue = createRunningAveragedStat<number>(runningAverageWindowSize); + return ( + timing: TaskTiming, + adHocTaskCounter: AdHocTaskCounter, + pollInterval: number + ): Pick<BackgroundTaskUtilizationStat, 'adhoc'> => { + const { duration, adjusted } = getServiceTimeStats(timing, pollInterval); + const created = adHocTaskCounter.count; + adHocTaskCounter.reset(); + return { + adhoc: { + created: { + counter: createdCounterQueue(created), + }, + ran: { + service_time: { + actual: actualQueue(duration), + adjusted: adjustedQueue(adjusted), + task_counter: taskCounterQueue(1), + }, + }, + }, + }; + }; +} + +function createTaskRunEventToRecurringStat(runningAverageWindowSize: number) { + const tasksPerMinQueue = createRunningAveragedStat<number>(runningAverageWindowSize); + const actualQueue = createRunningAveragedStat<number>(runningAverageWindowSize); + const adjustedQueue = createRunningAveragedStat<number>(runningAverageWindowSize); + const taskCounterQueue = createRunningAveragedStat<number>(runningAverageWindowSize); + return ( + timing: TaskTiming, + task: ConcreteTaskInstance, + pollInterval: number + ): Pick<BackgroundTaskUtilizationStat, 'recurring'> => { + const { duration, adjusted } = getServiceTimeStats(timing, pollInterval); + const interval = parseIntervalAsMinute(task.schedule?.interval!); + return { + recurring: { + tasks_per_min: tasksPerMinQueue(1 / interval), + ran: { + service_time: { + actual: actualQueue(duration), + adjusted: adjustedQueue(adjusted), + task_counter: taskCounterQueue(1), + }, + }, + }, + }; + }; +} + +function getServiceTimeStats(timing: TaskTiming, pollInterval: number) { + const duration = timing!.stop - timing!.start; + const adjusted = Math.ceil(duration / pollInterval) * pollInterval; + return { duration, adjusted }; +} + +function calculateSum(arr: number[]) { + return arr.reduce((acc, s) => (acc += s), 0); +} diff --git a/x-pack/plugins/task_manager/server/monitoring/index.ts b/x-pack/plugins/task_manager/server/monitoring/index.ts index de1bea796c038..9ee32e97d7758 100644 --- a/x-pack/plugins/task_manager/server/monitoring/index.ts +++ b/x-pack/plugins/task_manager/server/monitoring/index.ts @@ -17,6 +17,7 @@ import { TaskStore } from '../task_store'; import { TaskPollingLifecycle } from '../polling_lifecycle'; import { ManagedConfiguration } from '../lib/create_managed_configuration'; import { EphemeralTaskLifecycle } from '../ephemeral_task_lifecycle'; +import { AdHocTaskCounter } from '../lib/adhoc_task_counter'; export type { MonitoringStats, RawMonitoringStats } from './monitoring_stats_stream'; export { @@ -32,6 +33,7 @@ export function createMonitoringStats( config: TaskManagerConfig, managedConfig: ManagedConfiguration, logger: Logger, + adHocTaskCounter: AdHocTaskCounter, taskPollingLifecycle?: TaskPollingLifecycle, ephemeralTaskLifecycle?: EphemeralTaskLifecycle ): Observable<MonitoringStats> { @@ -42,6 +44,7 @@ export function createMonitoringStats( config, managedConfig, logger, + adHocTaskCounter, taskPollingLifecycle, ephemeralTaskLifecycle ), diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts index a16e01189f4c7..19485e41c2ae2 100644 --- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts +++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts @@ -30,12 +30,18 @@ import { TaskRunStat, SummarizedTaskRunStat, } from './task_run_statistics'; +import { + BackgroundTaskUtilizationStat, + createBackgroundTaskUtilizationAggregator, +} from './background_task_utilization_statistics'; + import { ConfigStat, createConfigurationAggregator } from './configuration_statistics'; import { TaskManagerConfig } from '../config'; import { AggregatedStatProvider } from './runtime_statistics_aggregator'; import { ManagedConfiguration } from '../lib/create_managed_configuration'; import { EphemeralTaskLifecycle } from '../ephemeral_task_lifecycle'; import { CapacityEstimationStat, withCapacityEstimate } from './capacity_estimation'; +import { AdHocTaskCounter } from '../lib/adhoc_task_counter'; export type { AggregatedStatProvider, AggregatedStat } from './runtime_statistics_aggregator'; @@ -46,6 +52,7 @@ export interface MonitoringStats { workload?: MonitoredStat<WorkloadStat>; runtime?: MonitoredStat<TaskRunStat>; ephemeral?: MonitoredStat<EphemeralTaskStat>; + utilization?: MonitoredStat<BackgroundTaskUtilizationStat>; }; } @@ -55,7 +62,7 @@ export enum HealthStatus { Error = 'error', } -interface MonitoredStat<T> { +export interface MonitoredStat<T> { timestamp: string; value: T; } @@ -80,6 +87,7 @@ export function createAggregators( config: TaskManagerConfig, managedConfig: ManagedConfiguration, logger: Logger, + adHocTaskCounter: AdHocTaskCounter, taskPollingLifecycle?: TaskPollingLifecycle, ephemeralTaskLifecycle?: EphemeralTaskLifecycle ): AggregatedStatProvider { @@ -96,7 +104,13 @@ export function createAggregators( ]; if (taskPollingLifecycle) { aggregators.push( - createTaskRunAggregator(taskPollingLifecycle, config.monitored_stats_running_average_window) + createTaskRunAggregator(taskPollingLifecycle, config.monitored_stats_running_average_window), + createBackgroundTaskUtilizationAggregator( + taskPollingLifecycle, + config.monitored_stats_running_average_window, + adHocTaskCounter, + config.poll_interval + ) ); } if (ephemeralTaskLifecycle && ephemeralTaskLifecycle.enabled) { @@ -145,7 +159,7 @@ export function summarizeMonitoringStats( { // eslint-disable-next-line @typescript-eslint/naming-convention last_update, - stats: { runtime, workload, configuration, ephemeral }, + stats: { runtime, workload, configuration, ephemeral, utilization }, }: MonitoringStats, config: TaskManagerConfig ): RawMonitoringStats { diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index d2f93903cc7dd..275f1e845bcd8 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -29,12 +29,13 @@ import { TaskDefinitionRegistry, TaskTypeDictionary, REMOVED_TYPES } from './tas import { AggregationOpts, FetchResult, SearchOpts, TaskStore } from './task_store'; import { createManagedConfiguration } from './lib/create_managed_configuration'; import { TaskScheduling } from './task_scheduling'; -import { healthRoute } from './routes'; +import { backgroundTaskUtilizationRoute, healthRoute } from './routes'; import { createMonitoringStats, MonitoringStats } from './monitoring'; import { EphemeralTaskLifecycle } from './ephemeral_task_lifecycle'; import { EphemeralTask, ConcreteTaskInstance } from './task'; import { registerTaskManagerUsageCollector } from './usage'; import { TASK_MANAGER_INDEX } from './constants'; +import { AdHocTaskCounter } from './lib/adhoc_task_counter'; export interface TaskManagerSetupContract { /** @@ -84,6 +85,7 @@ export class TaskManagerPlugin private monitoringStats$ = new Subject<MonitoringStats>(); private shouldRunBackgroundTasks: boolean; private readonly kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; + private adHocTaskCounter: AdHocTaskCounter; constructor(private readonly initContext: PluginInitializerContext) { this.initContext = initContext; @@ -92,6 +94,7 @@ export class TaskManagerPlugin this.definitions = new TaskTypeDictionary(this.logger); this.kibanaVersion = initContext.env.packageInfo.version; this.shouldRunBackgroundTasks = initContext.node.roles.backgroundTasks; + this.adHocTaskCounter = new AdHocTaskCounter(); } public setup( @@ -133,6 +136,18 @@ export class TaskManagerPlugin startServicesPromise.then(({ elasticsearch }) => elasticsearch.client), shouldRunTasks: this.shouldRunBackgroundTasks, }); + const monitoredUtilization$ = backgroundTaskUtilizationRoute({ + router, + monitoringStats$: this.monitoringStats$, + logger: this.logger, + taskManagerId: this.taskManagerId, + config: this.config!, + usageCounter: this.usageCounter!, + kibanaVersion: this.kibanaVersion, + kibanaIndexName: core.savedObjects.getKibanaIndex(), + getClusterClient: () => + startServicesPromise.then(({ elasticsearch }) => elasticsearch.client), + }); core.status.derivedStatus$.subscribe((status) => this.logger.debug(`status core.status.derivedStatus now set to ${status.level}`) @@ -155,6 +170,7 @@ export class TaskManagerPlugin registerTaskManagerUsageCollector( usageCollection, monitoredHealth$, + monitoredUtilization$, this.config.ephemeral_tasks.enabled, this.config.ephemeral_tasks.request_capacity, this.config.unsafe.exclude_task_types @@ -195,6 +211,7 @@ export class TaskManagerPlugin index: TASK_MANAGER_INDEX, definitions: this.definitions, taskManagerId: `kibana:${this.taskManagerId!}`, + adHocTaskCounter: this.adHocTaskCounter, }); const managedConfiguration = createManagedConfiguration({ @@ -237,6 +254,7 @@ export class TaskManagerPlugin this.config!, managedConfiguration, this.logger, + this.adHocTaskCounter, this.taskPollingLifecycle, this.ephemeralTaskLifecycle ).subscribe((stat) => this.monitoringStats$.next(stat)); diff --git a/x-pack/plugins/task_manager/server/routes/background_task_utilization.test.ts b/x-pack/plugins/task_manager/server/routes/background_task_utilization.test.ts new file mode 100644 index 0000000000000..a10952ddd0463 --- /dev/null +++ b/x-pack/plugins/task_manager/server/routes/background_task_utilization.test.ts @@ -0,0 +1,206 @@ +/* + * 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 { of, Subject } from 'rxjs'; +import uuid from 'uuid'; +import { httpServiceMock } from '@kbn/core/server/mocks'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { sleep } from '../test_utils'; +import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock'; +import { MonitoringStats } from '../monitoring'; +import { configSchema, TaskManagerConfig } from '../config'; +import { backgroundTaskUtilizationRoute } from './background_task_utilization'; +import { SecurityHasPrivilegesResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); +const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); + +const createMockClusterClient = (response: SecurityHasPrivilegesResponse) => { + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.asCurrentUser.security.hasPrivileges.mockResponse(response); + + const mockClusterClient = elasticsearchServiceMock.createClusterClient(); + mockClusterClient.asScoped.mockReturnValue(mockScopedClusterClient); + + return { mockClusterClient, mockScopedClusterClient }; +}; + +describe('backgroundTaskUtilizationRoute', () => { + const logger = loggingSystemMock.create().get(); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('registers the route', async () => { + const router = httpServiceMock.createRouter(); + backgroundTaskUtilizationRoute({ + router, + monitoringStats$: of(), + logger, + taskManagerId: uuid.v4(), + config: getTaskManagerConfig(), + kibanaVersion: '8.0', + kibanaIndexName: '.kibana', + getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()), + usageCounter: mockUsageCounter, + }); + + const [config] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot( + `"/internal/task_manager/_background_task_utilization"` + ); + }); + + it('checks user privileges and increments usage counter when API is accessed', async () => { + const { mockClusterClient, mockScopedClusterClient } = createMockClusterClient({ + has_all_requested: false, + } as SecurityHasPrivilegesResponse); + const router = httpServiceMock.createRouter(); + backgroundTaskUtilizationRoute({ + router, + monitoringStats$: of(), + logger, + taskManagerId: uuid.v4(), + config: getTaskManagerConfig(), + kibanaVersion: '8.0', + kibanaIndexName: 'foo', + getClusterClient: () => Promise.resolve(mockClusterClient), + usageCounter: mockUsageCounter, + }); + + const [, handler] = router.get.mock.calls[0]; + const [context, req, res] = mockHandlerArguments({}, {}, ['ok']); + await handler(context, req, res); + + expect(mockScopedClusterClient.asCurrentUser.security.hasPrivileges).toHaveBeenCalledWith({ + body: { + application: [ + { + application: `kibana-foo`, + resources: ['*'], + privileges: [`api:8.0:taskManager`], + }, + ], + }, + }); + expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(1); + expect(mockUsageCounter.incrementCounter).toHaveBeenNthCalledWith(1, { + counterName: `taskManagerBackgroundTaskUtilApiAccess`, + counterType: 'taskManagerBackgroundTaskUtilApi', + incrementBy: 1, + }); + }); + + it('checks user privileges and increments admin usage counter when API is accessed when user has access to task manager feature', async () => { + const { mockClusterClient, mockScopedClusterClient } = createMockClusterClient({ + has_all_requested: true, + } as SecurityHasPrivilegesResponse); + const router = httpServiceMock.createRouter(); + backgroundTaskUtilizationRoute({ + router, + monitoringStats$: of(), + logger, + taskManagerId: uuid.v4(), + config: getTaskManagerConfig(), + kibanaVersion: '8.0', + kibanaIndexName: 'foo', + getClusterClient: () => Promise.resolve(mockClusterClient), + usageCounter: mockUsageCounter, + }); + + const [, handler] = router.get.mock.calls[0]; + const [context, req, res] = mockHandlerArguments({}, {}, ['ok']); + await handler(context, req, res); + + expect(mockScopedClusterClient.asCurrentUser.security.hasPrivileges).toHaveBeenCalledWith({ + body: { + application: [ + { + application: `kibana-foo`, + resources: ['*'], + privileges: [`api:8.0:taskManager`], + }, + ], + }, + }); + + expect(mockUsageCounter.incrementCounter).toHaveBeenCalledTimes(2); + expect(mockUsageCounter.incrementCounter).toHaveBeenNthCalledWith(1, { + counterName: `taskManagerBackgroundTaskUtilApiAccess`, + counterType: 'taskManagerBackgroundTaskUtilApi', + incrementBy: 1, + }); + expect(mockUsageCounter.incrementCounter).toHaveBeenNthCalledWith(2, { + counterName: `taskManagerBackgroundTaskUtilApiAdminAccess`, + counterType: 'taskManagerBackgroundTaskUtilApi', + incrementBy: 1, + }); + }); + + it('skips checking user privileges if usage counter is undefined', async () => { + const { mockClusterClient, mockScopedClusterClient } = createMockClusterClient({ + has_all_requested: false, + } as SecurityHasPrivilegesResponse); + const router = httpServiceMock.createRouter(); + backgroundTaskUtilizationRoute({ + router, + monitoringStats$: of(), + logger, + taskManagerId: uuid.v4(), + config: getTaskManagerConfig(), + kibanaVersion: '8.0', + kibanaIndexName: 'foo', + getClusterClient: () => Promise.resolve(mockClusterClient), + }); + + const [, handler] = router.get.mock.calls[0]; + const [context, req, res] = mockHandlerArguments({}, {}, ['ok']); + await handler(context, req, res); + + expect(mockScopedClusterClient.asCurrentUser.security.hasPrivileges).not.toHaveBeenCalled(); + }); + + it(`logs an error if the utilization stats are null`, async () => { + const router = httpServiceMock.createRouter(); + const stats$ = new Subject<MonitoringStats>(); + const id = uuid.v4(); + backgroundTaskUtilizationRoute({ + router, + monitoringStats$: stats$, + logger, + taskManagerId: id, + config: getTaskManagerConfig(), + kibanaVersion: '8.0', + kibanaIndexName: '.kibana', + getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()), + usageCounter: mockUsageCounter, + }); + + stats$.next({ stats: {} } as MonitoringStats); + await sleep(1001); + + expect(logger.debug).toHaveBeenNthCalledWith( + 1, + 'Unable to get Task Manager background task utilization metrics.' + ); + }); +}); + +const getTaskManagerConfig = (overrides: Partial<TaskManagerConfig> = {}) => + configSchema.validate( + overrides.monitored_stats_required_freshness + ? { + // use `monitored_stats_required_freshness` as poll interval otherwise we might + // fail validation as it must be greather than the poll interval + poll_interval: overrides.monitored_stats_required_freshness, + ...overrides, + } + : overrides + ); diff --git a/x-pack/plugins/task_manager/server/routes/background_task_utilization.ts b/x-pack/plugins/task_manager/server/routes/background_task_utilization.ts new file mode 100644 index 0000000000000..a5ceaf5e5ca77 --- /dev/null +++ b/x-pack/plugins/task_manager/server/routes/background_task_utilization.ts @@ -0,0 +1,149 @@ +/* + * 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 { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, + Logger, +} from '@kbn/core/server'; +import { IClusterClient } from '@kbn/core/server'; +import { Observable, Subject } from 'rxjs'; +import { throttleTime, tap, map } from 'rxjs/operators'; +import { UsageCounter } from '@kbn/usage-collection-plugin/server'; +import { MonitoringStats } from '../monitoring'; +import { TaskManagerConfig } from '../config'; +import { + SummarizedBackgroundTaskUtilizationStat, + summarizeUtilizationStats, +} from '../monitoring/background_task_utilization_statistics'; +import { MonitoredStat } from '../monitoring/monitoring_stats_stream'; + +export interface MonitoredUtilization { + process_uuid: string; + timestamp: string; + last_update: string; + stats: MonitoredStat<SummarizedBackgroundTaskUtilizationStat> | null; +} + +export interface BackgroundTaskUtilRouteParams { + router: IRouter; + monitoringStats$: Observable<MonitoringStats>; + logger: Logger; + taskManagerId: string; + config: TaskManagerConfig; + kibanaVersion: string; + kibanaIndexName: string; + getClusterClient: () => Promise<IClusterClient>; + usageCounter?: UsageCounter; +} + +export function backgroundTaskUtilizationRoute( + params: BackgroundTaskUtilRouteParams +): Observable<MonitoredUtilization> { + const { + router, + monitoringStats$, + logger, + taskManagerId, + config, + kibanaVersion, + kibanaIndexName, + getClusterClient, + usageCounter, + } = params; + + const requiredHotStatsFreshness: number = config.monitored_stats_required_freshness; + + function getBackgroundTaskUtilization(monitoredStats: MonitoringStats) { + const summarizedStats = summarizeUtilizationStats({ + last_update: monitoredStats.last_update, + stats: monitoredStats.stats.utilization, + }); + const now = Date.now(); + const timestamp = new Date(now).toISOString(); + return { process_uuid: taskManagerId, timestamp, ...summarizedStats }; + } + + const monitoredUtilization$: Subject<MonitoredUtilization> = new Subject<MonitoredUtilization>(); + /* keep track of last utilization summary, as we'll return that to the next call to _background_task_utilization */ + let lastMonitoredStats: MonitoringStats | null = null; + + monitoringStats$ + .pipe( + throttleTime(requiredHotStatsFreshness), + tap((stats) => { + lastMonitoredStats = stats; + }), + // Only calculate the summarized stats (calculates all running averages and evaluates state) + // when needed by throttling down to the requiredHotStatsFreshness + map((stats) => getBackgroundTaskUtilization(stats)) + ) + .subscribe((utilizationStats) => { + monitoredUtilization$.next(utilizationStats); + if (utilizationStats.stats == null) { + logger.debug('Unable to get Task Manager background task utilization metrics.'); + } + }); + + router.get( + { + path: '/internal/task_manager/_background_task_utilization', + // Uncomment when we determine that we can restrict API usage to Global admins based on telemetry + // options: { tags: ['access:taskManager'] }, + validate: false, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest<unknown, unknown, unknown>, + res: KibanaResponseFactory + ): Promise<IKibanaResponse> { + // If we are able to count usage, we want to check whether the user has access to + // the `taskManager` feature, which is only available as part of the Global All privilege. + if (usageCounter) { + const clusterClient = await getClusterClient(); + const hasPrivilegesResponse = await clusterClient + .asScoped(req) + .asCurrentUser.security.hasPrivileges({ + body: { + application: [ + { + application: `kibana-${kibanaIndexName}`, + resources: ['*'], + privileges: [`api:${kibanaVersion}:taskManager`], + }, + ], + }, + }); + + // Keep track of total access vs admin access + usageCounter.incrementCounter({ + counterName: `taskManagerBackgroundTaskUtilApiAccess`, + counterType: 'taskManagerBackgroundTaskUtilApi', + incrementBy: 1, + }); + if (hasPrivilegesResponse.has_all_requested) { + usageCounter.incrementCounter({ + counterName: `taskManagerBackgroundTaskUtilApiAdminAccess`, + counterType: 'taskManagerBackgroundTaskUtilApi', + incrementBy: 1, + }); + } + } + + return res.ok({ + body: lastMonitoredStats + ? getBackgroundTaskUtilization(lastMonitoredStats) + : { process_uuid: taskManagerId, timestamp: new Date().toISOString(), stats: {} }, + }); + } + ); + + return monitoredUtilization$; +} diff --git a/x-pack/plugins/task_manager/server/routes/index.ts b/x-pack/plugins/task_manager/server/routes/index.ts index a3b39bc8eb752..f3ba539323f8e 100644 --- a/x-pack/plugins/task_manager/server/routes/index.ts +++ b/x-pack/plugins/task_manager/server/routes/index.ts @@ -6,3 +6,4 @@ */ export { healthRoute } from './health'; +export { backgroundTaskUtilizationRoute } from './background_task_utilization'; diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index 7bc731a0d8b6b..dfc21a7142ece 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -21,9 +21,11 @@ import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; import { SavedObjectAttributes, SavedObjectsErrorHelpers } from '@kbn/core/server'; import { TaskTypeDictionary } from './task_type_dictionary'; import { mockLogger } from './test_utils'; +import { AdHocTaskCounter } from './lib/adhoc_task_counter'; const savedObjectsClient = savedObjectsRepositoryMock.create(); const serializer = savedObjectsServiceMock.createSerializer(); +const adHocTaskCounter = new AdHocTaskCounter(); const randomId = () => `id-${_.random(1, 20)}`; @@ -69,9 +71,14 @@ describe('TaskStore', () => { esClient: elasticsearchServiceMock.createClusterClient().asInternalUser, definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, }); }); + afterEach(() => { + adHocTaskCounter.reset(); + }); + async function testSchedule(task: unknown) { savedObjectsClient.create.mockImplementation(async (type: string, attributes: unknown) => ({ id: 'testid', @@ -189,6 +196,31 @@ describe('TaskStore', () => { await expect(store.schedule(task)).rejects.toThrowErrorMatchingInlineSnapshot(`"Failure"`); expect(await firstErrorPromise).toMatchInlineSnapshot(`[Error: Failure]`); }); + + test('increments adHocTaskCounter', async () => { + const task: TaskInstance = { + id: 'id', + params: { hello: 'world' }, + state: { foo: 'bar' }, + taskType: 'report', + }; + + await testSchedule(task); + expect(adHocTaskCounter.count).toEqual(1); + }); + + test('does not increment adHocTaskCounter if the task is recurring', async () => { + const task: TaskInstance = { + id: 'id', + params: { hello: 'world' }, + state: { foo: 'bar' }, + taskType: 'report', + schedule: { interval: '1m' }, + }; + + await testSchedule(task); + expect(adHocTaskCounter.count).toEqual(0); + }); }); describe('fetch', () => { @@ -204,6 +236,7 @@ describe('TaskStore', () => { esClient, definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, }); }); @@ -272,6 +305,7 @@ describe('TaskStore', () => { esClient, definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, }); }); @@ -369,6 +403,7 @@ describe('TaskStore', () => { esClient, definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, }); }); @@ -471,6 +506,7 @@ describe('TaskStore', () => { esClient: elasticsearchServiceMock.createClusterClient().asInternalUser, definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, }); }); @@ -511,6 +547,7 @@ describe('TaskStore', () => { esClient: elasticsearchServiceMock.createClusterClient().asInternalUser, definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, }); }); @@ -544,6 +581,7 @@ describe('TaskStore', () => { esClient: elasticsearchServiceMock.createClusterClient().asInternalUser, definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, }); }); @@ -577,6 +615,7 @@ describe('TaskStore', () => { esClient: elasticsearchServiceMock.createClusterClient().asInternalUser, definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, }); }); @@ -661,6 +700,7 @@ describe('TaskStore', () => { esClient: elasticsearchServiceMock.createClusterClient().asInternalUser, definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, }); expect(await store.getLifecycle(task.id)).toEqual(status); @@ -680,6 +720,7 @@ describe('TaskStore', () => { esClient: elasticsearchServiceMock.createClusterClient().asInternalUser, definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, }); expect(await store.getLifecycle(randomId())).toEqual(TaskLifecycleResult.NotFound); @@ -697,6 +738,7 @@ describe('TaskStore', () => { esClient: elasticsearchServiceMock.createClusterClient().asInternalUser, definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, }); return expect(store.getLifecycle(randomId())).rejects.toThrow('Bad Request'); @@ -714,9 +756,14 @@ describe('TaskStore', () => { esClient: elasticsearchServiceMock.createClusterClient().asInternalUser, definitions: taskDefinitions, savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, }); }); + afterEach(() => { + adHocTaskCounter.reset(); + }); + async function testBulkSchedule(task: unknown) { savedObjectsClient.bulkCreate.mockImplementation(async () => ({ saved_objects: [ @@ -837,5 +884,30 @@ describe('TaskStore', () => { ); expect(await firstErrorPromise).toMatchInlineSnapshot(`[Error: Failure]`); }); + + test('increments adHocTaskCounter', async () => { + const task: TaskInstance = { + id: 'id', + params: { hello: 'world' }, + state: { foo: 'bar' }, + taskType: 'report', + }; + + const result = await testBulkSchedule([task]); + expect(adHocTaskCounter.count).toEqual(result.length); + }); + + test('does not increment adHocTaskCounter if the task is recurring', async () => { + const task: TaskInstance = { + id: 'id', + params: { hello: 'world' }, + state: { foo: 'bar' }, + taskType: 'report', + schedule: { interval: '1m' }, + }; + + await testBulkSchedule([task]); + expect(adHocTaskCounter.count).toEqual(0); + }); }); }); diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index c2c003ff6f7bb..e810ea5c1ef3e 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -9,7 +9,7 @@ * This module contains helpers for managing the task manager storage layer. */ import { Subject } from 'rxjs'; -import { omit, defaults } from 'lodash'; +import { omit, defaults, get } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { SavedObjectsBulkDeleteResponse } from '@kbn/core/server'; @@ -34,6 +34,7 @@ import { } from './task'; import { TaskTypeDictionary } from './task_type_dictionary'; +import { AdHocTaskCounter } from './lib/adhoc_task_counter'; export interface StoreOpts { esClient: ElasticsearchClient; @@ -42,6 +43,7 @@ export interface StoreOpts { definitions: TaskTypeDictionary; savedObjectsRepository: ISavedObjectsRepository; serializer: ISavedObjectsSerializer; + adHocTaskCounter: AdHocTaskCounter; } export interface SearchOpts { @@ -95,6 +97,7 @@ export class TaskStore { private definitions: TaskTypeDictionary; private savedObjectsRepository: ISavedObjectsRepository; private serializer: ISavedObjectsSerializer; + private adHocTaskCounter: AdHocTaskCounter; /** * Constructs a new TaskStore. @@ -112,6 +115,7 @@ export class TaskStore { this.definitions = opts.definitions; this.serializer = opts.serializer; this.savedObjectsRepository = opts.savedObjectsRepository; + this.adHocTaskCounter = opts.adHocTaskCounter; } /** @@ -140,6 +144,9 @@ export class TaskStore { taskInstanceToAttributes(taskInstance), { id: taskInstance.id, refresh: false } ); + if (get(taskInstance, 'schedule.interval', null) == null) { + this.adHocTaskCounter.increment(); + } } catch (e) { this.errors$.next(e); throw e; @@ -169,6 +176,11 @@ export class TaskStore { objects, { refresh: false } ); + this.adHocTaskCounter.increment( + taskInstances.filter((task) => { + return get(task, 'schedule.interval', null) == null; + }).length + ); } catch (e) { this.errors$.next(e); throw e; diff --git a/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts b/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts index 9fdd0c4988575..7931de8aaa48e 100644 --- a/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts +++ b/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts @@ -18,6 +18,7 @@ import { TaskPersistence } from '../task_events'; import { registerTaskManagerUsageCollector } from './task_manager_usage_collector'; import { sleep } from '../test_utils'; import { TaskManagerUsage } from './types'; +import { MonitoredUtilization } from '../routes/background_task_utilization'; describe('registerTaskManagerUsageCollector', () => { let collector: Collector<unknown>; @@ -25,6 +26,7 @@ describe('registerTaskManagerUsageCollector', () => { it('should report telemetry on the ephemeral queue', async () => { const monitoringStats$ = new Subject<MonitoredHealth>(); + const monitoringUtilization$ = new Subject<MonitoredUtilization>(); const usageCollectionMock = createUsageCollectionSetupMock(); const fetchContext = createCollectorFetchContextMock(); usageCollectionMock.makeUsageCollector.mockImplementation((config) => { @@ -32,10 +34,19 @@ describe('registerTaskManagerUsageCollector', () => { return createUsageCollectionSetupMock().makeUsageCollector(config); }); - registerTaskManagerUsageCollector(usageCollectionMock, monitoringStats$, true, 10, []); + registerTaskManagerUsageCollector( + usageCollectionMock, + monitoringStats$, + monitoringUtilization$, + true, + 10, + [] + ); const mockHealth = getMockMonitoredHealth(); monitoringStats$.next(mockHealth); + const mockUtilization = getMockMonitoredUtilization(); + monitoringUtilization$.next(mockUtilization); await sleep(1001); expect(usageCollectionMock.makeUsageCollector).toBeCalled(); @@ -52,6 +63,7 @@ describe('registerTaskManagerUsageCollector', () => { it('should report telemetry on the excluded task types', async () => { const monitoringStats$ = new Subject<MonitoredHealth>(); + const monitoringUtilization$ = new Subject<MonitoredUtilization>(); const usageCollectionMock = createUsageCollectionSetupMock(); const fetchContext = createCollectorFetchContextMock(); usageCollectionMock.makeUsageCollector.mockImplementation((config) => { @@ -59,18 +71,92 @@ describe('registerTaskManagerUsageCollector', () => { return createUsageCollectionSetupMock().makeUsageCollector(config); }); - registerTaskManagerUsageCollector(usageCollectionMock, monitoringStats$, true, 10, [ - 'actions:*', - ]); + registerTaskManagerUsageCollector( + usageCollectionMock, + monitoringStats$, + monitoringUtilization$, + true, + 10, + ['actions:*'] + ); const mockHealth = getMockMonitoredHealth(); monitoringStats$.next(mockHealth); + const mockUtilization = getMockMonitoredUtilization(); + monitoringUtilization$.next(mockUtilization); await sleep(1001); expect(usageCollectionMock.makeUsageCollector).toBeCalled(); const telemetry: TaskManagerUsage = (await collector.fetch(fetchContext)) as TaskManagerUsage; expect(telemetry.task_type_exclusion).toEqual(['actions:*']); }); + + it('should report telemetry on background task utilization', async () => { + const monitoringStats$ = new Subject<MonitoredHealth>(); + const monitoringUtilization$ = new Subject<MonitoredUtilization>(); + const usageCollectionMock = createUsageCollectionSetupMock(); + const fetchContext = createCollectorFetchContextMock(); + usageCollectionMock.makeUsageCollector.mockImplementation((config) => { + collector = new Collector(logger, config); + return createUsageCollectionSetupMock().makeUsageCollector(config); + }); + + registerTaskManagerUsageCollector( + usageCollectionMock, + monitoringStats$, + monitoringUtilization$, + true, + 10, + ['actions:*'] + ); + + const mockHealth = getMockMonitoredHealth(); + monitoringStats$.next(mockHealth); + const mockUtilization = getMockMonitoredUtilization(); + monitoringUtilization$.next(mockUtilization); + await sleep(1001); + + expect(usageCollectionMock.makeUsageCollector).toBeCalled(); + const telemetry: TaskManagerUsage = (await collector.fetch(fetchContext)) as TaskManagerUsage; + expect(telemetry.recurring_tasks).toEqual({ + actual_service_time: mockUtilization.stats?.value.recurring.ran.service_time.actual, + adjusted_service_time: mockUtilization.stats?.value.recurring.ran.service_time.adjusted, + }); + expect(telemetry.adhoc_tasks).toEqual({ + actual_service_time: mockUtilization.stats?.value.adhoc.ran.service_time.actual, + adjusted_service_time: mockUtilization.stats?.value.adhoc.ran.service_time.adjusted, + }); + }); + + it('should report telemetry on capacity', async () => { + const monitoringStats$ = new Subject<MonitoredHealth>(); + const monitoringUtilization$ = new Subject<MonitoredUtilization>(); + const usageCollectionMock = createUsageCollectionSetupMock(); + const fetchContext = createCollectorFetchContextMock(); + usageCollectionMock.makeUsageCollector.mockImplementation((config) => { + collector = new Collector(logger, config); + return createUsageCollectionSetupMock().makeUsageCollector(config); + }); + + registerTaskManagerUsageCollector( + usageCollectionMock, + monitoringStats$, + monitoringUtilization$, + true, + 10, + ['actions:*'] + ); + + const mockHealth = getMockMonitoredHealth(); + monitoringStats$.next(mockHealth); + const mockUtilization = getMockMonitoredUtilization(); + monitoringUtilization$.next(mockUtilization); + await sleep(1001); + + expect(usageCollectionMock.makeUsageCollector).toBeCalled(); + const telemetry: TaskManagerUsage = (await collector.fetch(fetchContext)) as TaskManagerUsage; + expect(telemetry.capacity).toEqual(10); + }); }); function getMockMonitoredHealth(overrides = {}): MonitoredHealth { @@ -187,7 +273,65 @@ function getMockMonitoredHealth(overrides = {}): MonitoredHealth { }, }, }, + capacity_estimation: { + timestamp: new Date().toISOString(), + status: HealthStatus.OK, + value: { + observed: { + observed_kibana_instances: 10, + max_throughput_per_minute: 10, + max_throughput_per_minute_per_kibana: 10, + minutes_to_drain_overdue: 10, + avg_required_throughput_per_minute: 10, + avg_required_throughput_per_minute_per_kibana: 10, + avg_recurring_required_throughput_per_minute: 10, + avg_recurring_required_throughput_per_minute_per_kibana: 10, + }, + proposed: { + provisioned_kibana: 10, + min_required_kibana: 10, + avg_recurring_required_throughput_per_minute_per_kibana: 10, + avg_required_throughput_per_minute_per_kibana: 10, + }, + }, + }, }, }; return merge(stub, overrides) as unknown as MonitoredHealth; } + +function getMockMonitoredUtilization(overrides = {}): MonitoredUtilization { + const stub: MonitoredUtilization = { + process_uuid: '1', + timestamp: new Date().toISOString(), + last_update: new Date().toISOString(), + stats: { + timestamp: new Date().toISOString(), + value: { + adhoc: { + created: { + counter: 5, + }, + ran: { + service_time: { + actual: 3000, + adjusted: 2500, + task_counter: 10, + }, + }, + }, + recurring: { + tasks_per_min: 2500, + ran: { + service_time: { + actual: 1000, + adjusted: 2000, + task_counter: 10, + }, + }, + }, + }, + }, + }; + return merge(stub, overrides) as unknown as MonitoredUtilization; +} diff --git a/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.ts b/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.ts index 82e9eb95dd760..a4bd3049a0b49 100644 --- a/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.ts +++ b/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.ts @@ -4,22 +4,28 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Observable } from 'rxjs'; +import { combineLatest, Observable } from 'rxjs'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { MonitoredHealth } from '../routes/health'; import { TaskManagerUsage } from './types'; +import { MonitoredUtilization } from '../routes/background_task_utilization'; export function createTaskManagerUsageCollector( usageCollection: UsageCollectionSetup, monitoringStats$: Observable<MonitoredHealth>, + monitoredUtilization$: Observable<MonitoredUtilization>, ephemeralTasksEnabled: boolean, ephemeralRequestCapacity: number, excludeTaskTypes: string[] ) { let lastMonitoredHealth: MonitoredHealth | null = null; - monitoringStats$.subscribe((health) => { - lastMonitoredHealth = health; - }); + let lastMonitoredUtilization: MonitoredUtilization | null = null; + combineLatest([monitoringStats$, monitoredUtilization$]) + .pipe() + .subscribe(([health, utilization]) => { + lastMonitoredHealth = health; + lastMonitoredUtilization = utilization; + }); return usageCollection.makeUsageCollector<TaskManagerUsage>({ type: 'task_manager', @@ -61,6 +67,21 @@ export function createTaskManagerUsageCollector( }, 0 ), + recurring_tasks: { + actual_service_time: + lastMonitoredUtilization?.stats?.value.recurring.ran.service_time.actual ?? 0, + adjusted_service_time: + lastMonitoredUtilization?.stats?.value.recurring.ran.service_time.adjusted ?? 0, + }, + adhoc_tasks: { + actual_service_time: + lastMonitoredUtilization?.stats?.value.adhoc.ran.service_time.actual ?? 0, + adjusted_service_time: + lastMonitoredUtilization?.stats?.value.adhoc.ran.service_time.adjusted ?? 0, + }, + capacity: + lastMonitoredHealth?.stats.capacity_estimation?.value.observed + .max_throughput_per_minute_per_kibana ?? 0, }; }, schema: { @@ -89,6 +110,15 @@ export function createTaskManagerUsageCollector( }, task_type_exclusion: { type: 'array', items: { type: 'keyword' } }, failed_tasks: { type: 'long' }, + recurring_tasks: { + actual_service_time: { type: 'long' }, + adjusted_service_time: { type: 'long' }, + }, + adhoc_tasks: { + actual_service_time: { type: 'long' }, + adjusted_service_time: { type: 'long' }, + }, + capacity: { type: 'long' }, }, }); } @@ -96,6 +126,7 @@ export function createTaskManagerUsageCollector( export function registerTaskManagerUsageCollector( usageCollection: UsageCollectionSetup, monitoringStats$: Observable<MonitoredHealth>, + monitoredUtilization$: Observable<MonitoredUtilization>, ephemeralTasksEnabled: boolean, ephemeralRequestCapacity: number, excludeTaskTypes: string[] @@ -103,6 +134,7 @@ export function registerTaskManagerUsageCollector( const collector = createTaskManagerUsageCollector( usageCollection, monitoringStats$, + monitoredUtilization$, ephemeralTasksEnabled, ephemeralRequestCapacity, excludeTaskTypes diff --git a/x-pack/plugins/task_manager/server/usage/types.ts b/x-pack/plugins/task_manager/server/usage/types.ts index f9ac823a58124..0e98d1d0685a0 100644 --- a/x-pack/plugins/task_manager/server/usage/types.ts +++ b/x-pack/plugins/task_manager/server/usage/types.ts @@ -31,4 +31,13 @@ export interface TaskManagerUsage { }; }; failed_tasks: number; + recurring_tasks: { + actual_service_time: number; + adjusted_service_time: number; + }; + adhoc_tasks: { + actual_service_time: number; + adjusted_service_time: number; + }; + capacity: number; } diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index af39095245086..bae65335dfac3 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -13336,6 +13336,29 @@ }, "failed_tasks": { "type": "long" + }, + "recurring_tasks": { + "properties": { + "actual_service_time": { + "type": "long" + }, + "adjusted_service_time": { + "type": "long" + } + } + }, + "adhoc_tasks": { + "properties": { + "actual_service_time": { + "type": "long" + }, + "adjusted_service_time": { + "type": "long" + } + } + }, + "capacity": { + "type": "long" } } }, diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/background_task_utilization_route.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/background_task_utilization_route.ts new file mode 100644 index 0000000000000..18314fe94fe44 --- /dev/null +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/background_task_utilization_route.ts @@ -0,0 +1,107 @@ +/* + * 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 expect from '@kbn/expect'; +import url from 'url'; +import supertest from 'supertest'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +interface MonitoringStats { + last_update: string; + status: string; + stats: { + timestamp: string; + value: { + adhoc: { + created: { + counter: number; + }; + ran: { + service_time: { + actual: number; + adjusted: number; + task_counter: number; + }; + }; + }; + recurring: { + tasks_per_min: number; + ran: { + service_time: { + actual: number; + adjusted: number; + task_counter: number; + }; + }; + }; + }; + }; +} + +export default function ({ getService }: FtrProviderContext) { + const config = getService('config'); + const retry = getService('retry'); + const request = supertest(url.format(config.get('servers.kibana'))); + + const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + + function getUtilizationRequest() { + return request + .get('/internal/task_manager/_background_task_utilization') + .set('kbn-xsrf', 'foo'); + } + + function getUtilization(): Promise<MonitoringStats> { + return getUtilizationRequest() + .expect(200) + .then((response) => response.body); + } + + function getBackgroundTaskUtilization(): Promise<MonitoringStats> { + return retry.try(async () => { + const utilization = await getUtilization(); + + if (utilization.stats) { + return utilization; + } + + await delay(500); + throw new Error('Stats have not run yet'); + }); + } + + describe('background task utilization', () => { + it('should return the task manager background task utilization for recurring stats', async () => { + const { + value: { + // eslint-disable-next-line @typescript-eslint/naming-convention + recurring: { tasks_per_min, ran }, + }, + } = (await getBackgroundTaskUtilization()).stats; + const serviceTime = ran.service_time; + expect(typeof tasks_per_min).to.eql('number'); + + expect(typeof serviceTime.actual).to.eql('number'); + expect(typeof serviceTime.adjusted).to.eql('number'); + expect(typeof serviceTime.task_counter).to.eql('number'); + }); + + it('should return the task manager background task utilization for adhoc stats', async () => { + const { + value: { + adhoc: { created, ran }, + }, + } = (await getBackgroundTaskUtilization()).stats; + const serviceTime = ran.service_time; + expect(typeof created.counter).to.eql('number'); + + expect(typeof serviceTime.actual).to.eql('number'); + expect(typeof serviceTime.adjusted).to.eql('number'); + expect(typeof serviceTime.task_counter).to.eql('number'); + }); + }); +} diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/index.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/index.ts index 6fd4f3e529dc6..e0a31a0f72fe8 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/index.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/index.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('task_manager', function taskManagerSuite() { + loadTestFile(require.resolve('./background_task_utilization_route')); loadTestFile(require.resolve('./health_route')); loadTestFile(require.resolve('./task_management')); loadTestFile(require.resolve('./task_management_scheduled_at')); From 51d0a32dbc8b8218390e999fda2926426df4c5d8 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Thu, 3 Nov 2022 14:09:02 +0100 Subject: [PATCH 68/86] starting with loading:true for packages and categories (#144502) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/applications/integrations/hooks/use_categories.tsx | 2 +- .../public/applications/integrations/hooks/use_packages.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_categories.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_categories.tsx index 8b441a229e9b7..abc601a3ceeb2 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_categories.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_categories.tsx @@ -14,7 +14,7 @@ import type { GetCategoriesResponse } from '../types'; export function useCategories(prerelease?: boolean) { const [data, setData] = useState<GetCategoriesResponse | undefined>(); const [error, setError] = useState<RequestError | undefined>(); - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(true); const [isPrereleaseEnabled, setIsPrereleaseEnabled] = useState(prerelease); const fetchData = useCallback(async () => { diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_packages.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_packages.tsx index efb2f96ed57f3..c2dfa7e7406fa 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_packages.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_packages.tsx @@ -14,7 +14,7 @@ import type { GetPackagesResponse } from '../types'; export function usePackages(prerelease?: boolean) { const [data, setData] = useState<GetPackagesResponse | undefined>(); const [error, setError] = useState<RequestError | undefined>(); - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(true); const [isPrereleaseEnabled, setIsPrereleaseEnabled] = useState(prerelease); const fetchData = useCallback(async () => { From 59d1fed3d1c53ac857cd231ed0c3dbbd4970ded6 Mon Sep 17 00:00:00 2001 From: Shahzad <shahzad.muhammad@elastic.co> Date: Thu, 3 Nov 2022 14:12:02 +0100 Subject: [PATCH 69/86] [Synthetics] Organise api tests (#144407) --- x-pack/test/api_integration/apis/index.ts | 1 + .../rest => synthetics}/add_monitor.ts | 7 +++-- .../add_monitor_private_location.ts | 8 ++--- .../add_monitor_project.ts | 11 ++++--- .../rest => synthetics}/delete_monitor.ts | 6 ++-- .../delete_monitor_project.ts | 6 ++-- .../rest => synthetics}/edit_monitor.ts | 6 ++-- .../rest => synthetics}/get_monitor.ts | 6 ++-- .../get_monitor_overview.ts | 6 ++-- .../get_monitor_project.ts | 6 ++-- .../api_integration/apis/synthetics/index.ts | 30 +++++++++++++++++++ .../services/private_location_test_service.ts | 4 +-- .../synthetics_enablement.ts | 4 +-- .../api_integration/apis/uptime/rest/index.ts | 13 -------- 14 files changed, 68 insertions(+), 46 deletions(-) rename x-pack/test/api_integration/apis/{uptime/rest => synthetics}/add_monitor.ts (99%) rename x-pack/test/api_integration/apis/{uptime/rest => synthetics}/add_monitor_private_location.ts (97%) rename x-pack/test/api_integration/apis/{uptime/rest => synthetics}/add_monitor_project.ts (99%) rename x-pack/test/api_integration/apis/{uptime/rest => synthetics}/delete_monitor.ts (97%) rename x-pack/test/api_integration/apis/{uptime/rest => synthetics}/delete_monitor_project.ts (99%) rename x-pack/test/api_integration/apis/{uptime/rest => synthetics}/edit_monitor.ts (98%) rename x-pack/test/api_integration/apis/{uptime/rest => synthetics}/get_monitor.ts (96%) rename x-pack/test/api_integration/apis/{uptime/rest => synthetics}/get_monitor_overview.ts (96%) rename x-pack/test/api_integration/apis/{uptime/rest => synthetics}/get_monitor_project.ts (99%) create mode 100644 x-pack/test/api_integration/apis/synthetics/index.ts rename x-pack/test/api_integration/apis/{uptime/rest => synthetics}/services/private_location_test_service.ts (90%) rename x-pack/test/api_integration/apis/{uptime/rest => synthetics}/synthetics_enablement.ts (99%) diff --git a/x-pack/test/api_integration/apis/index.ts b/x-pack/test/api_integration/apis/index.ts index 56c546e1349cd..0278c439c5995 100644 --- a/x-pack/test/api_integration/apis/index.ts +++ b/x-pack/test/api_integration/apis/index.ts @@ -22,6 +22,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./console')); loadTestFile(require.resolve('./management')); loadTestFile(require.resolve('./uptime')); + loadTestFile(require.resolve('./synthetics')); loadTestFile(require.resolve('./maps')); loadTestFile(require.resolve('./security_solution')); loadTestFile(require.resolve('./transform')); diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor.ts similarity index 99% rename from x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts rename to x-pack/test/api_integration/apis/synthetics/add_monitor.ts index f96479a50bc6f..2f49d9484ecb7 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor.ts @@ -4,9 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import expect from '@kbn/expect'; import uuid from 'uuid'; import { omit } from 'lodash'; -import expect from '@kbn/expect'; import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_management'; import { ConfigKey, DataStream, HTTPFields } from '@kbn/synthetics-plugin/common/runtime_types'; import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters'; @@ -15,10 +15,11 @@ import { DEFAULT_FIELDS } from '@kbn/synthetics-plugin/common/constants/monitor_ import { ALL_SPACES_ID } from '@kbn/security-plugin/common/constants'; import { syntheticsMonitorType } from '@kbn/synthetics-plugin/server/legacy_uptime/lib/saved_objects/synthetics_monitor'; import { format as formatUrl } from 'url'; + import supertest from 'supertest'; import { serviceApiKeyPrivileges } from '@kbn/synthetics-plugin/server/synthetics_service/get_api_key'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { getFixtureJson } from './helper/get_fixture_json'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; export default function ({ getService }: FtrProviderContext) { describe('AddNewMonitors', function () { diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_private_location.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts similarity index 97% rename from x-pack/test/api_integration/apis/uptime/rest/add_monitor_private_location.ts rename to x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts index 12ef5ac975406..e335e1b7f6cf4 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_private_location.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts @@ -5,16 +5,16 @@ * 2.0. */ import uuid from 'uuid'; -import expect from '@kbn/expect'; import { ConfigKey, HTTPFields } from '@kbn/synthetics-plugin/common/runtime_types'; import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters'; import { omit } from 'lodash'; import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_management'; import { PackagePolicy } from '@kbn/fleet-plugin/common'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { getFixtureJson } from './helper/get_fixture_json'; -import { comparePolicies, getTestSyntheticsPolicy } from './sample_data/test_policy'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; +import { comparePolicies, getTestSyntheticsPolicy } from '../uptime/rest/sample_data/test_policy'; import { PrivateLocationTestService } from './services/private_location_test_service'; export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts similarity index 99% rename from x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts rename to x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts index 02a40be5d2f40..98e96384a71b9 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts @@ -6,17 +6,20 @@ */ import fetch, { BodyInit, HeadersInit, Response } from 'node-fetch'; import uuid from 'uuid'; -import expect from '@kbn/expect'; import { format as formatUrl } from 'url'; import { ConfigKey, ProjectMonitorsRequest } from '@kbn/synthetics-plugin/common/runtime_types'; import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters'; import { syntheticsMonitorType } from '@kbn/synthetics-plugin/server/legacy_uptime/lib/saved_objects/synthetics_monitor'; import { PackagePolicy } from '@kbn/fleet-plugin/common'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { getFixtureJson } from './helper/get_fixture_json'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; import { PrivateLocationTestService } from './services/private_location_test_service'; -import { comparePolicies, getTestProjectSyntheticsPolicy } from './sample_data/test_policy'; +import { + comparePolicies, + getTestProjectSyntheticsPolicy, +} from '../uptime/rest/sample_data/test_policy'; export default function ({ getService }: FtrProviderContext) { describe('AddProjectMonitors', function () { diff --git a/x-pack/test/api_integration/apis/uptime/rest/delete_monitor.ts b/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts similarity index 97% rename from x-pack/test/api_integration/apis/uptime/rest/delete_monitor.ts rename to x-pack/test/api_integration/apis/synthetics/delete_monitor.ts index b9fd0fac8ac2a..175cf792a34e1 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/delete_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts @@ -5,11 +5,11 @@ * 2.0. */ import uuid from 'uuid'; -import expect from '@kbn/expect'; import { HTTPFields, MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types'; import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { getFixtureJson } from './helper/get_fixture_json'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; import { PrivateLocationTestService } from './services/private_location_test_service'; export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test/api_integration/apis/uptime/rest/delete_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts similarity index 99% rename from x-pack/test/api_integration/apis/uptime/rest/delete_monitor_project.ts rename to x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts index 25afc4e665180..ea8e295afacfe 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/delete_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/delete_monitor_project.ts @@ -5,7 +5,6 @@ * 2.0. */ import uuid from 'uuid'; -import expect from '@kbn/expect'; import { format as formatUrl } from 'url'; import { ConfigKey, ProjectMonitorsRequest } from '@kbn/synthetics-plugin/common/runtime_types'; import { INSUFFICIENT_FLEET_PERMISSIONS } from '@kbn/synthetics-plugin/server/synthetics_service/project_monitor/project_monitor_formatter'; @@ -13,8 +12,9 @@ import { REQUEST_TOO_LARGE } from '@kbn/synthetics-plugin/server/routes/monitor_ import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; import { syntheticsMonitorType } from '@kbn/synthetics-plugin/server/legacy_uptime/lib/saved_objects/synthetics_monitor'; import { PackagePolicy } from '@kbn/fleet-plugin/common'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { getFixtureJson } from './helper/get_fixture_json'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; import { PrivateLocationTestService } from './services/private_location_test_service'; import { parseStreamApiResponse } from './add_monitor_project'; diff --git a/x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts similarity index 98% rename from x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts rename to x-pack/test/api_integration/apis/synthetics/edit_monitor.ts index eb44aa36a76c8..e5ac722c73054 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/edit_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts @@ -5,14 +5,14 @@ * 2.0. */ import uuid from 'uuid'; -import expect from '@kbn/expect'; import { omit } from 'lodash'; import { SimpleSavedObject } from '@kbn/core/public'; import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_management'; import { ConfigKey, HTTPFields, MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types'; import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { getFixtureJson } from './helper/get_fixture_json'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; import { PrivateLocationTestService } from './services/private_location_test_service'; export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test/api_integration/apis/uptime/rest/get_monitor.ts b/x-pack/test/api_integration/apis/synthetics/get_monitor.ts similarity index 96% rename from x-pack/test/api_integration/apis/uptime/rest/get_monitor.ts rename to x-pack/test/api_integration/apis/synthetics/get_monitor.ts index b90014b657b4a..7c0a45cf583ae 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/get_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/get_monitor.ts @@ -5,12 +5,12 @@ * 2.0. */ -import expect from '@kbn/expect'; import { SimpleSavedObject } from '@kbn/core/public'; import { MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types'; import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { getFixtureJson } from './helper/get_fixture_json'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; export default function ({ getService }: FtrProviderContext) { describe('getSyntheticsMonitors', function () { diff --git a/x-pack/test/api_integration/apis/uptime/rest/get_monitor_overview.ts b/x-pack/test/api_integration/apis/synthetics/get_monitor_overview.ts similarity index 96% rename from x-pack/test/api_integration/apis/uptime/rest/get_monitor_overview.ts rename to x-pack/test/api_integration/apis/synthetics/get_monitor_overview.ts index 408ab34f6e5bb..ff690e0093f77 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/get_monitor_overview.ts +++ b/x-pack/test/api_integration/apis/synthetics/get_monitor_overview.ts @@ -5,12 +5,12 @@ * 2.0. */ -import expect from '@kbn/expect'; import { SimpleSavedObject } from '@kbn/core/public'; import { MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types'; import { SYNTHETICS_API_URLS, API_URLS } from '@kbn/synthetics-plugin/common/constants'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { getFixtureJson } from './helper/get_fixture_json'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; export default function ({ getService }: FtrProviderContext) { describe('[GET] /internal/synthetics/overview', function () { diff --git a/x-pack/test/api_integration/apis/uptime/rest/get_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts similarity index 99% rename from x-pack/test/api_integration/apis/uptime/rest/get_monitor_project.ts rename to x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts index bbd0715c45739..734d10f6355ee 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/get_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts @@ -5,7 +5,6 @@ * 2.0. */ import uuid from 'uuid'; -import expect from '@kbn/expect'; import type SuperTest from 'supertest'; import { format as formatUrl } from 'url'; import { @@ -14,8 +13,9 @@ import { ProjectMonitorMetaData, } from '@kbn/synthetics-plugin/common/runtime_types'; import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { getFixtureJson } from './helper/get_fixture_json'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; import { PrivateLocationTestService } from './services/private_location_test_service'; import { parseStreamApiResponse } from './add_monitor_project'; diff --git a/x-pack/test/api_integration/apis/synthetics/index.ts b/x-pack/test/api_integration/apis/synthetics/index.ts new file mode 100644 index 0000000000000..937d714a6a96f --- /dev/null +++ b/x-pack/test/api_integration/apis/synthetics/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const esDeleteAllIndices = getService('esDeleteAllIndices'); + + describe('synthetics CRUD routes', () => { + before(async () => { + await esDeleteAllIndices('heartbeat*'); + await esDeleteAllIndices('synthetics*'); + }); + + loadTestFile(require.resolve('./get_monitor')); + loadTestFile(require.resolve('./get_monitor_overview')); + loadTestFile(require.resolve('./add_monitor')); + loadTestFile(require.resolve('./add_monitor_project')); + loadTestFile(require.resolve('./get_monitor_project')); + loadTestFile(require.resolve('./add_monitor_private_location')); + loadTestFile(require.resolve('./edit_monitor')); + loadTestFile(require.resolve('./delete_monitor')); + loadTestFile(require.resolve('./delete_monitor_project')); + loadTestFile(require.resolve('./synthetics_enablement')); + }); +} diff --git a/x-pack/test/api_integration/apis/uptime/rest/services/private_location_test_service.ts b/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts similarity index 90% rename from x-pack/test/api_integration/apis/uptime/rest/services/private_location_test_service.ts rename to x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts index 6fa082bff2c10..3be86d6e39414 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/services/private_location_test_service.ts +++ b/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts @@ -7,8 +7,8 @@ import { privateLocationsSavedObjectName } from '@kbn/synthetics-plugin/common/saved_objects/private_locations'; import { privateLocationsSavedObjectId } from '@kbn/synthetics-plugin/server/legacy_uptime/lib/saved_objects/private_locations'; -import { FtrProviderContext } from '../../../../ftr_provider_context'; -import { KibanaSupertestProvider } from '../../../../../../../test/api_integration/services/supertest'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { KibanaSupertestProvider } from '../../../../../../test/api_integration/services/supertest'; export class PrivateLocationTestService { private supertestAPI: ReturnType<typeof KibanaSupertestProvider>; diff --git a/x-pack/test/api_integration/apis/uptime/rest/synthetics_enablement.ts b/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts similarity index 99% rename from x-pack/test/api_integration/apis/uptime/rest/synthetics_enablement.ts rename to x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts index 2a48e1e2ed10e..b3566f64574bf 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/synthetics_enablement.ts +++ b/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts @@ -5,10 +5,10 @@ * 2.0. */ -import expect from '@kbn/expect'; import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; import { serviceApiKeyPrivileges } from '@kbn/synthetics-plugin/server/synthetics_service/get_api_key'; -import { FtrProviderContext } from '../../../ftr_provider_context'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { describe('/internal/uptime/service/enablement', () => { diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts index 2e3e6f21f34c2..e83283b730c73 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/index.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -72,18 +72,5 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./monitor_states_real_data')); loadTestFile(require.resolve('./uptime_zip_url_deprecation.ts')); }); - - describe('uptime CRUD routes', () => { - loadTestFile(require.resolve('./get_monitor')); - loadTestFile(require.resolve('./get_monitor_overview')); - loadTestFile(require.resolve('./add_monitor')); - loadTestFile(require.resolve('./add_monitor_project')); - loadTestFile(require.resolve('./get_monitor_project')); - loadTestFile(require.resolve('./add_monitor_private_location')); - loadTestFile(require.resolve('./edit_monitor')); - loadTestFile(require.resolve('./delete_monitor')); - loadTestFile(require.resolve('./delete_monitor_project')); - loadTestFile(require.resolve('./synthetics_enablement')); - }); }); } From 1a1ee5420695c37c9dd06db4fd85ef51513519db Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 3 Nov 2022 09:14:39 -0400 Subject: [PATCH 70/86] skip failing test suite (#141864) --- .../spaces_only/tests/alerting/disable.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts index d4149c9cf2fb8..dab57143e0b6d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts @@ -26,7 +26,8 @@ export default function createDisableRuleTests({ getService }: FtrProviderContex const retry = getService('retry'); const supertest = getService('supertest'); - describe('disable', () => { + // Failing: See https://github.com/elastic/kibana/issues/141864 + describe.skip('disable', () => { const objectRemover = new ObjectRemover(supertestWithoutAuth); const ruleUtils = new RuleUtils({ space: Spaces.space1, supertestWithoutAuth }); From e5271bd250cb7125ef58f95585c2578b3a80f30d Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth <kaarina.tungseth@elastic.co> Date: Thu, 3 Nov 2022 09:15:20 -0500 Subject: [PATCH 71/86] [DOCS] Fixes landing page links (#144487) --- docs/index-custom-title-page.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/index-custom-title-page.html b/docs/index-custom-title-page.html index 86fd3a16b3bb3..7935a110ac40a 100644 --- a/docs/index-custom-title-page.html +++ b/docs/index-custom-title-page.html @@ -211,7 +211,7 @@ <h4 class="mt-3"> </div> <ul class="ul-col-md-2 ul-col-1"> <li> - <a href="/install.html">Install</a> + <a href="install.html">Install</a> </li> <li> <a href="settings.html">Configure settings</a> @@ -219,9 +219,6 @@ <h4 class="mt-3"> <li> <a href="upgrade.html">Prepare for an upgrade</a> </li> - <li> - <a href="upgrade.html">Prepare for an upgrade</a> - </li> <li> <a href="managing-licenses.html">Update your license</a> </ul> From 88815398e8b53d9f4142b208424b0aca4e589502 Mon Sep 17 00:00:00 2001 From: Gerard Soldevila <gerard.soldevila@elastic.co> Date: Thu, 3 Nov 2022 15:33:52 +0100 Subject: [PATCH 72/86] Add notifications plugin, offering basic email service (#143303) * Misc enhancements following PR comments * Adding functional tests * Fixing types * Fixing tests * Removing unnecessary Promise.all * Cleanup * Misc fixes and simplifications * Add missing tsconfig.json * [CI] Auto-commit changed files from 'node scripts/build_plugin_list_docs' * Add dependency to Actions plugin in tsconfig.json * Separate setup logic from start logic * Fix bulkEnqueueExecution params structure * Update README * Add UTs * Check license type >platinum for email notifications * Fix incorrect UTs * Import types when possible * Misc enhancements and code cleanup * Transform factory => provider, update start contract * Code cleanup, update README * Fix TS error * Fix CI types error * Address PR remarks * Address PR remarks #2 Co-authored-by: Ying Mao <ying.mao@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 1 + docs/developer/plugin-list.asciidoc | 4 + src/plugins/telemetry/README.md | 4 +- tsconfig.base.json | 2 + x-pack/plugins/actions/server/mocks.ts | 2 +- x-pack/plugins/actions/server/plugin.ts | 2 +- .../unsecured_actions_client.mock.ts | 2 +- x-pack/plugins/notifications/README.md | 70 +++++ x-pack/plugins/notifications/common/index.ts | 8 + x-pack/plugins/notifications/jest.config.js | 15 ++ x-pack/plugins/notifications/kibana.json | 12 + .../notifications/server/config/config.ts | 29 +++ .../notifications/server/config/index.ts | 8 + x-pack/plugins/notifications/server/index.ts | 18 ++ x-pack/plugins/notifications/server/mocks.ts | 52 ++++ .../notifications/server/plugin.test.ts | 107 ++++++++ x-pack/plugins/notifications/server/plugin.ts | 40 +++ .../services/connectors_email_service.test.ts | 111 ++++++++ .../services/connectors_email_service.ts | 30 +++ .../connectors_email_service_provider.test.ts | 243 ++++++++++++++++++ .../connectors_email_service_provider.ts | 98 +++++++ .../notifications/server/services/index.ts | 12 + .../services/licensed_email_service.test.ts | 125 +++++++++ .../server/services/licensed_email_service.ts | 48 ++++ .../notifications/server/services/types.ts | 35 +++ x-pack/plugins/notifications/server/types.ts | 14 + x-pack/plugins/notifications/tsconfig.json | 21 ++ 27 files changed, 1108 insertions(+), 5 deletions(-) create mode 100755 x-pack/plugins/notifications/README.md create mode 100644 x-pack/plugins/notifications/common/index.ts create mode 100644 x-pack/plugins/notifications/jest.config.js create mode 100755 x-pack/plugins/notifications/kibana.json create mode 100644 x-pack/plugins/notifications/server/config/config.ts create mode 100644 x-pack/plugins/notifications/server/config/index.ts create mode 100755 x-pack/plugins/notifications/server/index.ts create mode 100644 x-pack/plugins/notifications/server/mocks.ts create mode 100644 x-pack/plugins/notifications/server/plugin.test.ts create mode 100755 x-pack/plugins/notifications/server/plugin.ts create mode 100644 x-pack/plugins/notifications/server/services/connectors_email_service.test.ts create mode 100755 x-pack/plugins/notifications/server/services/connectors_email_service.ts create mode 100644 x-pack/plugins/notifications/server/services/connectors_email_service_provider.test.ts create mode 100755 x-pack/plugins/notifications/server/services/connectors_email_service_provider.ts create mode 100644 x-pack/plugins/notifications/server/services/index.ts create mode 100644 x-pack/plugins/notifications/server/services/licensed_email_service.test.ts create mode 100644 x-pack/plugins/notifications/server/services/licensed_email_service.ts create mode 100755 x-pack/plugins/notifications/server/services/types.ts create mode 100755 x-pack/plugins/notifications/server/types.ts create mode 100644 x-pack/plugins/notifications/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9cdf6e013bd25..160d7267a4f22 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -89,6 +89,7 @@ /x-pack/test/search_sessions_integration/ @elastic/kibana-app-services /test/plugin_functional/test_suites/panel_actions @elastic/kibana-app-services /test/plugin_functional/test_suites/data_plugin @elastic/kibana-app-services +/x-pack/plugins/notifications/ @elastic/kibana-app-services ### Observability Plugins diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 32d807e016ebb..1a9a610733f54 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -589,6 +589,10 @@ Elastic. |This plugin allows for other plugins to add data to Kibana stack monitoring documents. +|{kib-repo}blob/{branch}/x-pack/plugins/notifications/README.md[notifications] +|The Notifications plugin provides a set of services to help Solutions and plugins send notifications to users. + + |{kib-repo}blob/{branch}/x-pack/plugins/observability/README.md[observability] |This plugin provides shared components and services for use across observability solutions, as well as the observability landing page UI. diff --git a/src/plugins/telemetry/README.md b/src/plugins/telemetry/README.md index df0070effe754..6b57eeda9dc80 100644 --- a/src/plugins/telemetry/README.md +++ b/src/plugins/telemetry/README.md @@ -51,14 +51,14 @@ To use the exposed plugin start and setup contracts: import { TelemetryPluginsStart } from '../telemetry/server`; -interface MyPlyginStartDeps { +interface MyPluginStartDeps { telemetry?: TelemetryPluginsStart; } class MyPlugin { public async start( core: CoreStart, - { telemetry }: MyPlyginStartDeps + { telemetry }: MyPluginStartDeps ) { const isOptedIn = await telemetry?.getIsOptedIn(); ... diff --git a/tsconfig.base.json b/tsconfig.base.json index 95f620b6e4382..b5372e27d631c 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1104,6 +1104,8 @@ "@kbn/monitoring-collection-plugin/*": ["x-pack/plugins/monitoring_collection/*"], "@kbn/monitoring-plugin": ["x-pack/plugins/monitoring"], "@kbn/monitoring-plugin/*": ["x-pack/plugins/monitoring/*"], + "@kbn/notifications-plugin": ["x-pack/plugins/notifications"], + "@kbn/notifications-plugin/*": ["x-pack/plugins/notifications/*"], "@kbn/observability-plugin": ["x-pack/plugins/observability"], "@kbn/observability-plugin/*": ["x-pack/plugins/observability/*"], "@kbn/osquery-plugin": ["x-pack/plugins/osquery"], diff --git a/x-pack/plugins/actions/server/mocks.ts b/x-pack/plugins/actions/server/mocks.ts index 4d5846de9528f..34e02b9c43e58 100644 --- a/x-pack/plugins/actions/server/mocks.ts +++ b/x-pack/plugins/actions/server/mocks.ts @@ -39,7 +39,7 @@ const createStartMock = () => { isActionTypeEnabled: jest.fn(), isActionExecutable: jest.fn(), getActionsClientWithRequest: jest.fn().mockResolvedValue(actionsClientMock.create()), - getUnsecuredActionsClient: jest.fn().mockResolvedValue(unsecuredActionsClientMock.create()), + getUnsecuredActionsClient: jest.fn().mockReturnValue(unsecuredActionsClientMock.create()), getActionsAuthorizationWithRequest: jest .fn() .mockReturnValue(actionsAuthorizationMock.create()), diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index e24ac8247bfd8..8d2f7d6bb6320 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -102,7 +102,7 @@ import { IServiceAbstract, SubActionConnectorType } from './sub_action_framework import { SubActionConnector } from './sub_action_framework/sub_action_connector'; import { CaseConnector } from './sub_action_framework/case'; import { - IUnsecuredActionsClient, + type IUnsecuredActionsClient, UnsecuredActionsClient, } from './unsecured_actions_client/unsecured_actions_client'; import { createBulkUnsecuredExecutionEnqueuerFunction } from './create_unsecured_execute_function'; diff --git a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.mock.ts b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.mock.ts index eb8d4de53e7f3..61318a4707ca8 100644 --- a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.mock.ts +++ b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IUnsecuredActionsClient } from './unsecured_actions_client'; +import type { IUnsecuredActionsClient } from './unsecured_actions_client'; export type UnsecuredActionsClientMock = jest.Mocked<IUnsecuredActionsClient>; diff --git a/x-pack/plugins/notifications/README.md b/x-pack/plugins/notifications/README.md new file mode 100755 index 0000000000000..75ea13570ec2b --- /dev/null +++ b/x-pack/plugins/notifications/README.md @@ -0,0 +1,70 @@ +# Kibana Notifications Plugin + +The Notifications plugin provides a set of services to help Solutions and plugins send notifications to users. + +## Notifications Plugin public API + +### Start + +The `start` function exposes the following interface: + +- `isEmailServiceAvailable(): boolean`: + A function to check whether the deployment is properly configured and the EmailService can be correctly retrieved. +- `getEmailService(): EmailService`: +- A function to get the basic EmailService, which can be used to send plain text emails. If the EmailService is not available, trying to retrieve it will result in an Exception. + + +### Usage + +To use the exposed plugin start contract: + +1. Make sure `notifications` is in your `optionalPlugins` in the `kibana.json` file: + +```json5 +// <plugin>/kibana.json +{ +"id": "...", +"requiredPlugins": ["notifications"] +} +``` + +2. Use the exposed contract: + +```ts +// <plugin>/server/plugin.ts +import { NotificationsPluginStart } from '../notifications/server`; + +interface MyPluginStartDeps { + notifications?: NotificationsPluginStart; +} + +class MyPlugin { + public start( + core: CoreStart, + { notifications }: MyPluginStartDeps + ) { + if (notifications.isEmailServiceAvailable()) { + const emailService = notifications.getEmailService(); + emailService.sendPlainTextEmail({ + to: 'foo@bar.com', + subject: 'Some subject', + message: 'Hello world!', + }); + } + ... + } +} +``` + +### Requirements + +- This plugin currently depends on the `'actions'` plugin, as it uses `Connectors` under the hood. +- Note also that for each notification channel the corresponding connector must be preconfigured. E.g. to enable email notifications, an `Email` connector must exist in the system. +- Once the appropriate connectors are preconfigured in `kibana.yaml`, you can configure the `'notifications'` plugin by adding: + + ```yaml + notifications: + connectors: + default: + email: elastic-cloud-email # The identifier of the configured connector + ``` diff --git a/x-pack/plugins/notifications/common/index.ts b/x-pack/plugins/notifications/common/index.ts new file mode 100644 index 0000000000000..bc315c3c9e028 --- /dev/null +++ b/x-pack/plugins/notifications/common/index.ts @@ -0,0 +1,8 @@ +/* + * 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 const PLUGIN_ID = 'notifications'; diff --git a/x-pack/plugins/notifications/jest.config.js b/x-pack/plugins/notifications/jest.config.js new file mode 100644 index 0000000000000..b19a8f2efe334 --- /dev/null +++ b/x-pack/plugins/notifications/jest.config.js @@ -0,0 +1,15 @@ +/* + * 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. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../..', + roots: ['<rootDir>/x-pack/plugins/notifications'], + coverageDirectory: '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/notifications', + coverageReporters: ['text', 'html'], + collectCoverageFrom: ['<rootDir>/x-pack/plugins/notifications/{common,server}/**/*.{js,ts,tsx}'], +}; diff --git a/x-pack/plugins/notifications/kibana.json b/x-pack/plugins/notifications/kibana.json new file mode 100755 index 0000000000000..45cf4c4cd47b0 --- /dev/null +++ b/x-pack/plugins/notifications/kibana.json @@ -0,0 +1,12 @@ +{ + "id": "notifications", + "owner": { + "name": "App Services", + "githubTeam": "kibana-app-services" + }, + "version": "kibana", + "server": true, + "ui": false, + "requiredPlugins": ["actions", "licensing"], + "optionalPlugins": [] +} diff --git a/x-pack/plugins/notifications/server/config/config.ts b/x-pack/plugins/notifications/server/config/config.ts new file mode 100644 index 0000000000000..f2dc570adabe9 --- /dev/null +++ b/x-pack/plugins/notifications/server/config/config.ts @@ -0,0 +1,29 @@ +/* + * 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 { schema, type TypeOf } from '@kbn/config-schema'; +import type { PluginConfigDescriptor } from '@kbn/core/server'; + +export const configSchema = schema.object( + { + connectors: schema.maybe( + schema.object({ + default: schema.maybe( + schema.object({ + email: schema.maybe(schema.string()), + }) + ), + }) + ), + }, + { defaultValue: {} } +); +export type NotificationsConfigType = TypeOf<typeof configSchema>; + +export const config: PluginConfigDescriptor<NotificationsConfigType> = { + schema: configSchema, +}; diff --git a/x-pack/plugins/notifications/server/config/index.ts b/x-pack/plugins/notifications/server/config/index.ts new file mode 100644 index 0000000000000..662050c13d2ec --- /dev/null +++ b/x-pack/plugins/notifications/server/config/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { type NotificationsConfigType, config } from './config'; diff --git a/x-pack/plugins/notifications/server/index.ts b/x-pack/plugins/notifications/server/index.ts new file mode 100755 index 0000000000000..9e8785d680de5 --- /dev/null +++ b/x-pack/plugins/notifications/server/index.ts @@ -0,0 +1,18 @@ +/* + * 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 { PluginInitializerContext } from '@kbn/core/server'; +import { NotificationsPlugin } from './plugin'; +export { config } from './config'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. +export type { NotificationsPluginStart } from './types'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new NotificationsPlugin(initializerContext); +} diff --git a/x-pack/plugins/notifications/server/mocks.ts b/x-pack/plugins/notifications/server/mocks.ts new file mode 100644 index 0000000000000..6360e0ece597b --- /dev/null +++ b/x-pack/plugins/notifications/server/mocks.ts @@ -0,0 +1,52 @@ +/* + * 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 { PublicMethodsOf } from '@kbn/utility-types'; +import type { EmailService } from './services'; +import type { NotificationsPluginStart } from './types'; +import type { NotificationsPlugin } from './plugin'; + +const emailServiceMock: jest.Mocked<EmailService> = { + sendPlainTextEmail: jest.fn(), +}; + +const createEmailServiceMock = () => { + return emailServiceMock; +}; + +const startMock: jest.Mocked<NotificationsPluginStart> = { + isEmailServiceAvailable: jest.fn(), + getEmailService: jest.fn(createEmailServiceMock), +}; + +const createStartMock = () => { + return startMock; +}; + +const notificationsPluginMock: jest.Mocked<PublicMethodsOf<NotificationsPlugin>> = { + setup: jest.fn(), + start: jest.fn(createStartMock) as jest.Mock<NotificationsPluginStart>, + stop: jest.fn(), +}; + +const createNotificationsPluginMock = () => { + return notificationsPluginMock; +}; + +export const notificationsMock = { + createNotificationsPlugin: createNotificationsPluginMock, + createEmailService: createEmailServiceMock, + createStart: createStartMock, + clear: () => { + emailServiceMock.sendPlainTextEmail.mockClear(); + startMock.getEmailService.mockClear(); + startMock.isEmailServiceAvailable.mockClear(); + notificationsPluginMock.setup.mockClear(); + notificationsPluginMock.start.mockClear(); + notificationsPluginMock.stop.mockClear(); + }, +}; diff --git a/x-pack/plugins/notifications/server/plugin.test.ts b/x-pack/plugins/notifications/server/plugin.test.ts new file mode 100644 index 0000000000000..687414becb051 --- /dev/null +++ b/x-pack/plugins/notifications/server/plugin.test.ts @@ -0,0 +1,107 @@ +/* + * 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 { coreMock } from '@kbn/core/server/mocks'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; +import type { NotificationsConfigType } from './config'; +import { NotificationsPlugin } from './plugin'; +import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; +import { EmailServiceProvider } from './services/connectors_email_service_provider'; +import { EmailServiceStart } from './services'; + +jest.mock('./services/connectors_email_service_provider'); + +const emailServiceProviderMock = EmailServiceProvider as jest.MockedClass< + typeof EmailServiceProvider +>; + +const validConnectorConfig = { + connectors: { + default: { + email: 'validConnectorId', + }, + }, +}; + +const createNotificationsPlugin = (config: NotificationsConfigType) => { + const context = coreMock.createPluginInitializerContext<NotificationsConfigType>(config); + const plugin = new NotificationsPlugin(context); + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createStart(); + + const actionsSetup = actionsMock.createSetup(); + actionsSetup.isPreconfiguredConnector.mockImplementationOnce( + (connectorId) => connectorId === 'validConnectorId' + ); + const pluginSetup = { + actions: actionsSetup, + licensing: licensingMock.createSetup(), + }; + + const actionsStart = actionsMock.createStart(); + const pluginStart = { + actions: actionsStart, + licensing: licensingMock.createStart(), + }; + + return { + context, + logger: context.logger.get(), + plugin, + coreSetup, + coreStart, + actionsSetup, + pluginSetup, + actionsStart, + pluginStart, + }; +}; + +describe('Notifications Plugin', () => { + beforeEach(() => emailServiceProviderMock.mockClear()); + + it('should create an EmailServiceProvider passing in the configuration and logger from the initializer context', () => { + const { logger } = createNotificationsPlugin(validConnectorConfig); + expect(emailServiceProviderMock).toHaveBeenCalledTimes(1); + expect(emailServiceProviderMock).toHaveBeenCalledWith(validConnectorConfig, logger); + }); + + describe('setup()', () => { + it('should call setup() on the created EmailServiceProvider, passing in the setup plugin dependencies', () => { + const { plugin, coreSetup, pluginSetup } = createNotificationsPlugin(validConnectorConfig); + plugin.setup(coreSetup, pluginSetup); + expect(emailServiceProviderMock.mock.instances[0].setup).toHaveBeenCalledTimes(1); + expect(emailServiceProviderMock.mock.instances[0].setup).toBeCalledWith(pluginSetup); + }); + }); + + describe('start()', () => { + it('should call start() on the created EmailServiceProvider, passing in the setup plugin dependencies', () => { + const { plugin, coreStart, pluginStart } = createNotificationsPlugin(validConnectorConfig); + plugin.start(coreStart, pluginStart); + expect(emailServiceProviderMock.mock.instances[0].start).toHaveBeenCalledTimes(1); + expect(emailServiceProviderMock.mock.instances[0].start).toBeCalledWith(pluginStart); + }); + + it('should return EmailServiceProvider.start() contract as part of its contract', () => { + const { plugin, coreStart, pluginStart } = createNotificationsPlugin(validConnectorConfig); + + const emailStart: EmailServiceStart = { + getEmailService: jest.fn(), + isEmailServiceAvailable: jest.fn(), + }; + + const providerMock = emailServiceProviderMock.mock + .instances[0] as jest.Mocked<EmailServiceProvider>; + providerMock.start.mockReturnValue(emailStart); + const start = plugin.start(coreStart, pluginStart); + expect(emailServiceProviderMock.mock.instances[0].start).toHaveBeenCalledTimes(1); + expect(emailServiceProviderMock.mock.instances[0].start).toBeCalledWith(pluginStart); + expect(start).toEqual(expect.objectContaining(emailStart)); + }); + }); +}); diff --git a/x-pack/plugins/notifications/server/plugin.ts b/x-pack/plugins/notifications/server/plugin.ts new file mode 100755 index 0000000000000..562db1977a73c --- /dev/null +++ b/x-pack/plugins/notifications/server/plugin.ts @@ -0,0 +1,40 @@ +/* + * 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 { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '@kbn/core/server'; +import type { + NotificationsPluginSetupDeps, + NotificationsPluginStartDeps, + NotificationsPluginStart, +} from './types'; +import type { NotificationsConfigType } from './config'; +import { EmailServiceProvider } from './services/connectors_email_service_provider'; + +export class NotificationsPlugin implements Plugin<void, NotificationsPluginStart> { + private emailServiceProvider: EmailServiceProvider; + + constructor(initializerContext: PluginInitializerContext<NotificationsConfigType>) { + this.emailServiceProvider = new EmailServiceProvider( + initializerContext.config.get(), + initializerContext.logger.get() + ); + } + + public setup(_core: CoreSetup, plugins: NotificationsPluginSetupDeps) { + this.emailServiceProvider.setup(plugins); + } + + public start(_core: CoreStart, plugins: NotificationsPluginStartDeps) { + const emailStartContract = this.emailServiceProvider.start(plugins); + + return { + ...emailStartContract, + }; + } + + public stop() {} +} diff --git a/x-pack/plugins/notifications/server/services/connectors_email_service.test.ts b/x-pack/plugins/notifications/server/services/connectors_email_service.test.ts new file mode 100644 index 0000000000000..f07b3a3ab34ae --- /dev/null +++ b/x-pack/plugins/notifications/server/services/connectors_email_service.test.ts @@ -0,0 +1,111 @@ +/* + * 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 { unsecuredActionsClientMock } from '@kbn/actions-plugin/server/unsecured_actions_client/unsecured_actions_client.mock'; +import { ConnectorsEmailService } from './connectors_email_service'; +import type { PlainTextEmail } from './types'; + +const REQUESTER_ID = 'requesterId'; +const CONNECTOR_ID = 'connectorId'; + +describe('sendPlainTextEmail()', () => { + describe('calls the provided ActionsClient#bulkEnqueueExecution() with the appropriate params', () => { + it(`omits the 'relatedSavedObjects' field if no context is provided`, () => { + const actionsClient = unsecuredActionsClientMock.create(); + const email = new ConnectorsEmailService(REQUESTER_ID, CONNECTOR_ID, actionsClient); + const payload: PlainTextEmail = { + to: ['user1@email.com'], + subject: 'This is a notification email', + message: 'With some contents inside.', + }; + + email.sendPlainTextEmail(payload); + + expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); + expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledWith(REQUESTER_ID, [ + { + id: CONNECTOR_ID, + params: { + to: ['user1@email.com'], + subject: 'This is a notification email', + message: 'With some contents inside.', + }, + }, + ]); + }); + + it(`populates the 'relatedSavedObjects' field if context is provided`, () => { + const actionsClient = unsecuredActionsClientMock.create(); + const email = new ConnectorsEmailService(REQUESTER_ID, CONNECTOR_ID, actionsClient); + const payload: PlainTextEmail = { + to: ['user1@email.com', 'user2@email.com', 'user3@email.com'], + subject: 'This is a notification email', + message: 'With some contents inside.', + context: { + relatedObjects: [ + { + id: '9c9456a4-c160-46f5-96f7-e9ac734d0d9b', + type: 'cases', + namespace: 'space1', + }, + ], + }, + }; + + email.sendPlainTextEmail(payload); + + expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); + expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledWith(REQUESTER_ID, [ + { + id: CONNECTOR_ID, + params: { + to: ['user1@email.com'], + subject: 'This is a notification email', + message: 'With some contents inside.', + }, + relatedSavedObjects: [ + { + id: '9c9456a4-c160-46f5-96f7-e9ac734d0d9b', + type: 'cases', + namespace: 'space1', + }, + ], + }, + { + id: CONNECTOR_ID, + params: { + to: ['user2@email.com'], + subject: 'This is a notification email', + message: 'With some contents inside.', + }, + relatedSavedObjects: [ + { + id: '9c9456a4-c160-46f5-96f7-e9ac734d0d9b', + type: 'cases', + namespace: 'space1', + }, + ], + }, + { + id: CONNECTOR_ID, + params: { + to: ['user3@email.com'], + subject: 'This is a notification email', + message: 'With some contents inside.', + }, + relatedSavedObjects: [ + { + id: '9c9456a4-c160-46f5-96f7-e9ac734d0d9b', + type: 'cases', + namespace: 'space1', + }, + ], + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/notifications/server/services/connectors_email_service.ts b/x-pack/plugins/notifications/server/services/connectors_email_service.ts new file mode 100755 index 0000000000000..55586dd05b078 --- /dev/null +++ b/x-pack/plugins/notifications/server/services/connectors_email_service.ts @@ -0,0 +1,30 @@ +/* + * 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 { IUnsecuredActionsClient } from '@kbn/actions-plugin/server'; +import type { EmailService, PlainTextEmail } from './types'; + +export class ConnectorsEmailService implements EmailService { + constructor( + private requesterId: string, + private connectorId: string, + private actionsClient: IUnsecuredActionsClient + ) {} + + async sendPlainTextEmail(params: PlainTextEmail): Promise<void> { + const actions = params.to.map((to) => ({ + id: this.connectorId, + params: { + to: [to], + subject: params.subject, + message: params.message, + }, + relatedSavedObjects: params.context?.relatedObjects, + })); + return await this.actionsClient.bulkEnqueueExecution(this.requesterId, actions); + } +} diff --git a/x-pack/plugins/notifications/server/services/connectors_email_service_provider.test.ts b/x-pack/plugins/notifications/server/services/connectors_email_service_provider.test.ts new file mode 100644 index 0000000000000..6c36f94db1a7c --- /dev/null +++ b/x-pack/plugins/notifications/server/services/connectors_email_service_provider.test.ts @@ -0,0 +1,243 @@ +/* + * 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 { loggerMock } from '@kbn/logging-mocks'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; +import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; +import { LicensedEmailService } from './licensed_email_service'; +import { EmailServiceProvider } from './connectors_email_service_provider'; +import { ConnectorsEmailService } from './connectors_email_service'; +import { PLUGIN_ID } from '../../common'; + +jest.mock('./licensed_email_service'); +jest.mock('./connectors_email_service'); + +const licensedEmailServiceMock = LicensedEmailService as jest.MockedClass< + typeof LicensedEmailService +>; +const connectorsEmailServiceMock = ConnectorsEmailService as jest.MockedClass< + typeof ConnectorsEmailService +>; + +const missingConnectorConfig = { + connectors: { + default: {}, + }, +}; + +const invalidConnectorConfig = { + connectors: { + default: { + email: 'someUnexistingConnectorId', + }, + }, +}; + +const validConnectorConfig = { + connectors: { + default: { + email: 'validConnectorId', + }, + }, +}; + +describe('ConnectorsEmailServiceProvider', () => { + const logger = loggerMock.create(); + const actionsSetup = actionsMock.createSetup(); + actionsSetup.isPreconfiguredConnector.mockImplementation( + (connectorId) => connectorId === 'validConnectorId' + ); + + beforeEach(() => { + loggerMock.clear(logger); + licensedEmailServiceMock.mockClear(); + connectorsEmailServiceMock.mockClear(); + }); + + it('implements the IEmailServiceProvider interface', () => { + const serviceProvider = new EmailServiceProvider(validConnectorConfig, loggerMock.create()); + expect(serviceProvider.setup).toBeInstanceOf(Function); + expect(serviceProvider.start).toBeInstanceOf(Function); + }); + + describe('setup()', () => { + it('should log a warning if Actions or Licensing plugins are not available', () => { + const serviceProvider = new EmailServiceProvider(validConnectorConfig, logger); + serviceProvider.setup({ + actions: actionsSetup, + }); + + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith( + `Email Service Error: 'actions' and 'licensing' plugins are required.` + ); + // eslint-disable-next-line dot-notation + expect(serviceProvider['setupSuccessful']).toEqual(false); + }); + + it('should log a warning if no default email connector has been defined', () => { + const serviceProvider = new EmailServiceProvider(missingConnectorConfig, logger); + serviceProvider.setup({ + actions: actionsSetup, + licensing: licensingMock.createSetup(), + }); + + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith( + `Email Service Error: Email connector not specified.` + ); + // eslint-disable-next-line dot-notation + expect(serviceProvider['setupSuccessful']).toEqual(false); + }); + + it('should log a warning if the specified email connector is not a preconfigured connector', () => { + const serviceProvider = new EmailServiceProvider(invalidConnectorConfig, logger); + serviceProvider.setup({ + actions: actionsSetup, + licensing: licensingMock.createSetup(), + }); + + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith( + `Email Service Error: Unexisting email connector 'someUnexistingConnectorId' specified.` + ); + // eslint-disable-next-line dot-notation + expect(serviceProvider['setupSuccessful']).toEqual(false); + }); + + it('should not log a warning if required plugins are present and the specified email connector is valid', () => { + const serviceProvider = new EmailServiceProvider(validConnectorConfig, logger); + serviceProvider.setup({ + actions: actionsSetup, + licensing: licensingMock.createSetup(), + }); + + expect(logger.warn).not.toHaveBeenCalled(); + // eslint-disable-next-line dot-notation + expect(serviceProvider['setupSuccessful']).toEqual(true); + }); + }); + + describe('start()', () => { + it('returns an object that implements the EmailServiceStart contract', () => { + const serviceProvider = new EmailServiceProvider(missingConnectorConfig, logger); + const start = serviceProvider.start({}); + expect(start.getEmailService).toBeInstanceOf(Function); + expect(start.isEmailServiceAvailable).toBeInstanceOf(Function); + }); + + describe('if setup has not been run', () => { + it('the start contract methods fail accordingly', () => { + const serviceProvider = new EmailServiceProvider(missingConnectorConfig, logger); + const start = serviceProvider.start({}); + expect(start.isEmailServiceAvailable()).toEqual(false); + expect(() => { + start.getEmailService(); + }).toThrowErrorMatchingInlineSnapshot(`"Email Service Error: setup() has not been run"`); + }); + }); + + describe('if setup() did not complete successfully', () => { + it('the start contract methods fail accordingly', () => { + const serviceProvider = new EmailServiceProvider(invalidConnectorConfig, logger); + serviceProvider.setup({ + actions: actionsSetup, + licensing: licensingMock.createSetup(), + }); + const start = serviceProvider.start({ + actions: actionsMock.createStart(), + licensing: licensingMock.createStart(), + }); + expect(start.isEmailServiceAvailable()).toEqual(false); + expect(() => { + start.getEmailService(); + }).toThrowErrorMatchingInlineSnapshot( + `"Email Service Error: Unexisting email connector 'someUnexistingConnectorId' specified."` + ); + }); + }); + + describe('if setup() did complete successfully and Action and Licensing plugin start contracts are available', () => { + it('attempts to build an UnsecuredActionsClient', () => { + const serviceProvider = new EmailServiceProvider(validConnectorConfig, logger); + const actionsStart = actionsMock.createStart(); + + serviceProvider.setup({ + actions: actionsSetup, + licensing: licensingMock.createSetup(), + }); + serviceProvider.start({ + actions: actionsStart, + licensing: licensingMock.createStart(), + }); + expect(actionsStart.getUnsecuredActionsClient).toHaveBeenCalledTimes(1); + }); + + describe('if getUnsecuredActionsClient() throws an Exception', () => { + it('catches the exception, and the start contract methods fail accordingly', () => { + const serviceProvider = new EmailServiceProvider(validConnectorConfig, logger); + const actionsStart = actionsMock.createStart(); + actionsStart.getUnsecuredActionsClient.mockImplementation(() => { + throw new Error('Something went terribly wrong.'); + }); + + serviceProvider.setup({ + actions: actionsSetup, + licensing: licensingMock.createSetup(), + }); + const start = serviceProvider.start({ + actions: actionsStart, + licensing: licensingMock.createStart(), + }); + + expect(start.isEmailServiceAvailable()).toEqual(false); + expect(() => { + start.getEmailService(); + }).toThrowErrorMatchingInlineSnapshot( + `"Email Service Error: Something went terribly wrong."` + ); + }); + }); + + describe('if getUnsecuredActionsClient() returns an UnsecuredActionsClient', () => { + it('returns a start contract that provides valid EmailService', () => { + const serviceProvider = new EmailServiceProvider(validConnectorConfig, logger); + const licensingStart = licensingMock.createStart(); + const actionsStart = actionsMock.createStart(); + + serviceProvider.setup({ + actions: actionsSetup, + licensing: licensingMock.createSetup(), + }); + const start = serviceProvider.start({ + actions: actionsStart, + licensing: licensingStart, + }); + + expect(start.isEmailServiceAvailable()).toEqual(true); + const email = start.getEmailService(); + expect(email).toBeInstanceOf(LicensedEmailService); + expect(licensedEmailServiceMock).toHaveBeenCalledTimes(1); + + expect(licensedEmailServiceMock).toHaveBeenCalledWith( + connectorsEmailServiceMock.mock.instances[0], + licensingStart.license$, + 'platinum', + expect.objectContaining({ debug: expect.any(Function), warn: expect.any(Function) }) + ); + + expect(connectorsEmailServiceMock).toHaveBeenCalledTimes(1); + expect(connectorsEmailServiceMock).toHaveBeenCalledWith( + PLUGIN_ID, + validConnectorConfig.connectors.default.email, + actionsStart.getUnsecuredActionsClient() + ); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/notifications/server/services/connectors_email_service_provider.ts b/x-pack/plugins/notifications/server/services/connectors_email_service_provider.ts new file mode 100755 index 0000000000000..f034116eb701c --- /dev/null +++ b/x-pack/plugins/notifications/server/services/connectors_email_service_provider.ts @@ -0,0 +1,98 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import { PluginSetupContract, PluginStartContract } from '@kbn/actions-plugin/server'; +import { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/server'; +import type { EmailService, EmailServiceStart, IEmailServiceProvider } from './types'; +import type { NotificationsConfigType } from '../config'; +import { LicensedEmailService } from './licensed_email_service'; +import { ConnectorsEmailService } from './connectors_email_service'; +import { PLUGIN_ID } from '../../common'; + +const MINIMUM_LICENSE = 'platinum'; + +export interface EmailServiceSetupDeps { + actions?: PluginSetupContract; + licensing?: LicensingPluginSetup; +} + +export interface EmailServiceStartDeps { + actions?: PluginStartContract; + licensing?: LicensingPluginStart; +} + +export class EmailServiceProvider + implements IEmailServiceProvider<EmailServiceSetupDeps, EmailServiceStartDeps> +{ + private setupSuccessful: boolean; + private setupError: string; + + constructor(private config: NotificationsConfigType, private logger: Logger) { + this.setupSuccessful = false; + this.setupError = 'Email Service Error: setup() has not been run'; + } + + public setup(plugins: EmailServiceSetupDeps) { + const { actions, licensing } = plugins; + + if (!actions || !licensing) { + return this._registerServiceError(`Error: 'actions' and 'licensing' plugins are required.`); + } + + const emailConnector = this.config.connectors?.default?.email; + if (!emailConnector) { + return this._registerServiceError('Error: Email connector not specified.'); + } + + if (!actions.isPreconfiguredConnector(emailConnector)) { + return this._registerServiceError( + `Error: Unexisting email connector '${emailConnector}' specified.` + ); + } + + this.setupSuccessful = true; + this.setupError = ''; + } + + public start(plugins: EmailServiceStartDeps): EmailServiceStart { + const { actions, licensing } = plugins; + + let email: EmailService; + if (this.setupSuccessful && actions && licensing) { + const emailConnector = this.config.connectors!.default!.email!; + + try { + const unsecuredActionsClient = actions.getUnsecuredActionsClient(); + email = new LicensedEmailService( + new ConnectorsEmailService(PLUGIN_ID, emailConnector, unsecuredActionsClient), + licensing.license$, + MINIMUM_LICENSE, + this.logger + ); + } catch (err) { + this._registerServiceError(err); + } + } + + return { + isEmailServiceAvailable: () => !!email, + getEmailService: () => { + if (!email) { + throw new Error(this.setupError); + } + return email; + }, + }; + } + + private _registerServiceError(error: string) { + const message = `Email Service ${error}`; + this.setupError = message; + this.logger.warn(message); + } +} diff --git a/x-pack/plugins/notifications/server/services/index.ts b/x-pack/plugins/notifications/server/services/index.ts new file mode 100644 index 0000000000000..f0ad8abb4885d --- /dev/null +++ b/x-pack/plugins/notifications/server/services/index.ts @@ -0,0 +1,12 @@ +/* + * 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 type { EmailService, EmailServiceStart, PlainTextEmail } from './types'; +export type { + EmailServiceSetupDeps, + EmailServiceStartDeps, +} from './connectors_email_service_provider'; diff --git a/x-pack/plugins/notifications/server/services/licensed_email_service.test.ts b/x-pack/plugins/notifications/server/services/licensed_email_service.test.ts new file mode 100644 index 0000000000000..ac196ebb7321b --- /dev/null +++ b/x-pack/plugins/notifications/server/services/licensed_email_service.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { Subject } from 'rxjs'; +import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; +import { loggerMock } from '@kbn/logging-mocks'; +import { LicensedEmailService } from './licensed_email_service'; +import type { ILicense } from '@kbn/licensing-plugin/server'; +import type { EmailService, PlainTextEmail } from './types'; + +const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +const emailServiceMock: EmailService = { + sendPlainTextEmail: jest.fn(), +}; + +const validLicense = licensingMock.createLicenseMock(); +const invalidLicense = licensingMock.createLicenseMock(); +invalidLicense.type = 'basic'; +invalidLicense.check = jest.fn(() => ({ + state: 'invalid', + message: 'This is an invalid testing license', +})) as unknown as any; + +const someEmail: PlainTextEmail = { + to: ['user1@email.com'], + subject: 'Some subject', + message: 'Some message', +}; + +describe('LicensedEmailService', () => { + const logger = loggerMock.create(); + + beforeEach(() => loggerMock.clear(logger)); + it('observes license$ changes and logs info or warning messages accordingly', () => { + const license$ = new Subject<ILicense>(); + new LicensedEmailService(emailServiceMock, license$, 'platinum', logger); + license$.next(invalidLicense); + + expect(logger.debug).not.toHaveBeenCalled(); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith('This is an invalid testing license'); + + license$.next(validLicense); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.debug).toHaveBeenCalledTimes(1); + expect(logger.debug).toHaveBeenCalledWith( + 'Your current license allows sending email notifications' + ); + }); + + describe('sendPlainTextEmail()', () => { + it('does not call the underlying email service until the license is determined and valid', async () => { + const license$ = new Subject<ILicense>(); + const email = new LicensedEmailService(emailServiceMock, license$, 'platinum', logger); + + email.sendPlainTextEmail(someEmail); + expect(emailServiceMock.sendPlainTextEmail).not.toHaveBeenCalled(); + license$.next(validLicense); + + await delay(1); + + expect(emailServiceMock.sendPlainTextEmail).toHaveBeenCalledTimes(1); + expect(emailServiceMock.sendPlainTextEmail).toHaveBeenCalledWith(someEmail); + }); + + it('does not call the underlying email service if the license is invalid', async () => { + const license$ = new Subject<ILicense>(); + const email = new LicensedEmailService(emailServiceMock, license$, 'platinum', logger); + license$.next(invalidLicense); + + try { + await email.sendPlainTextEmail(someEmail); + } catch (err) { + expect(err.message).toEqual( + 'The current license does not allow sending email notifications' + ); + return; + } + + expect('it should have thrown').toEqual('but it did not'); + }); + + it('does not log a warning for every email attempt, but rather for every license change', async () => { + const license$ = new Subject<ILicense>(); + const email = new LicensedEmailService(emailServiceMock, license$, 'platinum', logger); + license$.next(invalidLicense); + license$.next(validLicense); + license$.next(invalidLicense); + + expect(logger.debug).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledTimes(2); + + let emailsOk = 0; + let emailsKo = 0; + const silentSend = async () => { + try { + await email.sendPlainTextEmail(someEmail); + emailsOk++; + } catch (err) { + emailsKo++; + } + }; + + await silentSend(); + await silentSend(); + await silentSend(); + await silentSend(); + license$.next(validLicense); + await silentSend(); + await silentSend(); + await silentSend(); + await silentSend(); + + expect(logger.debug).toHaveBeenCalledTimes(2); + expect(logger.warn).toHaveBeenCalledTimes(2); + expect(emailsKo).toEqual(4); + expect(emailsOk).toEqual(4); + }); + }); +}); diff --git a/x-pack/plugins/notifications/server/services/licensed_email_service.ts b/x-pack/plugins/notifications/server/services/licensed_email_service.ts new file mode 100644 index 0000000000000..63fc6f8d13df3 --- /dev/null +++ b/x-pack/plugins/notifications/server/services/licensed_email_service.ts @@ -0,0 +1,48 @@ +/* + * 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 { Logger } from '@kbn/logging'; +import type { ILicense, LicenseType } from '@kbn/licensing-plugin/server'; +import { firstValueFrom, map, type Observable, ReplaySubject, type Subject } from 'rxjs'; +import type { EmailService, PlainTextEmail } from './types'; +import { PLUGIN_ID } from '../../common'; + +export class LicensedEmailService implements EmailService { + private validLicense$: Subject<boolean> = new ReplaySubject(1); + + constructor( + private emailService: EmailService, + license$: Observable<ILicense>, + private minimumLicense: LicenseType, + private logger: Logger + ) { + // no need to explicitly unsubscribe as the license$ observable already completes on stop() + license$.pipe(map((license) => this.checkValidLicense(license))).subscribe(this.validLicense$); + } + + async sendPlainTextEmail(payload: PlainTextEmail): Promise<void> { + if (await firstValueFrom(this.validLicense$, { defaultValue: false })) { + await this.emailService.sendPlainTextEmail(payload); + } else { + throw new Error('The current license does not allow sending email notifications'); + } + } + + private checkValidLicense(license: ILicense): boolean { + const licenseCheck = license.check(PLUGIN_ID, this.minimumLicense); + + if (licenseCheck.state === 'valid') { + this.logger.debug('Your current license allows sending email notifications'); + return true; + } + + this.logger.warn( + licenseCheck.message || 'The current license does not allow sending email notifications' + ); + return false; + } +} diff --git a/x-pack/plugins/notifications/server/services/types.ts b/x-pack/plugins/notifications/server/services/types.ts new file mode 100755 index 0000000000000..798b4d7e24699 --- /dev/null +++ b/x-pack/plugins/notifications/server/services/types.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. + */ + +export interface EmailService { + sendPlainTextEmail(payload: PlainTextEmail): Promise<void>; +} + +export interface EmailServiceStart { + isEmailServiceAvailable(): boolean; + getEmailService(): EmailService; +} + +export interface IEmailServiceProvider<T, U> { + setup(setupDeps: T): void; + start(startDeps: U): EmailServiceStart; +} + +export interface RelatedSavedObject { + id: string; + type: string; + namespace?: string; // namespace is undefined for the spaceId 'default' +} + +export interface PlainTextEmail { + to: string[]; + subject: string; + message: string; + context?: { + relatedObjects?: RelatedSavedObject[]; + }; +} diff --git a/x-pack/plugins/notifications/server/types.ts b/x-pack/plugins/notifications/server/types.ts new file mode 100755 index 0000000000000..e7132a33cbb19 --- /dev/null +++ b/x-pack/plugins/notifications/server/types.ts @@ -0,0 +1,14 @@ +/* + * 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 { EmailServiceStart, EmailServiceSetupDeps, EmailServiceStartDeps } from './services'; + +// The 'notifications' plugin is currently only exposing an email service. +// If we want to expose other services in the future, we should update these types accordingly +export type NotificationsPluginSetupDeps = EmailServiceSetupDeps; +export type NotificationsPluginStartDeps = EmailServiceStartDeps; +export type NotificationsPluginStart = EmailServiceStart; diff --git a/x-pack/plugins/notifications/tsconfig.json b/x-pack/plugins/notifications/tsconfig.json new file mode 100644 index 0000000000000..6f2c186803b0c --- /dev/null +++ b/x-pack/plugins/notifications/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "server/**/*", + // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 + "server/**/*.json", + "public/**/*", + "common/**/*" + ], + "kbn_references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../actions/tsconfig.json" }, + { "path": "../licensing/tsconfig.json" } + ] +} From 1505c0688da10595e9081c45140752be1400dae0 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko <dzmitry.lemechko@elastic.co> Date: Thu, 3 Nov 2022 16:17:55 +0100 Subject: [PATCH 73/86] attach-screenshots-from-journey-steps (#144447) --- .../scripts/steps/functional/performance_playwright.sh | 8 ++++++++ packages/kbn-journeys/journey/journey_screenshots.ts | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.buildkite/scripts/steps/functional/performance_playwright.sh b/.buildkite/scripts/steps/functional/performance_playwright.sh index 6f0399b2de90d..5be8f62c7e01f 100644 --- a/.buildkite/scripts/steps/functional/performance_playwright.sh +++ b/.buildkite/scripts/steps/functional/performance_playwright.sh @@ -119,6 +119,14 @@ while read -r journey; do done done <<< "$journeys" +echo "--- Upload journey step screenshots" +JOURNEY_SCREENSHOTS_DIR="${KIBANA_DIR}/data/journey_screenshots" +if [ -d "$JOURNEY_SCREENSHOTS_DIR" ]; then + cd "$JOURNEY_SCREENSHOTS_DIR" + buildkite-agent artifact upload "**/*fullscreen*.png" + cd "$KIBANA_DIR" +fi + echo "--- report/record failed journeys" if [ "${failedJourneys[*]}" != "" ]; then buildkite-agent meta-data set "failed-journeys" "$(printf "%s\n" "${failedJourneys[@]}")" diff --git a/packages/kbn-journeys/journey/journey_screenshots.ts b/packages/kbn-journeys/journey/journey_screenshots.ts index adf1021fa163d..955d00ca4c372 100644 --- a/packages/kbn-journeys/journey/journey_screenshots.ts +++ b/packages/kbn-journeys/journey/journey_screenshots.ts @@ -92,7 +92,7 @@ export class JourneyScreenshots { await this.lock(async () => { const filename = FtrScreenshotFilename.create(`${step.index}-${step.name}-failure`); const fullscreenFilename = FtrScreenshotFilename.create( - `${step.index}-${step.name}-failure-fullscreen` + `${step.index}-${step.name.replace(/\s/g, '-')}-failure-fullscreen` ); this.#manifest.steps.push({ type: 'failure', @@ -113,7 +113,7 @@ export class JourneyScreenshots { await this.lock(async () => { const filename = FtrScreenshotFilename.create(`${step.index}-${step.name}`); const fullscreenFilename = FtrScreenshotFilename.create( - `${step.index}-${step.name}-fullscreen` + `${step.index}-${step.name.replace(/\s/g, '-')}-fullscreen` ); this.#manifest.steps.push({ type: 'success', From 47949709eea3d6d2a872f72fc2abd6348cbb30c6 Mon Sep 17 00:00:00 2001 From: Shahzad <shahzad.muhammad@elastic.co> Date: Thu, 3 Nov 2022 16:29:22 +0100 Subject: [PATCH 74/86] [Synthetics] Show to date monitor summary for new monitors (#144456) --- .../hooks/use_earliest_start_data.ts | 35 +++++++++++++ .../monitor_summary/availability_panel.tsx | 7 +-- .../monitor_error_sparklines.tsx | 11 ++-- .../monitor_summary/monitor_errors_count.tsx | 2 +- .../monitor_summary/monitor_summary.tsx | 50 +++++++++++++------ .../public/apps/synthetics/routes.tsx | 10 ++-- 6 files changed, 85 insertions(+), 30 deletions(-) create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_earliest_start_data.ts diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_earliest_start_data.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_earliest_start_data.ts new file mode 100644 index 0000000000000..decb2a5294cc1 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_earliest_start_data.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 { useFetcher } from '@kbn/observability-plugin/public'; +import { useParams } from 'react-router-dom'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useMemo } from 'react'; +import moment from 'moment'; +import { useMonitorQueryId } from './use_monitor_query_id'; + +export const useEarliestStartDate = () => { + const monitorId = useMonitorQueryId(); + + const { monitorId: soId } = useParams<{ monitorId: string }>(); + + const { savedObjects } = useKibana().services; + + const { data: dataFromSO, loading } = useFetcher(async () => { + return savedObjects?.client?.get('synthetics-monitor', soId); + }, [monitorId]); + + return useMemo(() => { + if (dataFromSO?.createdAt) { + const diff = moment(dataFromSO?.createdAt).diff(moment().subtract(30, 'day'), 'days'); + if (diff > 0) { + return { from: dataFromSO?.createdAt, loading }; + } + } + return { from: 'now-30d/d', loading }; + }, [dataFromSO?.createdAt, loading]); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_panel.tsx index 7838bc3dd616a..eaab0dfb92eff 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_panel.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_panel.tsx @@ -8,10 +8,10 @@ import React from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { ReportTypes } from '@kbn/observability-plugin/public'; -import { useParams } from 'react-router-dom'; import { ClientPluginsStart } from '../../../../../plugin'; import { KpiWrapper } from './kpi_wrapper'; +import { useMonitorQueryId } from '../hooks/use_monitor_query_id'; interface AvailabilityPanelprops { from: string; @@ -24,7 +24,8 @@ export const AvailabilityPanel = (props: AvailabilityPanelprops) => { observability: { ExploratoryViewEmbeddable }, }, } = useKibana<ClientPluginsStart>(); - const { monitorId } = useParams<{ monitorId: string }>(); + + const monitorId = useMonitorQueryId(); return ( <KpiWrapper> @@ -37,7 +38,7 @@ export const AvailabilityPanel = (props: AvailabilityPanelprops) => { name: 'Monitor availability', dataType: 'synthetics', selectedMetricField: 'monitor_availability', - reportDefinitions: { config_id: [monitorId] }, + reportDefinitions: { 'monitor.id': [monitorId] }, }, ]} /> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_error_sparklines.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_error_sparklines.tsx index 731b623c65e1b..515d338b4c33a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_error_sparklines.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_error_sparklines.tsx @@ -11,7 +11,11 @@ import { useParams } from 'react-router-dom'; import { useEuiTheme } from '@elastic/eui'; import { ClientPluginsStart } from '../../../../../plugin'; -export const MonitorErrorSparklines = () => { +interface Props { + from: string; + to: string; +} +export const MonitorErrorSparklines = (props: Props) => { const { observability } = useKibana<ClientPluginsStart>().services; const { ExploratoryViewEmbeddable } = observability; @@ -29,10 +33,7 @@ export const MonitorErrorSparklines = () => { attributes={[ { seriesType: 'area', - time: { - from: 'now-30d/d', - to: 'now', - }, + time: props, reportDefinitions: { 'monitor.id': [monitorId] }, dataType: 'synthetics', selectedMetricField: 'state.id', diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx index 30a220f3f10b8..850aec1a9d4a1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx @@ -32,7 +32,7 @@ export const MonitorErrorsCount = (props: MonitorErrorsCountProps) => { attributes={[ { time: props, - reportDefinitions: { config_id: [monitorId] }, + reportDefinitions: { 'monitor.id': [monitorId] }, dataType: 'synthetics', selectedMetricField: 'monitor_errors', name: 'synthetics-series-1', diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx index 1b19d3ca5fdf7..11ff2313c706f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx @@ -13,10 +13,11 @@ import { EuiFlexItem, EuiText, EuiSpacer, - useEuiTheme, + EuiLoadingSpinner, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useEarliestStartDate } from '../hooks/use_earliest_start_data'; import { MonitorErrorSparklines } from './monitor_error_sparklines'; import { DurationSparklines } from './duration_sparklines'; import { MonitorDurationTrend } from './duration_trend'; @@ -30,17 +31,20 @@ import { TestRunsTable } from './test_runs_table'; import { MonitorErrorsCount } from './monitor_errors_count'; export const MonitorSummary = () => { - const { euiTheme } = useEuiTheme(); - - // TODO this range needs to be adjusted dynamically https://github.com/elastic/kibana/issues/143472 - const from = 'now-30d/d'; + const { from, loading } = useEarliestStartDate(); const to = 'now'; + if (loading) { + return <EuiLoadingSpinner size="xl" />; + } + + const dateLabel = from === 'now-30d/d' ? LAST_30_DAYS_LABEL : TO_DATE_LABEL; + return ( <> <EuiFlexGroup gutterSize="m"> <EuiFlexItem grow={1}> - <EuiPanel hasShadow={false} hasBorder> + <EuiPanel hasShadow={false} hasBorder paddingSize="m"> <EuiTitle size="xs"> <h3>{MONITOR_DETAILS_LABEL}</h3> </EuiTitle> @@ -48,10 +52,20 @@ export const MonitorSummary = () => { </EuiPanel> </EuiFlexItem> <EuiFlexItem grow={2}> - <EuiPanel hasShadow={false} hasBorder paddingSize="s" css={{ height: 158 }}> - <EuiTitle size="xs"> - <h3 css={{ margin: euiTheme.size.s, marginBottom: 0 }}>{LAST_30DAYS_LABEL}</h3> - </EuiTitle> + <EuiPanel hasShadow={false} hasBorder paddingSize="m" css={{ height: 158 }}> + <EuiFlexGroup alignItems="center" gutterSize="m"> + <EuiFlexItem grow={false}> + <EuiTitle size="xs"> + <h3>{SUMMARY_LABEL}</h3> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem> + <EuiText color="subdued" size="s"> + {dateLabel} + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> + <EuiFlexGroup gutterSize="s"> <EuiFlexItem> <AvailabilityPanel from={from} to={to} /> @@ -69,15 +83,15 @@ export const MonitorSummary = () => { <MonitorErrorsCount from={from} to={to} /> </EuiFlexItem> <EuiFlexItem> - <MonitorErrorSparklines /> + <MonitorErrorSparklines from={from} to={to} /> </EuiFlexItem> </EuiFlexGroup> </EuiPanel> <EuiSpacer size="m" /> <EuiFlexGroup gutterSize="m"> <EuiFlexItem> - <EuiPanel hasShadow={false} hasBorder> - <EuiFlexGroup alignItems="center"> + <EuiPanel hasShadow={false} paddingSize="m" hasBorder> + <EuiFlexGroup alignItems="center" gutterSize="m"> <EuiFlexItem grow={false}> <EuiTitle size="xs"> <h3>{DURATION_TREND_LABEL}</h3> @@ -85,7 +99,7 @@ export const MonitorSummary = () => { </EuiFlexItem> <EuiFlexItem> <EuiText color="subdued" size="s"> - {LAST_30_DAYS_LABEL} + {dateLabel} </EuiText> </EuiFlexItem> </EuiFlexGroup> @@ -116,8 +130,12 @@ const MONITOR_DETAILS_LABEL = i18n.translate('xpack.synthetics.detailsPanel.moni defaultMessage: 'Monitor details', }); -const LAST_30DAYS_LABEL = i18n.translate('xpack.synthetics.detailsPanel.last30Days', { - defaultMessage: 'Last 30 days', +const SUMMARY_LABEL = i18n.translate('xpack.synthetics.detailsPanel.summary', { + defaultMessage: 'Summary', +}); + +const TO_DATE_LABEL = i18n.translate('xpack.synthetics.detailsPanel.toDate', { + defaultMessage: 'To date', }); const DURATION_TREND_LABEL = i18n.translate('xpack.synthetics.detailsPanel.durationTrends', { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx index ebc7bd197e0f9..be85e9bdcc08d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx @@ -112,7 +112,7 @@ const getRoutes = ( </MonitorDetailsPage> ), dataTestSubj: 'syntheticsMonitorDetailsPage', - pageHeader: getMonitorSummaryHeader(history, syntheticsPath, 'summary'), + pageHeader: getMonitorSummaryHeader(history, syntheticsPath, 'overview'), }, { title: i18n.translate('xpack.synthetics.monitorHistory.title', { @@ -329,7 +329,7 @@ const getRoutes = ( const getMonitorSummaryHeader = ( history: ReturnType<typeof useHistory>, syntheticsPath: string, - selectedTab: 'summary' | 'history' | 'errors' + selectedTab: 'overview' | 'history' | 'errors' ): EuiPageHeaderProps => { // Not a component, but it doesn't matter. Hooks are just functions const match = useRouteMatch<{ monitorId: string }>(MONITOR_ROUTE); // eslint-disable-line react-hooks/rules-of-hooks @@ -367,10 +367,10 @@ const getMonitorSummaryHeader = ( ], tabs: [ { - label: i18n.translate('xpack.synthetics.monitorSummaryTab.title', { - defaultMessage: 'Summary', + label: i18n.translate('xpack.synthetics.monitorOverviewTab.title', { + defaultMessage: 'Overview', }), - isSelected: selectedTab === 'summary', + isSelected: selectedTab === 'overview', href: `${syntheticsPath}${MONITOR_ROUTE.replace(':monitorId?', monitorId)}${search}`, }, { From 360d5d73c2f950cc8aee0a14597cc1dd5bcd1c2a Mon Sep 17 00:00:00 2001 From: Ying Mao <ying.mao@elastic.co> Date: Thu, 3 Nov 2022 11:30:48 -0400 Subject: [PATCH 75/86] [ResponseOps][Alerting] Ping the response-ops team whenever a new rule type is registered (#144424) * Checking registered rule types * Adding comment Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/alerting/server/mocks.ts | 1 + x-pack/plugins/alerting/server/plugin.ts | 3 + .../server/rule_type_registry.mock.ts | 1 + .../server/rule_type_registry.test.ts | 31 ++++++++ .../alerting/server/rule_type_registry.ts | 4 ++ .../fixtures/plugins/alerts/server/plugin.ts | 6 +- .../fixtures/plugins/alerts/server/routes.ts | 20 ++++++ .../alerting/check_registered_rule_types.ts | 72 +++++++++++++++++++ .../spaces_only/tests/alerting/index.ts | 1 + 9 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 x-pack/test/alerting_api_integration/spaces_only/tests/alerting/check_registered_rule_types.ts diff --git a/x-pack/plugins/alerting/server/mocks.ts b/x-pack/plugins/alerting/server/mocks.ts index b5e1a96d300dc..0a99489822d38 100644 --- a/x-pack/plugins/alerting/server/mocks.ts +++ b/x-pack/plugins/alerting/server/mocks.ts @@ -30,6 +30,7 @@ const createSetupMock = () => { const createStartMock = () => { const mock: jest.Mocked<PluginStartContract> = { listTypes: jest.fn(), + getAllTypes: jest.fn(), getAlertingAuthorizationWithRequest: jest.fn(), getRulesClientWithRequest: jest.fn().mockResolvedValue(rulesClientMock.create()), getFrameworkHealth: jest.fn(), diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 7450dcb1a45d0..48d1bfee78e40 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -122,6 +122,8 @@ export interface PluginSetupContract { export interface PluginStartContract { listTypes: RuleTypeRegistry['list']; + getAllTypes: RuleTypeRegistry['getAllTypes']; + getRulesClientWithRequest(request: KibanaRequest): RulesClientApi; getAlertingAuthorizationWithRequest( @@ -464,6 +466,7 @@ export class AlertingPlugin { return { listTypes: ruleTypeRegistry!.list.bind(this.ruleTypeRegistry!), + getAllTypes: ruleTypeRegistry!.getAllTypes.bind(this.ruleTypeRegistry!), getAlertingAuthorizationWithRequest, getRulesClientWithRequest, getFrameworkHealth: async () => diff --git a/x-pack/plugins/alerting/server/rule_type_registry.mock.ts b/x-pack/plugins/alerting/server/rule_type_registry.mock.ts index 0af9d4b2d2342..706484fdd92f6 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.mock.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.mock.ts @@ -16,6 +16,7 @@ const createRuleTypeRegistryMock = () => { register: jest.fn(), get: jest.fn(), list: jest.fn(), + getAllTypes: jest.fn(), ensureRuleTypeEnabled: jest.fn(), }; return mocked; diff --git a/x-pack/plugins/alerting/server/rule_type_registry.test.ts b/x-pack/plugins/alerting/server/rule_type_registry.test.ts index ed52ebf8b04da..5ca22edaa9e04 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.test.ts @@ -611,6 +611,37 @@ describe('Create Lifecycle', () => { }); }); + describe('getAllTypes()', () => { + test('should return empty when nothing is registered', () => { + const registry = new RuleTypeRegistry(ruleTypeRegistryParams); + const result = registry.getAllTypes(); + expect(result).toEqual([]); + }); + + test('should return list of registered type ids', () => { + const registry = new RuleTypeRegistry(ruleTypeRegistryParams); + registry.register({ + id: 'test', + name: 'Test', + actionGroups: [ + { + id: 'testActionGroup', + name: 'Test Action Group', + }, + ], + defaultActionGroupId: 'testActionGroup', + doesSetRecoveryContext: false, + isExportable: true, + ruleTaskTimeout: '20m', + minimumLicenseRequired: 'basic', + executor: jest.fn(), + producer: 'alerts', + }); + const result = registry.getAllTypes(); + expect(result).toEqual(['test']); + }); + }); + describe('ensureRuleTypeEnabled', () => { let ruleTypeRegistry: RuleTypeRegistry; diff --git a/x-pack/plugins/alerting/server/rule_type_registry.ts b/x-pack/plugins/alerting/server/rule_type_registry.ts index 338450746781b..b908f7cb67b87 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.ts @@ -371,6 +371,10 @@ export class RuleTypeRegistry { ) ); } + + public getAllTypes(): string[] { + return [...this.ruleTypes.keys()]; + } } function normalizedActionVariables(actionVariables: RuleType['actionVariables']) { diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts index 2f24648cff73f..57ba67f4a4eb2 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts @@ -8,7 +8,10 @@ import { Plugin, CoreSetup, CoreStart, Logger, PluginInitializerContext } from '@kbn/core/server'; import { firstValueFrom, Subject } from 'rxjs'; import { PluginSetupContract as ActionsPluginSetup } from '@kbn/actions-plugin/server/plugin'; -import { PluginSetupContract as AlertingPluginSetup } from '@kbn/alerting-plugin/server/plugin'; +import { + PluginStartContract as AlertingPluginsStart, + PluginSetupContract as AlertingPluginSetup, +} from '@kbn/alerting-plugin/server/plugin'; import { TaskManagerSetupContract, TaskManagerStartContract, @@ -30,6 +33,7 @@ export interface FixtureSetupDeps { } export interface FixtureStartDeps { + alerting: AlertingPluginsStart; encryptedSavedObjects: EncryptedSavedObjectsPluginStart; security?: SecurityPluginStart; spaces?: SpacesPluginStart; diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts index e69265eb3f054..cc6403613c3b5 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts @@ -32,6 +32,26 @@ export function defineRoutes( { logger }: { logger: Logger } ) { const router = core.http.createRouter(); + router.get( + { + path: '/api/alerts_fixture/registered_rule_types', + validate: {}, + }, + async ( + context: RequestHandlerContext, + req: KibanaRequest<any, any, any, any>, + res: KibanaResponseFactory + ): Promise<IKibanaResponse<any>> => { + try { + const [_, { alerting }] = await core.getStartServices(); + return res.ok({ + body: alerting.getAllTypes(), + }); + } catch (err) { + return res.badRequest({ body: err }); + } + } + ); router.put( { path: '/api/alerts_fixture/{id}/replace_api_key', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/check_registered_rule_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/check_registered_rule_types.ts new file mode 100644 index 0000000000000..84fdcb7bbe95b --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/check_registered_rule_types.ts @@ -0,0 +1,72 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function createRegisteredRuleTypeTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + // This test is intended to fail when new rule types are registered. + // To resolve, add the new rule type ID to this list. This will trigger + // a CODEOWNERS review by Response Ops. + describe('check registered rule types', () => { + it('should list all registered rule types', async () => { + const registeredRuleTypes = await supertest + .get('/api/alerts_fixture/registered_rule_types') + .expect(200) + .then((response) => response.body); + + expect( + registeredRuleTypes.filter((ruleType: string) => !ruleType.startsWith('test.')) + ).to.eql([ + 'example.always-firing', + 'transform_health', + '.index-threshold', + '.geo-containment', + '.es-query', + 'xpack.ml.anomaly_detection_alert', + 'xpack.ml.anomaly_detection_jobs_health', + 'xpack.uptime.alerts.monitorStatus', + 'xpack.uptime.alerts.tlsCertificate', + 'xpack.uptime.alerts.durationAnomaly', + 'xpack.uptime.alerts.tls', + 'siem.eqlRule', + 'siem.savedQueryRule', + 'siem.indicatorRule', + 'siem.mlRule', + 'siem.queryRule', + 'siem.thresholdRule', + 'siem.newTermsRule', + 'siem.notifications', + 'metrics.alert.anomaly', + 'logs.alert.document.count', + 'metrics.alert.inventory.threshold', + 'metrics.alert.threshold', + 'monitoring_alert_cluster_health', + 'monitoring_alert_license_expiration', + 'monitoring_alert_cpu_usage', + 'monitoring_alert_missing_monitoring_data', + 'monitoring_alert_disk_usage', + 'monitoring_alert_thread_pool_search_rejections', + 'monitoring_alert_thread_pool_write_rejections', + 'monitoring_alert_jvm_memory_usage', + 'monitoring_alert_nodes_changed', + 'monitoring_alert_logstash_version_mismatch', + 'monitoring_alert_kibana_version_mismatch', + 'monitoring_alert_elasticsearch_version_mismatch', + 'monitoring_ccr_read_exceptions', + 'monitoring_shard_size', + 'apm.transaction_duration', + 'apm.anomaly', + 'apm.error_rate', + 'apm.transaction_error_rate', + ]); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts index e09cf3121adec..d0981fbc1e93a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts @@ -50,6 +50,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./capped_action_type')); loadTestFile(require.resolve('./scheduled_task_id')); loadTestFile(require.resolve('./run_soon')); + loadTestFile(require.resolve('./check_registered_rule_types')); // Do not place test files here, due to https://github.com/elastic/kibana/issues/123059 // note that this test will destroy existing spaces From 8ae41f2f7f3e386771e9d3d55f0208dbaa4161d5 Mon Sep 17 00:00:00 2001 From: Dominique Clarke <dominique.clarke@elastic.co> Date: Thu, 3 Nov 2022 11:47:13 -0400 Subject: [PATCH 76/86] [Synthetics] add monitor id migration (#143879) * synthetics - add monitor id migration * add id to all monitors * add config id to migration * adjust import * update tests * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * update tests * adjust types * rename id key * rename id key * adjust types and tests * adjust test * rename HEARTBEAT_ID to MONITOR_QUERY_ID * update flaky test Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../migrations/check_registered_types.test.ts | 2 +- .../common/constants/monitor_defaults.ts | 4 +- .../common/constants/monitor_management.ts | 4 +- .../common/formatters/common/formatters.ts | 4 +- .../monitor_management/locations.ts | 2 +- .../monitor_management/monitor_types.ts | 4 +- .../synthetics_private_locations.ts | 1 + .../synthetics/getting_started.journey.ts | 2 + .../monitor_add_edit/form/defaults.test.tsx | 3 + .../monitor_summary/test_runs_table.tsx | 3 +- .../__mocks__/synthetics_store.mock.ts | 1 + .../fleet_package/common/normalizers.ts | 4 +- .../legacy_uptime/lib/requests/get_monitor.ts | 4 +- .../migrations/monitors/8.6.0.test.ts | 216 ++++++++++++++++++ .../migrations/monitors/8.6.0.ts | 35 +++ .../migrations/monitors/index.ts | 12 + .../lib/saved_objects/saved_objects.ts | 6 +- .../lib/saved_objects/synthetics_monitor.ts | 152 ++++++------ .../routes/monitor_cruds/add_monitor.ts | 4 +- .../bulk_cruds/add_monitor_bulk.ts | 2 + .../routes/monitor_cruds/delete_monitor.ts | 7 +- .../routes/monitor_cruds/edit_monitor.ts | 7 +- .../monitor_cruds/monitor_validation.test.ts | 2 + .../synthetics_service/test_now_monitor.ts | 7 +- .../synthetics_service/formatters/common.ts | 4 +- .../normalizers/browser_monitor.ts | 2 +- .../project_monitor_formatter.test.ts | 20 +- .../project_monitor_formatter.ts | 20 +- .../synthetics_service/synthetics_service.ts | 7 +- .../apis/synthetics/add_monitor.ts | 13 +- .../add_monitor_private_location.ts | 21 +- .../apis/synthetics/add_monitor_project.ts | 16 +- .../apis/synthetics/edit_monitor.ts | 32 ++- .../apis/synthetics/get_monitor.ts | 4 +- 34 files changed, 479 insertions(+), 148 deletions(-) create mode 100644 x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.6.0.test.ts create mode 100644 x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.6.0.ts create mode 100644 x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/index.ts diff --git a/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts index ad86d9d3cd980..f66686d53f1cb 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts @@ -130,7 +130,7 @@ describe('checking migration metadata changes on all registered SO types', () => "siem-ui-timeline-pinned-event": "e2697b38751506c7fce6e8b7207a830483dc4283", "space": "c4a0acce1bd4b9cce85154f2a350624a53111c59", "spaces-usage-stats": "922d3235bbf519e3fb3b260e27248b1df8249b79", - "synthetics-monitor": "7bebb6511c70359386f9e20c982db86259c7915c", + "synthetics-monitor": "0c62bf304aebd2134b20627519713819da896eb1", "synthetics-privates-locations": "dd00385f4a27ef062c3e57312eeb3799872fa4af", "tag": "39413f4578cc2128c9a0fda97d0acd1c8862c47a", "task": "ef53d0f070bd54957b8fe22fae3b1ff208913f76", diff --git a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts index 9f40d49d7086b..71db982ef750d 100644 --- a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts +++ b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts @@ -48,9 +48,7 @@ export const DEFAULT_COMMON_FIELDS: CommonFields = { [ConfigKey.MONITOR_SOURCE_TYPE]: SourceType.UI, [ConfigKey.JOURNEY_ID]: '', [ConfigKey.CONFIG_HASH]: '', - - // Deprecated, slated to be removed in a future version - [ConfigKey.ID]: '', + [ConfigKey.MONITOR_QUERY_ID]: '', }; export const DEFAULT_BROWSER_ADVANCED_FIELDS: BrowserAdvancedFields = { diff --git a/x-pack/plugins/synthetics/common/constants/monitor_management.ts b/x-pack/plugins/synthetics/common/constants/monitor_management.ts index bdf5dc6548f20..63f4a64447b3b 100644 --- a/x-pack/plugins/synthetics/common/constants/monitor_management.ts +++ b/x-pack/plugins/synthetics/common/constants/monitor_management.ts @@ -78,9 +78,7 @@ export enum ConfigKey { ZIP_URL_TLS_KEY_PASSPHRASE = 'source.zip_url.ssl.key_passphrase', ZIP_URL_TLS_VERIFICATION_MODE = 'source.zip_url.ssl.verification_mode', ZIP_URL_TLS_VERSION = 'source.zip_url.ssl.supported_protocols', - - // deprecated, slated to be removed in a future version - ID = 'id', + MONITOR_QUERY_ID = 'id', } export const secretKeys = [ diff --git a/x-pack/plugins/synthetics/common/formatters/common/formatters.ts b/x-pack/plugins/synthetics/common/formatters/common/formatters.ts index ab7c3bc9cfa29..794de9a6b62a4 100644 --- a/x-pack/plugins/synthetics/common/formatters/common/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/common/formatters.ts @@ -33,9 +33,7 @@ export const commonFormatters: CommonFormatMap = { [ConfigKey.CUSTOM_HEARTBEAT_ID]: null, [ConfigKey.ORIGINAL_SPACE]: null, [ConfigKey.CONFIG_HASH]: null, - - // Deprecated, slated to be removed in a later release - [ConfigKey.ID]: null, + [ConfigKey.MONITOR_QUERY_ID]: null, }; export const arrayToJsonFormatter = (value: string[] = []) => diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/locations.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/locations.ts index 9f27becbbb023..30ab08b880c20 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/locations.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/locations.ts @@ -82,8 +82,8 @@ export const MonitorServiceLocationCodec = t.intersection([ label: t.string, geo: LocationGeoCodec, url: t.string, - isInvalid: t.boolean, isServiceManaged: t.boolean, + status: t.string, }), ]); diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts index 78bca7a681461..e76ab594e7de7 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts @@ -75,19 +75,19 @@ export const CommonFieldsCodec = t.intersection([ [ConfigKey.APM_SERVICE_NAME]: t.string, [ConfigKey.TAGS]: t.array(t.string), [ConfigKey.LOCATIONS]: t.array(t.union([MonitorServiceLocationCodec, PrivateLocationCodec])), + [ConfigKey.MONITOR_QUERY_ID]: t.string, + [ConfigKey.CONFIG_ID]: t.string, }), t.partial({ [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorTypeCodec, [ConfigKey.TIMEOUT]: t.union([t.string, t.null]), [ConfigKey.REVISION]: t.number, [ConfigKey.MONITOR_SOURCE_TYPE]: SourceTypeCodec, - [ConfigKey.CONFIG_ID]: t.string, [ConfigKey.CONFIG_HASH]: t.string, [ConfigKey.JOURNEY_ID]: t.string, [ConfigKey.PROJECT_ID]: t.string, [ConfigKey.ORIGINAL_SPACE]: t.string, [ConfigKey.CUSTOM_HEARTBEAT_ID]: t.string, - [ConfigKey.ID]: t.string, }), ]); diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_private_locations.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_private_locations.ts index 556f1e56ed102..822b59741f844 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_private_locations.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_private_locations.ts @@ -16,6 +16,7 @@ export const PrivateLocationCodec = t.intersection([ }), t.partial({ isServiceManaged: t.boolean, + isInvalid: t.boolean, /* Empty Lat lon was accidentally saved as an empty string instead of undefined or null * Need a migration to fix */ geo: t.interface({ lat: t.union([t.string, t.number]), lon: t.union([t.string, t.number]) }), diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/getting_started.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/getting_started.journey.ts index 2d9f1f888a936..bffc68911b2f6 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/getting_started.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/getting_started.journey.ts @@ -7,6 +7,7 @@ import { journey, step, expect, before, Page } from '@elastic/synthetics'; import { syntheticsAppPageProvider } from '../../page_objects/synthetics_app'; +import { cleanTestMonitors } from './services/add_monitor'; journey(`Getting Started Page`, async ({ page, params }: { page: Page; params: any }) => { const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl }); @@ -19,6 +20,7 @@ journey(`Getting Started Page`, async ({ page, params }: { page: Page; params: a }; before(async () => { + await cleanTestMonitors(params); await syntheticsApp.waitForLoadingToFinish(); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/defaults.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/defaults.test.tsx index 192c76e414b07..dcb009c809a5e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/defaults.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/defaults.test.tsx @@ -72,10 +72,13 @@ describe('defaults', () => { type: 'browser', 'url.port': null, urls: '', + id: '', + config_id: '', } as SyntheticsMonitor; it('correctly formats monitor type to form type', () => { expect(formatDefaultFormValues(monitorValues)).toEqual({ + ...monitorValues, __ui: { script_source: { file_name: '', diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx index 891d1694de4bc..d9b1beb45088a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx @@ -139,7 +139,8 @@ export const TestRunsTable = ({ paginable = true, from, to }: TestRunsTableProps }, ]; - const historyIdParam = monitor?.[ConfigKey.CUSTOM_HEARTBEAT_ID] ?? monitor?.[ConfigKey.ID]; + const historyIdParam = + monitor?.[ConfigKey.CUSTOM_HEARTBEAT_ID] ?? monitor?.[ConfigKey.MONITOR_QUERY_ID]; return ( <EuiPanel hasShadow={false} hasBorder css={{ minHeight: 200 }}> <EuiFlexGroup alignItems="center" gutterSize="s"> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts index 3e213879ca66f..c591b90ac5440 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts @@ -360,6 +360,7 @@ function getMonitorDetailsMockSlice() { }, syntheticsMonitor: { id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', + config_id: '4afd3980-0b72-11ed-9c10-b57918ea89d6', type: DataStream.BROWSER, enabled: true, schedule: { unit: ScheduleUnit.MINUTES, number: '10' }, diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts index f4678b5569872..79e50bf8a9d96 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts @@ -97,7 +97,5 @@ export const commonNormalizers: CommonNormalizerMap = { [ConfigKey.CUSTOM_HEARTBEAT_ID]: getCommonNormalizer(ConfigKey.CUSTOM_HEARTBEAT_ID), [ConfigKey.ORIGINAL_SPACE]: getCommonNormalizer(ConfigKey.ORIGINAL_SPACE), [ConfigKey.CONFIG_HASH]: getCommonNormalizer(ConfigKey.CONFIG_HASH), - - // Deprecated, slated to be removed in a future release - [ConfigKey.ID]: getCommonNormalizer(ConfigKey.ID), + [ConfigKey.MONITOR_QUERY_ID]: getCommonNormalizer(ConfigKey.MONITOR_QUERY_ID), }; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_monitor.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_monitor.ts index 9b92463f78641..c085e5c7d7158 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_monitor.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/get_monitor.ts @@ -12,7 +12,7 @@ import { EncryptedSyntheticsMonitor, SyntheticsMonitor, } from '../../../../common/runtime_types'; -import { syntheticsMonitor, syntheticsMonitorType } from '../saved_objects/synthetics_monitor'; +import { syntheticsMonitorType } from '../saved_objects/synthetics_monitor'; import { normalizeSecrets } from '../../../synthetics_service/utils/secrets'; export const getSyntheticsMonitor = async ({ @@ -32,7 +32,7 @@ export const getSyntheticsMonitor = async ({ const decryptedMonitor = await encryptedSavedObjectsClient.getDecryptedAsInternalUser<SyntheticsMonitorWithSecrets>( - syntheticsMonitor.name, + syntheticsMonitorType, monitorId, { namespace: encryptedMonitor.namespaces?.[0], diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.6.0.test.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.6.0.test.ts new file mode 100644 index 0000000000000..68e3416174c8f --- /dev/null +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.6.0.test.ts @@ -0,0 +1,216 @@ +/* + * 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 { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; +import { migration860 } from './8.6.0'; +import { migrationMocks } from '@kbn/core/server/mocks'; +import { + ConfigKey, + LocationStatus, + SyntheticsMonitorWithSecrets, +} from '../../../../../../common/runtime_types'; + +const context = migrationMocks.createContext(); +const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); + +const monitor850UI = { + id: 'ac38d021-515b-425c-9f92-e6212dadef9a', + type: 'synthetics-monitor', + namespaces: ['default'], + updated_at: '2022-10-24T14:17:01.875Z', + version: 'WzgzOCwyQr==', + attributes: { + type: 'http', + form_monitor_type: 'http', + enabled: true, + schedule: { number: '3', unit: 'm' }, + 'service.name': '', + config_id: '', + tags: [], + timeout: '16', + name: 'Dominique Clarke', + locations: [{ id: 'us_central', isServiceManaged: true }], + namespace: 'default', + origin: 'ui', + journey_id: '', + id: '', + __ui: { is_tls_enabled: false, is_zip_url_tls_enabled: false }, + urls: 'https://elastic.co', + max_redirects: '0', + 'url.port': null, + proxy_url: '', + 'response.include_body': 'on_error', + 'response.include_headers': true, + 'check.response.status': [], + 'check.request.method': 'GET', + 'ssl.certificate_authorities': '', + 'ssl.certificate': '', + 'ssl.verification_mode': 'full', + 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], + revision: 1, + secrets: + '{"password":"","check.request.body":{"type":"text","value":""},"check.request.headers":{},"check.response.body.negative":[],"check.response.body.positive":[],"check.response.headers":{},"ssl.key":"","ssl.key_passphrase":"","username":""}', + } as SyntheticsMonitorWithSecrets, + references: [], + coreMigrationVersion: '8.5.0', +}; + +const monitor850Project = { + id: '3ab5c90f-aa7f-4370-ada2-b559191398f0', + type: 'synthetics-monitor', + namespaces: ['default'], + updated_at: '2022-10-24T15:39:41.510Z', + version: 'WzE2OSwxXQ==', + attributes: { + type: 'browser', + form_monitor_type: 'multistep', + enabled: true, + schedule: { number: '3', unit: 'm' }, + 'service.name': '', + config_id: '', + tags: [], + timeout: null, + name: 'a', + locations: [ + { + id: 'us_central', + label: 'North America - US Central', + geo: { lat: 41.25, lon: -95.86 }, + url: 'https://us-central.synthetics.elastic.dev', + isServiceManaged: true, + status: LocationStatus.GA, + isInvalid: false, + }, + ], + namespace: 'default', + origin: 'project', + journey_id: 'a', + id: '', + project_id: 'test2', + playwright_options: '{"ignoreHTTPSErrors":true,"headless":true}', + __ui: { + script_source: { is_generated_script: false, file_name: '' }, + is_zip_url_tls_enabled: false, + }, + 'url.port': null, + 'source.zip_url.url': '', + 'source.zip_url.folder': '', + 'source.zip_url.proxy_url': '', + playwright_text_assertion: '', + urls: '', + screenshots: 'on', + 'filter_journeys.match': 'a', + 'filter_journeys.tags': [], + ignore_https_errors: false, + 'throttling.is_enabled': true, + 'throttling.download_speed': '10', + 'throttling.upload_speed': '5', + 'throttling.latency': '10', + 'throttling.config': '10d/5u/10l', + 'ssl.certificate_authorities': '', + 'ssl.certificate': '', + 'ssl.verification_mode': 'full', + 'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], + original_space: 'default', + custom_heartbeat_id: 'a-test2-default', + revision: 1, + secrets: + '{"params":"{\\"url\\":\\"https://elastic.github.io/synthetics-demo/\\"}","source.inline.script":"","source.project.content":"UEsDBBQACAAIAAAAIQAAAAAAAAAAAAAAAAAXAAAAam91cm5leXMvb25lLmpvdXJuZXkudHNVkL1uwzAMhHc/BeFJAQyrLdAlQYouXbp3KjqwMhMrlUVVohMYgd+9in+AdCHED6c7kloDpkSy1R+JYtINd9bb356Mw/hDuqGzToOXlsSapIWSPOkT99HTkDR7qpemllTYLnAUuMLCKkhCoYKOvRWOMMIhcgflKzlM2e/OudwVZ4xgIqHQ+/wd9qA8drSB/QtcC1h96j6RuvUA0kYWcdYftzATgIYv3jE2W3h8qBbWh5k8r8DlGG+Gm2YiY67jZpfrMvuUXIG6QsBjfgSM2KWsWYeBaTlVOuy9aQFDcNagWPZllW86eAPqTgyAF7QyudVHFlazY91HN+Wu+bc67orPErNP+V1+1QeOb2hapXDy+3ejzLL+D1BLBwgqc7lrFgEAAMYBAABQSwECLQMUAAgACAAAACEAKnO5axYBAADGAQAAFwAAAAAAAAAAACAApIEAAAAAam91cm5leXMvb25lLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBFAAAAWwEAAAAA","source.zip_url.username":"","source.zip_url.password":"","synthetics_args":[],"ssl.key":"","ssl.key_passphrase":""}', + } as SyntheticsMonitorWithSecrets, + references: [], + coreMigrationVersion: '8.5.0', +}; + +describe('Case migrations v8.5.0 -> v8.6.0', () => { + beforeEach(() => { + jest.resetAllMocks(); + encryptedSavedObjectsSetup.createMigration.mockImplementation(({ migration }) => migration); + }); + + it('UI monitors - adds saved object id to the id field', () => { + expect(migration860(encryptedSavedObjectsSetup)(monitor850UI, context)).toEqual({ + ...monitor850UI, + attributes: { + ...monitor850UI.attributes, + [ConfigKey.MONITOR_QUERY_ID]: monitor850UI.id, + [ConfigKey.CONFIG_ID]: monitor850UI.id, + }, + }); + }); + + it('project monitors - adds custom heartbeat id to id field', () => { + expect(migration860(encryptedSavedObjectsSetup)(monitor850Project, context)).toEqual({ + ...monitor850Project, + attributes: { + ...monitor850Project.attributes, + [ConfigKey.MONITOR_QUERY_ID]: monitor850Project.attributes[ConfigKey.CUSTOM_HEARTBEAT_ID], + [ConfigKey.CONFIG_ID]: monitor850Project.id, + }, + }); + }); + + it('handles empty custom heartbeat id string', () => { + const attributes = { ...monitor850UI.attributes, [ConfigKey.CUSTOM_HEARTBEAT_ID]: '' }; + expect( + migration860(encryptedSavedObjectsSetup)( + { + ...monitor850UI, + attributes, + }, + context + ) + ).toEqual({ + ...monitor850UI, + attributes: { + ...attributes, + [ConfigKey.MONITOR_QUERY_ID]: monitor850UI.id, + [ConfigKey.CONFIG_ID]: monitor850UI.id, + }, + }); + }); + + it('handles null custom heartbeat id string', () => { + const attributes = { ...monitor850UI.attributes, [ConfigKey.CUSTOM_HEARTBEAT_ID]: null }; + + expect( + migration860(encryptedSavedObjectsSetup)( + { + ...monitor850UI, + // @ts-ignore + attributes, + }, + context + ) + ).toEqual({ + ...monitor850UI, + attributes: { + ...attributes, + [ConfigKey.MONITOR_QUERY_ID]: monitor850UI.id, + [ConfigKey.CONFIG_ID]: monitor850UI.id, + }, + }); + }); + + it('handles undefined custom heartbeat id string', () => { + const attributes = { ...monitor850UI.attributes, [ConfigKey.CUSTOM_HEARTBEAT_ID]: undefined }; + expect( + migration860(encryptedSavedObjectsSetup)( + { + ...monitor850UI, + attributes, + }, + context + ) + ).toEqual({ + ...monitor850UI, + attributes: { + ...attributes, + [ConfigKey.MONITOR_QUERY_ID]: monitor850UI.id, + [ConfigKey.CONFIG_ID]: monitor850UI.id, + }, + }); + }); +}); diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.6.0.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.6.0.ts new file mode 100644 index 0000000000000..d4bea87a45bb4 --- /dev/null +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/8.6.0.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 { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { SavedObjectUnsanitizedDoc } from '@kbn/core/server'; +import { ConfigKey, SyntheticsMonitorWithSecrets } from '../../../../../../common/runtime_types'; + +export const migration860 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => { + return encryptedSavedObjects.createMigration< + SyntheticsMonitorWithSecrets, + SyntheticsMonitorWithSecrets + >({ + isMigrationNeededPredicate: function shouldBeMigrated( + doc + ): doc is SavedObjectUnsanitizedDoc<SyntheticsMonitorWithSecrets> { + return true; + }, + migration: ( + doc: SavedObjectUnsanitizedDoc<SyntheticsMonitorWithSecrets> + ): SavedObjectUnsanitizedDoc<SyntheticsMonitorWithSecrets> => { + const { attributes, id } = doc; + return { + ...doc, + attributes: { + ...attributes, + [ConfigKey.MONITOR_QUERY_ID]: attributes[ConfigKey.CUSTOM_HEARTBEAT_ID] || id, + [ConfigKey.CONFIG_ID]: id, + }, + }; + }, + }); +}; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/index.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/index.ts new file mode 100644 index 0000000000000..bb26f51a603e6 --- /dev/null +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/migrations/monitors/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { migration860 } from './8.6.0'; + +export const monitorMigrations = { + '8.6.0': migration860, +}; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/saved_objects.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/saved_objects.ts index 0a06d1926a855..24d3cc03b6ca2 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/saved_objects.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/saved_objects.ts @@ -15,7 +15,7 @@ import { DynamicSettings } from '../../../../common/runtime_types'; import { UMSavedObjectsQueryFn } from '../adapters'; import { UptimeConfig } from '../../../../common/config'; import { settingsObjectId, umDynamicSettings } from './uptime_settings'; -import { syntheticsMonitor } from './synthetics_monitor'; +import { syntheticsMonitorType, getSyntheticsMonitorSavedObjectType } from './synthetics_monitor'; import { syntheticsServiceApiKey } from './service_api_key'; export const registerUptimeSavedObjects = ( @@ -25,7 +25,7 @@ export const registerUptimeSavedObjects = ( savedObjectsService.registerType(umDynamicSettings); savedObjectsService.registerType(privateLocationsSavedObject); - savedObjectsService.registerType(syntheticsMonitor); + savedObjectsService.registerType(getSyntheticsMonitorSavedObjectType(encryptedSavedObjects)); savedObjectsService.registerType(syntheticsServiceApiKey); encryptedSavedObjects.registerType({ @@ -34,7 +34,7 @@ export const registerUptimeSavedObjects = ( }); encryptedSavedObjects.registerType({ - type: syntheticsMonitor.name, + type: syntheticsMonitorType, attributesToEncrypt: new Set([ 'secrets', /* adding secretKeys to the list of attributes to encrypt ensures diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts index 94a8a0085e170..72715091c06de 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/synthetics_monitor.ts @@ -4,95 +4,103 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; import { SavedObjectsType } from '@kbn/core/server'; import { i18n } from '@kbn/i18n'; +import { monitorMigrations } from './migrations/monitors'; export const syntheticsMonitorType = 'synthetics-monitor'; -export const syntheticsMonitor: SavedObjectsType = { - name: syntheticsMonitorType, - hidden: false, - namespaceType: 'single', - mappings: { - dynamic: false, - properties: { - name: { - type: 'text', - fields: { - keyword: { - type: 'keyword', - ignore_above: 256, - normalizer: 'lowercase', +export const getSyntheticsMonitorSavedObjectType = ( + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup +): SavedObjectsType => { + return { + name: syntheticsMonitorType, + hidden: false, + namespaceType: 'single', + migrations: { + '8.6.0': monitorMigrations['8.6.0'](encryptedSavedObjects), + }, + mappings: { + dynamic: false, + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + normalizer: 'lowercase', + }, }, }, - }, - type: { - type: 'text', - fields: { - keyword: { - type: 'keyword', - ignore_above: 256, + type: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, }, }, - }, - urls: { - type: 'text', - fields: { - keyword: { - type: 'keyword', - ignore_above: 256, + urls: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, }, }, - }, - journey_id: { - type: 'keyword', - }, - project_id: { - type: 'keyword', - }, - origin: { - type: 'keyword', - }, - hash: { - type: 'keyword', - }, - locations: { - properties: { - id: { - type: 'keyword', - ignore_above: 256, - fields: { - text: { - type: 'text', + journey_id: { + type: 'keyword', + }, + project_id: { + type: 'keyword', + }, + origin: { + type: 'keyword', + }, + hash: { + type: 'keyword', + }, + locations: { + properties: { + id: { + type: 'keyword', + ignore_above: 256, + fields: { + text: { + type: 'text', + }, }, }, }, }, - }, - custom_heartbeat_id: { - type: 'keyword', - }, - tags: { - type: 'keyword', - }, - schedule: { - properties: { - number: { - type: 'integer', + custom_heartbeat_id: { + type: 'keyword', + }, + tags: { + type: 'keyword', + }, + schedule: { + properties: { + number: { + type: 'integer', + }, }, }, }, }, - }, - management: { - importableAndExportable: false, - icon: 'uptimeApp', - getTitle: (savedObject) => - savedObject.attributes.name + - ' - ' + - i18n.translate('xpack.synthetics.syntheticsMonitors', { - defaultMessage: 'Uptime - Monitor', - }), - }, + management: { + importableAndExportable: false, + icon: 'uptimeApp', + getTitle: (savedObject) => + savedObject.attributes.name + + ' - ' + + i18n.translate('xpack.synthetics.syntheticsMonitors', { + defaultMessage: 'Uptime - Monitor', + }), + }, + }; }; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts index 6fcc91ec96ffb..138224a5a11ea 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts @@ -118,7 +118,7 @@ export const createNewSavedObjectMonitor = async ({ savedObjectsClient, normalizedMonitor, }: { - id?: string; + id: string; savedObjectsClient: SavedObjectsClientContract; normalizedMonitor: SyntheticsMonitor; }) => { @@ -126,6 +126,8 @@ export const createNewSavedObjectMonitor = async ({ syntheticsMonitorType, formatSecrets({ ...normalizedMonitor, + [ConfigKey.MONITOR_QUERY_ID]: normalizedMonitor[ConfigKey.CUSTOM_HEARTBEAT_ID] || id, + [ConfigKey.CONFIG_ID]: id, revision: 1, }), id diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/bulk_cruds/add_monitor_bulk.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/bulk_cruds/add_monitor_bulk.ts index 8a7799e14f0cb..1d8ed3dc9eb67 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/bulk_cruds/add_monitor_bulk.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/bulk_cruds/add_monitor_bulk.ts @@ -35,6 +35,8 @@ export const createNewSavedObjectMonitorBulk = async ({ type: syntheticsMonitorType, attributes: formatSecrets({ ...monitor, + [ConfigKey.MONITOR_QUERY_ID]: monitor[ConfigKey.CUSTOM_HEARTBEAT_ID] || id, + [ConfigKey.CONFIG_ID]: id, revision: 1, }), })); diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts index 751e8c5d51ec9..831f4b2c341e8 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts @@ -19,10 +19,7 @@ import { } from '../../../common/runtime_types'; import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes/types'; import { API_URLS } from '../../../common/constants'; -import { - syntheticsMonitorType, - syntheticsMonitor, -} from '../../legacy_uptime/lib/saved_objects/synthetics_monitor'; +import { syntheticsMonitorType } from '../../legacy_uptime/lib/saved_objects/synthetics_monitor'; import { getMonitorNotFoundResponse } from '../synthetics_service/service_errors'; import { sendTelemetryEvents, @@ -100,7 +97,7 @@ export const deleteMonitor = async ({ const monitor = await encryptedSavedObjectsClient.getDecryptedAsInternalUser<SyntheticsMonitorWithSecrets>( - syntheticsMonitor.name, + syntheticsMonitorType, monitorId, { namespace: encryptedMonitor.namespaces?.[0], diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts index 0413992780716..086ce90744ca9 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts @@ -24,10 +24,7 @@ import { } from '../../../common/runtime_types'; import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes/types'; import { API_URLS } from '../../../common/constants'; -import { - syntheticsMonitorType, - syntheticsMonitor, -} from '../../legacy_uptime/lib/saved_objects/synthetics_monitor'; +import { syntheticsMonitorType } from '../../legacy_uptime/lib/saved_objects/synthetics_monitor'; import { validateMonitor } from './monitor_validation'; import { getMonitorNotFoundResponse } from '../synthetics_service/service_errors'; import { @@ -72,7 +69,7 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ( * on the monitor list table. We do not decrypt monitors in bulk for the monitor list table */ const decryptedPreviousMonitor = await encryptedSavedObjectsClient.getDecryptedAsInternalUser<SyntheticsMonitorWithSecrets>( - syntheticsMonitor.name, + syntheticsMonitorType, monitorId, { namespace: previousMonitor.namespaces?.[0], diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.test.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.test.ts index 76bda0f4a2f12..b927e1a37e21a 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.test.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/monitor_validation.test.ts @@ -58,6 +58,7 @@ describe('validateMonitor', () => { [ConfigKey.MONITOR_TYPE]: DataStream.ICMP, [ConfigKey.NAME]: 'test-monitor-name', [ConfigKey.CONFIG_ID]: 'test-monitor-id', + [ConfigKey.MONITOR_QUERY_ID]: '', [ConfigKey.ENABLED]: true, [ConfigKey.TAGS]: testTags, [ConfigKey.SCHEDULE]: testSchedule, @@ -445,6 +446,7 @@ function getJsonPayload() { ' ],' + ' "name": "test-monitor-name",' + ' "config_id": "test-monitor-id",' + + ' "id": "test-id",' + ' "namespace": "testnamespace",' + ' "locations": [{' + ' "id": "eu-west-01",' + diff --git a/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts b/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts index 0a2c3f599ff62..1c6d6548c575d 100644 --- a/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts @@ -14,10 +14,7 @@ import { } from '../../../common/runtime_types'; import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes/types'; import { API_URLS } from '../../../common/constants'; -import { - syntheticsMonitor, - syntheticsMonitorType, -} from '../../legacy_uptime/lib/saved_objects/synthetics_monitor'; +import { syntheticsMonitorType } from '../../legacy_uptime/lib/saved_objects/synthetics_monitor'; import { formatHeartbeatRequest } from '../../synthetics_service/formatters/format_configs'; import { normalizeSecrets } from '../../synthetics_service/utils/secrets'; @@ -45,7 +42,7 @@ export const testNowMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ const monitorWithSecrets = await encryptedClient.getDecryptedAsInternalUser<SyntheticsMonitorWithSecrets>( - syntheticsMonitor.name, + syntheticsMonitorType, monitorId ); const normalizedMonitor = normalizeSecrets(monitorWithSecrets); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts index f88f23ca38037..5db57ee5fdeb8 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts @@ -35,9 +35,7 @@ export const commonFormatters: CommonFormatMap = { [ConfigKey.CUSTOM_HEARTBEAT_ID]: null, [ConfigKey.ORIGINAL_SPACE]: null, [ConfigKey.CONFIG_HASH]: null, - - // Deprecated, slated to be removed in a later releae - [ConfigKey.ID]: null, + [ConfigKey.MONITOR_QUERY_ID]: null, }; export const arrayFormatter = (value: string[] = []) => (value.length ? value : null); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts index 8eba2cd2651db..778a801e3a00c 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.ts @@ -54,7 +54,7 @@ export const getNormalizeBrowserFields = ({ monitor.throttling?.upload || defaultFields[ConfigKey.UPLOAD_SPEED] }`, [ConfigKey.IS_THROTTLING_ENABLED]: - Boolean(monitor.throttling) || defaultFields[ConfigKey.IS_THROTTLING_ENABLED], + Boolean(monitor.throttling) ?? defaultFields[ConfigKey.IS_THROTTLING_ENABLED], [ConfigKey.LATENCY]: `${monitor.throttling?.latency || defaultFields[ConfigKey.LATENCY]}`, [ConfigKey.IGNORE_HTTPS_ERRORS]: monitor.ignoreHTTPSErrors || defaultFields[ConfigKey.IGNORE_HTTPS_ERRORS], diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts index 5892f53344d48..2566c6a358fba 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts @@ -10,7 +10,7 @@ import { INSUFFICIENT_FLEET_PERMISSIONS, ProjectMonitorFormatter, } from './project_monitor_formatter'; -import { DataStream, LocationStatus } from '../../../common/runtime_types'; +import { ConfigKey, DataStream, LocationStatus } from '../../../common/runtime_types'; import { DEFAULT_FIELDS } from '../../../common/constants/monitor_defaults'; import { times } from 'lodash'; import { SyntheticsService } from '../synthetics_service'; @@ -468,8 +468,22 @@ describe('ProjectMonitorFormatter', () => { expect(soClient.bulkCreate).toHaveBeenCalledWith( expect.arrayContaining([ - expect.objectContaining(soData[0]), - expect.objectContaining(soData[1]), + expect.objectContaining({ + ...soData[0], + attributes: { + ...soData[0].attributes, + [ConfigKey.MONITOR_QUERY_ID]: expect.any(String), + [ConfigKey.CONFIG_ID]: expect.any(String), + }, + }), + expect.objectContaining({ + ...soData[1], + attributes: { + ...soData[1].attributes, + [ConfigKey.MONITOR_QUERY_ID]: expect.any(String), + [ConfigKey.CONFIG_ID]: expect.any(String), + }, + }), ]) ); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts index b7407042b0272..a430d25bcb407 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts @@ -5,7 +5,7 @@ * 2.0. */ import type { Subject } from 'rxjs'; -import { isEqual } from 'lodash'; +import { isEqual, omit } from 'lodash'; import pMap from 'p-map'; import { KibanaRequest } from '@kbn/core/server'; import { @@ -30,10 +30,7 @@ import { MonitorFields, PrivateLocation, } from '../../../common/runtime_types'; -import { - syntheticsMonitorType, - syntheticsMonitor, -} from '../../legacy_uptime/lib/saved_objects/synthetics_monitor'; +import { syntheticsMonitorType } from '../../legacy_uptime/lib/saved_objects/synthetics_monitor'; import type { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; import { formatSecrets, normalizeSecrets } from '../utils/secrets'; import { @@ -347,7 +344,7 @@ export class ProjectMonitorFormatter { monitors, async (monitor) => this.encryptedSavedObjectsClient.getDecryptedAsInternalUser<SyntheticsMonitorWithSecrets>( - syntheticsMonitor.name, + syntheticsMonitorType, monitor.id, { namespace: monitor.namespaces?.[0], @@ -378,10 +375,13 @@ export class ProjectMonitorFormatter { const previousMonitor = monitors[i].previousMonitor; const normalizedMonitor = monitors[i].monitor; - const { - attributes: { [ConfigKey.REVISION]: _, ...normalizedPreviousMonitorAttributes }, - } = normalizeSecrets(decryptedPreviousMonitor); - const hasMonitorBeenEdited = !isEqual(normalizedMonitor, normalizedPreviousMonitorAttributes); + const keysToOmit = [ConfigKey.REVISION, ConfigKey.MONITOR_QUERY_ID, ConfigKey.CONFIG_ID]; + const { attributes: normalizedPreviousMonitorAttributes } = + normalizeSecrets(decryptedPreviousMonitor); + const hasMonitorBeenEdited = !isEqual( + omit(normalizedMonitor, keysToOmit), + omit(normalizedPreviousMonitorAttributes, keysToOmit) + ); if (hasMonitorBeenEdited) { const monitorWithRevision = formatSecrets({ diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index 5107e0bf26743..1634f3f18a67f 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -21,10 +21,7 @@ import { UptimeServerSetup } from '../legacy_uptime/lib/adapters'; import { installSyntheticsIndexTemplates } from '../routes/synthetics_service/install_index_templates'; import { SyntheticsServiceApiKey } from '../../common/runtime_types/synthetics_service_api_key'; import { getAPIKeyForSyntheticsService } from './get_api_key'; -import { - syntheticsMonitorType, - syntheticsMonitor, -} from '../legacy_uptime/lib/saved_objects/synthetics_monitor'; +import { syntheticsMonitorType } from '../legacy_uptime/lib/saved_objects/synthetics_monitor'; import { getEsHosts } from './get_es_hosts'; import { ServiceConfig } from '../../common/config'; import { ServiceAPIClient } from './service_api_client'; @@ -422,7 +419,7 @@ export class SyntheticsService { new Promise((resolve) => { encryptedClient .getDecryptedAsInternalUser<SyntheticsMonitorWithSecrets>( - syntheticsMonitor.name, + syntheticsMonitorType, monitor.id, { namespace: monitor.namespaces?.[0], diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor.ts index 2f49d9484ecb7..e736a5cf79306 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor.ts @@ -49,7 +49,16 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .send(newMonitor); - expect(apiResponse.body.attributes).eql(omit(newMonitor, secretKeys)); + expect(apiResponse.body.attributes).eql( + omit( + { + ...newMonitor, + [ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id, + [ConfigKey.CONFIG_ID]: apiResponse.body.id, + }, + secretKeys + ) + ); }); it('returns bad request if payload is invalid for HTTP monitor', async () => { @@ -107,6 +116,8 @@ export default function ({ getService }: FtrProviderContext) { { ...DEFAULT_FIELDS[DataStream.HTTP], ...newMonitor, + [ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id, + [ConfigKey.CONFIG_ID]: apiResponse.body.id, revision: 1, }, secretKeys diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts index e335e1b7f6cf4..e4835644bd935 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts @@ -100,7 +100,16 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true') .send(newMonitor); - expect(apiResponse.body.attributes).eql(omit(newMonitor, secretKeys)); + expect(apiResponse.body.attributes).eql( + omit( + { + ...newMonitor, + [ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id, + [ConfigKey.CONFIG_ID]: apiResponse.body.id, + }, + secretKeys + ) + ); newMonitorId = apiResponse.body.id; }); @@ -266,7 +275,15 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(apiResponse.body.attributes).eql( - omit({ ...monitor, [ConfigKey.NAMESPACE]: formatKibanaNamespace(SPACE_ID) }, secretKeys) + omit( + { + ...monitor, + [ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id, + [ConfigKey.CONFIG_ID]: apiResponse.body.id, + [ConfigKey.NAMESPACE]: formatKibanaNamespace(SPACE_ID), + }, + secretKeys + ) ); monitorId = apiResponse.body.id; diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts index 98e96384a71b9..65e9aa759f09c 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project.ts @@ -134,7 +134,7 @@ export default function ({ getService }: FtrProviderContext) { is_generated_script: false, }, }, - config_id: '', + config_id: decryptedCreatedMonitor.body.id, custom_heartbeat_id: `${journeyId}-test-suite-default`, enabled: true, 'filter_journeys.match': 'check if title is present', @@ -196,7 +196,7 @@ export default function ({ getService }: FtrProviderContext) { type: 'browser', 'url.port': null, urls: '', - id: '', + id: `${journeyId}-test-suite-default`, hash: 'ekrjelkjrelkjre', }); } @@ -254,7 +254,7 @@ export default function ({ getService }: FtrProviderContext) { }, 'check.request.method': 'POST', 'check.response.status': ['200'], - config_id: '', + config_id: decryptedCreatedMonitor.body.id, custom_heartbeat_id: `${journeyId}-test-suite-default`, 'check.response.body.negative': [], 'check.response.body.positive': ['Saved', 'saved'], @@ -311,7 +311,7 @@ export default function ({ getService }: FtrProviderContext) { type: 'http', urls: Array.isArray(monitor.urls) ? monitor.urls?.[0] : monitor.urls, 'url.port': null, - id: '', + id: `${journeyId}-test-suite-default`, hash: 'ekrjelkjrelkjre', }); } @@ -367,7 +367,7 @@ export default function ({ getService }: FtrProviderContext) { __ui: { is_tls_enabled: false, }, - config_id: '', + config_id: decryptedCreatedMonitor.body.id, custom_heartbeat_id: `${journeyId}-test-suite-default`, 'check.receive': '', 'check.send': '', @@ -413,7 +413,7 @@ export default function ({ getService }: FtrProviderContext) { hosts: Array.isArray(monitor.hosts) ? monitor.hosts?.[0] : monitor.hosts, 'url.port': null, urls: '', - id: '', + id: `${journeyId}-test-suite-default`, hash: 'ekrjelkjrelkjre', }); } @@ -466,7 +466,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(decryptedCreatedMonitor.body.attributes).to.eql({ - config_id: '', + config_id: decryptedCreatedMonitor.body.id, custom_heartbeat_id: `${journeyId}-test-suite-default`, enabled: true, form_monitor_type: 'icmp', @@ -516,7 +516,7 @@ export default function ({ getService }: FtrProviderContext) { monitor.wait?.slice(-1) === 's' ? monitor.wait?.slice(0, -1) : `${parseInt(monitor.wait?.slice(0, -1) || '1', 10) * 60}`, - id: '', + id: `${journeyId}-test-suite-default`, hash: 'ekrjelkjrelkjre', }); } diff --git a/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts index e5ac722c73054..6d9b1db7a753f 100644 --- a/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/edit_monitor.ts @@ -61,7 +61,16 @@ export default function ({ getService }: FtrProviderContext) { newMonitor as MonitorFields ); - expect(savedMonitor).eql(omit(newMonitor, secretKeys)); + expect(savedMonitor).eql( + omit( + { + ...newMonitor, + [ConfigKey.MONITOR_QUERY_ID]: monitorId, + [ConfigKey.CONFIG_ID]: monitorId, + }, + secretKeys + ) + ); const updates: Partial<HTTPFields> = { [ConfigKey.URLS]: 'https://modified-host.com', @@ -116,7 +125,16 @@ export default function ({ getService }: FtrProviderContext) { newMonitor as MonitorFields ); - expect(savedMonitor).eql(omit(newMonitor, secretKeys)); + expect(savedMonitor).eql( + omit( + { + ...newMonitor, + [ConfigKey.MONITOR_QUERY_ID]: monitorId, + [ConfigKey.CONFIG_ID]: monitorId, + }, + secretKeys + ) + ); const updates: Partial<HTTPFields> = { [ConfigKey.URLS]: 'https://modified-host.com', @@ -147,7 +165,6 @@ export default function ({ getService }: FtrProviderContext) { const modifiedMonitor = omit( { - ...newMonitor, ...updates, [ConfigKey.METADATA]: { ...newMonitor[ConfigKey.METADATA], @@ -164,7 +181,14 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); expect(editResponse.body.attributes).eql( - omit({ ...modifiedMonitor, revision: 2 }, secretKeys) + omit( + { + ...savedMonitor, + ...modifiedMonitor, + revision: 2, + }, + secretKeys + ) ); expect(editResponse.body.attributes).not.to.have.keys('unknownkey'); }); diff --git a/x-pack/test/api_integration/apis/synthetics/get_monitor.ts b/x-pack/test/api_integration/apis/synthetics/get_monitor.ts index 7c0a45cf583ae..5394ca64545e6 100644 --- a/x-pack/test/api_integration/apis/synthetics/get_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/get_monitor.ts @@ -6,7 +6,7 @@ */ import { SimpleSavedObject } from '@kbn/core/public'; -import { MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types'; +import { ConfigKey, MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types'; import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -97,6 +97,8 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.body.attributes).eql({ ...monitors[0], + [ConfigKey.MONITOR_QUERY_ID]: apiResponse.body.id, + [ConfigKey.CONFIG_ID]: apiResponse.body.id, revision: 1, }); }); From aa31c35f436b4dc429e8f8ec9dd9f637a5c511bd Mon Sep 17 00:00:00 2001 From: Dmitry Tomashevich <39378793+dimaanj@users.noreply.github.com> Date: Thu, 3 Nov 2022 18:58:22 +0300 Subject: [PATCH 77/86] [Discover] Enable context docs navigation from embeddable using adhoc data views (#142126) * [Discover] add locators * [Discover] improve context, discover, single views navigations * [Discover] fix some ci failures * [Discover] fix some functional tests, add ability to go back to saved search from embeddable via context view * [Discover] remove logs * [Discover] improve browser history management on update of adhoc data view from context view * [Discover] remove not relevant tests * [Discover] add functionals * [Discover] fix some issues * [Discover] update tests * [Discover] fix remaining tests * [Discover] remove redundant function * [Discover] remove field editing in context * [Discover] remove redundant edit field from context tests * [Discover] apply suggestions * [Discover] try to fix flaky test * [Discover] fix unit tests, try to fix flaky test * [Discover] apply suggestions * [Discover] apply suggestions * [Discover] simplify the code * Update src/plugins/discover/public/application/doc/single_doc_route.tsx Co-authored-by: Matthias Wilhelm <ankertal@gmail.com> * Update src/plugins/discover/public/components/discover_grid/discover_grid.tsx Co-authored-by: Matthias Wilhelm <ankertal@gmail.com> * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * [Discover] apply suggestions * [Discover] suggest solution for new tab navigation and copy link ebility * [Discover] fix ci, apply suggestions * [Discover] apply suggestions, fix bug * Update src/plugins/discover/public/hooks/use_navigation_props.test.tsx Co-authored-by: Davis McPhee <davismcphee@hotmail.com> * [Discover] add href test Co-authored-by: Matthias Wilhelm <ankertal@gmail.com> Co-authored-by: Davis McPhee <davismcphee@hotmail.com> --- .../discover/public/__mocks__/services.ts | 7 + .../application/context/context_app.test.tsx | 10 + .../application/context/context_app.tsx | 58 ++-- .../context/context_app_content.tsx | 3 - .../application/context/context_app_route.tsx | 53 ++-- .../context/hooks/use_context_app_state.ts | 2 +- .../context/services/locator.test.ts | 103 +++++++ .../application/context/services/locator.ts | 75 +++++ .../context/utils/replace_context_location.ts | 14 + .../public/application/discover_router.tsx | 12 +- .../application/doc/components/doc.test.tsx | 3 + .../public/application/doc/components/doc.tsx | 14 +- .../public/application/doc/locator.test.ts | 34 +++ .../public/application/doc/locator.ts | 56 ++++ .../application/doc/single_doc_route.tsx | 69 ++--- .../discover/public/application/index.tsx | 10 +- .../components/layout/discover_documents.tsx | 1 + .../main/discover_main_route.test.tsx | 6 +- .../application/main/discover_main_route.tsx | 19 +- src/plugins/discover/public/build_services.ts | 10 +- .../discover_grid/discover_grid.tsx | 13 + .../discover_grid_flyout.test.tsx | 57 ++-- .../discover_grid/discover_grid_flyout.tsx | 24 +- .../doc_table/components/table_row.tsx | 52 ++-- .../components/table_row_details.tsx | 66 +++-- .../doc_table/create_doc_table_embeddable.tsx | 1 + .../doc_table/doc_table_wrapper.tsx | 25 +- .../embeddable/saved_search_embeddable.tsx | 6 + .../public/hooks/use_data_view.test.tsx | 75 +++-- .../discover/public/hooks/use_data_view.tsx | 24 +- .../hooks/use_navigation_props.test.tsx | 149 +++++----- .../public/hooks/use_navigation_props.tsx | 264 ++++++++++-------- src/plugins/discover/public/locator.ts | 7 +- src/plugins/discover/public/plugin.tsx | 42 ++- .../public/utils/with_query_params.test.tsx | 37 --- .../public/utils/with_query_params.tsx | 39 --- .../apps/context/_context_navigation.ts | 58 +++- .../apps/context/_discover_navigation.ts | 12 +- .../context/classic/_discover_navigation.ts | 14 +- .../apps/discover/group2/_adhoc_data_views.ts | 46 ++- .../discover/group2/_data_grid_context.ts | 15 +- test/functional/services/data_grid.ts | 1 + test/functional/services/toasts.ts | 6 +- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 46 files changed, 1075 insertions(+), 520 deletions(-) create mode 100644 src/plugins/discover/public/application/context/services/locator.test.ts create mode 100644 src/plugins/discover/public/application/context/services/locator.ts create mode 100644 src/plugins/discover/public/application/context/utils/replace_context_location.ts create mode 100644 src/plugins/discover/public/application/doc/locator.test.ts create mode 100644 src/plugins/discover/public/application/doc/locator.ts delete mode 100644 src/plugins/discover/public/utils/with_query_params.test.tsx delete mode 100644 src/plugins/discover/public/utils/with_query_params.tsx diff --git a/src/plugins/discover/public/__mocks__/services.ts b/src/plugins/discover/public/__mocks__/services.ts index 97de63d231a46..eaae356c03c1a 100644 --- a/src/plugins/discover/public/__mocks__/services.ts +++ b/src/plugins/discover/public/__mocks__/services.ts @@ -126,4 +126,11 @@ export const discoverServiceMock = { savedObjectsTagging: {}, dataViews: dataViewsMock, timefilter: { createFilter: jest.fn() }, + locator: { + useUrl: jest.fn(() => ''), + navigate: jest.fn(), + getUrl: jest.fn(() => Promise.resolve('')), + }, + contextLocator: { getRedirectUrl: jest.fn(() => '') }, + singleDocLocator: { getRedirectUrl: jest.fn(() => '') }, } as unknown as DiscoverServices; diff --git a/src/plugins/discover/public/application/context/context_app.test.tsx b/src/plugins/discover/public/application/context/context_app.test.tsx index 5b5fa21f12483..5b05195227290 100644 --- a/src/plugins/discover/public/application/context/context_app.test.tsx +++ b/src/plugins/discover/public/application/context/context_app.test.tsx @@ -22,6 +22,7 @@ import { themeServiceMock } from '@kbn/core/public/mocks'; import { LocalStorageMock } from '../../__mocks__/local_storage_mock'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import type { HistoryLocationState } from '../../build_services'; const mockFilterManager = createFilterManagerMock(); const mockNavigationPlugin = { @@ -64,11 +65,20 @@ describe('ContextApp test', () => { filterManager: mockFilterManager, uiSettings: uiSettingsMock, storage: new LocalStorageMock({}), + chrome: { setBreadcrumbs: jest.fn() }, + locator: { + useUrl: jest.fn(() => ''), + navigate: jest.fn(), + getUrl: jest.fn(() => Promise.resolve('mock-url')), + }, + contextLocator: { getRedirectUrl: jest.fn(() => '') }, + singleDocLocator: { getRedirectUrl: jest.fn(() => '') }, } as unknown as DiscoverServices; const defaultProps = { dataView: dataViewMock, anchorId: 'mocked_anchor_id', + locationState: {} as HistoryLocationState, }; const topNavProps = { diff --git a/src/plugins/discover/public/application/context/context_app.tsx b/src/plugins/discover/public/application/context/context_app.tsx index 3146e634c4575..7fc2d60fe1040 100644 --- a/src/plugins/discover/public/application/context/context_app.tsx +++ b/src/plugins/discover/public/application/context/context_app.tsx @@ -33,34 +33,61 @@ import { ContextAppContent } from './context_app_content'; import { SurrDocType } from './services/context'; import { DocViewFilterFn } from '../../services/doc_views/doc_views_types'; import { useDiscoverServices } from '../../hooks/use_discover_services'; +import { getRootBreadcrumbs } from '../../utils/breadcrumbs'; const ContextAppContentMemoized = memo(ContextAppContent); export interface ContextAppProps { dataView: DataView; anchorId: string; + referrer?: string; } -export const ContextApp = ({ dataView, anchorId }: ContextAppProps) => { +export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) => { const services = useDiscoverServices(); - const { uiSettings, capabilities, dataViews, navigation, filterManager, core } = services; + const { locator, uiSettings, capabilities, dataViews, navigation, filterManager, core } = + services; const isLegacy = useMemo(() => uiSettings.get(DOC_TABLE_LEGACY), [uiSettings]); const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]); - useExecutionContext(core.executionContext, { - type: 'application', - page: 'context', - id: dataView.id || '', - }); - /** * Context app state */ - const { appState, globalState, setAppState } = useContextAppState({ services, dataView }); + const { appState, globalState, stateContainer } = useContextAppState({ + services, + dataView, + }); const prevAppState = useRef<AppState>(); const prevGlobalState = useRef<GlobalState>({ filters: [] }); + const { columns, onAddColumn, onRemoveColumn, onSetColumns } = useColumns({ + capabilities, + config: uiSettings, + dataView, + dataViews, + state: appState, + useNewFieldsApi, + setAppState: stateContainer.setAppState, + }); + + useEffect(() => { + services.chrome.setBreadcrumbs([ + ...getRootBreadcrumbs(referrer), + { + text: i18n.translate('discover.context.breadcrumb', { + defaultMessage: 'Surrounding documents', + }), + }, + ]); + }, [locator, referrer, services.chrome]); + + useExecutionContext(core.executionContext, { + type: 'application', + page: 'context', + id: dataView.id || '', + }); + /** * Context fetched state */ @@ -71,6 +98,7 @@ export const ContextApp = ({ dataView, anchorId }: ContextAppProps) => { appState, useNewFieldsApi, }); + /** * Reset state when anchor changes */ @@ -110,15 +138,6 @@ export const ContextApp = ({ dataView, anchorId }: ContextAppProps) => { fetchedState.anchor.id, ]); - const { columns, onAddColumn, onRemoveColumn, onSetColumns } = useColumns({ - capabilities, - config: uiSettings, - dataView, - dataViews, - state: appState, - useNewFieldsApi, - setAppState, - }); const rows = useMemo( () => [ ...(fetchedState.predecessors || []), @@ -201,7 +220,7 @@ export const ContextApp = ({ dataView, anchorId }: ContextAppProps) => { onSetColumns={onSetColumns} predecessorCount={appState.predecessorCount} successorCount={appState.successorCount} - setAppState={setAppState} + setAppState={stateContainer.setAppState} addFilter={addFilter as DocViewFilterFn} rows={rows} predecessors={fetchedState.predecessors} @@ -209,7 +228,6 @@ export const ContextApp = ({ dataView, anchorId }: ContextAppProps) => { anchorStatus={fetchedState.anchorStatus.value} predecessorsStatus={fetchedState.predecessorsStatus.value} successorsStatus={fetchedState.successorsStatus.value} - onFieldEdited={fetchAllRows} /> </EuiPageContent> </EuiPage> diff --git a/src/plugins/discover/public/application/context/context_app_content.tsx b/src/plugins/discover/public/application/context/context_app_content.tsx index 7471f15092c3b..4a24363525998 100644 --- a/src/plugins/discover/public/application/context/context_app_content.tsx +++ b/src/plugins/discover/public/application/context/context_app_content.tsx @@ -42,7 +42,6 @@ export interface ContextAppContentProps { isLegacy: boolean; setAppState: (newState: Partial<AppState>) => void; addFilter: DocViewFilterFn; - onFieldEdited: () => void; } const controlColumnIds = ['openDetails']; @@ -73,7 +72,6 @@ export function ContextAppContent({ isLegacy, setAppState, addFilter, - onFieldEdited, }: ContextAppContentProps) { const { uiSettings: config } = useDiscoverServices(); @@ -162,7 +160,6 @@ export function ContextAppContent({ onAddColumn={onAddColumn} onRemoveColumn={onRemoveColumn} onSetColumns={onSetColumns} - onFieldEdited={onFieldEdited} /> </div> )} diff --git a/src/plugins/discover/public/application/context/context_app_route.tsx b/src/plugins/discover/public/application/context/context_app_route.tsx index e256f026cd336..b1e8e39cb3e71 100644 --- a/src/plugins/discover/public/application/context/context_app_route.tsx +++ b/src/plugins/discover/public/application/context/context_app_route.tsx @@ -5,17 +5,16 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { useParams } from 'react-router-dom'; -import { i18n } from '@kbn/i18n'; import { EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import type { ScopedHistory } from '@kbn/core/public'; import { ContextApp } from './context_app'; -import { getRootBreadcrumbs } from '../../utils/breadcrumbs'; import { LoadingIndicator } from '../../components/common/loading_indicator'; +import { getScopedHistory } from '../../kibana_services'; import { useDataView } from '../../hooks/use_data_view'; -import { useMainRouteBreadcrumb } from '../../hooks/use_navigation_props'; -import { useDiscoverServices } from '../../hooks/use_discover_services'; +import type { ContextHistoryLocationState } from './services/locator'; export interface ContextUrlParams { dataViewId: string; @@ -23,26 +22,34 @@ export interface ContextUrlParams { } export function ContextAppRoute() { - const services = useDiscoverServices(); - const { chrome } = services; - - const { dataViewId, id } = useParams<ContextUrlParams>(); - const anchorId = decodeURIComponent(id); - const usedDataViewId = decodeURIComponent(dataViewId); - const breadcrumb = useMainRouteBreadcrumb(); + const locationState = useMemo( + () => getScopedHistory().location.state as ContextHistoryLocationState | undefined, + [] + ); + /** + * Updates history state when gets undefined. + * Should be removed once url state will be deleted from context page. + */ useEffect(() => { - chrome.setBreadcrumbs([ - ...getRootBreadcrumbs(breadcrumb), - { - text: i18n.translate('discover.context.breadcrumb', { - defaultMessage: 'Surrounding documents', - }), - }, - ]); - }, [chrome, breadcrumb]); + const scopedHistory = getScopedHistory() as ScopedHistory< + ContextHistoryLocationState | undefined + >; + const unlisten = scopedHistory.listen((location) => { + const currentState = location.state; + if (!currentState?.referrer && locationState) { + const newLocation = { ...location, state: { ...currentState, ...locationState } }; + scopedHistory.replace(newLocation); + } + }); + return () => unlisten(); + }, [locationState]); + + const { dataViewId: encodedDataViewId, id } = useParams<ContextUrlParams>(); + const dataViewId = decodeURIComponent(encodedDataViewId); + const anchorId = decodeURIComponent(id); - const { dataView, error } = useDataView(services.dataViews, usedDataViewId); + const { dataView, error } = useDataView({ index: locationState?.dataViewSpec || dataViewId }); if (error) { return ( @@ -70,5 +77,5 @@ export function ContextAppRoute() { return <LoadingIndicator />; } - return <ContextApp anchorId={anchorId} dataView={dataView} />; + return <ContextApp anchorId={anchorId} dataView={dataView} referrer={locationState?.referrer} />; } diff --git a/src/plugins/discover/public/application/context/hooks/use_context_app_state.ts b/src/plugins/discover/public/application/context/hooks/use_context_app_state.ts index db6ba390f67a7..ac3a0879d5379 100644 --- a/src/plugins/discover/public/application/context/hooks/use_context_app_state.ts +++ b/src/plugins/discover/public/application/context/hooks/use_context_app_state.ts @@ -67,6 +67,6 @@ export function useContextAppState({ return { appState, globalState, - setAppState: stateContainer.setAppState, + stateContainer, }; } diff --git a/src/plugins/discover/public/application/context/services/locator.test.ts b/src/plugins/discover/public/application/context/services/locator.test.ts new file mode 100644 index 0000000000000..da545e3fc3b56 --- /dev/null +++ b/src/plugins/discover/public/application/context/services/locator.test.ts @@ -0,0 +1,103 @@ +/* + * 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 { getStatesFromKbnUrl } from '@kbn/kibana-utils-plugin/public'; +import { DiscoverContextAppLocatorDefinition } from './locator'; + +const dataViewId: string = 'c367b774-a4c2-11ea-bb37-0242ac130002'; + +interface SetupParams { + useHash?: boolean; +} + +const setup = async ({ useHash = false }: SetupParams = {}) => { + const locator = new DiscoverContextAppLocatorDefinition({ useHash }); + return { locator }; +}; + +const appStateParams = { + columns: ['mock-column'], + filters: [ + { + meta: { + disabled: false, + negate: false, + type: 'phrase', + key: 'mock-key', + value: 'mock-value', + params: { query: 'mock-value' }, + index: dataViewId, + }, + query: { match_phrase: { 'mock-key': 'mock-value' } }, + }, + ], +}; + +describe('Discover context url generator', () => { + test('can create basic link to context', async () => { + const { locator } = await setup(); + const { app, path } = await locator.getLocation({ + index: dataViewId, + rowId: 'mock-row-id', + referrer: 'mock-referrer', + }); + const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g']); + + expect(app).toBe('discover'); + expect(_a).toEqual({}); + expect(_g).toEqual({}); + }); + + test('should fill history state for context view', async () => { + const { locator } = await setup(); + + const { path, state } = await locator.getLocation({ + index: dataViewId, + rowId: 'mock-row-id', + ...appStateParams, + referrer: 'mock-referrer', + }); + + const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g']); + expect(path).toMatchInlineSnapshot( + `"#/context/c367b774-a4c2-11ea-bb37-0242ac130002/mock-row-id?_g=(filters:!())&_a=(columns:!(mock-column),filters:!((meta:(disabled:!f,index:c367b774-a4c2-11ea-bb37-0242ac130002,key:mock-key,negate:!f,params:(query:mock-value),type:phrase,value:mock-value),query:(match_phrase:(mock-key:mock-value)))))"` + ); + expect(state).toEqual({ referrer: 'mock-referrer' }); + expect(_a).toEqual(appStateParams); + expect(_g).toEqual({ filters: [] }); + }); + + test('when useHash set to false, sets data view ID in the generated URL', async () => { + const { locator } = await setup(); + const { path } = await locator.getLocation({ + index: dataViewId, + rowId: 'mock-row-id', + ...appStateParams, + referrer: 'mock-referrer', + }); + + expect(path.indexOf(dataViewId) > -1).toBe(true); + expect(path).toMatchInlineSnapshot( + `"#/context/c367b774-a4c2-11ea-bb37-0242ac130002/mock-row-id?_g=(filters:!())&_a=(columns:!(mock-column),filters:!((meta:(disabled:!f,index:c367b774-a4c2-11ea-bb37-0242ac130002,key:mock-key,negate:!f,params:(query:mock-value),type:phrase,value:mock-value),query:(match_phrase:(mock-key:mock-value)))))"` + ); + }); + + test('when useHash set to true, does not set data view ID in the generated URL', async () => { + const { locator } = await setup({ useHash: true }); + const { path } = await locator.getLocation({ + index: dataViewId, + rowId: 'mock-row-id', + ...appStateParams, + referrer: 'mock-referrer', + }); + + expect(path).toMatchInlineSnapshot( + `"#/context/c367b774-a4c2-11ea-bb37-0242ac130002/mock-row-id?_g=h@3a04046&_a=h@9ad8c77"` + ); + }); +}); diff --git a/src/plugins/discover/public/application/context/services/locator.ts b/src/plugins/discover/public/application/context/services/locator.ts new file mode 100644 index 0000000000000..29bb01e1af58a --- /dev/null +++ b/src/plugins/discover/public/application/context/services/locator.ts @@ -0,0 +1,75 @@ +/* + * 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 { SerializableRecord } from '@kbn/utility-types'; +import type { Filter } from '@kbn/es-query'; +import type { GlobalQueryStateFromUrl } from '@kbn/data-plugin/public'; +import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; +import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; +import type { DataViewSpec } from '@kbn/data-views-plugin/public'; +export const DISCOVER_CONTEXT_APP_LOCATOR = 'DISCOVER_CONTEXT_APP_LOCATOR'; + +export interface DiscoverContextAppLocatorParams extends SerializableRecord { + index: string | DataViewSpec; // spec in case of adhoc data view + rowId: string; + columns?: string[]; + filters?: Filter[]; + referrer: string; // discover main view url +} + +export type DiscoverContextAppLocator = LocatorPublic<DiscoverContextAppLocatorParams>; + +export interface DiscoverContextAppLocatorDependencies { + useHash: boolean; +} + +export interface ContextHistoryLocationState { + referrer: string; + dataViewSpec?: DataViewSpec; +} + +export class DiscoverContextAppLocatorDefinition + implements LocatorDefinition<DiscoverContextAppLocatorParams> +{ + public readonly id = DISCOVER_CONTEXT_APP_LOCATOR; + + constructor(protected readonly deps: DiscoverContextAppLocatorDependencies) {} + + public readonly getLocation = async (params: DiscoverContextAppLocatorParams) => { + const useHash = this.deps.useHash; + const { index, rowId, columns, filters, referrer } = params; + + const appState: { filters?: Filter[]; columns?: string[] } = {}; + const queryState: GlobalQueryStateFromUrl = {}; + + const { isFilterPinned } = await import('@kbn/es-query'); + if (filters && filters.length) appState.filters = filters?.filter((f) => !isFilterPinned(f)); + if (columns) appState.columns = columns; + + if (filters && filters.length) queryState.filters = filters?.filter((f) => isFilterPinned(f)); + + let dataViewId; + const state: ContextHistoryLocationState = { referrer }; + if (typeof index === 'object') { + state.dataViewSpec = index; + dataViewId = index.id!; + } else { + dataViewId = index; + } + + let path = `#/context/${dataViewId}/${rowId}`; + path = setStateToKbnUrl<GlobalQueryStateFromUrl>('_g', queryState, { useHash }, path); + path = setStateToKbnUrl('_a', appState, { useHash }, path); + + return { + app: 'discover', + path, + state, + }; + }; +} diff --git a/src/plugins/discover/public/application/context/utils/replace_context_location.ts b/src/plugins/discover/public/application/context/utils/replace_context_location.ts new file mode 100644 index 0000000000000..21e18ea76e016 --- /dev/null +++ b/src/plugins/discover/public/application/context/utils/replace_context_location.ts @@ -0,0 +1,14 @@ +/* + * 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 { DiscoverContextAppLocator, DiscoverContextAppLocatorParams } from '../services/locator'; + +export const replaceContextLocation = ( + contextLocator: DiscoverContextAppLocator, + params: DiscoverContextAppLocatorParams +) => contextLocator.navigate(params, { replace: true }); diff --git a/src/plugins/discover/public/application/discover_router.tsx b/src/plugins/discover/public/application/discover_router.tsx index 1fe847d17beb7..9cac8a4f1f50a 100644 --- a/src/plugins/discover/public/application/discover_router.tsx +++ b/src/plugins/discover/public/application/discover_router.tsx @@ -17,14 +17,8 @@ import { DiscoverMainRoute } from './main'; import { NotFoundRoute } from './not_found'; import { DiscoverServices } from '../build_services'; import { ViewAlertRoute } from './view_alert'; -import { HistoryLocationState } from '../locator'; -export const discoverRouter = ( - services: DiscoverServices, - history: History, - isDev: boolean, - historyLocationState?: HistoryLocationState -) => ( +export const discoverRouter = (services: DiscoverServices, history: History, isDev: boolean) => ( <KibanaContextProvider services={services}> <EuiErrorBoundary> <Router history={history} data-test-subj="discover-react-router"> @@ -45,10 +39,10 @@ export const discoverRouter = ( <ViewAlertRoute /> </Route> <Route path="/view/:id"> - <DiscoverMainRoute isDev={isDev} historyLocationState={historyLocationState} /> + <DiscoverMainRoute isDev={isDev} /> </Route> <Route path="/" exact> - <DiscoverMainRoute isDev={isDev} historyLocationState={historyLocationState} /> + <DiscoverMainRoute isDev={isDev} /> </Route> <NotFoundRoute /> </Switch> diff --git a/src/plugins/discover/public/application/doc/components/doc.test.tsx b/src/plugins/discover/public/application/doc/components/doc.test.tsx index a2faa7778c275..1a808bb05db7f 100644 --- a/src/plugins/discover/public/application/doc/components/doc.test.tsx +++ b/src/plugins/discover/public/application/doc/components/doc.test.tsx @@ -57,6 +57,7 @@ async function mountDoc(update = false) { id: '1', index: 'index1', dataView: dataViewMock, + referrer: 'mock-referrer', } as DocProps; let comp!: ReactWrapper; const services = { @@ -82,6 +83,8 @@ async function mountDoc(update = false) { } }, }, + locator: { getUrl: jest.fn(() => Promise.resolve('mock-url')) }, + chrome: { setBreadcrumbs: jest.fn() }, }; await act(async () => { comp = mountWithIntl( diff --git a/src/plugins/discover/public/application/doc/components/doc.tsx b/src/plugins/discover/public/application/doc/components/doc.tsx index 8ec6133b73328..c55a200ee47e5 100644 --- a/src/plugins/discover/public/application/doc/components/doc.tsx +++ b/src/plugins/discover/public/application/doc/components/doc.tsx @@ -17,6 +17,7 @@ import { } from '@elastic/eui'; import type { DataView } from '@kbn/data-views-plugin/public'; import { i18n } from '@kbn/i18n'; +import { getRootBreadcrumbs } from '../../../utils/breadcrumbs'; import { DocViewer } from '../../../services/doc_views/components/doc_viewer'; import { ElasticRequestState } from '../types'; import { useEsDocSearch } from '../../../hooks/use_es_doc_search'; @@ -39,12 +40,16 @@ export interface DocProps { * If set, will always request source, regardless of the global `fieldsFromSource` setting */ requestSource?: boolean; + /** + * Discover main view url + */ + referrer?: string; } export function Doc(props: DocProps) { const { dataView } = props; const [reqState, hit] = useEsDocSearch(props); - const { docLinks } = useDiscoverServices(); + const { locator, chrome, docLinks } = useDiscoverServices(); const indexExistsLink = docLinks.links.apis.indexExists; const singleDocTitle = useRef<HTMLHeadingElement>(null); @@ -52,6 +57,13 @@ export function Doc(props: DocProps) { singleDocTitle.current?.focus(); }, []); + useEffect(() => { + chrome.setBreadcrumbs([ + ...getRootBreadcrumbs(props.referrer), + { text: `${props.index}#${props.id}` }, + ]); + }, [chrome, props.referrer, props.index, props.id, dataView, locator]); + return ( <EuiPage> <h1 diff --git a/src/plugins/discover/public/application/doc/locator.test.ts b/src/plugins/discover/public/application/doc/locator.test.ts new file mode 100644 index 0000000000000..d84eff8638122 --- /dev/null +++ b/src/plugins/discover/public/application/doc/locator.test.ts @@ -0,0 +1,34 @@ +/* + * 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 { DiscoverSingleDocLocatorDefinition } from './locator'; + +const dataViewId: string = 'c367b774-a4c2-11ea-bb37-0242ac130002'; + +const setup = () => { + const locator = new DiscoverSingleDocLocatorDefinition(); + return { locator }; +}; + +describe('Discover single doc url generator', () => { + test('should create init single doc page', async () => { + const { locator } = setup(); + const { app, path, state } = await locator.getLocation({ + index: dataViewId, + rowId: 'mock-row-id', + rowIndex: 'mock-row-index', + referrer: 'mock-referrer', + }); + + expect(app).toBe('discover'); + expect(state).toEqual({ referrer: 'mock-referrer' }); + expect(path).toMatchInlineSnapshot( + `"#/doc/c367b774-a4c2-11ea-bb37-0242ac130002/mock-row-index?id=mock-row-id"` + ); + }); +}); diff --git a/src/plugins/discover/public/application/doc/locator.ts b/src/plugins/discover/public/application/doc/locator.ts new file mode 100644 index 0000000000000..f3ee0c2942070 --- /dev/null +++ b/src/plugins/discover/public/application/doc/locator.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 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 { SerializableRecord } from '@kbn/utility-types'; +import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; +import { DataViewSpec } from '@kbn/data-views-plugin/public'; + +export const DISCOVER_SINGLE_DOC_LOCATOR = 'DISCOVER_SINGLE_DOC_LOCATOR'; + +export interface DiscoverSingleDocLocatorParams extends SerializableRecord { + index: string | DataViewSpec; // spec in case of adhoc data view + rowId: string; + rowIndex: string; + referrer: string; // discover main view url +} + +export type DiscoverSingleDocLocator = LocatorPublic<DiscoverSingleDocLocatorParams>; + +export interface DocHistoryLocationState { + referrer: string; + dataViewSpec?: DataViewSpec; +} + +export class DiscoverSingleDocLocatorDefinition + implements LocatorDefinition<DiscoverSingleDocLocatorParams> +{ + public readonly id = DISCOVER_SINGLE_DOC_LOCATOR; + + constructor() {} + + public readonly getLocation = async (params: DiscoverSingleDocLocatorParams) => { + const { index, rowId, rowIndex, referrer } = params; + + let dataViewId; + const state: DocHistoryLocationState = { referrer }; + if (typeof index === 'object') { + state.dataViewSpec = index; + dataViewId = index.id!; + } else { + dataViewId = index; + } + + const path = `#/doc/${dataViewId}/${rowIndex}?id=${rowId}`; + + return { + app: 'discover', + path, + state, + }; + }; +} diff --git a/src/plugins/discover/public/application/doc/single_doc_route.tsx b/src/plugins/discover/public/application/doc/single_doc_route.tsx index 99474923b8c9a..07edc52c40687 100644 --- a/src/plugins/discover/public/application/doc/single_doc_route.tsx +++ b/src/plugins/discover/public/application/doc/single_doc_route.tsx @@ -5,37 +5,37 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { useEffect } from 'react'; -import { useParams } from 'react-router-dom'; +import React, { useEffect, useMemo } from 'react'; +import { useLocation, useParams } from 'react-router-dom'; import { EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; -import { getRootBreadcrumbs } from '../../utils/breadcrumbs'; +import { i18n } from '@kbn/i18n'; import { LoadingIndicator } from '../../components/common/loading_indicator'; -import { useDataView } from '../../hooks/use_data_view'; -import { withQueryParams } from '../../utils/with_query_params'; -import { useMainRouteBreadcrumb } from '../../hooks/use_navigation_props'; import { Doc } from './components/doc'; import { useDiscoverServices } from '../../hooks/use_discover_services'; - -export interface SingleDocRouteProps { - /** - * Document id - */ - id: string; -} +import { getScopedHistory } from '../../kibana_services'; +import { DiscoverError } from '../../components/common/error_alert'; +import { useDataView } from '../../hooks/use_data_view'; +import { DocHistoryLocationState } from './locator'; export interface DocUrlParams { dataViewId: string; index: string; } -const SingleDoc = ({ id }: SingleDocRouteProps) => { - const services = useDiscoverServices(); - const { chrome, timefilter, core } = services; - +export const SingleDocRoute = () => { + const { timefilter, core } = useDiscoverServices(); + const { search } = useLocation(); const { dataViewId, index } = useParams<DocUrlParams>(); - const breadcrumb = useMainRouteBreadcrumb(); + + const query = useMemo(() => new URLSearchParams(search), [search]); + const id = query.get('id'); + + const locationState = useMemo( + () => getScopedHistory().location.state as DocHistoryLocationState | undefined, + [] + ); useExecutionContext(core.executionContext, { type: 'application', @@ -43,21 +43,14 @@ const SingleDoc = ({ id }: SingleDocRouteProps) => { id: dataViewId, }); - useEffect(() => { - chrome.setBreadcrumbs([ - ...getRootBreadcrumbs(breadcrumb), - { - text: `${index}#${id}`, - }, - ]); - }, [chrome, index, id, breadcrumb]); - useEffect(() => { timefilter.disableAutoRefreshSelector(); timefilter.disableTimeRangeSelector(); - }); + }, [timefilter]); - const { dataView, error } = useDataView(services.dataViews, dataViewId); + const { dataView, error } = useDataView({ + index: locationState?.dataViewSpec || decodeURIComponent(dataViewId), + }); if (error) { return ( @@ -85,11 +78,23 @@ const SingleDoc = ({ id }: SingleDocRouteProps) => { return <LoadingIndicator />; } + if (!id) { + return ( + <DiscoverError + error={ + new Error( + i18n.translate('discover.discoverError.missingIdParamError', { + defaultMessage: 'URL query string is missing id.', + }) + ) + } + /> + ); + } + return ( <div className="app-container"> - <Doc id={id} index={index} dataView={dataView} /> + <Doc id={id} index={index} dataView={dataView} referrer={locationState?.referrer} /> </div> ); }; - -export const SingleDocRoute = withQueryParams(SingleDoc, ['id']); diff --git a/src/plugins/discover/public/application/index.tsx b/src/plugins/discover/public/application/index.tsx index 2542a639be71f..5ae2ed76923b5 100644 --- a/src/plugins/discover/public/application/index.tsx +++ b/src/plugins/discover/public/application/index.tsx @@ -9,14 +9,8 @@ import { i18n } from '@kbn/i18n'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; import { discoverRouter } from './discover_router'; import { DiscoverServices } from '../build_services'; -import { HistoryLocationState } from '../locator'; -export const renderApp = ( - element: HTMLElement, - services: DiscoverServices, - isDev: boolean, - historyLocationState?: HistoryLocationState -) => { +export const renderApp = (element: HTMLElement, services: DiscoverServices, isDev: boolean) => { const { history: getHistory, capabilities, chrome, data, core } = services; const history = getHistory(); @@ -32,7 +26,7 @@ export const renderApp = ( }); } const unmount = toMountPoint( - wrapWithTheme(discoverRouter(services, history, isDev, historyLocationState), core.theme.theme$) + wrapWithTheme(discoverRouter(services, history, isDev), core.theme.theme$) )(element); return () => { diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx index 2aacf598e58c4..1ebde4c4a6aac 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx @@ -215,6 +215,7 @@ function DiscoverDocumentsComponent({ rowsPerPageState={state.rowsPerPage} onUpdateRowsPerPage={onUpdateRowsPerPage} onFieldEdited={onFieldEdited} + savedSearchId={savedSearch.id} /> </div> </> diff --git a/src/plugins/discover/public/application/main/discover_main_route.test.tsx b/src/plugins/discover/public/application/main/discover_main_route.test.tsx index aead62c038e47..7ee1c7e4b134f 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.test.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.test.tsx @@ -8,13 +8,13 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { waitFor } from '@testing-library/react'; -import { setHeaderActionMenuMounter } from '../../kibana_services'; +import { setHeaderActionMenuMounter, setScopedHistory } from '../../kibana_services'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { discoverServiceMock } from '../../__mocks__/services'; import { DiscoverMainRoute } from './discover_main_route'; import { MemoryRouter } from 'react-router-dom'; import { dataViewMock } from '../../__mocks__/data_view'; -import { SavedObject } from '@kbn/core/public'; +import { SavedObject, ScopedHistory } from '@kbn/core/public'; import { DataViewSavedObjectAttrs } from '@kbn/data-views-plugin/common'; import { DiscoverMainApp } from './discover_main_app'; import { SearchSource } from '@kbn/data-plugin/common'; @@ -26,6 +26,8 @@ jest.mock('./discover_main_app', () => { }; }); +setScopedHistory({ location: {} } as ScopedHistory); + describe('DiscoverMainRoute', () => { test('renders the main app when hasESData=true & hasUserDataView=true ', async () => { const component = mountComponent(true, true); diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index 4d3dccaa68001..e6b2ca1d197bb 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { useEffect, useState, memo, useCallback } from 'react'; +import React, { useEffect, useState, memo, useCallback, useMemo } from 'react'; import { useParams, useHistory } from 'react-router-dom'; import { DataViewListItem } from '@kbn/data-plugin/public'; import { DataViewSavedObjectConflictError } from '@kbn/data-views-plugin/public'; @@ -27,9 +27,9 @@ import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../../utils/bread import { LoadingIndicator } from '../../components/common/loading_indicator'; import { DiscoverError } from '../../components/common/error_alert'; import { useDiscoverServices } from '../../hooks/use_discover_services'; -import { getUrlTracker } from '../../kibana_services'; +import { getScopedHistory, getUrlTracker } from '../../kibana_services'; import { restoreStateFromSavedSearch } from '../../services/saved_searches/restore_from_saved_search'; -import { HistoryLocationState } from '../../locator'; +import { MainHistoryLocationState } from '../../locator'; const DiscoverMainAppMemoized = memo(DiscoverMainApp); @@ -39,7 +39,6 @@ interface DiscoverLandingParams { interface Props { isDev: boolean; - historyLocationState?: HistoryLocationState; } export function DiscoverMainRoute(props: Props) { @@ -64,6 +63,14 @@ export function DiscoverMainRoute(props: Props) { const [showNoDataPage, setShowNoDataPage] = useState<boolean>(false); const { id } = useParams<DiscoverLandingParams>(); + /** + * Get location state of scoped history only on initial load + */ + const historyLocationState = useMemo( + () => getScopedHistory().location.state as MainHistoryLocationState | undefined, + [] + ); + useExecutionContext(core.executionContext, { type: 'application', page: 'app', @@ -99,7 +106,7 @@ export function DiscoverMainRoute(props: Props) { data.dataViews, config, index, - props.historyLocationState?.dataViewSpec + historyLocationState?.dataViewSpec ); const ipList = ip.list; @@ -117,7 +124,7 @@ export function DiscoverMainRoute(props: Props) { data.dataViews, history, isDev, - props.historyLocationState?.dataViewSpec, + historyLocationState?.dataViewSpec, toastNotifications, services, ] diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index 86675ae54441a..8271e7681f7da 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -49,6 +49,8 @@ import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/ import { DiscoverAppLocator } from './locator'; import { getHistory } from './kibana_services'; import { DiscoverStartPlugins } from './plugin'; +import { DiscoverContextAppLocator } from './application/context/services/locator'; +import { DiscoverSingleDocLocator } from './application/doc/locator'; export interface HistoryLocationState { referrer: string; @@ -85,6 +87,8 @@ export interface DiscoverServices { spaces?: SpacesApi; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; locator: DiscoverAppLocator; + contextLocator: DiscoverContextAppLocator; + singleDocLocator: DiscoverSingleDocLocator; expressions: ExpressionsStart; charts: ChartsPluginStart; savedObjectsManagement: SavedObjectsManagementPluginStart; @@ -96,7 +100,9 @@ export const buildServices = memoize(function ( core: CoreStart, plugins: DiscoverStartPlugins, context: PluginInitializerContext, - locator: DiscoverAppLocator + locator: DiscoverAppLocator, + contextLocator: DiscoverContextAppLocator, + singleDocLocator: DiscoverSingleDocLocator ): DiscoverServices { const { usageCollection } = plugins; const storage = new Storage(localStorage); @@ -134,6 +140,8 @@ export const buildServices = memoize(function ( dataViewEditor: plugins.dataViewEditor, triggersActionsUi: plugins.triggersActionsUi, locator, + contextLocator, + singleDocLocator, expressions: plugins.expressions, charts: plugins.charts, savedObjectsTagging: plugins.savedObjectsTaggingOss?.getTaggingApi(), diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx index 2da63f982e305..c732f06a05dd7 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx @@ -24,6 +24,7 @@ import { } from '@elastic/eui'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { SortOrder } from '@kbn/saved-search-plugin/public'; +import { Filter } from '@kbn/es-query'; import { DocViewFilterFn } from '../../services/doc_views/doc_views_types'; import { getSchemaDetectors } from './discover_grid_schema'; import { DiscoverGridFlyout } from './discover_grid_flyout'; @@ -178,6 +179,14 @@ export interface DiscoverGridProps { * Callback to execute on edit runtime field */ onFieldEdited?: () => void; + /** + * Filters applied by saved search embeddable + */ + filters?: Filter[]; + /** + * Saved search id used for links to single doc and surrounding docs in the flyout + */ + savedSearchId?: string; } export const EuiDataGridMemoized = React.memo(EuiDataGrid); @@ -191,6 +200,8 @@ export const DiscoverGrid = ({ isLoading, expandedDoc, onAddColumn, + filters, + savedSearchId, onFilter, onRemoveColumn, onResize, @@ -621,6 +632,8 @@ export const DiscoverGrid = ({ hits={displayedRows} // if default columns are used, dont make them part of the URL - the context state handling will take care to restore them columns={defaultColumns ? [] : displayedColumns} + filters={filters} + savedSearchId={savedSearchId} onFilter={onFilter} onRemoveColumn={onRemoveColumn} onAddColumn={onAddColumn} diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx index 193a1667d2bb9..bdda6d80436c8 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx @@ -21,11 +21,22 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { DataTableRecord, EsHitRecord } from '../../types'; import { buildDataTableRecord } from '../../utils/build_data_record'; +import { act } from 'react-dom/test-utils'; +import { ReactWrapper } from 'enzyme'; + +const waitNextTick = () => new Promise((resolve) => setTimeout(resolve, 0)); + +const waitNextUpdate = async (component: ReactWrapper) => { + await act(async () => { + await waitNextTick(); + }); + component.update(); +}; describe('Discover flyout', function () { setDocViewsRegistry(new DocViewsRegistry()); - const mountComponent = ({ + const mountComponent = async ({ dataView, hits, hitIndex, @@ -39,6 +50,13 @@ describe('Discover flyout', function () { filterManager: createFilterManagerMock(), addBasePath: (path: string) => `/base${path}`, history: () => ({ location: {} }), + locator: { + useUrl: jest.fn(() => ''), + navigate: jest.fn(), + getUrl: jest.fn(() => Promise.resolve('mock-referrer')), + }, + contextLocator: { getRedirectUrl: jest.fn(() => 'mock-context-redirect-url') }, + singleDocLocator: { getRedirectUrl: jest.fn(() => 'mock-doc-redirect-url') }, } as unknown as DiscoverServices; const hit = buildDataTableRecord( @@ -67,42 +85,39 @@ describe('Discover flyout', function () { ); const component = mountWithIntl(<Proxy {...props} />); + await waitNextUpdate(component); return { component, props }; }; it('should be rendered correctly using an data view without timefield', async () => { - const { component, props } = mountComponent({}); + const { component, props } = await mountComponent({}); const url = findTestSubject(component, 'docTableRowAction').prop('href'); - expect(url).toMatchInlineSnapshot(`"/base/app/discover#/doc/the-data-view-id/i?id=1"`); + expect(url).toMatchInlineSnapshot(`"mock-doc-redirect-url"`); findTestSubject(component, 'euiFlyoutCloseButton').simulate('click'); expect(props.onClose).toHaveBeenCalled(); }); it('should be rendered correctly using an data view with timefield', async () => { - const { component, props } = mountComponent({ dataView: dataViewWithTimefieldMock }); + const { component, props } = await mountComponent({ dataView: dataViewWithTimefieldMock }); const actions = findTestSubject(component, 'docTableRowAction'); expect(actions.length).toBe(2); - expect(actions.first().prop('href')).toMatchInlineSnapshot( - `"/base/app/discover#/doc/index-pattern-with-timefield-id/i?id=1"` - ); - expect(actions.last().prop('href')).toMatchInlineSnapshot( - `"/base/app/discover#/context/index-pattern-with-timefield-id/1?_g=(filters:!())&_a=(columns:!(date),filters:!())"` - ); + expect(actions.first().prop('href')).toMatchInlineSnapshot(`"mock-doc-redirect-url"`); + expect(actions.last().prop('href')).toMatchInlineSnapshot(`"mock-context-redirect-url"`); findTestSubject(component, 'euiFlyoutCloseButton').simulate('click'); expect(props.onClose).toHaveBeenCalled(); }); it('displays document navigation when there is more than 1 doc available', async () => { - const { component } = mountComponent({ dataView: dataViewWithTimefieldMock }); + const { component } = await mountComponent({ dataView: dataViewWithTimefieldMock }); const docNav = findTestSubject(component, 'dscDocNavigation'); expect(docNav.length).toBeTruthy(); }); it('displays no document navigation when there are 0 docs available', async () => { - const { component } = mountComponent({ hits: [] }); + const { component } = await mountComponent({ hits: [] }); const docNav = findTestSubject(component, 'dscDocNavigation'); expect(docNav.length).toBeFalsy(); }); @@ -125,14 +140,14 @@ describe('Discover flyout', function () { _source: { date: '2020-20-01T12:12:12.124', name: 'test2', extension: 'jpg' }, }, ].map((hit) => buildDataTableRecord(hit, dataViewMock)); - const { component } = mountComponent({ hits }); + const { component } = await mountComponent({ hits }); const docNav = findTestSubject(component, 'dscDocNavigation'); expect(docNav.length).toBeFalsy(); }); it('allows you to navigate to the next doc, if expanded doc is the first', async () => { // scenario: you've expanded a doc, and in the next request different docs where fetched - const { component, props } = mountComponent({}); + const { component, props } = await mountComponent({}); findTestSubject(component, 'pagination-button-next').simulate('click'); // we selected 1, so we'd expect 2 expect(props.setExpandedDoc.mock.calls[0][0].raw._id).toBe('2'); @@ -140,28 +155,28 @@ describe('Discover flyout', function () { it('doesnt allow you to navigate to the previous doc, if expanded doc is the first', async () => { // scenario: you've expanded a doc, and in the next request differed docs where fetched - const { component, props } = mountComponent({}); + const { component, props } = await mountComponent({}); findTestSubject(component, 'pagination-button-previous').simulate('click'); expect(props.setExpandedDoc).toHaveBeenCalledTimes(0); }); it('doesnt allow you to navigate to the next doc, if expanded doc is the last', async () => { // scenario: you've expanded a doc, and in the next request differed docs where fetched - const { component, props } = mountComponent({ hitIndex: esHits.length - 1 }); + const { component, props } = await mountComponent({ hitIndex: esHits.length - 1 }); findTestSubject(component, 'pagination-button-next').simulate('click'); expect(props.setExpandedDoc).toHaveBeenCalledTimes(0); }); it('allows you to navigate to the previous doc, if expanded doc is the last', async () => { // scenario: you've expanded a doc, and in the next request differed docs where fetched - const { component, props } = mountComponent({ hitIndex: esHits.length - 1 }); + const { component, props } = await mountComponent({ hitIndex: esHits.length - 1 }); findTestSubject(component, 'pagination-button-previous').simulate('click'); expect(props.setExpandedDoc).toHaveBeenCalledTimes(1); expect(props.setExpandedDoc.mock.calls[0][0].raw._id).toBe('4'); }); - it('allows navigating with arrow keys through documents', () => { - const { component, props } = mountComponent({}); + it('allows navigating with arrow keys through documents', async () => { + const { component, props } = await mountComponent({}); findTestSubject(component, 'docTableDetailsFlyout').simulate('keydown', { key: 'ArrowRight' }); expect(props.setExpandedDoc).toHaveBeenCalledWith(expect.objectContaining({ id: 'i::2::' })); component.setProps({ ...props, hit: props.hits[1] }); @@ -169,8 +184,8 @@ describe('Discover flyout', function () { expect(props.setExpandedDoc).toHaveBeenCalledWith(expect.objectContaining({ id: 'i::1::' })); }); - it('should not navigate with keypresses when already at the border of documents', () => { - const { component, props } = mountComponent({}); + it('should not navigate with keypresses when already at the border of documents', async () => { + const { component, props } = await mountComponent({}); findTestSubject(component, 'docTableDetailsFlyout').simulate('keydown', { key: 'ArrowLeft' }); expect(props.setExpandedDoc).not.toHaveBeenCalled(); component.setProps({ ...props, hit: props.hits[props.hits.length - 1] }); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx index fb701eda3f19f..dd541a9cc4de8 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.tsx @@ -25,6 +25,7 @@ import { EuiHideFor, keys, } from '@elastic/eui'; +import { Filter } from '@kbn/es-query'; import { DocViewer } from '../../services/doc_views/components/doc_viewer/doc_viewer'; import { DocViewFilterFn } from '../../services/doc_views/doc_views_types'; import { useNavigationProps } from '../../hooks/use_navigation_props'; @@ -32,6 +33,8 @@ import { useDiscoverServices } from '../../hooks/use_discover_services'; import type { DataTableRecord } from '../../types'; export interface DiscoverGridFlyoutProps { + savedSearchId?: string; + filters?: Filter[]; columns: string[]; hit: DataTableRecord; hits?: DataTableRecord[]; @@ -56,6 +59,8 @@ export function DiscoverGridFlyout({ hits, dataView, columns, + savedSearchId, + filters, onFilter, onClose, onRemoveColumn, @@ -95,14 +100,9 @@ export function DiscoverGridFlyout({ [activePage, setPage] ); - const { singleDocProps, surrDocsProps } = useNavigationProps({ - dataViewId: dataView.id!, - rowIndex: hit.raw._index, - rowId: hit.raw._id, - filterManager: services.filterManager, - addBasePath: services.addBasePath, - columns, - }); + const { singleDocHref, contextViewHref, onOpenSingleDoc, onOpenContextView } = useNavigationProps( + { dataView, rowIndex: hit.raw._index, rowId: hit.raw._id, columns, filters, savedSearchId } + ); return ( <EuiPortal> @@ -140,13 +140,15 @@ export function DiscoverGridFlyout({ </EuiFlexItem> </EuiHideFor> <EuiFlexItem grow={false}> + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} <EuiButtonEmpty size="s" iconSize="s" iconType="document" flush="left" data-test-subj="docTableRowAction" - {...singleDocProps} + href={singleDocHref} + onClick={onOpenSingleDoc} > {i18n.translate('discover.grid.tableRow.viewSingleDocumentLinkTextSimple', { defaultMessage: 'Single document', @@ -156,12 +158,14 @@ export function DiscoverGridFlyout({ {dataView.isTimeBased() && dataView.id && ( <EuiFlexGroup alignItems="center" responsive={false} gutterSize="none"> <EuiFlexItem grow={false}> + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} <EuiButtonEmpty size="s" iconSize="s" iconType="documents" flush="left" - {...surrDocsProps} + onClick={onOpenContextView} + href={contextViewHref} data-test-subj="docTableRowAction" > {i18n.translate( diff --git a/src/plugins/discover/public/components/doc_table/components/table_row.tsx b/src/plugins/discover/public/components/doc_table/components/table_row.tsx index ecccc92f5030f..c08154f1ce5db 100644 --- a/src/plugins/discover/public/components/doc_table/components/table_row.tsx +++ b/src/plugins/discover/public/components/doc_table/components/table_row.tsx @@ -11,11 +11,11 @@ import classNames from 'classnames'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiIcon } from '@elastic/eui'; import { DataView } from '@kbn/data-views-plugin/public'; +import { Filter } from '@kbn/es-query'; import { formatFieldValue } from '../../../utils/format_value'; import { DocViewer } from '../../../services/doc_views/components/doc_viewer'; import { TableCell } from './table_row/table_cell'; import { formatRow, formatTopLevelObject } from '../utils/row_formatter'; -import { useNavigationProps } from '../../../hooks/use_navigation_props'; import { DocViewFilterFn } from '../../../services/doc_views/doc_views_types'; import { DataTableRecord, EsHitRecord } from '../../../types'; import { TableRowDetails } from './table_row_details'; @@ -29,6 +29,8 @@ export type DocTableRow = EsHitRecord & { export interface TableRowProps { columns: string[]; filter: DocViewFilterFn; + filters?: Filter[]; + savedSearchId?: string; row: DataTableRecord; dataView: DataView; useNewFieldsApi: boolean; @@ -38,8 +40,10 @@ export interface TableRowProps { } export const TableRow = ({ + filters, columns, filter, + savedSearchId, row, dataView, useNewFieldsApi, @@ -47,7 +51,7 @@ export const TableRow = ({ onAddColumn, onRemoveColumn, }: TableRowProps) => { - const { uiSettings, filterManager, fieldFormats, addBasePath } = useDiscoverServices(); + const { uiSettings, fieldFormats } = useDiscoverServices(); const [maxEntries, hideTimeColumn] = useMemo( () => [ uiSettings.get(MAX_DOC_FIELDS_DISPLAYED), @@ -98,15 +102,6 @@ export const TableRow = ({ [filter, dataView.fields, row.flattened] ); - const { singleDocProps, surrDocsProps } = useNavigationProps({ - dataViewId: dataView.id!, - rowIndex: row.raw._index, - rowId: row.raw._id, - filterManager, - addBasePath, - columns, - }); - const rowCells = [ <td className="kbnDocTableCell__toggleDetails" key="toggleDetailsCell"> <EuiButtonEmpty @@ -203,22 +198,27 @@ export const TableRow = ({ {rowCells} </tr> <tr data-test-subj="docTableDetailsRow" className="kbnDocTableDetails__row"> - <TableRowDetails - open={open} - colLength={(columns.length || 1) + 2} - isTimeBased={dataView.isTimeBased()} - singleDocProps={singleDocProps} - surrDocsProps={surrDocsProps} - > - <DocViewer - columns={columns} - filter={filter} - hit={row} + {open && ( + <TableRowDetails + colLength={(columns.length || 1) + 2} + isTimeBased={dataView.isTimeBased()} dataView={dataView} - onAddColumn={onAddColumn} - onRemoveColumn={onRemoveColumn} - /> - </TableRowDetails> + rowIndex={row.raw._index} + rowId={row.raw._id} + columns={columns} + filters={filters} + savedSearchId={savedSearchId} + > + <DocViewer + columns={columns} + filter={filter} + hit={row} + dataView={dataView} + onAddColumn={onAddColumn} + onRemoveColumn={onRemoveColumn} + /> + </TableRowDetails> + )} </tr> </Fragment> ); diff --git a/src/plugins/discover/public/components/doc_table/components/table_row_details.tsx b/src/plugins/discover/public/components/doc_table/components/table_row_details.tsx index 89c29c4846fc4..08ebd3072b17e 100644 --- a/src/plugins/discover/public/components/doc_table/components/table_row_details.tsx +++ b/src/plugins/discover/public/components/doc_table/components/table_row_details.tsx @@ -7,29 +7,45 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiTitle } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiButtonEmpty, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { DiscoverNavigationProps } from '../../../hooks/use_navigation_props'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import type { Filter } from '@kbn/es-query'; +import { useNavigationProps } from '../../../hooks/use_navigation_props'; + interface TableRowDetailsProps { - open: boolean; + children: JSX.Element; colLength: number; + rowIndex: string; + rowId: string; + columns: string[]; isTimeBased: boolean; - singleDocProps: DiscoverNavigationProps; - surrDocsProps: DiscoverNavigationProps; - children: JSX.Element; + dataView: DataView; + filters?: Filter[]; + savedSearchId?: string; } export const TableRowDetails = ({ - open, colLength, isTimeBased, - singleDocProps, - surrDocsProps, children, + dataView, + rowIndex, + rowId, + columns, + filters, + savedSearchId, }: TableRowDetailsProps) => { - if (!open) { - return null; - } + const { singleDocHref, contextViewHref, onOpenSingleDoc, onOpenContextView } = useNavigationProps( + { + dataView, + rowIndex, + rowId, + columns, + filters, + savedSearchId, + } + ); return ( <td colSpan={(colLength || 1) + 2}> @@ -55,21 +71,39 @@ export const TableRowDetails = ({ <EuiFlexGroup gutterSize="l" alignItems="center" responsive={false}> <EuiFlexItem grow={false}> {isTimeBased && ( - <EuiLink data-test-subj="docTableRowAction" {...surrDocsProps}> + /* eslint-disable-next-line @elastic/eui/href-or-on-click */ + <EuiButtonEmpty + size="s" + iconSize="s" + iconType="document" + flush="left" + data-test-subj="docTableRowAction" + href={contextViewHref} + onClick={onOpenContextView} + > <FormattedMessage id="discover.docTable.tableRow.viewSurroundingDocumentsLinkText" defaultMessage="View surrounding documents" /> - </EuiLink> + </EuiButtonEmpty> )} </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiLink data-test-subj="docTableRowAction" {...singleDocProps}> + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + <EuiButtonEmpty + size="s" + iconSize="s" + iconType="document" + flush="left" + data-test-subj="docTableRowAction" + href={singleDocHref} + onClick={onOpenSingleDoc} + > <FormattedMessage id="discover.docTable.tableRow.viewSingleDocumentLinkText" defaultMessage="View single document" /> - </EuiLink> + </EuiButtonEmpty> </EuiFlexItem> </EuiFlexGroup> </EuiFlexItem> diff --git a/src/plugins/discover/public/components/doc_table/create_doc_table_embeddable.tsx b/src/plugins/discover/public/components/doc_table/create_doc_table_embeddable.tsx index dbfe99d37bbd2..c6312a5515076 100644 --- a/src/plugins/discover/public/components/doc_table/create_doc_table_embeddable.tsx +++ b/src/plugins/discover/public/components/doc_table/create_doc_table_embeddable.tsx @@ -25,6 +25,7 @@ export function DiscoverDocTableEmbeddable(renderProps: DocTableEmbeddableProps) onMoveColumn={renderProps.onMoveColumn} onRemoveColumn={renderProps.onRemoveColumn} sort={renderProps.sort} + filters={renderProps.filters} onFilter={renderProps.onFilter} useNewFieldsApi={renderProps.useNewFieldsApi} searchDescription={renderProps.searchDescription} diff --git a/src/plugins/discover/public/components/doc_table/doc_table_wrapper.tsx b/src/plugins/discover/public/components/doc_table/doc_table_wrapper.tsx index 23f8b3e2e888b..fa4753587d758 100644 --- a/src/plugins/discover/public/components/doc_table/doc_table_wrapper.tsx +++ b/src/plugins/discover/public/components/doc_table/doc_table_wrapper.tsx @@ -11,6 +11,7 @@ import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import type { SortOrder } from '@kbn/saved-search-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; +import { Filter } from '@kbn/es-query'; import { TableHeader } from './components/table_header/table_header'; import { SHOW_MULTIFIELDS } from '../../../common'; import { TableRow } from './components/table_row'; @@ -56,6 +57,14 @@ export interface DocTableProps { * Loading state */ isLoading: boolean; + /** + * Filters applied by embeddalbe + */ + filters?: Filter[]; + /** + * Saved search id + */ + savedSearchId?: string; /** * Filter callback */ @@ -100,6 +109,8 @@ export const DocTableWrapper = forwardRef( { render, columns, + filters, + savedSearchId, rows, dataView, onSort, @@ -161,6 +172,8 @@ export const DocTableWrapper = forwardRef( <TableRow key={`${current.id}${current.raw._score}${current.raw._version}`} columns={columns} + filters={filters} + savedSearchId={savedSearchId} filter={onFilter} dataView={dataView} row={current} @@ -171,7 +184,17 @@ export const DocTableWrapper = forwardRef( /> )); }, - [columns, onFilter, dataView, useNewFieldsApi, fieldsToShow, onAddColumn, onRemoveColumn] + [ + columns, + filters, + savedSearchId, + onFilter, + dataView, + useNewFieldsApi, + fieldsToShow, + onAddColumn, + onRemoveColumn, + ] ); return ( diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx index 440fe6a8084a9..92fe7a1de5206 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx @@ -63,6 +63,8 @@ import { fetchSql } from '../application/main/utils/fetch_sql'; export type SearchProps = Partial<DiscoverGridProps> & Partial<DocTableProps> & { + savedSearchId?: string; + filters?: Filter[]; settings?: DiscoverGridSettings; description?: string; sharedItemTitle?: string; @@ -302,6 +304,8 @@ export class SavedSearchEmbeddable const props: SearchProps = { columns: this.savedSearch.columns, + savedSearchId: this.savedSearch.id, + filters: this.savedSearch.searchSource.getField('filter') as Filter[], dataView, isLoading: false, sort, @@ -450,6 +454,8 @@ export class SavedSearchEmbeddable searchProps.sharedItemTitle = this.panelTitle; searchProps.rowHeightState = this.input.rowHeight || this.savedSearch.rowHeight; searchProps.rowsPerPageState = this.input.rowsPerPage || this.savedSearch.rowsPerPage; + searchProps.filters = this.savedSearch.searchSource.getField('filter') as Filter[]; + searchProps.savedSearchId = this.savedSearch.id; if (forceFetch || isFetchRequired) { this.filtersSearchSource.setField('filter', this.input.filters); this.filtersSearchSource.setField('query', this.input.query); diff --git a/src/plugins/discover/public/hooks/use_data_view.test.tsx b/src/plugins/discover/public/hooks/use_data_view.test.tsx index 8ea48d314759c..11e10289ec3f9 100644 --- a/src/plugins/discover/public/hooks/use_data_view.test.tsx +++ b/src/plugins/discover/public/hooks/use_data_view.test.tsx @@ -5,27 +5,68 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { useDataView } from './use_data_view'; -import { dataViewMock } from '../__mocks__/data_view'; -import { dataViewsMock } from '../__mocks__/data_views'; + +import React from 'react'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { renderHook } from '@testing-library/react-hooks'; +import { useDataView } from './use_data_view'; -describe('Use data view', () => { - test('returning a valid data view', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useDataView(dataViewsMock, 'the-data-view-id') - ); - await waitForNextUpdate(); - expect(result.current.dataView).toBe(dataViewMock); - expect(result.current.error).toBe(undefined); +const adhocDataView = { + id: '2', + title: 'test2', + fields: [], +}; + +const dataViews = [ + { + id: '1', + title: 'test', + fields: [], + }, + adhocDataView, +]; + +const mockServices = { + dataViews: { + get: jest.fn((dataViewId: string) => + Promise.resolve(dataViews.find(({ id }) => id === dataViewId)) + ), + create: jest.fn((spec) => Promise.resolve(spec)), + }, +}; + +const render = async ({ dataViewId }: { dataViewId: string }) => { + const hookResult = renderHook(() => useDataView({ index: dataViewId }), { + wrapper: ({ children }) => ( + <KibanaContextProvider services={mockServices}>{children}</KibanaContextProvider> + ), }); + await hookResult.waitForNextUpdate(); - test('returning an error', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useDataView(dataViewsMock, 'invalid-data-view-id') + return hookResult; +}; + +describe('useDataView', () => { + it('should load save data view', async () => { + const { result } = await render({ dataViewId: '1' }); + expect(mockServices.dataViews.get).toHaveBeenCalledWith('1'); + expect(result.current.dataView).toEqual(dataViews[0]); + }); + + it('should throw an error on saved data view load ', async () => { + mockServices.dataViews.get.mockImplementationOnce(() => + Promise.reject(new Error('can not load')) ); - await waitForNextUpdate(); - expect(result.current.dataView).toBe(undefined); - expect(result.current.error).toBeTruthy(); + + const { result } = await render({ dataViewId: '1' }); + expect(result.current.error!.message).toEqual('can not load'); + }); + + it('should get adhoc data view from cache', async () => { + const { result } = await render({ dataViewId: '2' }); + + expect(mockServices.dataViews.get).toHaveBeenCalledWith(adhocDataView.id); + expect(mockServices.dataViews.create).toBeCalledTimes(0); + expect(result.current.dataView).toEqual(adhocDataView); }); }); diff --git a/src/plugins/discover/public/hooks/use_data_view.tsx b/src/plugins/discover/public/hooks/use_data_view.tsx index f218f3468de2f..254bdb9a6bde4 100644 --- a/src/plugins/discover/public/hooks/use_data_view.tsx +++ b/src/plugins/discover/public/hooks/use_data_view.tsx @@ -6,22 +6,18 @@ * Side Public License, v 1. */ import { useEffect, useState } from 'react'; -import { DataView, DataViewsContract } from '@kbn/data-views-plugin/public'; +import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/public'; +import { useDiscoverServices } from './use_discover_services'; -export const useDataView = (dataViews: DataViewsContract, dataViewId: string) => { - const [dataView, setDataView] = useState<DataView | undefined>(undefined); - const [error, setError] = useState(); +export const useDataView = ({ index }: { index: string | DataViewSpec }) => { + const { dataViews } = useDiscoverServices(); + const [dataView, setDataView] = useState<DataView>(); + const [error, setError] = useState<Error>(); useEffect(() => { - async function loadDataView() { - try { - const item = await dataViews.get(dataViewId); - setDataView(item); - } catch (e) { - setError(e); - } - } - loadDataView(); - }, [dataViewId, dataViews]); + const promise = typeof index === 'object' ? dataViews.create(index) : dataViews.get(index); + promise.then(setDataView).catch(setError); + }, [dataViews, index]); + return { dataView, error }; }; diff --git a/src/plugins/discover/public/hooks/use_navigation_props.test.tsx b/src/plugins/discover/public/hooks/use_navigation_props.test.tsx index 322dc6407df8b..99074e78f4c81 100644 --- a/src/plugins/discover/public/hooks/use_navigation_props.test.tsx +++ b/src/plugins/discover/public/hooks/use_navigation_props.test.tsx @@ -6,90 +6,99 @@ * Side Public License, v 1. */ -import React, { ReactElement } from 'react'; +import React, { MouseEvent } from 'react'; import { renderHook } from '@testing-library/react-hooks'; -import { createFilterManagerMock } from '@kbn/data-plugin/public/query/filter_manager/filter_manager.mock'; -import { getContextHash, useNavigationProps, UseNavigationProps } from './use_navigation_props'; -import { Router } from 'react-router-dom'; -import { createMemoryHistory } from 'history'; +import { useNavigationProps } from './use_navigation_props'; +import type { DataView } from '@kbn/data-views-plugin/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { MemoryRouter } from 'react-router-dom'; -const filterManager = createFilterManagerMock(); -const defaultProps = { - dataViewId: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', - rowIndex: 'kibana_sample_data_ecommerce', - rowId: 'QmsYdX0BQ6gV8MTfoPYE', - columns: ['customer_first_name', 'products.manufacturer'], - filterManager, - addBasePath: jest.fn(), -} as UseNavigationProps; -const basePathPrefix = 'localhost:5601/xqj'; - -const getSearch = () => { - return `?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now)) - &_a=(columns:!(${defaultProps.columns.join()}),filters:!(),index:${defaultProps.dataViewId} - ,interval:auto,query:(language:kuery,query:''),sort:!(!(order_date,desc)))`; -}; - -const getSingeDocRoute = () => { - return `/doc/${defaultProps.dataViewId}/${defaultProps.rowIndex}`; +const mockServices = { + singleDocLocator: { getRedirectUrl: jest.fn(() => 'mock-doc-redirect-url'), navigate: jest.fn() }, + contextLocator: { + getRedirectUrl: jest.fn(() => 'mock-context-redirect-url'), + navigate: jest.fn(), + }, + locator: { + getUrl: jest.fn(() => Promise.resolve('mock-referrer')), + useUrl: jest.fn(() => 'mock-referrer'), + }, + filterManager: { + getAppFilters: jest.fn(() => []), + getGlobalFilters: jest.fn(() => []), + }, + data: { + query: { + queryString: { getQuery: jest.fn(() => ({ query: 'response:200', language: 'kuery' })) }, + timefilter: { timefilter: { getTime: jest.fn(() => ({ from: 'now-15m', to: 'now' })) } }, + }, + }, }; -const getContextRoute = () => { - return `/context/${defaultProps.dataViewId}/${defaultProps.rowId}`; -}; +const dataViewMock = { + id: '1', + title: 'test', + fields: [], + isPersisted: () => false, + toSpec: () => ({ + id: '1', + title: 'test', + fields: [], + }), +} as unknown as DataView; -const render = (withRouter = true, props?: Partial<UseNavigationProps>) => { - const history = createMemoryHistory({ - initialEntries: ['/' + getSearch()], - }); - const wrapper = ({ children }: { children: ReactElement }) => ( - <KibanaContextProvider services={{ history: () => history }}> - {withRouter ? <Router history={history}>{children}</Router> : children} - </KibanaContextProvider> +const render = async () => { + const renderResult = renderHook( + () => + useNavigationProps({ + dataView: dataViewMock, + rowIndex: 'mock-index', + rowId: 'mock-id', + columns: ['mock-column'], + }), + { + wrapper: ({ children }) => ( + <MemoryRouter initialEntries={['/']}> + <KibanaContextProvider services={mockServices}>{children}</KibanaContextProvider> + </MemoryRouter> + ), + } ); - return { - result: renderHook(() => useNavigationProps({ ...defaultProps, ...props }), { wrapper }).result, - history, - }; + await renderResult.waitForNextUpdate(); + return renderResult; }; describe('useNavigationProps', () => { - test('should provide valid breadcrumb for single doc page from main view', () => { - const { result, history } = render(); - - // @ts-expect-error - result.current.singleDocProps.onClick(); - expect(history.location.pathname).toEqual(getSingeDocRoute()); - expect(history.location.search).toEqual( - `?id=${defaultProps.rowId}&breadcrumb=${encodeURIComponent(`#/${getSearch()}`)}` - ); - }); + it('should call context and single doc callbacks with correct params', async () => { + const { result } = await render(); + const commonParams = { + index: { + id: '1', + title: 'test', + fields: [], + }, + rowId: 'mock-id', + referrer: 'mock-referrer', + }; - test('should provide valid breadcrumb for context page from main view', () => { - const { result, history } = render(); + await result.current.onOpenContextView({ preventDefault: jest.fn() } as unknown as MouseEvent); + expect(mockServices.contextLocator.navigate.mock.calls[0][0]).toEqual({ + ...commonParams, + columns: ['mock-column'], + filters: [], + }); - // @ts-expect-error - result.current.surrDocsProps.onClick(); - expect(history.location.pathname).toEqual(getContextRoute()); - expect(history.location.search).toEqual( - `?${getContextHash(defaultProps.columns, filterManager)}&breadcrumb=${encodeURIComponent( - `#/${getSearch()}` - )}` - ); + await result.current.onOpenSingleDoc({ preventDefault: jest.fn() } as unknown as MouseEvent); + expect(mockServices.singleDocLocator.navigate.mock.calls[0][0]).toEqual({ + ...commonParams, + rowIndex: 'mock-index', + }); }); - test('should create valid links to the context and single doc pages from embeddable', () => { - const { result } = render(false, { addBasePath: (val: string) => `${basePathPrefix}${val}` }); + test('should create valid links to the context and single doc pages', async () => { + const { result } = await render(); - expect(result.current.singleDocProps.href!).toEqual( - `${basePathPrefix}/app/discover#${getSingeDocRoute()}?id=${defaultProps.rowId}` - ); - expect(result.current.surrDocsProps.href!).toEqual( - `${basePathPrefix}/app/discover#${getContextRoute()}?${getContextHash( - defaultProps.columns, - filterManager - )}` - ); + expect(result.current.singleDocHref).toMatchInlineSnapshot(`"mock-doc-redirect-url"`); + expect(result.current.contextViewHref).toMatchInlineSnapshot(`"mock-context-redirect-url"`); }); }); diff --git a/src/plugins/discover/public/hooks/use_navigation_props.tsx b/src/plugins/discover/public/hooks/use_navigation_props.tsx index cf839bda7c1a4..eab1bfb2937c2 100644 --- a/src/plugins/discover/public/hooks/use_navigation_props.tsx +++ b/src/plugins/discover/public/hooks/use_navigation_props.tsx @@ -6,146 +6,178 @@ * Side Public License, v 1. */ -import { MouseEventHandler, useMemo } from 'react'; -import { useHistory, matchPath } from 'react-router-dom'; -import type { Location } from 'history'; -import { stringify } from 'query-string'; -import rison from 'rison-node'; -import { disableFilter } from '@kbn/es-query'; -import { FilterManager } from '@kbn/data-plugin/public'; -import { url } from '@kbn/kibana-utils-plugin/common'; +import { useCallback, useEffect, useMemo, useState, MouseEventHandler, MouseEvent } from 'react'; +import { AggregateQuery, Query, TimeRange, Filter, disableFilter } from '@kbn/es-query'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { useHistory } from 'react-router-dom'; +import { DataPublicPluginStart, FilterManager } from '@kbn/data-plugin/public'; import { useDiscoverServices } from './use_discover_services'; -export type DiscoverNavigationProps = { onClick: () => void } | { href: string }; - export interface UseNavigationProps { - dataViewId: string; + dataView: DataView; rowIndex: string; rowId: string; columns: string[]; - filterManager: FilterManager; - addBasePath: (url: string) => string; + savedSearchId?: string; + // provided by embeddable only + filters?: Filter[]; } -export const getContextHash = (columns: string[], filterManager: FilterManager) => { - const globalFilters = filterManager.getGlobalFilters(); - const appFilters = filterManager.getAppFilters(); - - return stringify( - url.encodeQuery({ - _g: rison.encode({ - filters: globalFilters || [], - }), - _a: rison.encode({ - columns, - filters: (appFilters || []).map(disableFilter), - }), - }), - { encode: false, sort: false } - ); -}; - -/** - * When it's context route, breadcrumb link should point to the main discover page anyway. - * Otherwise, we are on main page and should create breadcrumb link from it. - * Current history object should be used in callback, since url state might be changed - * after expanded document opened. - */ +const getStateParams = ({ + isEmbeddableView, + columns, + filters, + filterManager, + data, + savedSearchId, +}: { + isEmbeddableView: boolean; + columns: string[]; + savedSearchId?: string; + filters?: Filter[]; + filterManager: FilterManager; + data: DataPublicPluginStart; +}) => { + let appliedFilters: Filter[] = []; + let query: Query | AggregateQuery | undefined; + let timeRange: TimeRange | undefined; + if (!isEmbeddableView) { + // applied from discover main and context app + appliedFilters = [...filterManager.getGlobalFilters(), ...filterManager.getAppFilters()]; + query = data.query.queryString.getQuery(); + timeRange = data.query.timefilter.timefilter.getTime(); + } else if (isEmbeddableView && filters?.length) { + // applied from saved search embeddable + appliedFilters = filters; + } -const getCurrentBreadcrumbs = ( - isContextRoute: boolean, - currentLocation: Location, - prevBreadcrumb?: string -) => { - return isContextRoute ? prevBreadcrumb : '#' + currentLocation.pathname + currentLocation.search; + return { + columns, + query, + timeRange, + filters: appliedFilters, + savedSearchId, + }; }; -const getCurrentBreadcrumb = (search: string | undefined) => - new URLSearchParams(search).get('breadcrumb') || undefined; - -export const useMainRouteBreadcrumb = () => { - const history = useHistory(); - return useMemo(() => getCurrentBreadcrumb(history.location.search), [history.location.search]); -}; +const isModifiedEvent = (event: MouseEvent) => + !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); export const useNavigationProps = ({ - dataViewId, + dataView, rowIndex, rowId, columns, - filterManager, - addBasePath, + savedSearchId, + filters, }: UseNavigationProps) => { - const history = useHistory(); - const currentLocation = useDiscoverServices().history().location; - - const prevBreadcrumb = useMemo( - () => getCurrentBreadcrumb(history?.location?.search), - [history?.location?.search] + const isEmbeddableView = !useHistory(); + const services = useDiscoverServices(); + const [singleDocHref, setSingleDocHref] = useState(''); + const [contextViewHref, setContextViewHref] = useState(''); + + const index = useMemo( + () => (dataView.isPersisted() ? dataView.id! : dataView.toSpec(false)), + [dataView] ); - const singleDocHref = addBasePath( - `/app/discover#/doc/${dataViewId}/${rowIndex}?id=${encodeURIComponent(rowId)}` + const buildParams = useCallback( + () => + getStateParams({ + isEmbeddableView, + columns, + filters, + filterManager: services.filterManager, + data: services.data, + savedSearchId, + }), + [columns, filters, isEmbeddableView, savedSearchId, services.data, services.filterManager] ); - const contextSearchHash = getContextHash(columns, filterManager); - - const surDocsHref = addBasePath( - `/app/discover#/context/${encodeURIComponent(dataViewId)}/${encodeURIComponent( - rowId - )}?${contextSearchHash}` + useEffect(() => { + const dataViewId = typeof index === 'object' ? index.id : index; + services.locator + .getUrl({ dataViewId, ...buildParams() }) + .then((referrer) => { + return services.singleDocLocator.getRedirectUrl({ index, rowIndex, rowId, referrer }); + }) + .then(setSingleDocHref); + }, [ + index, + rowIndex, + rowId, + services.singleDocLocator, + setSingleDocHref, + services.locator, + buildParams, + ]); + + useEffect(() => { + const params = buildParams(); + const dataViewId = typeof index === 'object' ? index.id : index; + services.locator + .getUrl({ dataViewId, ...params }) + .then((referrer) => + services.contextLocator.getRedirectUrl({ + index, + rowId, + columns: params.columns, + filters: params.filters?.map(disableFilter), + referrer, + }) + ) + .then(setContextViewHref); + }, [ + index, + rowIndex, + rowId, + setContextViewHref, + buildParams, + services.contextLocator, + services.locator, + ]); + + const onOpenSingleDoc: MouseEventHandler = useCallback( + (event) => { + if (isModifiedEvent(event)) { + return; + } + event.preventDefault(); + const dataViewId = typeof index === 'object' ? index.id : index; + services.locator + .getUrl({ dataViewId, ...buildParams() }) + .then((referrer) => + services.singleDocLocator.navigate({ index, rowIndex, rowId, referrer }) + ); + }, + [buildParams, index, rowId, rowIndex, services.locator, services.singleDocLocator] ); - /** - * When history can be accessed via hooks, - * it's used by discover main or context route. - */ - if (!!history) { - const isContextRoute = matchPath(history.location.pathname, { - path: '/context/:dataViewId/:id', - exact: true, - }); - const currentBreadcrumb = encodeURIComponent( - getCurrentBreadcrumbs(!!isContextRoute, currentLocation, prevBreadcrumb) ?? '' - ); - - const onOpenSingleDoc: MouseEventHandler<HTMLAnchorElement> = (event) => { - event?.preventDefault?.(); - - history.push({ - pathname: `/doc/${dataViewId}/${rowIndex}`, - search: `?id=${encodeURIComponent(rowId)}&breadcrumb=${currentBreadcrumb}`, - }); - }; - - const onOpenSurrDocs: MouseEventHandler<HTMLAnchorElement> = (event) => { - event?.preventDefault?.(); - - history.push({ - pathname: `/context/${encodeURIComponent(dataViewId)}/${encodeURIComponent(String(rowId))}`, - search: `?${contextSearchHash}&breadcrumb=${currentBreadcrumb}`, - }); - }; - - return { - singleDocProps: { - onClick: onOpenSingleDoc, - href: `${singleDocHref}&breadcrumb=${currentBreadcrumb}`, - }, - surrDocsProps: { - onClick: onOpenSurrDocs, - href: `${surDocsHref}&breadcrumb=${currentBreadcrumb}`, - }, - }; - } + const onOpenContextView: MouseEventHandler = useCallback( + (event) => { + const params = buildParams(); + if (isModifiedEvent(event)) { + return; + } + event.preventDefault(); + const dataViewId = typeof index === 'object' ? index.id : index; + services.locator.getUrl({ dataViewId, ...params }).then((referrer) => + services.contextLocator.navigate({ + index, + rowId, + columns: params.columns, + filters: params.filters?.map(disableFilter), + referrer, + }) + ); + }, + [buildParams, index, rowId, services.contextLocator, services.locator] + ); - // for embeddable absolute href should be kept return { - singleDocProps: { - href: singleDocHref, - }, - surrDocsProps: { - href: surDocsHref, - }, + singleDocHref, + contextViewHref, + onOpenSingleDoc, + onOpenContextView, }; }; diff --git a/src/plugins/discover/public/locator.ts b/src/plugins/discover/public/locator.ts index eb8d1039f27c5..e59c61c98412c 100644 --- a/src/plugins/discover/public/locator.ts +++ b/src/plugins/discover/public/locator.ts @@ -99,7 +99,10 @@ export interface DiscoverAppLocatorDependencies { useHash: boolean; } -export interface HistoryLocationState { +/** + * Location state of scoped history (history instance of Kibana Platform application service) + */ +export interface MainHistoryLocationState { dataViewSpec?: DataViewSpec; } @@ -157,7 +160,7 @@ export class DiscoverAppLocatorDefinition implements LocatorDefinition<DiscoverA if (viewMode) appState.viewMode = viewMode; if (hideAggregatedPreview) appState.hideAggregatedPreview = hideAggregatedPreview; - const state: HistoryLocationState = {}; + const state: MainHistoryLocationState = {}; if (dataViewSpec) { state.dataViewSpec = dataViewSpec; } diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index ce809f2a3a092..8ea69f80bdde1 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -53,7 +53,7 @@ import { } from './kibana_services'; import { registerFeature } from './register_feature'; import { buildServices } from './build_services'; -import { DiscoverAppLocator, DiscoverAppLocatorDefinition, HistoryLocationState } from './locator'; +import { DiscoverAppLocator, DiscoverAppLocatorDefinition } from './locator'; import { SearchEmbeddableFactory } from './embeddable'; import { DeferredSpinner } from './components'; import { ViewSavedSearchAction } from './embeddable/view_saved_search_action'; @@ -61,6 +61,14 @@ import { injectTruncateStyles } from './utils/truncate_styles'; import { DOC_TABLE_LEGACY, TRUNCATE_MAX_HEIGHT } from '../common'; import { useDiscoverServices } from './hooks/use_discover_services'; import { initializeKbnUrlTracking } from './utils/initialize_kbn_url_tracking'; +import { + DiscoverContextAppLocator, + DiscoverContextAppLocatorDefinition, +} from './application/context/services/locator'; +import { + DiscoverSingleDocLocator, + DiscoverSingleDocLocatorDefinition, +} from './application/doc/locator'; const DocViewerLegacyTable = React.lazy( () => import('./services/doc_views/components/doc_viewer_table/legacy') @@ -199,15 +207,23 @@ export class DiscoverPlugin private docViewsRegistry: DocViewsRegistry | null = null; private stopUrlTracking: (() => void) | undefined = undefined; private locator?: DiscoverAppLocator; + private contextLocator?: DiscoverContextAppLocator; + private singleDocLocator?: DiscoverSingleDocLocator; setup(core: CoreSetup<DiscoverStartPlugins, DiscoverStart>, plugins: DiscoverSetupPlugins) { const baseUrl = core.http.basePath.prepend('/app/discover'); const isDev = this.initializerContext.env.mode.dev; if (plugins.share) { + const useHash = core.uiSettings.get('state:storeInSessionStorage'); this.locator = plugins.share.url.locators.create( - new DiscoverAppLocatorDefinition({ - useHash: core.uiSettings.get('state:storeInSessionStorage'), - }) + new DiscoverAppLocatorDefinition({ useHash }) + ); + + this.contextLocator = plugins.share.url.locators.create( + new DiscoverContextAppLocatorDefinition({ useHash }) + ); + this.singleDocLocator = plugins.share.url.locators.create( + new DiscoverSingleDocLocatorDefinition() ); } @@ -288,9 +304,6 @@ export class DiscoverPlugin setHeaderActionMenuMounter(params.setHeaderActionMenu); syncHistoryLocations(); appMounted(); - const historyLocationState = params.history.location.state as - | HistoryLocationState - | undefined; // dispatch synthetic hash change event to update hash history objects // this is necessary because hash updates triggered by using popState won't trigger this event naturally. @@ -302,7 +315,9 @@ export class DiscoverPlugin coreStart, discoverStartPlugins, this.initializerContext, - this.locator! + this.locator!, + this.contextLocator!, + this.singleDocLocator! ); // make sure the data view list is up to date @@ -312,7 +327,7 @@ export class DiscoverPlugin // FIXME: Temporarily hide overflow-y in Discover app when Field Stats table is shown // due to EUI bug https://github.com/elastic/eui/pull/5152 params.element.classList.add('dscAppWrapper'); - const unmount = renderApp(params.element, services, isDev, historyLocationState); + const unmount = renderApp(params.element, services, isDev); return () => { unlistenParentHistory(); unmount(); @@ -393,7 +408,14 @@ export class DiscoverPlugin const getDiscoverServices = async () => { const [coreStart, discoverStartPlugins] = await core.getStartServices(); - return buildServices(coreStart, discoverStartPlugins, this.initializerContext, this.locator!); + return buildServices( + coreStart, + discoverStartPlugins, + this.initializerContext, + this.locator!, + this.contextLocator!, + this.singleDocLocator! + ); }; const factory = new SearchEmbeddableFactory(getStartServices, getDiscoverServices); diff --git a/src/plugins/discover/public/utils/with_query_params.test.tsx b/src/plugins/discover/public/utils/with_query_params.test.tsx deleted file mode 100644 index e23688c0031c7..0000000000000 --- a/src/plugins/discover/public/utils/with_query_params.test.tsx +++ /dev/null @@ -1,37 +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 React, { ReactElement } from 'react'; -import { Router } from 'react-router-dom'; -import { createMemoryHistory } from 'history'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { withQueryParams } from './with_query_params'; - -const mountComponent = (children: ReactElement, query = '') => { - const history = createMemoryHistory({ - initialEntries: ['/' + query], - }); - return mountWithIntl(<Router history={history}>{children}</Router>); -}; - -describe('withQueryParams', () => { - it('should display error message, when query does not contain required parameters', () => { - const Component = withQueryParams(() => <div />, ['id', 'query']); - const component = mountComponent(<Component />); - - expect(component.html()).toContain('Cannot load this page'); - expect(component.html()).toContain('URL query string is missing id, query.'); - }); - - it('should not display error message, when query contain required parameters', () => { - const Component = withQueryParams(() => <div />, ['id', 'query']); - const component = mountComponent(<Component />, '?id=one&query=another'); - - expect(component.html()).not.toContain('URL query string is missing id, query.'); - }); -}); diff --git a/src/plugins/discover/public/utils/with_query_params.tsx b/src/plugins/discover/public/utils/with_query_params.tsx deleted file mode 100644 index 159ba6740cf64..0000000000000 --- a/src/plugins/discover/public/utils/with_query_params.tsx +++ /dev/null @@ -1,39 +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 React, { useMemo } from 'react'; -import { i18n } from '@kbn/i18n'; -import { useLocation } from 'react-router-dom'; -import { DiscoverError } from '../components/common/error_alert'; - -export function withQueryParams<T>(Component: React.ComponentType<T>, requiredParams: string[]) { - return () => { - const { search } = useLocation(); - const query = useMemo(() => new URLSearchParams(search), [search]); - - const missingParamNames = useMemo( - () => requiredParams.filter((currentParamName) => !query.get(currentParamName)), - [query] - ); - - if (missingParamNames.length !== 0) { - const missingParamsList = missingParamNames.join(', '); - const errorMessage = i18n.translate('discover.discoverError.missingQueryParamsError', { - defaultMessage: 'URL query string is missing {missingParamsList}.', - values: { missingParamsList }, - }); - - return <DiscoverError error={new Error(errorMessage)} />; - } - - const queryProps = Object.fromEntries( - requiredParams.map((current) => [[current], query.get(current)]) - ); - return <Component {...queryProps} />; - }; -} diff --git a/test/functional/apps/context/_context_navigation.ts b/test/functional/apps/context/_context_navigation.ts index d87ef6275f3d3..2451e351b6d9c 100644 --- a/test/functional/apps/context/_context_navigation.ts +++ b/test/functional/apps/context/_context_navigation.ts @@ -26,6 +26,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const filterBar = getService('filterBar'); const find = getService('find'); + const checkMainViewFilters = async () => { + for (const [columnName, value] of TEST_FILTER_COLUMN_NAMES) { + expect(await filterBar.hasFilter(columnName, value, true)).to.eql(true); + } + expect(await PageObjects.timePicker.getTimeConfigAsAbsoluteTimes()).to.eql({ + start: 'Sep 18, 2015 @ 06:31:44.000', + end: 'Sep 23, 2015 @ 18:31:44.000', + }); + }; + describe('discover - context - back navigation', function contextSize() { before(async () => { await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); @@ -75,13 +85,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await find.clickByCssSelector(`[data-test-subj="breadcrumb first"]`); await PageObjects.discover.waitForDocTableLoadingComplete(); - for (const [columnName, value] of TEST_FILTER_COLUMN_NAMES) { - expect(await filterBar.hasFilter(columnName, value)).to.eql(true); - } - expect(await PageObjects.timePicker.getTimeConfigAsAbsoluteTimes()).to.eql({ - start: 'Sep 18, 2015 @ 06:31:44.000', - end: 'Sep 23, 2015 @ 18:31:44.000', - }); + await checkMainViewFilters(); return true; } ); @@ -100,16 +104,42 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await find.clickByCssSelector(`[data-test-subj="breadcrumb first"]`); await PageObjects.discover.waitForDocTableLoadingComplete(); - for (const [columnName, value] of TEST_FILTER_COLUMN_NAMES) { - expect(await filterBar.hasFilter(columnName, value)).to.eql(true); - } - expect(await PageObjects.timePicker.getTimeConfigAsAbsoluteTimes()).to.eql({ - start: 'Sep 18, 2015 @ 06:31:44.000', - end: 'Sep 23, 2015 @ 18:31:44.000', - }); + await checkMainViewFilters(); return true; } ); }); + + it('should go back via breadcrumbs with updated state after a goBack browser', async function () { + await dataGrid.clickRowToggle({ rowIndex: 0 }); + const rowActions = await dataGrid.getRowActions({ rowIndex: 0 }); + await rowActions[1].click(); + await PageObjects.context.waitUntilContextLoadingHasFinished(); + + await PageObjects.common.sleep(5000); + + // update url state + await filterBar.removeFilter('agent'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await find.clickByCssSelector(`[data-test-subj="breadcrumb first"]`); + await PageObjects.header.waitUntilLoadingHasFinished(); + + expect(await filterBar.getFilterCount()).to.eql(2); + await checkMainViewFilters(); + + await browser.goBack(); + await PageObjects.context.waitUntilContextLoadingHasFinished(); + + expect(await filterBar.getFilterCount()).to.eql(1); + const [filterName, filterValue] = TEST_FILTER_COLUMN_NAMES[1]; + expect(await filterBar.hasFilter(filterName, filterValue, false)).to.eql(true); + + await find.clickByCssSelector(`[data-test-subj="breadcrumb first"]`); + await PageObjects.header.waitUntilLoadingHasFinished(); + + expect(await filterBar.getFilterCount()).to.eql(2); + await checkMainViewFilters(); + }); }); } diff --git a/test/functional/apps/context/_discover_navigation.ts b/test/functional/apps/context/_discover_navigation.ts index 52efeefeb6546..d955b02599856 100644 --- a/test/functional/apps/context/_discover_navigation.ts +++ b/test/functional/apps/context/_discover_navigation.ts @@ -143,10 +143,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const rowActions = await dataGrid.getRowActions({ rowIndex: 0 }); await rowActions[0].click(); await PageObjects.common.sleep(250); - // accept alert if it pops up + + // close popup const alert = await browser.getAlert(); await alert?.accept(); - expect(await browser.getCurrentUrl()).to.contain('#/doc'); + if (await testSubjects.exists('confirmModalConfirmButton')) { + await testSubjects.click('confirmModalConfirmButton'); + } + + await retry.waitFor('navigate to doc view', async () => { + const currentUrl = await browser.getCurrentUrl(); + return currentUrl.includes('#/doc'); + }); await retry.waitFor('doc view being rendered', async () => { return await PageObjects.discover.isShowingDocViewer(); }); diff --git a/test/functional/apps/context/classic/_discover_navigation.ts b/test/functional/apps/context/classic/_discover_navigation.ts index 70047518979b0..cc265ecceaa53 100644 --- a/test/functional/apps/context/classic/_discover_navigation.ts +++ b/test/functional/apps/context/classic/_discover_navigation.ts @@ -33,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const kibanaServer = getService('kibanaServer'); - describe('context link in discover', () => { + describe('context link in discover classic', () => { before(async () => { await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await kibanaServer.uiSettings.update({ @@ -146,10 +146,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const rowActions = await docTable.getRowActions({ rowIndex: 0 }); await rowActions[1].click(); await PageObjects.common.sleep(250); - // accept alert if it pops up + + // close popup const alert = await browser.getAlert(); await alert?.accept(); - expect(await browser.getCurrentUrl()).to.contain('#/doc'); + if (await testSubjects.exists('confirmModalConfirmButton')) { + await testSubjects.click('confirmModalConfirmButton'); + } + + await retry.waitFor('navigate to doc view', async () => { + const currentUrl = await browser.getCurrentUrl(); + return currentUrl.includes('#/doc'); + }); await retry.waitFor('doc view being rendered', async () => { return await PageObjects.discover.isShowingDocViewer(); }); diff --git a/test/functional/apps/discover/group2/_adhoc_data_views.ts b/test/functional/apps/discover/group2/_adhoc_data_views.ts index 9a1f4dd84d300..ebce26a4a4310 100644 --- a/test/functional/apps/discover/group2/_adhoc_data_views.ts +++ b/test/functional/apps/discover/group2/_adhoc_data_views.ts @@ -57,6 +57,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should navigate back correctly from to surrounding and single views', async () => { await PageObjects.discover.createAdHocDataView('logstash', true); await PageObjects.header.waitUntilLoadingHasFinished(); + const first = await PageObjects.discover.getCurrentDataViewId(); + + await PageObjects.discover.addRuntimeField( + '_bytes-runtimefield', + `emit(doc["bytes"].value.toString())` + ); + await PageObjects.discover.clickFieldListItemToggle('_bytes-runtimefield'); + + const second = await PageObjects.discover.getCurrentDataViewId(); + expect(first).not.to.equal(second); // navigate to context view await dataGrid.clickRowToggle({ rowIndex: 0 }); @@ -182,9 +192,34 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(+second).to.equal(+first * 2); }); - it('should update id after data view field edit', async () => { - await PageObjects.common.navigateToApp('discover'); + it('should open saved search by navigation to context from embeddable', async () => { + // navigate to context view + await dataGrid.clickRowToggle({ rowIndex: 0 }); + const [, surrDocs] = await dataGrid.getRowActions({ rowIndex: 0 }); + await surrDocs.click(); + + // close popup + const alert = await browser.getAlert(); + await alert?.accept(); + if (await testSubjects.exists('confirmModalConfirmButton')) { + await testSubjects.click('confirmModalConfirmButton'); + } + await PageObjects.context.waitUntilContextLoadingHasFinished(); + + // open saved search + await find.clickByCssSelector(`[data-test-subj="breadcrumb first"]`); await PageObjects.header.waitUntilLoadingHasFinished(); + + const savedSearch = await find.byCssSelector(`[data-test-subj="breadcrumb last"]`); + const savedSearchName = await savedSearch.getVisibleText(); + expect(savedSearchName).to.be.equal('logst*-ss-_bytes-runtimefield'); + + // test the header now + const header = await dataGrid.getHeaderFields(); + expect(header.join(' ')).to.have.string('_bytes-runtimefield'); + }); + + it('should update id after data view field edit', async () => { await PageObjects.discover.loadSavedSearch('logst*-ss-_bytes-runtimefield'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -227,13 +262,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.goBack(); await PageObjects.header.waitUntilLoadingHasFinished(); - const firstToast = await toasts.getToastContent(1); - expect(firstToast).to.equal( + const [firstToast, secondToast] = await toasts.getAllToastElements(); + expect(await firstToast.getVisibleText()).to.equal( `"${first}" is not a configured data view ID\nShowing the saved data view: "logstas*" (${second})` ); - const secondToast = await toasts.getToastContent(2); - expect(secondToast).to.equal( + expect(await secondToast.getVisibleText()).to.equal( `Different index references\nData view id references in some of the applied filters differ from the current data view.` ); }); diff --git a/test/functional/apps/discover/group2/_data_grid_context.ts b/test/functional/apps/discover/group2/_data_grid_context.ts index 407ec8dd542f9..41a8d1aba6dea 100644 --- a/test/functional/apps/discover/group2/_data_grid_context.ts +++ b/test/functional/apps/discover/group2/_data_grid_context.ts @@ -20,6 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const filterBar = getService('filterBar'); const dataGrid = getService('dataGrid'); + const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects([ 'common', 'discover', @@ -107,12 +108,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataGrid.clickRowToggle({ rowIndex: 0 }); const rowActions = await dataGrid.getRowActions({ rowIndex: 0 }); await rowActions[1].click(); - await PageObjects.common.sleep(250); - // accept alert if it pops up + + // close popup const alert = await browser.getAlert(); await alert?.accept(); - expect(await browser.getCurrentUrl()).to.contain('#/context'); - await PageObjects.header.waitUntilLoadingHasFinished(); + if (await testSubjects.exists('confirmModalConfirmButton')) { + await testSubjects.click('confirmModalConfirmButton'); + } + + await retry.waitFor('navigate to context', async () => { + const currentUrl = await browser.getCurrentUrl(); + return currentUrl.includes('#/context'); + }); await retry.waitFor('document table has a length of 6', async () => { const nrOfDocs = (await dataGrid.getBodyRows()).length; log.debug('document table length', nrOfDocs); diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index 0c3464e103eca..7fde46031e737 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -296,6 +296,7 @@ export class DataGridService extends FtrService { await this.openColMenuByField(field); await this.find.clickByButtonText('Copy name'); } + public async clickEditField(field: string) { await this.openColMenuByField(field); await this.testSubjects.click('gridEditFieldButton'); diff --git a/test/functional/services/toasts.ts b/test/functional/services/toasts.ts index 6866c2a8b2fd2..9e7755c09b662 100644 --- a/test/functional/services/toasts.ts +++ b/test/functional/services/toasts.ts @@ -67,9 +67,9 @@ export class ToastsService extends FtrService { return await list.findByCssSelector(`.euiToast:nth-child(${index})`); } - public async getToastContent(index: number) { - const toast = await this.getToastElement(index); - return await toast.getVisibleText(); + public async getAllToastElements() { + const list = await this.getGlobalToastList(); + return await list.findAllByCssSelector(`.euiToast`); } private async getGlobalToastList() { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 315081311e48d..9e334bb8ec7f8 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -1920,7 +1920,6 @@ "discover.context.olderDocumentsWarning": "Seuls {docCount} documents plus anciens que le document ancré ont été trouvés.", "discover.context.pageTitle": "Les documents relatifs à #{anchorId}", "discover.contextViewRoute.errorMessage": "Aucune donnée correspondante pour l'ID {dataViewId}", - "discover.discoverError.missingQueryParamsError": "La chaîne de requête URL est manquante {missingParamsList}.", "discover.doc.pageTitle": "Document unique - #{id}", "discover.doc.somethingWentWrongDescription": "Index {indexName} manquant.", "discover.docExplorerCallout.bodyMessage": "Triez, sélectionnez et comparez rapidement les données, redimensionnez les colonnes et affichez les documents en plein écran grâce à l'{documentExplorer}.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a0044c2613305..3ed2387f36680 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1918,7 +1918,6 @@ "discover.context.olderDocumentsWarning": "アンカーよりも古いドキュメントは{docCount}件しか見つかりませんでした。", "discover.context.pageTitle": "#{anchorId}の周りのドキュメント", "discover.contextViewRoute.errorMessage": "ID {dataViewId}の一致するデータビューが見つかりません", - "discover.discoverError.missingQueryParamsError": "URLクエリ文字列が見つかりません{missingParamsList}。", "discover.doc.pageTitle": "1つのドキュメント - #{id}", "discover.doc.somethingWentWrongDescription": "{indexName}が見つかりません。", "discover.docTable.limitedSearchResultLabel": "{resultCount}件の結果のみが表示されます。検索結果を絞り込みます。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 42668ff33f42e..e7194fc92904e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1920,7 +1920,6 @@ "discover.context.olderDocumentsWarning": "仅可以找到 {docCount} 个比定位标记旧的文档。", "discover.context.pageTitle": "#{anchorId} 周围的文档", "discover.contextViewRoute.errorMessage": "没有与 ID {dataViewId} 相匹配的数据视图", - "discover.discoverError.missingQueryParamsError": "URL 查询字符串缺少 {missingParamsList}。", "discover.doc.pageTitle": "单个文档 - #{id}", "discover.doc.somethingWentWrongDescription": "{indexName} 缺失。", "discover.docExplorerCallout.bodyMessage": "使用 {documentExplorer} 快速排序、选择和比较数据,调整列大小并以全屏方式查看文档。", From fc62b8201357e9fa9ca5b719c77b1d56e3ea1db4 Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski <tomasz.ciecierski@elastic.co> Date: Thu, 3 Nov 2022 16:59:33 +0100 Subject: [PATCH 78/86] [Osquery] Fix more bugs (#143370) --- .../osquery/cypress/e2e/all/packs.cy.ts | 100 +++++++++++++++++- .../osquery/cypress/screens/live_query.ts | 5 + .../public/form/results_type_field.tsx | 9 +- .../public/lens/view_results_in_lens.tsx | 5 + .../form/live_query_query_field.tsx | 3 +- .../form/pack_queries_status_table.tsx | 11 ++ .../public/packs/form/queries_field.tsx | 8 +- .../queries/ecs_mapping_editor_field.tsx | 2 +- .../osquery/public/results/results_table.tsx | 8 +- .../public/routes/saved_queries/edit/tabs.tsx | 9 +- .../timelines/add_to_timeline_button.tsx | 2 +- .../server/routes/pack/create_pack_route.ts | 7 +- .../server/routes/pack/update_pack_route.ts | 15 ++- .../osquery/server/routes/pack/utils.test.ts | 67 +++++++----- .../osquery/server/routes/pack/utils.ts | 41 +++++-- 15 files changed, 227 insertions(+), 65 deletions(-) diff --git a/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts index 0d77b7f4ff5f1..7dc3d1e646075 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts @@ -17,7 +17,7 @@ import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver'; import { preparePack } from '../../tasks/packs'; import { addIntegration, closeModalIfVisible } from '../../tasks/integrations'; import { DEFAULT_POLICY } from '../../screens/fleet'; -import { getSavedQueriesDropdown } from '../../screens/live_query'; +import { getIdFormField, getSavedQueriesDropdown } from '../../screens/live_query'; import { ROLES } from '../../test'; import { getRandomInt } from '../../tasks/helpers'; @@ -47,6 +47,104 @@ describe('ALL - Packs', () => { runKbnArchiverScript(ArchiverMethod.UNLOAD, 'ecs_mapping_3'); }); + it('Check if result type is correct', () => { + cy.contains('Packs').click(); + findAndClickButton('Add pack'); + findFormFieldByRowsLabelAndType('Name', 'ResultType'); + findAndClickButton('Add query'); + cy.contains('Attach next query'); + getIdFormField().type('Query1'); + inputQuery('select * from uptime;'); + cy.wait(500); // wait for the validation to trigger - cypress is way faster than users ;) + cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click(); + findAndClickButton('Add query'); + cy.contains('Attach next query'); + getIdFormField().type('Query2'); + inputQuery('select * from uptime;'); + + cy.getBySel('resultsTypeField').click(); + cy.contains('Differential').click(); + cy.wait(500); // wait for the validation to trigger - cypress is way faster than users ;) + + cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click(); + findAndClickButton('Add query'); + cy.contains('Attach next query'); + getIdFormField().type('Query3'); + inputQuery('select * from uptime;'); + cy.getBySel('resultsTypeField').click(); + cy.contains('Differential (Ignore removals)').click(); + cy.wait(500); // wait for the validation to trigger - cypress is way faster than users ;) + + cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click(); + findAndClickButton('Save pack'); + cy.react('ScheduledQueryNameComponent', { + props: { + name: 'ResultType', + }, + }).click(); + + findAndClickButton('Edit'); + cy.contains('Query1'); + cy.contains('Query2'); + cy.contains('Query3'); + cy.react('CustomItemAction', { + props: { index: 0, item: { id: 'Query1' } }, + }).click(); + cy.getBySel('resultsTypeField').contains('Snapshot').click(); + cy.contains('Differential').click(); + + cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click(); + + cy.react('CustomItemAction', { + props: { index: 0, item: { id: 'Query2' } }, + }).click(); + cy.getBySel('resultsTypeField').contains('Differential').click(); + cy.contains('Differential (Ignore removals)').click(); + + cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click(); + cy.react('CustomItemAction', { + props: { index: 0, item: { id: 'Query3' } }, + }).click(); + cy.getBySel('resultsTypeField').contains('(Ignore removals)').click(); + cy.contains('Snapshot').click(); + + cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click(); + findFormFieldByRowsLabelAndType( + 'Scheduled agent policies (optional)', + 'fleet server {downArrow} {enter}' + ); + findAndClickButton('Update pack'); + closeModalIfVisible(); + + cy.contains( + 'Create packs to organize sets of queries and to schedule queries for agent policies.' + ); + const queries = { + Query1: { + interval: 3600, + query: 'select * from uptime;', + removed: true, + snapshot: false, + }, + Query2: { + interval: 3600, + query: 'select * from uptime;', + removed: false, + snapshot: false, + }, + Query3: { + interval: 3600, + query: 'select * from uptime;', + }, + }; + cy.request('/internal/osquery/fleet_wrapper/package_policies').then((response) => { + const item = response.body.items.find( + (policy: { policy_id: string }) => policy.policy_id === 'fleet-server-policy' + ); + + expect(item.inputs[0].config.osquery.value.packs.ResultType.queries).to.deep.equal(queries); + }); + }); it('should add a pack from a saved query', () => { cy.contains('Packs').click(); findAndClickButton('Add pack'); diff --git a/x-pack/plugins/osquery/cypress/screens/live_query.ts b/x-pack/plugins/osquery/cypress/screens/live_query.ts index d3be652c24c2c..32d3a54881b1d 100644 --- a/x-pack/plugins/osquery/cypress/screens/live_query.ts +++ b/x-pack/plugins/osquery/cypress/screens/live_query.ts @@ -17,3 +17,8 @@ export const getSavedQueriesDropdown = () => cy.react('EuiComboBox', { props: { placeholder: 'Search for a query to run, or write a new query below' }, }); + +export const getIdFormField = () => + cy.react('EuiFormRow', { + props: { label: 'ID' }, + }); diff --git a/x-pack/plugins/osquery/public/form/results_type_field.tsx b/x-pack/plugins/osquery/public/form/results_type_field.tsx index ccc1961259c38..37fe9d9ceee38 100644 --- a/x-pack/plugins/osquery/public/form/results_type_field.tsx +++ b/x-pack/plugins/osquery/public/form/results_type_field.tsx @@ -14,7 +14,7 @@ import { EuiFlexItem, EuiText, } from '@elastic/eui'; -import { useController } from 'react-hook-form'; +import { useController, useFormState } from 'react-hook-form'; import { FormattedMessage } from '@kbn/i18n-react'; import deepEqual from 'fast-deep-equal'; import { i18n } from '@kbn/i18n'; @@ -57,18 +57,20 @@ interface ResultsTypeFieldProps { const ResultsTypeFieldComponent: React.FC<ResultsTypeFieldProps> = ({ euiFieldProps = {} }) => { const [selectedOption, setSelectedOption] = useState(SNAPSHOT_OPTION.value); + const { defaultValues } = useFormState(); + const { field: { onChange: onSnapshotChange, value: snapshotValue }, } = useController({ name: 'snapshot', - defaultValue: true, + defaultValue: defaultValues?.snapshot, }); const { field: { onChange: onRemovedChange, value: removedValue }, } = useController({ name: 'removed', - defaultValue: false, + defaultValue: defaultValues?.removed, }); const handleChange = useCallback( @@ -142,6 +144,7 @@ const ResultsTypeFieldComponent: React.FC<ResultsTypeFieldProps> = ({ euiFieldPr fullWidth > <EuiSuperSelect + data-test-subj={'resultsTypeField'} options={FIELD_OPTIONS} fullWidth valueOfSelected={selectedOption} diff --git a/x-pack/plugins/osquery/public/lens/view_results_in_lens.tsx b/x-pack/plugins/osquery/public/lens/view_results_in_lens.tsx index 1e3c8b896f048..247255d9ffba3 100644 --- a/x-pack/plugins/osquery/public/lens/view_results_in_lens.tsx +++ b/x-pack/plugins/osquery/public/lens/view_results_in_lens.tsx @@ -39,6 +39,7 @@ const ViewResultsInLensActionComponent: React.FC<ViewResultsInLensActionProps> = mode, }) => { const lensService = useKibana().services.lens; + const isLensAvailable = lensService?.canUseEditor(); const { data: logsDataView } = useLogsDataView({ skip: !actionId, checkOnly: true }); const handleClick = useCallback( @@ -68,6 +69,10 @@ const ViewResultsInLensActionComponent: React.FC<ViewResultsInLensActionProps> = const isDisabled = useMemo(() => !actionId || !logsDataView, [actionId, logsDataView]); + if (!isLensAvailable) { + return null; + } + if (buttonType === ViewResultsActionButtonType.button) { return ( <EuiButtonEmpty size="xs" iconType="lensApp" onClick={handleClick} isDisabled={isDisabled}> diff --git a/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx b/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx index 4c762ee3b1a8e..c2d5286e92a83 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx @@ -101,8 +101,7 @@ const LiveQueryQueryFieldComponent: React.FC<LiveQueryQueryFieldProps> = ({ () => !( permissions.writeLiveQueries || - permissions.runSavedQueries || - permissions.readSavedQueries + (permissions.runSavedQueries && permissions.readSavedQueries) ), [permissions.readSavedQueries, permissions.runSavedQueries, permissions.writeLiveQueries] ); diff --git a/x-pack/plugins/osquery/public/live_queries/form/pack_queries_status_table.tsx b/x-pack/plugins/osquery/public/live_queries/form/pack_queries_status_table.tsx index 52f5f0897cb31..1f38bd3d65dcb 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/pack_queries_status_table.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/pack_queries_status_table.tsx @@ -49,6 +49,17 @@ const EMPTY_ARRAY: PackQueryStatusItem[] = []; const StyledEuiBasicTable = styled(EuiBasicTable)` .euiTableRow.euiTableRow-isExpandedRow > td > div { padding: 0; + border: 1px solid #d3dae6; + } + div.euiDataGrid__virtualized::-webkit-scrollbar { + display: none; + } + .euiDataGrid > div { + .euiDataGrid__scrollOverlay { + box-shadow: none; + } + border-left: 0px; + border-right: 0px; } `; diff --git a/x-pack/plugins/osquery/public/packs/form/queries_field.tsx b/x-pack/plugins/osquery/public/packs/form/queries_field.tsx index 03ab8de199269..e9456b18d5416 100644 --- a/x-pack/plugins/osquery/public/packs/form/queries_field.tsx +++ b/x-pack/plugins/osquery/public/packs/form/queries_field.tsx @@ -97,12 +97,8 @@ const QueriesFieldComponent: React.FC<QueriesFieldProps> = ({ euiFieldProps }) = draft.ecs_mapping = updatedQuery.ecs_mapping; } - if (updatedQuery.snapshot === false) { - draft.snapshot = updatedQuery.snapshot; - if (updatedQuery.removed !== undefined) { - draft.removed = updatedQuery.removed; - } - } + draft.snapshot = updatedQuery.snapshot; + draft.removed = updatedQuery.removed; return draft; }) diff --git a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx index 8978aff73d863..147150e7fb6b6 100644 --- a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx +++ b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx @@ -588,10 +588,10 @@ const OsqueryColumnFieldComponent: React.FC<OsqueryColumnFieldProps> = ({ rowHeight={32} isClearable singleSelection={isSingleSelection ? SINGLE_SELECTION : false} - options={(resultTypeField.value === 'field' && euiFieldProps.options) || EMPTY_ARRAY} idAria={idAria} helpText={selectedOptions[0]?.value?.description} {...euiFieldProps} + options={(resultTypeField.value === 'field' && euiFieldProps.options) || EMPTY_ARRAY} /> </EuiFlexItem> </EuiFlexGroup> diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx index c8912328fa463..23dca7ea1e019 100644 --- a/x-pack/plugins/osquery/public/results/results_table.tsx +++ b/x-pack/plugins/osquery/public/results/results_table.tsx @@ -28,6 +28,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react'; import type { ECSMapping } from '@kbn/osquery-io-ts-types'; import { pagePathGetters } from '@kbn/fleet-plugin/public'; +import styled from 'styled-components'; import { AddToTimelineButton } from '../timelines/add_to_timeline_button'; import { useAllResults } from './use_all_results'; import type { ResultEdges } from '../../common/search_strategy'; @@ -46,6 +47,10 @@ import { AddToCaseWrapper } from '../cases/add_to_cases'; const DataContext = createContext<ResultEdges>([]); +const StyledEuiDataGrid = styled(EuiDataGrid)` + max-height: 500px; +`; + export interface ResultsTableComponentProps { actionId: string; selectedAgent?: string; @@ -419,7 +424,7 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({ </EuiPanel> ) : ( <DataContext.Provider value={allResultsData?.edges}> - <EuiDataGrid + <StyledEuiDataGrid data-test-subj="osqueryResultsTable" aria-label="Osquery results" columns={columns} @@ -429,7 +434,6 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({ leadingControlColumns={leadingControlColumns} sorting={tableSorting} pagination={tablePagination} - height="500px" toolbarVisibility={toolbarVisibility} /> </DataContext.Provider> diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx index 2cf2b75b8e744..81ccb30d65e10 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx @@ -9,9 +9,16 @@ import { EuiTabbedContent, EuiNotificationBadge } from '@elastic/eui'; import React, { useMemo } from 'react'; import type { ECSMapping } from '@kbn/osquery-io-ts-types'; +import styled from 'styled-components'; import { ResultsTable } from '../../../results/results_table'; import { ActionResultsSummary } from '../../../action_results/action_results_summary'; +const StyledEuiTabbedContent = styled(EuiTabbedContent)` + div.euiTabs { + padding-left: 8px; + } +`; + interface ResultTabsProps { actionId: string; agentIds?: string[]; @@ -64,7 +71,7 @@ const ResultTabsComponent: React.FC<ResultTabsProps> = ({ ); return ( - <EuiTabbedContent + <StyledEuiTabbedContent // TODO: extend the EuiTabbedContent component to support EuiTabs props // bottomBorder={false} tabs={tabs} diff --git a/x-pack/plugins/osquery/public/timelines/add_to_timeline_button.tsx b/x-pack/plugins/osquery/public/timelines/add_to_timeline_button.tsx index 3d6e476bf02f1..48cd3a0ff71c1 100644 --- a/x-pack/plugins/osquery/public/timelines/add_to_timeline_button.tsx +++ b/x-pack/plugins/osquery/public/timelines/add_to_timeline_button.tsx @@ -28,7 +28,7 @@ export const AddToTimelineButton = (props: AddToTimelineButtonProps) => { const queryIds = isArray(value) ? value : [value]; const TimelineIconComponent = useCallback( (timelineComponentProps) => ( - <EuiButtonIcon iconType={'timelines'} {...timelineComponentProps} size="xs" {...iconProps} /> + <EuiButtonIcon iconType="timelines" {...timelineComponentProps} size="xs" {...iconProps} /> ), [iconProps] ); diff --git a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts index 3dda3cdb36b0d..2726355d23dcd 100644 --- a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts @@ -19,7 +19,7 @@ import type { OsqueryAppContext } from '../../lib/osquery_app_context_services'; import { OSQUERY_INTEGRATION_NAME } from '../../../common'; import { PLUGIN_ID } from '../../../common'; import { packSavedObjectType } from '../../../common/types'; -import { convertPackQueriesToSO, convertSOQueriesToPack } from './utils'; +import { convertPackQueriesToSO, convertSOQueriesToPackConfig } from './utils'; import { getInternalSavedObjectsClient } from '../utils'; import type { PackSavedObjectAttributes } from '../../common/types'; @@ -144,10 +144,7 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte } set(draft, `inputs[0].config.osquery.value.packs.${packSO.attributes.name}`, { - queries: convertSOQueriesToPack(queries, { - removeMultiLines: true, - removeResultType: true, - }), + queries: convertSOQueriesToPackConfig(queries), }); return draft; diff --git a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts index 5b0d76131ee28..984f59bb51a29 100644 --- a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts @@ -20,7 +20,11 @@ import { OSQUERY_INTEGRATION_NAME } from '../../../common'; import { packSavedObjectType } from '../../../common/types'; import type { OsqueryAppContext } from '../../lib/osquery_app_context_services'; import { PLUGIN_ID } from '../../../common'; -import { convertSOQueriesToPack, convertPackQueriesToSO } from './utils'; +import { + convertSOQueriesToPack, + convertPackQueriesToSO, + convertSOQueriesToPackConfig, +} from './utils'; import { getInternalSavedObjectsClient } from '../utils'; import type { PackSavedObjectAttributes } from '../../common/types'; @@ -283,10 +287,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte draft, `inputs[0].config.osquery.value.packs.${updatedPackSO.attributes.name}`, { - queries: convertSOQueriesToPack(updatedPackSO.attributes.queries, { - removeMultiLines: true, - removeResultType: true, - }), + queries: convertSOQueriesToPackConfig(updatedPackSO.attributes.queries), } ); @@ -316,9 +317,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte draft, `inputs[0].config.osquery.value.packs.${updatedPackSO.attributes.name}`, { - queries: convertSOQueriesToPack(updatedPackSO.attributes.queries, { - removeResultType: true, - }), + queries: convertSOQueriesToPackConfig(updatedPackSO.attributes.queries), } ); diff --git a/x-pack/plugins/osquery/server/routes/pack/utils.test.ts b/x-pack/plugins/osquery/server/routes/pack/utils.test.ts index 3ddb074a6edbd..662bd5aa9824c 100644 --- a/x-pack/plugins/osquery/server/routes/pack/utils.test.ts +++ b/x-pack/plugins/osquery/server/routes/pack/utils.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { convertSOQueriesToPack } from './utils'; +import { convertSOQueriesToPack, convertSOQueriesToPackConfig } from './utils'; const getTestQueries = (additionalFields?: Record<string, unknown>, packName = 'default') => ({ [packName]: { @@ -31,12 +31,13 @@ const getTestQueries = (additionalFields?: Record<string, unknown>, packName = ' }, }); -const oneLiner = { +const getOneLiner = (additionParams: Record<string, unknown>) => ({ default: { interval: 3600, query: `select u.username, p.pid, p.name, pos.local_address, pos.local_port, p.path, p.cmdline, pos.remote_address, pos.remote_port from processes as p join users as u on u.uid=p.uid join process_open_sockets as pos on pos.pid=p.pid where pos.remote_port !='0' limit 1000;`, + ...additionParams, }, -}; +}); describe('Pack utils', () => { describe('convertSOQueriesToPack', () => { @@ -44,43 +45,59 @@ describe('Pack utils', () => { const convertedQueries = convertSOQueriesToPack(getTestQueries()); expect(convertedQueries).toStrictEqual(getTestQueries()); }); - test('converts to pack with converting query to single line', () => { - const convertedQueries = convertSOQueriesToPack(getTestQueries(), { removeMultiLines: true }); - expect(convertedQueries).toStrictEqual({ - ...oneLiner, - }); - }); test('converts to object with pack names after query.id', () => { const convertedQueries = convertSOQueriesToPack(getTestQueries({ id: 'testId' })); expect(convertedQueries).toStrictEqual(getTestQueries({}, 'testId')); }); - test('converts with results snapshot set false', () => { - const convertedQueries = convertSOQueriesToPack( - getTestQueries({ snapshot: false, removed: true }), - { removeResultType: true } - ); - expect(convertedQueries).toStrictEqual(getTestQueries({ removed: true, snapshot: false })); - }); - test('converts with results snapshot set true and removed false', () => { + + test('converts with results snapshot set true and removed true', () => { const convertedQueries = convertSOQueriesToPack( - getTestQueries({ snapshot: true, removed: true }), - { removeResultType: true } + getTestQueries({ snapshot: true, removed: true }) ); - expect(convertedQueries).toStrictEqual(getTestQueries({})); + expect(convertedQueries).toStrictEqual(getTestQueries({ snapshot: true, removed: true })); }); test('converts with results snapshot set true but removed false', () => { const convertedQueries = convertSOQueriesToPack( - getTestQueries({ snapshot: true, removed: false }), - { removeResultType: true } + getTestQueries({ snapshot: true, removed: false }) ); - expect(convertedQueries).toStrictEqual(getTestQueries({})); + expect(convertedQueries).toStrictEqual(getTestQueries({ snapshot: true, removed: false })); }); test('converts with both results set to false', () => { const convertedQueries = convertSOQueriesToPack( - getTestQueries({ snapshot: false, removed: false }), - { removeResultType: true } + getTestQueries({ snapshot: false, removed: false }) ); expect(convertedQueries).toStrictEqual(getTestQueries({ removed: false, snapshot: false })); }); }); + describe('convertSOQueriesToPackConfig', () => { + test('converts to pack with converting query to single line', () => { + const convertedQueries = convertSOQueriesToPackConfig(getTestQueries()); + expect(convertedQueries).toStrictEqual(getOneLiner({})); + }); + + test('if snapshot true and removed true - return empty {}', () => { + const convertedQueries = convertSOQueriesToPackConfig( + getTestQueries({ snapshot: true, removed: true }) + ); + expect(convertedQueries).toStrictEqual(getOneLiner({})); + }); + test('if snapshot true and removed false - return empty {}', () => { + const convertedQueries = convertSOQueriesToPackConfig( + getTestQueries({ snapshot: true, removed: false }) + ); + expect(convertedQueries).toStrictEqual(getOneLiner({})); + }); + test('converts with results snapshot set false', () => { + const convertedQueries = convertSOQueriesToPackConfig( + getTestQueries({ snapshot: false, removed: true }) + ); + expect(convertedQueries).toStrictEqual(getOneLiner({ snapshot: false, removed: true })); + }); + test('converts with both results set to false', () => { + const convertedQueries = convertSOQueriesToPackConfig( + getTestQueries({ snapshot: false, removed: false }) + ); + expect(convertedQueries).toStrictEqual(getOneLiner({ removed: false, snapshot: false })); + }); + }); }); diff --git a/x-pack/plugins/osquery/server/routes/pack/utils.ts b/x-pack/plugins/osquery/server/routes/pack/utils.ts index 4342cdb3ead8e..1630f303d4f79 100644 --- a/x-pack/plugins/osquery/server/routes/pack/utils.ts +++ b/x-pack/plugins/osquery/server/routes/pack/utils.ts @@ -18,9 +18,7 @@ export const convertPackQueriesToSO = (queries) => const ecsMapping = value.ecs_mapping && convertECSMappingToArray(value.ecs_mapping); acc.push({ id: key, - ...pick(value, ['name', 'query', 'interval', 'platform', 'version']), - ...(value.snapshot !== undefined ? { snapshot: value.snapshot } : {}), - ...(value.removed !== undefined ? { removed: value.removed } : {}), + ...pick(value, ['name', 'query', 'interval', 'platform', 'version', 'snapshot', 'removed']), ...(ecsMapping ? { ecs_mapping: ecsMapping } : {}), }); @@ -39,27 +37,50 @@ export const convertPackQueriesToSO = (queries) => export const convertSOQueriesToPack = ( // @ts-expect-error update types - queries, - options?: { removeMultiLines?: boolean; removeResultType?: boolean } + queries +) => + reduce( + queries, + // eslint-disable-next-line @typescript-eslint/naming-convention + (acc, { id: queryId, ecs_mapping, query, platform, ...rest }, key) => { + const index = queryId ? queryId : key; + acc[index] = { + ...rest, + query, + ...(!isEmpty(ecs_mapping) + ? isArray(ecs_mapping) + ? { ecs_mapping: convertECSMappingToObject(ecs_mapping) } + : { ecs_mapping } + : {}), + ...(platform === DEFAULT_PLATFORM || platform === undefined ? {} : { platform }), + }; + + return acc; + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + {} as Record<string, any> + ); + +export const convertSOQueriesToPackConfig = ( + // @ts-expect-error update types + queries ) => reduce( queries, // eslint-disable-next-line @typescript-eslint/naming-convention (acc, { id: queryId, ecs_mapping, query, platform, removed, snapshot, ...rest }, key) => { - const resultType = !snapshot ? { removed, snapshot } : {}; + const resultType = snapshot === false ? { removed, snapshot } : {}; const index = queryId ? queryId : key; acc[index] = { ...rest, - query: options?.removeMultiLines ? removeMultilines(query) : query, + query: removeMultilines(query), ...(!isEmpty(ecs_mapping) ? isArray(ecs_mapping) ? { ecs_mapping: convertECSMappingToObject(ecs_mapping) } : { ecs_mapping } : {}), ...(platform === DEFAULT_PLATFORM || platform === undefined ? {} : { platform }), - ...(options?.removeResultType - ? resultType - : { ...(snapshot ? { snapshot } : {}), ...(removed ? { removed } : {}) }), + ...resultType, }; return acc; From e799ee8f74c93b3143206f0d024390f94a7f664c Mon Sep 17 00:00:00 2001 From: Joe Reuter <johannes.reuter@elastic.co> Date: Thu, 3 Nov 2022 17:32:18 +0100 Subject: [PATCH 79/86] add section about re-render performance (#144535) --- x-pack/plugins/lens/readme.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/plugins/lens/readme.md b/x-pack/plugins/lens/readme.md index d276a45eb00ac..4dbb303fe8090 100644 --- a/x-pack/plugins/lens/readme.md +++ b/x-pack/plugins/lens/readme.md @@ -159,6 +159,14 @@ The Lens embeddable is handling data fetching internally, this means as soon as * When refreshing, simply call `session.start` again and update your state - Lens will discard the existing cache and re-fetch even if the query doesn't change at all * When unmounting your app, call `session.clear` to end the current session +## Performance considerations + +As the Lens embeddable is doing data fetching and processing internally as soon as props are passed to it, it's beneficial to make sure it's not rendered with new props if that's avoidable. Lens is aborting in-flight search requests as soon as the chart configuration changes based on props, but there's still non-trivial work kicked off in multiple parts of the stack. To avoid this, make sure to keep these things in mind: +* Changing the reference of the `attributes` prop will cause the Lens vis to re-initialize from scratch. Try to keep it stable as long as possible, e.g. by using `useMemo` instead of re-constructing it on the fly on every render +* Pass time range and filters in via the dedicated props instead of part of the `attributes` to avoid re-initalization. Changing time range or filters might kick off another search request so it makes sense to keep this stable as well, but this can also be controlled somewhat by the session id (see section above) +* The chart will adjust itself automatically to layout changes, no need to trigger another re-render in this situation + + ## Getting data tables and requests/responses The Lens embeddable is handling both data fetching and rendering - all the user has to do is to supply the configuration. However in some cases the resulting values are necessary for other parts of the UI - to access them pass supply an `onLoad` callback prop to the component. It will be called with an `adapters` object which allows you to access the current data tables and requests/responses: From 42337135d06b075e91fac81eeb86a4edca576a72 Mon Sep 17 00:00:00 2001 From: Cristina Amico <criamico@users.noreply.github.com> Date: Thu, 3 Nov 2022 17:47:45 +0100 Subject: [PATCH 80/86] [Fleet] Fix refresh in settings section and flyout state (#144542) * [Fleet] Fix refresh in settings section and flyout state * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../hooks/use_fleet_server_host.ts | 14 +------------- .../steps/get_started.tsx | 2 +- .../settings_page/fleet_server_hosts_section.tsx | 12 ++++-------- .../fleet/sections/settings/index.tsx | 15 +++++++++++++-- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts index 34df4465358e4..7dfd6bb1faf60 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useState } from 'react'; import { intersection } from 'lodash'; @@ -61,18 +61,6 @@ export const useFleetServerHost = (): FleetServerHostForm => { const { data } = useGetFleetServerHosts(); - useEffect(() => { - const fleetServerHosts = data?.items ?? []; - const defaultHost = fleetServerHosts.find((item) => item.is_default === true); - - // Get the default host, otherwise the first fleet server found - if (defaultHost) { - setFleetServerHost(defaultHost); - } else { - setFleetServerHost(fleetServerHosts[0]); - } - }, [data?.items, fleetServerHost]); - const saveFleetServerHost = useCallback( async (newFleetServerHost: FleetServerHost) => { setIsFleetServerHostSubmitted(false); diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx index 3224874ea0b95..26b149bdfa2de 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx @@ -64,7 +64,7 @@ const GettingStartedStepContent: React.FunctionComponent<QuickStartCreateForm> = <EuiText> <FormattedMessage id="xpack.fleet.fleetServerFlyout.generateFleetServerPolicySuccessInstructions" - defaultMessage="Fleet server policy and service token have been generated. Host configured at {hostUrl}. You can edit your Fleet Server hosts in {fleetSettingsLink}." + defaultMessage="Fleet server policy and service token have been generated. Host configured at {hostUrl}. You can edit your Fleet Server hosts in {fleetSettingsLink}." values={{ hostUrl: <EuiCode>{fleetServerHost?.host_urls[0]}</EuiCode>, fleetSettingsLink: ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/fleet_server_hosts_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/fleet_server_hosts_section.tsx index 806df98d517b3..1114efb814a21 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/fleet_server_hosts_section.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/settings_page/fleet_server_hosts_section.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React from 'react'; import { EuiTitle, EuiLink, EuiText, EuiSpacer, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { FleetServerHost } from '../../../../types'; -import { useFlyoutContext, useStartServices } from '../../../../hooks'; +import { useLink, useStartServices } from '../../../../hooks'; import { FleetServerHostsTable } from '../fleet_server_hosts_table'; export interface FleetServerHostsSectionProps { @@ -24,11 +24,7 @@ export const FleetServerHostsSection: React.FunctionComponent<FleetServerHostsSe deleteFleetServerHost, }) => { const { docLinks } = useStartServices(); - const flyoutContext = useFlyoutContext(); - - const onClickAddFleetServer = useCallback(() => { - flyoutContext.openFleetServerFlyout(); - }, [flyoutContext]); + const { getHref } = useLink(); return ( <> @@ -65,7 +61,7 @@ export const FleetServerHostsSection: React.FunctionComponent<FleetServerHostsSe <EuiSpacer size="s" /> <EuiButtonEmpty iconType="plusInCircle" - onClick={onClickAddFleetServer} + href={getHref('settings_create_fleet_server_hosts')} data-test-subj="settings.fleetServerHosts.addFleetServerHostBtn" > <FormattedMessage diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/index.tsx index bd0b7124a9c77..fde10e18a67c5 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/index.tsx @@ -14,11 +14,14 @@ import { useGetOutputs, useGetDownloadSources, useGetFleetServerHosts, + useFlyoutContext, } from '../../hooks'; import { FLEET_ROUTING_PATHS, pagePathGetters } from '../../constants'; import { DefaultLayout } from '../../layouts'; import { Loading } from '../../components'; +import { FleetServerFlyout } from '../../components'; + import { SettingsPage } from './components/settings_page'; import { withConfirmModalProvider } from './hooks/use_confirm_modal'; import { FleetServerHostsFlyout } from './components/fleet_server_hosts_flyout'; @@ -35,6 +38,7 @@ export const SettingsApp = withConfirmModalProvider(() => { const outputs = useGetOutputs(); const fleetServerHosts = useGetFleetServerHosts(); const downloadSources = useGetDownloadSources(); + const flyoutContext = useFlyoutContext(); const { deleteOutput } = useDeleteOutput(outputs.resendRequest); const { deleteDownloadSource } = useDeleteDownloadSource(downloadSources.resendRequest); @@ -45,11 +49,18 @@ export const SettingsApp = withConfirmModalProvider(() => { const resendFleetServerHostsRequest = fleetServerHosts.resendRequest; const onCloseCallback = useCallback(() => { + flyoutContext.closeFleetServerFlyout(); resendOutputRequest(); resendDownloadSourceRequest(); resendFleetServerHostsRequest(); history.replace(pagePathGetters.settings()[1]); - }, [resendOutputRequest, resendDownloadSourceRequest, resendFleetServerHostsRequest, history]); + }, [ + flyoutContext, + resendOutputRequest, + resendDownloadSourceRequest, + resendFleetServerHostsRequest, + history, + ]); if ( (outputs.isLoading && outputs.isInitialRequest) || @@ -91,7 +102,7 @@ export const SettingsApp = withConfirmModalProvider(() => { </Route> <Route path={FLEET_ROUTING_PATHS.settings_create_fleet_server_hosts}> <EuiPortal> - <FleetServerHostsFlyout onClose={onCloseCallback} /> + <FleetServerFlyout onClose={onCloseCallback} /> </EuiPortal> </Route> <Route path={FLEET_ROUTING_PATHS.settings_create_outputs}> From 190354ae30c26c7fb98d7141dae144fc6f812964 Mon Sep 17 00:00:00 2001 From: Maxim Palenov <maxim.palenov@elastic.co> Date: Thu, 3 Nov 2022 19:14:42 +0200 Subject: [PATCH 81/86] [Security Solution] Fix success bulk edit toast message (#144497) **Resolves:** https://github.com/elastic/kibana/issues/139897 ## Summary Fixes bulk edit success toast's message. Displays `You've successfully updated X rules. If you did not select to apply changes to rules using Kibana data views, those rules were not updated and will continue using data views.` when rule's index patterns were edited and `You've successfully updated X rules.` in the other cases. *Before:* https://user-images.githubusercontent.com/3775283/199676233-d3287d97-8e66-4938-bc80-7c643ca0e0ad.mov *After:* https://user-images.githubusercontent.com/3775283/199676250-0814ea26-a9bb-4402-a8db-8c2f0e394563.mov ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../e2e/detection_rules/bulk_edit_rules.cy.ts | 45 +++++++++++++++++++ .../bulk_actions/show_bulk_success_toast.ts | 22 --------- .../logic/bulk_actions/translations.ts | 32 +++++++++++-- .../logic/bulk_actions/use_bulk_export.ts | 36 ++------------- .../use_download_exported_rules.ts | 39 ++++++++++++++++ .../bulk_actions/use_execute_bulk_action.ts | 26 ++++++++--- ..._toast.ts => use_show_bulk_error_toast.ts} | 29 +++++++----- .../use_show_bulk_success_toast.ts | 38 ++++++++++++++++ .../bulk_actions/use_bulk_actions.tsx | 10 ++--- .../rules_table/use_rules_table_actions.tsx | 14 ++---- .../rules/rule_actions_overflow/index.tsx | 16 +++---- .../detection_engine/rules/translations.ts | 10 ++++- 12 files changed, 215 insertions(+), 102 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_success_toast.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules.ts rename x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/{show_bulk_error_toast.ts => use_show_bulk_error_toast.ts} (55%) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_success_toast.ts diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules.cy.ts index 0819581e14815..13a6fa0333779 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules.cy.ts @@ -289,6 +289,32 @@ describe('Detection rules, bulk edit', () => { checkTagsInTagsFilter(resultingTagsInFilter); }); + it('Display success toast after adding tags', () => { + const tagsToBeAdded = ['tag-to-add-1', 'tag-to-add-2']; + + // check if only pre-populated tags exist in the tags filter + checkTagsInTagsFilter(prePopulatedTags); + + cy.get(EUI_FILTER_SELECT_ITEM) + .should('have.length', prePopulatedTags.length) + .each(($el, index) => { + cy.wrap($el).should('have.text', prePopulatedTags[index]); + }); + + selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); + + // open add tags form and add 2 new tags + openBulkEditAddTagsForm(); + typeTags(tagsToBeAdded); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited }); + + cy.get(TOASTER_BODY).should( + 'have.text', + `You've successfully updated ${expectedNumberOfCustomRulesToBeEdited} rules` + ); + }); + it('Overwrite tags in custom rules', () => { const tagsToOverwrite = ['overwrite-tag-1']; @@ -392,6 +418,25 @@ describe('Detection rules, bulk edit', () => { hasIndexPatterns(resultingIndexPatterns.join('')); }); + it('Display success toast after editing the index pattern', () => { + const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*']; + + // select only rules that are not ML + selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); + unselectRuleByName(getMachineLearningRule().name); + + openBulkEditAddIndexPatternsForm(); + typeIndexPatterns(indexPattersToBeAdded); + submitBulkEditForm(); + + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfNotMLRules }); + + cy.get(TOASTER_BODY).should( + 'have.text', + `You've successfully updated ${expectedNumberOfNotMLRules} rules. If you did not select to apply changes to rules using Kibana data views, those rules were not updated and will continue using data views.` + ); + }); + it('Overwrite index patterns in custom rules', () => { const indexPattersToWrite = ['index-to-write-1-*', 'index-to-write-2-*']; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_success_toast.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_success_toast.ts deleted file mode 100644 index 7992747ba2f06..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_success_toast.ts +++ /dev/null @@ -1,22 +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 type { BulkActionSummary } from '..'; -import type { UseAppToasts } from '../../../../common/hooks/use_app_toasts'; -import type { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; -import { explainBulkSuccess, summarizeBulkSuccess } from './translations'; - -export function showBulkSuccessToast( - toasts: UseAppToasts, - action: BulkActionType, - summary: BulkActionSummary -): void { - toasts.addSuccess({ - title: summarizeBulkSuccess(action), - text: explainBulkSuccess(action, summary), - }); -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts index 76e0b702c5e94..f637a4d8970ba 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts @@ -6,7 +6,11 @@ */ import type { HTTPError } from '../../../../../common/detection_engine/types'; -import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import type { BulkActionEditPayload } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { + BulkActionEditType, + BulkActionType, +} from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; import type { BulkActionResponse, BulkActionSummary } from '../../api/api'; @@ -32,7 +36,10 @@ export function summarizeBulkSuccess(action: BulkActionType): string { } } -export function explainBulkSuccess(action: BulkActionType, summary: BulkActionSummary): string { +export function explainBulkSuccess( + action: Exclude<BulkActionType, BulkActionType.edit>, + summary: BulkActionSummary +): string { switch (action) { case BulkActionType.export: return getExportSuccessToastMessage(summary.succeeded, summary.total); @@ -48,10 +55,27 @@ export function explainBulkSuccess(action: BulkActionType, summary: BulkActionSu case BulkActionType.disable: return i18n.RULES_BULK_DISABLE_SUCCESS_DESCRIPTION(summary.succeeded); + } +} - case BulkActionType.edit: - return i18n.RULES_BULK_EDIT_SUCCESS_DESCRIPTION(summary.succeeded); +export function explainBulkEditSuccess( + editPayload: BulkActionEditPayload[], + summary: BulkActionSummary +): string { + if ( + editPayload.some( + (x) => + x.type === BulkActionEditType.add_index_patterns || + x.type === BulkActionEditType.set_index_patterns || + x.type === BulkActionEditType.delete_index_patterns + ) + ) { + return `${i18n.RULES_BULK_EDIT_SUCCESS_DESCRIPTION(summary.succeeded)}. ${ + i18n.RULES_BULK_EDIT_SUCCESS_INDEX_EDIT_DESCRIPTION + }`; } + + return i18n.RULES_BULK_EDIT_SUCCESS_DESCRIPTION(summary.succeeded); } export function summarizeBulkError(action: BulkActionType): string { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts index 59c38208d8b10..38a0fbcf50234 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts @@ -7,21 +7,15 @@ import { useCallback } from 'react'; import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; -import type { UseAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { downloadBlob } from '../../../../common/utils/download_blob'; -import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; -import { getExportedRulesCounts } from '../../../rule_management_ui/components/rules_table/helpers'; import { useBulkExportMutation } from '../../api/hooks/use_bulk_export_mutation'; -import { showBulkErrorToast } from './show_bulk_error_toast'; -import { showBulkSuccessToast } from './show_bulk_success_toast'; +import { useShowBulkErrorToast } from './use_show_bulk_error_toast'; import type { QueryOrIds } from '../../api/api'; import { useGuessRuleIdsForBulkAction } from './use_guess_rule_ids_for_bulk_action'; export function useBulkExport() { - const toasts = useAppToasts(); const { mutateAsync } = useBulkExportMutation(); + const showBulkErrorToast = useShowBulkErrorToast(); const guessRuleIdsForBulkAction = useGuessRuleIdsForBulkAction(); const rulesTableContext = useRulesTableContextOptional(); const setLoadingRules = rulesTableContext?.actions.setLoadingRules; @@ -35,35 +29,13 @@ export function useBulkExport() { }); return await mutateAsync(queryOrIds); } catch (error) { - showBulkErrorToast(toasts, BulkActionType.export, error); + showBulkErrorToast({ actionType: BulkActionType.export, error }); } finally { setLoadingRules?.({ ids: [], action: null }); } }, - [guessRuleIdsForBulkAction, setLoadingRules, mutateAsync, toasts] + [guessRuleIdsForBulkAction, setLoadingRules, mutateAsync, showBulkErrorToast] ); return { bulkExport }; } - -/** - * downloads exported rules, received from export action - * @param params.response - Blob results with exported rules - * @param params.toasts - {@link UseAppToasts} toasts service - * @param params.onSuccess - {@link OnActionSuccessCallback} optional toast to display when action successful - * @param params.onError - {@link OnActionErrorCallback} optional toast to display when action failed - */ -export async function downloadExportedRules({ - response, - toasts, -}: { - response: Blob; - toasts: UseAppToasts; -}) { - try { - downloadBlob(response, `${i18n.EXPORT_FILENAME}.ndjson`); - showBulkSuccessToast(toasts, BulkActionType.export, await getExportedRulesCounts(response)); - } catch (error) { - showBulkErrorToast(toasts, BulkActionType.export, error); - } -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules.ts new file mode 100644 index 0000000000000..bf607aeb0473e --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules.ts @@ -0,0 +1,39 @@ +/* + * 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 { useCallback } from 'react'; +import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { downloadBlob } from '../../../../common/utils/download_blob'; +import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; +import { getExportedRulesCounts } from '../../../rule_management_ui/components/rules_table/helpers'; +import { useShowBulkErrorToast } from './use_show_bulk_error_toast'; +import { useShowBulkSuccessToast } from './use_show_bulk_success_toast'; + +const DEFAULT_EXPORT_FILENAME = `${i18n.EXPORT_FILENAME}.ndjson`; + +/** + * downloads exported rules, received from export action + */ +export function useDownloadExportedRules() { + const showBulkSuccessToast = useShowBulkSuccessToast(); + const showBulkErrorToast = useShowBulkErrorToast(); + + return useCallback( + async (response: Blob) => { + try { + downloadBlob(response, DEFAULT_EXPORT_FILENAME); + showBulkSuccessToast({ + actionType: BulkActionType.export, + summary: await getExportedRulesCounts(response), + }); + } catch (error) { + showBulkErrorToast({ actionType: BulkActionType.export, error }); + } + }, + [showBulkSuccessToast, showBulkErrorToast] + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts index 80bf92c8e3583..a74708879ca21 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts @@ -12,13 +12,12 @@ import { APP_UI_ID } from '../../../../../common/constants'; import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { SecurityPageName } from '../../../../app/types'; import { getEditRuleUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../../common/lib/telemetry'; import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; import type { BulkAction } from '../../api/api'; import { useBulkActionMutation } from '../../api/hooks/use_bulk_action_mutation'; -import { showBulkErrorToast } from './show_bulk_error_toast'; -import { showBulkSuccessToast } from './show_bulk_success_toast'; +import { useShowBulkErrorToast } from './use_show_bulk_error_toast'; +import { useShowBulkSuccessToast } from './use_show_bulk_success_toast'; import { useGuessRuleIdsForBulkAction } from './use_guess_rule_ids_for_bulk_action'; export const goToRuleEditPage = ( @@ -36,8 +35,9 @@ interface UseExecuteBulkActionOptions { } export const useExecuteBulkAction = (options?: UseExecuteBulkActionOptions) => { - const toasts = useAppToasts(); const { mutateAsync } = useBulkActionMutation(); + const showBulkSuccessToast = useShowBulkSuccessToast(); + const showBulkErrorToast = useShowBulkErrorToast(); const guessRuleIdsForBulkAction = useGuessRuleIdsForBulkAction(); const rulesTableContext = useRulesTableContextOptional(); const setLoadingRules = rulesTableContext?.actions.setLoadingRules; @@ -54,17 +54,29 @@ export const useExecuteBulkAction = (options?: UseExecuteBulkActionOptions) => { sendTelemetry(bulkAction.type, response); if (!options?.suppressSuccessToast) { - showBulkSuccessToast(toasts, bulkAction.type, response.attributes.summary); + showBulkSuccessToast({ + actionType: bulkAction.type, + summary: response.attributes.summary, + editPayload: + bulkAction.type === BulkActionType.edit ? bulkAction.editPayload : undefined, + }); } return response; } catch (error) { - showBulkErrorToast(toasts, bulkAction.type, error); + showBulkErrorToast({ actionType: bulkAction.type, error }); } finally { setLoadingRules?.({ ids: [], action: null }); } }, - [options?.suppressSuccessToast, guessRuleIdsForBulkAction, setLoadingRules, mutateAsync, toasts] + [ + options?.suppressSuccessToast, + guessRuleIdsForBulkAction, + setLoadingRules, + mutateAsync, + showBulkSuccessToast, + showBulkErrorToast, + ] ); return { executeBulkAction }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_error_toast.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_error_toast.ts similarity index 55% rename from x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_error_toast.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_error_toast.ts index 1c88db4b28ab1..70ea504a76159 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/show_bulk_error_toast.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_error_toast.ts @@ -5,20 +5,29 @@ * 2.0. */ +import { useCallback } from 'react'; import type { HTTPError } from '../../../../../common/detection_engine/types'; -import type { UseAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import type { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { explainBulkError, summarizeBulkError } from './translations'; -export function showBulkErrorToast( - toasts: UseAppToasts, - action: BulkActionType, - error: HTTPError -): void { - toasts.addError(populateErrorStack(error), { - title: summarizeBulkError(action), - toastMessage: explainBulkError(action, error), - }); +interface ShowBulkErrorToastProps { + actionType: BulkActionType; + error: HTTPError; +} + +export function useShowBulkErrorToast() { + const toasts = useAppToasts(); + + return useCallback( + ({ actionType, error }: ShowBulkErrorToastProps) => { + toasts.addError(populateErrorStack(error), { + title: summarizeBulkError(actionType), + toastMessage: explainBulkError(actionType, error), + }); + }, + [toasts] + ); } function populateErrorStack(error: HTTPError): HTTPError { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_success_toast.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_success_toast.ts new file mode 100644 index 0000000000000..0fd2acc1cdf4b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_success_toast.ts @@ -0,0 +1,38 @@ +/* + * 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 { useCallback } from 'react'; +import type { BulkActionSummary } from '..'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import type { BulkActionEditPayload } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { explainBulkEditSuccess, explainBulkSuccess, summarizeBulkSuccess } from './translations'; + +interface ShowBulkSuccessToastProps { + actionType: BulkActionType; + summary: BulkActionSummary; + editPayload?: BulkActionEditPayload[]; +} + +export function useShowBulkSuccessToast() { + const toasts = useAppToasts(); + + return useCallback( + ({ actionType, summary, editPayload }: ShowBulkSuccessToastProps) => { + const text = + actionType === BulkActionType.edit + ? explainBulkEditSuccess(editPayload ?? [], summary) + : explainBulkSuccess(actionType, summary); + + toasts.addSuccess({ + title: summarizeBulkSuccess(actionType), + text, + }); + }, + [toasts] + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx index 6dc2664e513ca..74c2471b00f7e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx @@ -24,11 +24,9 @@ import { useStartTransaction } from '../../../../../common/lib/apm/use_start_tra import { canEditRuleWithActions } from '../../../../../common/utils/privileges'; import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; import * as detectionI18n from '../../../../../detections/pages/detection_engine/translations'; -import { - downloadExportedRules, - useBulkExport, -} from '../../../../rule_management/logic/bulk_actions/use_bulk_export'; +import { useBulkExport } from '../../../../rule_management/logic/bulk_actions/use_bulk_export'; import { useExecuteBulkAction } from '../../../../rule_management/logic/bulk_actions/use_execute_bulk_action'; +import { useDownloadExportedRules } from '../../../../rule_management/logic/bulk_actions/use_download_exported_rules'; import type { FilterOptions } from '../../../../rule_management/logic/types'; import { convertRulesFilterToKQL } from '../../../../rule_management/logic/utils'; import { getExportedRulesDetails } from '../helpers'; @@ -69,6 +67,7 @@ export const useBulkActions = ({ const { startTransaction } = useStartTransaction(); const { executeBulkAction } = useExecuteBulkAction(); const { bulkExport } = useBulkExport(); + const downloadExportedRules = useDownloadExportedRules(); const { state: { isAllSelected, rules, loadingRuleIds, selectedRuleIds }, @@ -176,7 +175,7 @@ export const useBulkActions = ({ return; } - await downloadExportedRules({ response, toasts }); + await downloadExportedRules(response); }; const handleBulkEdit = (bulkEditActionType: BulkActionEditType) => async () => { @@ -461,6 +460,7 @@ export const useBulkActions = ({ executeBulkActionsDryRun, filterOptions, completeBulkEditForm, + downloadExportedRules, ] ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx index 6ee7acd438c6e..185ca7040fe21 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx @@ -9,30 +9,27 @@ import type { DefaultItemAction } from '@elastic/eui'; import { EuiToolTip } from '@elastic/eui'; import React from 'react'; import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; import { useKibana } from '../../../../common/lib/kibana'; import { canEditRuleWithActions } from '../../../../common/utils/privileges'; import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; import type { Rule } from '../../../rule_management/logic'; -import { - downloadExportedRules, - useBulkExport, -} from '../../../rule_management/logic/bulk_actions/use_bulk_export'; +import { useBulkExport } from '../../../rule_management/logic/bulk_actions/use_bulk_export'; import { goToRuleEditPage, useExecuteBulkAction, } from '../../../rule_management/logic/bulk_actions/use_execute_bulk_action'; +import { useDownloadExportedRules } from '../../../rule_management/logic/bulk_actions/use_download_exported_rules'; import { useHasActionsPrivileges } from './use_has_actions_privileges'; export const useRulesTableActions = (): Array<DefaultItemAction<Rule>> => { const { navigateToApp } = useKibana().services.application; const hasActionsPrivileges = useHasActionsPrivileges(); - const toasts = useAppToasts(); const { startTransaction } = useStartTransaction(); const { executeBulkAction } = useExecuteBulkAction(); const { bulkExport } = useBulkExport(); + const downloadExportedRules = useDownloadExportedRules(); return [ { @@ -86,10 +83,7 @@ export const useRulesTableActions = (): Array<DefaultItemAction<Rule>> => { startTransaction({ name: SINGLE_RULE_ACTIONS.EXPORT }); const response = await bulkExport({ ids: [rule.id] }); if (response) { - await downloadExportedRules({ - response, - toasts, - }); + await downloadExportedRules(response); } }, enabled: (rule: Rule) => !rule.immutable, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx index 898666623c495..1281e67a49b71 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx @@ -17,21 +17,18 @@ import styled from 'styled-components'; import { APP_UI_ID, SecurityPageName } from '../../../../../common/constants'; import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { getRulesUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useBoolState } from '../../../../common/hooks/use_bool_state'; import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; import { useKibana } from '../../../../common/lib/kibana'; import { canEditRuleWithActions } from '../../../../common/utils/privileges'; import type { Rule } from '../../../../detection_engine/rule_management/logic'; -import { - downloadExportedRules, - useBulkExport, -} from '../../../../detection_engine/rule_management/logic/bulk_actions/use_bulk_export'; +import { useBulkExport } from '../../../../detection_engine/rule_management/logic/bulk_actions/use_bulk_export'; import { goToRuleEditPage, useExecuteBulkAction, } from '../../../../detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action'; +import { useDownloadExportedRules } from '../../../../detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules'; import * as i18nActions from '../../../pages/detection_engine/rules/translations'; import * as i18n from './translations'; @@ -62,10 +59,10 @@ const RuleActionsOverflowComponent = ({ }: RuleActionsOverflowComponentProps) => { const [isPopoverOpen, , closePopover, togglePopover] = useBoolState(); const { navigateToApp } = useKibana().services.application; - const toasts = useAppToasts(); const { startTransaction } = useStartTransaction(); const { executeBulkAction } = useExecuteBulkAction({ suppressSuccessToast: true }); const { bulkExport } = useBulkExport(); + const downloadExportedRules = useDownloadExportedRules(); const onRuleDeletedCallback = useCallback(() => { navigateToApp(APP_UI_ID, { @@ -117,10 +114,7 @@ const RuleActionsOverflowComponent = ({ closePopover(); const response = await bulkExport({ ids: [rule.id] }); if (response) { - await downloadExportedRules({ - response, - toasts, - }); + await downloadExportedRules(response); } }} > @@ -155,8 +149,8 @@ const RuleActionsOverflowComponent = ({ onRuleDeletedCallback, rule, startTransaction, - toasts, userHasPermissions, + downloadExportedRules, ] ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index b9948ca90578b..c96ac7f576874 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -1071,10 +1071,18 @@ export const RULES_BULK_EDIT_SUCCESS_DESCRIPTION = (rulesCount: number) => { values: { rulesCount }, defaultMessage: - "You've successfully updated {rulesCount, plural, =1 {# rule} other {# rules}}. If you did not select to apply changes to rules using Kibana data views, those rules were not updated and will continue using data views.", + "You've successfully updated {rulesCount, plural, =1 {# rule} other {# rules}}", } ); +export const RULES_BULK_EDIT_SUCCESS_INDEX_EDIT_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.successIndexEditToastDescription', + { + defaultMessage: + 'If you did not select to apply changes to rules using Kibana data views, those rules were not updated and will continue using data views.', + } +); + export const RULES_BULK_EDIT_FAILURE = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.errorToastTitle', { From 63ea54ecddb98a9192451767b978e731359a25c0 Mon Sep 17 00:00:00 2001 From: Alison Goryachev <alison.goryachev@elastic.co> Date: Thu, 3 Nov 2022 13:41:39 -0400 Subject: [PATCH 82/86] [Guided onboarding] Update kubernetes dashboard url (#144376) --- .../public/constants/guides_config/observability.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts b/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts index 31263e1b0f54b..e02041fd06d41 100644 --- a/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts @@ -52,7 +52,7 @@ export const observabilityConfig: GuideConfig = { ), location: { appID: 'dashboards', - path: '#/view/kubernetes-e0195ce0-bcaf-11ec-b64f-7dd6e8e82013', + path: '#/view/kubernetes-f4dc26db-1b53-4ea2-a78b-1bfab8ea267c', }, manualCompletion: { title: i18n.translate( From 24e87982efa1297d26bb4686b825d266ecddc152 Mon Sep 17 00:00:00 2001 From: Joe Reuter <johannes.reuter@elastic.co> Date: Thu, 3 Nov 2022 19:05:25 +0100 Subject: [PATCH 83/86] change codeowners (#144547) --- .github/CODEOWNERS | 60 +++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 160d7267a4f22..0c74adfe026e1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -22,31 +22,31 @@ /src/plugins/saved_objects_finder/ @elastic/kibana-data-discovery # Vis Editors -/x-pack/plugins/lens/ @elastic/kibana-vis-editors -/src/plugins/charts/ @elastic/kibana-vis-editors -/src/plugins/vis_default_editor/ @elastic/kibana-vis-editors -/src/plugins/vis_types/metric/ @elastic/kibana-vis-editors -/src/plugins/vis_types/table/ @elastic/kibana-vis-editors -/src/plugins/vis_types/tagcloud/ @elastic/kibana-vis-editors -/src/plugins/vis_types/timelion/ @elastic/kibana-vis-editors -/src/plugins/vis_types/timeseries/ @elastic/kibana-vis-editors -/src/plugins/vis_types/vega/ @elastic/kibana-vis-editors -/src/plugins/vis_types/vislib/ @elastic/kibana-vis-editors -/src/plugins/vis_types/xy/ @elastic/kibana-vis-editors -/src/plugins/vis_types/pie/ @elastic/kibana-vis-editors -/src/plugins/vis_types/heatmap/ @elastic/kibana-vis-editors -/src/plugins/visualize/ @elastic/kibana-vis-editors -/src/plugins/visualizations/ @elastic/kibana-vis-editors -/src/plugins/chart_expressions/expression_tagcloud/ @elastic/kibana-vis-editors -/src/plugins/chart_expressions/expression_metric/ @elastic/kibana-vis-editors -/src/plugins/chart_expressions/expression_heatmap/ @elastic/kibana-vis-editors -/src/plugins/chart_expressions/expression_gauge/ @elastic/kibana-vis-editors -/src/plugins/chart_expressions/expression_partition_vis/ @elastic/kibana-vis-editors -/src/plugins/chart_expressions/expression_xy/ @elastic/kibana-vis-editors -/src/plugins/url_forwarding/ @elastic/kibana-vis-editors -/x-pack/test/functional/apps/lens @elastic/kibana-vis-editors -/x-pack/test/api_integration/apis/lens/ @elastic/kibana-vis-editors -/test/functional/apps/visualize/ @elastic/kibana-vis-editors +/x-pack/plugins/lens/ @elastic/kibana-visualizations +/src/plugins/charts/ @elastic/kibana-visualizations +/src/plugins/vis_default_editor/ @elastic/kibana-visualizations +/src/plugins/vis_types/metric/ @elastic/kibana-visualizations +/src/plugins/vis_types/table/ @elastic/kibana-visualizations +/src/plugins/vis_types/tagcloud/ @elastic/kibana-visualizations +/src/plugins/vis_types/timelion/ @elastic/kibana-visualizations +/src/plugins/vis_types/timeseries/ @elastic/kibana-visualizations +/src/plugins/vis_types/vega/ @elastic/kibana-visualizations +/src/plugins/vis_types/vislib/ @elastic/kibana-visualizations +/src/plugins/vis_types/xy/ @elastic/kibana-visualizations +/src/plugins/vis_types/pie/ @elastic/kibana-visualizations +/src/plugins/vis_types/heatmap/ @elastic/kibana-visualizations +/src/plugins/visualize/ @elastic/kibana-visualizations +/src/plugins/visualizations/ @elastic/kibana-visualizations +/src/plugins/chart_expressions/expression_tagcloud/ @elastic/kibana-visualizations +/src/plugins/chart_expressions/expression_metric/ @elastic/kibana-visualizations +/src/plugins/chart_expressions/expression_heatmap/ @elastic/kibana-visualizations +/src/plugins/chart_expressions/expression_gauge/ @elastic/kibana-visualizations +/src/plugins/chart_expressions/expression_partition_vis/ @elastic/kibana-visualizations +/src/plugins/chart_expressions/expression_xy/ @elastic/kibana-visualizations +/src/plugins/url_forwarding/ @elastic/kibana-visualizations +/x-pack/test/functional/apps/lens @elastic/kibana-visualizations +/x-pack/test/api_integration/apis/lens/ @elastic/kibana-visualizations +/test/functional/apps/visualize/ @elastic/kibana-visualizations # Application Services /examples/bfetch_explorer/ @elastic/kibana-app-services @@ -885,12 +885,12 @@ packages/kbn-babel-preset @elastic/kibana-operations packages/kbn-bazel-packages @elastic/kibana-operations packages/kbn-bazel-runner @elastic/kibana-operations packages/kbn-cases-components @elastic/response-ops -packages/kbn-chart-icons @elastic/kibana-vis-editors +packages/kbn-chart-icons @elastic/kibana-visualizations packages/kbn-ci-stats-core @elastic/kibana-operations packages/kbn-ci-stats-performance-metrics @elastic/kibana-operations packages/kbn-ci-stats-reporter @elastic/kibana-operations packages/kbn-cli-dev-mode @elastic/kibana-operations -packages/kbn-coloring @elastic/kibana-vis-editors +packages/kbn-coloring @elastic/kibana-visualizations packages/kbn-config @elastic/kibana-core packages/kbn-config-mocks @elastic/kibana-core packages/kbn-config-schema @elastic/kibana-core @@ -933,7 +933,7 @@ packages/kbn-io-ts-utils @elastic/apm-ui packages/kbn-jest-serializers @elastic/kibana-operations packages/kbn-journeys @elastic/kibana-operations packages/kbn-kibana-manifest-schema @elastic/kibana-operations -packages/kbn-language-documentation-popover @elastic/kibana-vis-editors +packages/kbn-language-documentation-popover @elastic/kibana-visualizations packages/kbn-logging @elastic/kibana-core packages/kbn-logging-mocks @elastic/kibana-core packages/kbn-managed-vscode-config @elastic/kibana-operations @@ -982,8 +982,8 @@ packages/kbn-telemetry-tools @elastic/kibana-core packages/kbn-test @elastic/kibana-operations packages/kbn-test-jest-helpers @elastic/kibana-operations packages/kbn-test-subj-selector @elastic/kibana-operations -packages/kbn-timelion-grammar @elastic/kibana-vis-editors -packages/kbn-tinymath @elastic/kibana-vis-editors +packages/kbn-timelion-grammar @elastic/kibana-visualizations +packages/kbn-tinymath @elastic/kibana-visualizations packages/kbn-tooling-log @elastic/kibana-operations packages/kbn-type-summarizer @elastic/kibana-operations packages/kbn-type-summarizer-cli @elastic/kibana-operations From 1855f2207d0a1f7cc497192082568509d7878f17 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski <jon@elastic.co> Date: Thu, 3 Nov 2022 13:13:03 -0500 Subject: [PATCH 84/86] update codeowners --- .github/CODEOWNERS | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0c74adfe026e1..caf16d1050725 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -885,12 +885,12 @@ packages/kbn-babel-preset @elastic/kibana-operations packages/kbn-bazel-packages @elastic/kibana-operations packages/kbn-bazel-runner @elastic/kibana-operations packages/kbn-cases-components @elastic/response-ops -packages/kbn-chart-icons @elastic/kibana-visualizations +packages/kbn-chart-icons @elastic/kibana-vis-editors packages/kbn-ci-stats-core @elastic/kibana-operations packages/kbn-ci-stats-performance-metrics @elastic/kibana-operations packages/kbn-ci-stats-reporter @elastic/kibana-operations packages/kbn-cli-dev-mode @elastic/kibana-operations -packages/kbn-coloring @elastic/kibana-visualizations +packages/kbn-coloring @elastic/kibana-vis-editors packages/kbn-config @elastic/kibana-core packages/kbn-config-mocks @elastic/kibana-core packages/kbn-config-schema @elastic/kibana-core @@ -933,7 +933,7 @@ packages/kbn-io-ts-utils @elastic/apm-ui packages/kbn-jest-serializers @elastic/kibana-operations packages/kbn-journeys @elastic/kibana-operations packages/kbn-kibana-manifest-schema @elastic/kibana-operations -packages/kbn-language-documentation-popover @elastic/kibana-visualizations +packages/kbn-language-documentation-popover @elastic/kibana-vis-editors packages/kbn-logging @elastic/kibana-core packages/kbn-logging-mocks @elastic/kibana-core packages/kbn-managed-vscode-config @elastic/kibana-operations @@ -982,8 +982,8 @@ packages/kbn-telemetry-tools @elastic/kibana-core packages/kbn-test @elastic/kibana-operations packages/kbn-test-jest-helpers @elastic/kibana-operations packages/kbn-test-subj-selector @elastic/kibana-operations -packages/kbn-timelion-grammar @elastic/kibana-visualizations -packages/kbn-tinymath @elastic/kibana-visualizations +packages/kbn-timelion-grammar @elastic/kibana-vis-editors +packages/kbn-tinymath @elastic/kibana-vis-editors packages/kbn-tooling-log @elastic/kibana-operations packages/kbn-type-summarizer @elastic/kibana-operations packages/kbn-type-summarizer-cli @elastic/kibana-operations From f065d66bb8bfb8fb4e0edfb27e857c394b4ae7d1 Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov <kuznetsov.yaroslav.yk@gmail.com> Date: Thu, 3 Nov 2022 20:20:41 +0200 Subject: [PATCH 85/86] [Lens] PartitionVis expression types improvement. (#144248) * Partition labels added. * Theme function added. * Added partitionVis types safety. * Fixed visdimension generation. * Update x-pack/plugins/lens/public/visualizations/partition/to_expression.ts Co-authored-by: Andrew Tate <drewctate@gmail.com> * Update x-pack/plugins/lens/public/visualizations/partition/to_expression.ts Co-authored-by: Andrew Tate <drewctate@gmail.com> * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Fixed types. Co-authored-by: Andrew Tate <drewctate@gmail.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../expression_partition_vis/common/index.ts | 1 + .../common/types/expression_functions.ts | 8 + x-pack/plugins/lens/kibana.json | 1 + .../visualizations/partition/to_expression.ts | 249 ++++++++---------- x-pack/plugins/lens/tsconfig.json | 1 + 5 files changed, 116 insertions(+), 144 deletions(-) diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/index.ts b/src/plugins/chart_expressions/expression_partition_vis/common/index.ts index 559d597caf90c..d51838b334a09 100755 --- a/src/plugins/chart_expressions/expression_partition_vis/common/index.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/index.ts @@ -31,6 +31,7 @@ export type { TreemapVisExpressionFunctionDefinition, MosaicVisExpressionFunctionDefinition, WaffleVisExpressionFunctionDefinition, + PartitionLabelsExpressionFunctionDefinition, } from './types/expression_functions'; export type { diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_functions.ts index fe5910990d62a..30c5aba33ebf1 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_functions.ts @@ -18,6 +18,7 @@ import { TREEMAP_VIS_EXPRESSION_NAME, MOSAIC_VIS_EXPRESSION_NAME, WAFFLE_VIS_EXPRESSION_NAME, + PARTITION_LABELS_FUNCTION, } from '../constants'; import { RenderValue, @@ -91,3 +92,10 @@ export enum ChartTypes { MOSAIC = 'mosaic', WAFFLE = 'waffle', } + +export type PartitionLabelsExpressionFunctionDefinition = ExpressionFunctionDefinition< + typeof PARTITION_LABELS_FUNCTION, + Datatable | null, + PartitionLabelsArguments, + ExpressionValuePartitionLabels +>; diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index 6921d2afa5082..ad4475ed9faa8 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -32,6 +32,7 @@ ], "optionalPlugins": [ "expressionLegacyMetricVis", + "expressionPartitionVis", "usageCollection", "taskManager", "globalSearch", diff --git a/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts b/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts index 3f66965dc98b6..42371f85e5984 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts +++ b/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts @@ -5,11 +5,24 @@ * 2.0. */ -import type { Ast, AstFunction } from '@kbn/interpreter'; +import type { Ast } from '@kbn/interpreter'; import { Position } from '@elastic/charts'; import type { PaletteOutput, PaletteRegistry } from '@kbn/coloring'; import { buildExpression, buildExpressionFunction } from '@kbn/expressions-plugin/public'; +import type { + LabelPositions, + MosaicVisExpressionFunctionDefinition, + PartitionLabelsExpressionFunctionDefinition, + PieVisExpressionFunctionDefinition, + TreemapVisExpressionFunctionDefinition, + ValueFormats, + LegendDisplay as PartitionVisLegendDisplay, + WaffleVisExpressionFunctionDefinition, +} from '@kbn/expression-partition-vis-plugin/common'; +import { ExpressionFunctionTheme } from '@kbn/expressions-plugin/common'; +import { ExpressionFunctionVisDimension } from '@kbn/visualizations-plugin/common'; +import type { CollapseExpressionFunction } from '../../../common/expressions'; import type { Operation, DatasourcePublicAPI, DatasourceLayers } from '../../types'; import { DEFAULT_PERCENT_DECIMALS } from './constants'; import { shouldShowValuesInLegend } from './render_helpers'; @@ -45,15 +58,6 @@ type GenerateExpressionAstFunction = ( paletteService: PaletteRegistry ) => Ast | null; -type GenerateExpressionAstArguments = ( - state: PieVisualizationState, - attributes: Attributes, - operations: OperationColumnId[], - layer: PieLayerState, - datasourceLayers: DatasourceLayers, - paletteService: PaletteRegistry -) => Ast['chain'][number]['arguments']; - type GenerateLabelsAstArguments = ( state: PieVisualizationState, attributes: Attributes, @@ -74,30 +78,25 @@ export const getSortedGroups = ( return Array.from(new Set(originalOrder?.concat(layer[accessor] ?? []))); }; -const prepareDimension = (accessor: string) => { - const visdimension = buildExpressionFunction('visdimension', { accessor }); - return buildExpression([visdimension]).toAst(); -}; +const prepareDimension = (accessor: string) => + buildExpression([ + buildExpressionFunction<ExpressionFunctionVisDimension>('visdimension', { accessor }), + ]).toAst(); const generateCommonLabelsAstArgs: GenerateLabelsAstArguments = (state, attributes, layer) => { - const show = [!attributes.isPreview && layer.categoryDisplay !== CategoryDisplay.HIDE]; - const position = layer.categoryDisplay !== CategoryDisplay.HIDE ? [layer.categoryDisplay] : []; - const values = [layer.numberDisplay !== NumberDisplay.HIDDEN]; - const valuesFormat = layer.numberDisplay !== NumberDisplay.HIDDEN ? [layer.numberDisplay] : []; - const percentDecimals = [layer.percentDecimals ?? DEFAULT_PERCENT_DECIMALS]; + const show = !attributes.isPreview && layer.categoryDisplay !== CategoryDisplay.HIDE; + const position = + layer.categoryDisplay !== CategoryDisplay.HIDE ? (layer.categoryDisplay as LabelPositions) : []; + const values = layer.numberDisplay !== NumberDisplay.HIDDEN; + const valuesFormat = + layer.numberDisplay !== NumberDisplay.HIDDEN ? (layer.numberDisplay as ValueFormats) : []; + const percentDecimals = layer.percentDecimals ?? DEFAULT_PERCENT_DECIMALS; + const partitionLabelsFn = buildExpressionFunction<PartitionLabelsExpressionFunctionDefinition>( + 'partitionLabels', + { show, position, values, valuesFormat, percentDecimals } + ); - return [ - { - type: 'expression', - chain: [ - { - type: 'function', - function: 'partitionLabels', - arguments: { show, position, values, valuesFormat, percentDecimals }, - }, - ], - }, - ]; + return [buildExpression([partitionLabelsFn]).toAst()]; }; const generateWaffleLabelsAstArguments: GenerateLabelsAstArguments = (...args) => { @@ -117,29 +116,22 @@ const generatePaletteAstArguments = ( ): [Ast] => palette ? [ - { - type: 'expression', - chain: [ - { - type: 'function', - function: 'theme', - arguments: { - variable: ['palette'], - default: [paletteService.get(palette.name).toExpression(palette.params)], - }, - }, - ], - }, + buildExpression([ + buildExpressionFunction<ExpressionFunctionTheme>('theme', { + variable: 'palette', + default: paletteService.get(palette.name).toExpression(palette.params), + }), + ]).toAst(), ] : [paletteService.get('default').toExpression()]; -const generateCommonArguments: GenerateExpressionAstArguments = ( - state, - attributes, - operations, - layer, - datasourceLayers, - paletteService +const generateCommonArguments = ( + state: PieVisualizationState, + attributes: Attributes, + operations: OperationColumnId[], + layer: PieLayerState, + datasourceLayers: DatasourceLayers, + paletteService: PaletteRegistry ) => { return { labels: generateCommonLabelsAstArgs(state, attributes, layer), @@ -147,108 +139,81 @@ const generateCommonArguments: GenerateExpressionAstArguments = ( .filter(({ columnId }) => !isCollapsed(columnId, layer)) .map(({ columnId }) => columnId) .map(prepareDimension), - metric: layer.metric ? [prepareDimension(layer.metric)] : [], - legendDisplay: [attributes.isPreview ? LegendDisplay.HIDE : layer.legendDisplay], - legendPosition: [layer.legendPosition || Position.Right], - maxLegendLines: [layer.legendMaxLines ?? 1], - legendSize: layer.legendSize ? [layer.legendSize] : [], - nestedLegend: [!!layer.nestedLegend], - truncateLegend: [ + metric: layer.metric ? prepareDimension(layer.metric) : '', + legendDisplay: (attributes.isPreview + ? LegendDisplay.HIDE + : layer.legendDisplay) as PartitionVisLegendDisplay, + legendPosition: layer.legendPosition || Position.Right, + maxLegendLines: layer.legendMaxLines ?? 1, + legendSize: layer.legendSize, + nestedLegend: !!layer.nestedLegend, + truncateLegend: layer.truncateLegend ?? getDefaultVisualValuesForLayer(state, datasourceLayers).truncateText, - ], palette: generatePaletteAstArguments(paletteService, state.palette), + addTooltip: false, }; }; -const generatePieVisAst: GenerateExpressionAstFunction = (...rest) => ({ - type: 'expression', - chain: [ - { - type: 'function', - function: 'pieVis', - arguments: { - ...generateCommonArguments(...rest), - respectSourceOrder: [false], - startFromSecondLargestSlice: [true], - }, - }, - ], -}); +const generatePieVisAst: GenerateExpressionAstFunction = (...rest) => + buildExpression([ + buildExpressionFunction<PieVisExpressionFunctionDefinition>('pieVis', { + ...generateCommonArguments(...rest), + respectSourceOrder: false, + startFromSecondLargestSlice: true, + isDonut: false, + }), + ]).toAst(); const generateDonutVisAst: GenerateExpressionAstFunction = (...rest) => { const [, , , layer] = rest; - return { - type: 'expression', - chain: [ - { - type: 'function', - function: 'pieVis', - arguments: { - ...generateCommonArguments(...rest), - respectSourceOrder: [false], - isDonut: [true], - startFromSecondLargestSlice: [true], - emptySizeRatio: [layer.emptySizeRatio ?? EmptySizeRatios.SMALL], - }, - }, - ], - }; + + return buildExpression([ + buildExpressionFunction<PieVisExpressionFunctionDefinition>('pieVis', { + ...generateCommonArguments(...rest), + respectSourceOrder: false, + isDonut: true, + startFromSecondLargestSlice: true, + emptySizeRatio: layer.emptySizeRatio ?? EmptySizeRatios.SMALL, + }), + ]).toAst(); }; const generateTreemapVisAst: GenerateExpressionAstFunction = (...rest) => { const [, , , layer] = rest; - return { - type: 'expression', - chain: [ - { - type: 'function', - function: 'treemapVis', - arguments: { - ...generateCommonArguments(...rest), - nestedLegend: [!!layer.nestedLegend], - }, - }, - ], - }; + + return buildExpression([ + buildExpressionFunction<TreemapVisExpressionFunctionDefinition>('treemapVis', { + ...generateCommonArguments(...rest), + nestedLegend: !!layer.nestedLegend, + }), + ]).toAst(); }; -const generateMosaicVisAst: GenerateExpressionAstFunction = (...rest) => ({ - type: 'expression', - chain: [ - { - type: 'function', - function: 'mosaicVis', - arguments: { - ...generateCommonArguments(...rest), - // flip order of bucket dimensions so the rows are fetched before the columns to keep them stable - buckets: rest[2] - .filter(({ columnId }) => !isCollapsed(columnId, rest[3])) - .reverse() - .map((o) => o.columnId) - .map(prepareDimension), - }, - }, - ], -}); +const generateMosaicVisAst: GenerateExpressionAstFunction = (...rest) => + buildExpression([ + buildExpressionFunction<MosaicVisExpressionFunctionDefinition>('mosaicVis', { + ...generateCommonArguments(...rest), + // flip order of bucket dimensions so the rows are fetched before the columns to keep them stable + buckets: rest[2] + .filter(({ columnId }) => !isCollapsed(columnId, rest[3])) + .reverse() + .map((o) => o.columnId) + .map(prepareDimension), + }), + ]).toAst(); const generateWaffleVisAst: GenerateExpressionAstFunction = (...rest) => { const { buckets, nestedLegend, ...args } = generateCommonArguments(...rest); const [state, attributes, , layer] = rest; - return { - type: 'expression', - chain: [ - { - type: 'function', - function: 'waffleVis', - arguments: { - ...args, - bucket: buckets, - labels: generateWaffleLabelsAstArguments(state, attributes, layer), - showValuesInLegend: [shouldShowValuesInLegend(layer, state.shape)], - }, - }, - ], - }; + + return buildExpression([ + buildExpressionFunction<WaffleVisExpressionFunctionDefinition>('waffleVis', { + ...args, + bucket: buckets, + labels: generateWaffleLabelsAstArguments(state, attributes, layer), + showValuesInLegend: shouldShowValuesInLegend(layer, state.shape), + }), + ]).toAst(); }; const generateExprAst: GenerateExpressionAstFunction = (state, ...restArgs) => @@ -306,15 +271,11 @@ function expressionHelper( ...groups .filter((columnId) => layer.collapseFns?.[columnId]) .map((columnId) => { - return { - type: 'function', - function: 'lens_collapse', - arguments: { - by: groups.filter((chk) => chk !== columnId), - metric: [layer.metric], - fn: [layer.collapseFns![columnId]!], - }, - } as AstFunction; + return buildExpressionFunction<CollapseExpressionFunction>('lens_collapse', { + by: groups.filter((chk) => chk !== columnId), + metric: layer.metric ? [layer.metric] : [], + fn: [layer.collapseFns![columnId]!], + }).toAst(); }), ...(visualizationAst ? visualizationAst.chain : []), ], diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index f4f343bbb4e5b..0cb74b2b1a87a 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -31,6 +31,7 @@ { "path": "../../../src/plugins/embeddable/tsconfig.json"}, { "path": "../../../src/plugins/presentation_util/tsconfig.json"}, { "path": "../../../src/plugins/field_formats/tsconfig.json"}, + { "path": "../../../src/plugins/chart_expressions/expression_partition_vis/tsconfig.json"}, { "path": "../../../src/plugins/chart_expressions/expression_heatmap/tsconfig.json"}, { "path": "../../../src/plugins/chart_expressions/expression_gauge/tsconfig.json"}, { "path": "../../../src/plugins/chart_expressions/expression_legacy_metric/tsconfig.json"}, From 4e4955b7f4fa7e95d9e07a562485ad21b0d056d1 Mon Sep 17 00:00:00 2001 From: Ashokaditya <1849116+ashokaditya@users.noreply.github.com> Date: Thu, 3 Nov 2022 19:23:35 +0100 Subject: [PATCH 86/86] [Security Solution][Endpoint][Response Actions] Update deleted file message for file link (#144454) * update deleted file message for file link fixes elastic/security-team/issues/5310 * update tests fixes elastic/security-team/issues/5310 * update types * update test fixes elastic/security-team/issues/5310 --- .../common/utils/resolve_path_variables.ts | 5 +- .../response_actions_log.test.tsx | 71 ++++++++++++++++++- ...esponse_action_file_download_link.test.tsx | 12 +++- .../response_action_file_download_link.tsx | 20 ++---- 4 files changed, 89 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/utils/resolve_path_variables.ts b/x-pack/plugins/security_solution/public/common/utils/resolve_path_variables.ts index 89067e575665d..585cfb61aafe2 100644 --- a/x-pack/plugins/security_solution/public/common/utils/resolve_path_variables.ts +++ b/x-pack/plugins/security_solution/public/common/utils/resolve_path_variables.ts @@ -5,7 +5,10 @@ * 2.0. */ -export const resolvePathVariables = (path: string, variables: { [K: string]: string | number }) => +export const resolvePathVariables = ( + path: string, + variables: { [K: string]: string | number } +): string => Object.keys(variables).reduce((acc, paramName) => { return acc.replace(new RegExp(`\{${paramName}\}`, 'g'), String(variables[paramName])); }, path); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx index ea6cbffd830ef..8d9c2ae8ec60c 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx @@ -15,13 +15,18 @@ import { type AppContextTestRender, } from '../../../common/mock/endpoint'; import { ResponseActionsLog } from './response_actions_log'; -import type { ActionListApiResponse } from '../../../../common/endpoint/types'; +import type { + ActionFileInfoApiResponse, + ActionListApiResponse, +} from '../../../../common/endpoint/types'; import { MANAGEMENT_PATH } from '../../../../common/constants'; import { getActionListMock } from './mocks'; import { useGetEndpointsList } from '../../hooks/endpoint/use_get_endpoints_list'; import uuid from 'uuid'; import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../../../../common/endpoint/service/response_actions/constants'; import { useUserPrivileges as _useUserPrivileges } from '../../../common/components/user_privileges'; +import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks'; +import { waitFor } from '@testing-library/react'; let mockUseGetEndpointActionList: { isFetched?: boolean; @@ -116,6 +121,19 @@ jest.mock('../../hooks/endpoint/use_get_endpoints_list'); jest.mock('../../../common/components/user_privileges'); +let mockUseGetFileInfo: { + isFetching?: boolean; + error?: Partial<IHttpFetchError> | null; + data?: ActionFileInfoApiResponse; +}; +jest.mock('../../hooks/response_actions/use_get_file_info', () => { + const original = jest.requireActual('../../hooks/response_actions/use_get_file_info'); + return { + ...original, + useGetFileInfo: () => mockUseGetFileInfo, + }; +}); + const mockUseGetEndpointsList = useGetEndpointsList as jest.Mock; describe('Response actions history', () => { @@ -131,6 +149,7 @@ describe('Response actions history', () => { let renderResult: ReturnType<typeof render>; let history: AppContextTestRender['history']; let mockedContext: AppContextTestRender; + let apiMocks: ReturnType<typeof responseActionsHttpMocks>; const refetchFunction = jest.fn(); const baseMockedActionList = { @@ -219,6 +238,10 @@ describe('Response actions history', () => { }); describe('With Data', () => { + beforeEach(() => { + apiMocks = responseActionsHttpMocks(mockedContext.coreStart.http); + }); + it('should show table when there is data', async () => { render(); @@ -422,11 +445,22 @@ describe('Response actions history', () => { data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), }; + mockUseGetFileInfo = { + isFetching: false, + error: null, + data: apiMocks.responseProvider.fileInfo(), + }; + render(); - const { getByTestId } = renderResult; + const { getByTestId } = renderResult; const expandButton = getByTestId(`${testPrefix}-expand-button`); userEvent.click(expandButton); + + await waitFor(() => { + expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); + }); + const downloadLink = getByTestId(`${testPrefix}-getFileDownloadLink`); expect(downloadLink).toBeTruthy(); expect(downloadLink.textContent).toEqual( @@ -434,6 +468,39 @@ describe('Response actions history', () => { ); }); + it('should show file unavailable for download for `get-file` action WITH file operation permission when file is deleted', async () => { + mockUseGetEndpointActionList = { + ...baseMockedActionList, + data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), + }; + + const fileInfo = apiMocks.responseProvider.fileInfo(); + fileInfo.data.status = 'DELETED'; + + apiMocks.responseProvider.fileInfo.mockReturnValue(fileInfo); + + mockUseGetFileInfo = { + isFetching: false, + error: null, + data: apiMocks.responseProvider.fileInfo(), + }; + + render(); + + const { getByTestId } = renderResult; + const expandButton = getByTestId(`${testPrefix}-expand-button`); + userEvent.click(expandButton); + + await waitFor(() => { + expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); + }); + + const unavailableText = getByTestId( + `${testPrefix}-getFileDownloadLink-fileNoLongerAvailable` + ); + expect(unavailableText).toBeTruthy(); + }); + it('should not contain download link in expanded row for `get-file` action when NO file operation permission', async () => { const privileges = useUserPrivilegesMock(); diff --git a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx index c243687b67378..83f314c0d9857 100644 --- a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx @@ -46,7 +46,7 @@ describe('When using the `ResponseActionFileDownloadLink` component', () => { action: new EndpointActionGenerator('seed').generateActionDetails< ResponseActionGetFileOutputContent, ResponseActionGetFileParameters - >({ command: 'get-file', completedAt: new Date().toISOString() }), + >({ command: 'get-file' }), 'data-test-subj': 'test', }; @@ -56,8 +56,11 @@ describe('When using the `ResponseActionFileDownloadLink` component', () => { }; }); - it('should show download button if file is available', () => { + it('should show download button if file is available', async () => { render(); + await waitFor(() => { + expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); + }); expect(renderResult.getByTestId('test-downloadButton')).not.toBeNull(); expect(renderResult.getByTestId('test-passcodeMessage')).toHaveTextContent( @@ -65,9 +68,12 @@ describe('When using the `ResponseActionFileDownloadLink` component', () => { ); }); - it('should display custom button label', () => { + it('should display custom button label', async () => { renderProps.buttonTitle = 'hello'; render(); + await waitFor(() => { + expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalled(); + }); expect(renderResult.getByTestId('test-downloadButton')).toHaveTextContent('hello'); }); diff --git a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx index 3d9f419749691..66b12604a160d 100644 --- a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx +++ b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx @@ -15,7 +15,6 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; -import moment from 'moment'; import { resolvePathVariables } from '../../../common/utils/resolve_path_variables'; import { FormattedError } from '../formatted_error'; import { useGetFileInfo } from '../../hooks/response_actions/use_get_file_info'; @@ -36,7 +35,7 @@ const DEFAULT_BUTTON_TITLE = i18n.translate( export const FILE_NO_LONGER_AVAILABLE_MESSAGE = i18n.translate( 'xpack.securitySolution.responseActionFileDownloadLink.fileNoLongerAvailable', - { defaultMessage: 'File is no longer available for download.' } + { defaultMessage: 'File has expired and is no longer available for download.' } ); export interface ResponseActionFileDownloadLinkProps { @@ -65,16 +64,11 @@ export const ResponseActionFileDownloadLink = memo<ResponseActionFileDownloadLin const getTestId = useTestIdGenerator(dataTestSubj); const { canWriteFileOperations } = useUserPrivileges().endpointPrivileges; - // We don't need to call the file info API every time, especially if this component is used from the - // console, where the link is displayed within a short time. So we only do the API call if the - // action was completed more than 2 days ago. - const checkIfStillAvailable = useMemo(() => { - return ( - action.isCompleted && action.wasSuccessful && moment().diff(action.completedAt, 'days') > 2 - ); - }, [action.completedAt, action.isCompleted, action.wasSuccessful]); + const shouldFetchFileInfo: boolean = useMemo(() => { + return action.isCompleted && action.wasSuccessful; + }, [action.isCompleted, action.wasSuccessful]); - const downloadUrl = useMemo(() => { + const downloadUrl: string = useMemo(() => { return resolvePathVariables(ACTION_AGENT_FILE_DOWNLOAD_ROUTE, { action_id: action.id, agent_id: agentId ?? action.agents[0], @@ -86,7 +80,7 @@ export const ResponseActionFileDownloadLink = memo<ResponseActionFileDownloadLin data: fileInfo, error, } = useGetFileInfo(action, undefined, { - enabled: canWriteFileOperations && checkIfStillAvailable, + enabled: canWriteFileOperations && shouldFetchFileInfo, }); if (!canWriteFileOperations || !action.isCompleted || !action.wasSuccessful) { @@ -100,7 +94,7 @@ export const ResponseActionFileDownloadLink = memo<ResponseActionFileDownloadLin // Check if file is no longer available if ((error && error?.response?.status === 404) || fileInfo?.data.status === 'DELETED') { return ( - <EuiText size="s" data-test-subj={getTestId('fileNoLongerAvailable')}> + <EuiText size={textSize} data-test-subj={getTestId('fileNoLongerAvailable')}> {FILE_NO_LONGER_AVAILABLE_MESSAGE} </EuiText> );