diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index 1e0ac8548906e..525cbba463d2d 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -1898,9 +1898,6 @@ "fleet-uninstall-tokens": { "dynamic": false, "properties": { - "created_at": { - "type": "date" - }, "policy_id": { "type": "keyword" }, diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index cd7ef7ea94031..9859886cfd6d5 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -96,7 +96,7 @@ describe('checking migration metadata changes on all registered SO types', () => "fleet-message-signing-keys": "93421f43fed2526b59092a4e3c65d64bc2266c0f", "fleet-preconfiguration-deletion-record": "c52ea1e13c919afe8a5e8e3adbb7080980ecc08e", "fleet-proxy": "6cb688f0d2dd856400c1dbc998b28704ff70363d", - "fleet-uninstall-tokens": "d25a8aedb522d2b839ab0950160777528122070f", + "fleet-uninstall-tokens": "ed8aa37e3cdd69e4360709e64944bb81cae0c025", "graph-workspace": "5cc6bb1455b078fd848c37324672163f09b5e376", "guided-onboarding-guide-state": "d338972ed887ac480c09a1a7fbf582d6a3827c91", "guided-onboarding-plugin-state": "bc109e5ef46ca594fdc179eda15f3095ca0a37a4", diff --git a/src/plugins/chart_expressions/common/index.ts b/src/plugins/chart_expressions/common/index.ts index 4373260657909..0983b1ed28d4d 100644 --- a/src/plugins/chart_expressions/common/index.ts +++ b/src/plugins/chart_expressions/common/index.ts @@ -6,5 +6,10 @@ * Side Public License, v 1. */ -export { extractContainerType, extractVisualizationType, getOverridesFor } from './utils'; +export { + extractContainerType, + extractVisualizationType, + getOverridesFor, + isOnAggBasedEditor, +} from './utils'; export type { Simplify, MakeOverridesSerializable } from './types'; diff --git a/src/plugins/chart_expressions/common/utils.test.ts b/src/plugins/chart_expressions/common/utils.test.ts index 2ed71e9a17b92..48519c9f6f1a9 100644 --- a/src/plugins/chart_expressions/common/utils.test.ts +++ b/src/plugins/chart_expressions/common/utils.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { getOverridesFor } from './utils'; +import { getOverridesFor, isOnAggBasedEditor } from './utils'; describe('Overrides utilities', () => { describe('getOverridesFor', () => { @@ -31,3 +31,77 @@ describe('Overrides utilities', () => { }); }); }); + +describe('isOnAggBasedEditor', () => { + it('should return false if is on dashboard', () => { + const context = { + type: 'dashboard', + description: 'test', + child: { + type: 'lens', + name: 'lnsPie', + id: 'd8bb29a7-13a4-43fa-a162-d7705050bb6c', + description: 'test', + url: '/gdu/app/lens#/edit_by_value', + }, + }; + expect(isOnAggBasedEditor(context)).toEqual(false); + }); + + it('should return false if is on editor but lens', () => { + const context = { + type: 'application', + description: 'test', + child: { + type: 'lens', + name: 'lnsPie', + id: 'd8bb29a7-13a4-43fa-a162-d7705050bb6c', + description: 'test', + url: '/gdu/app/lens#/edit_by_value', + }, + }; + expect(isOnAggBasedEditor(context)).toEqual(false); + }); + + it('should return false if is on dashboard but agg_based', () => { + const context = { + type: 'dashboard', + description: 'test', + child: { + type: 'agg_based', + name: 'pie', + id: 'd8bb29a7-13a4-43fa-a162-d7705050bb6c', + description: 'test', + url: '', + }, + }; + expect(isOnAggBasedEditor(context)).toEqual(false); + }); + + it('should return true if is on editor but agg_based', () => { + const context = { + type: 'application', + description: 'test', + child: { + type: 'agg_based', + name: 'pie', + id: 'd8bb29a7-13a4-43fa-a162-d7705050bb6c', + description: 'test', + url: '', + }, + }; + expect(isOnAggBasedEditor(context)).toEqual(true); + }); + + it('should return false if child is missing', () => { + const context = { + type: 'application', + description: 'test', + }; + expect(isOnAggBasedEditor(context)).toEqual(false); + }); + + it('should return false if context is missing', () => { + expect(isOnAggBasedEditor()).toEqual(false); + }); +}); diff --git a/src/plugins/chart_expressions/common/utils.ts b/src/plugins/chart_expressions/common/utils.ts index 2966532c44117..db2e564efc4b3 100644 --- a/src/plugins/chart_expressions/common/utils.ts +++ b/src/plugins/chart_expressions/common/utils.ts @@ -20,6 +20,31 @@ export const extractContainerType = (context?: KibanaExecutionContext): string | } }; +/* Function to identify if the pie is rendered inside the aggBased editor + Context comes with this format + { + type: 'dashboard', // application for lens, agg based charts + description: 'test', + child: { + type: 'lens', // agg_based for legacy editor + name: 'pie', + id: 'id', + description: 'test', + url: '', + }, + }; */ +export const isOnAggBasedEditor = (context?: KibanaExecutionContext): boolean => { + if (context) { + return Boolean( + context.type && + context.type === 'application' && + context.child && + context.child.type === 'agg_based' + ); + } + return false; +}; + export const extractVisualizationType = (context?: KibanaExecutionContext): string | undefined => { if (context) { const recursiveGet = (item: KibanaExecutionContext): KibanaExecutionContext | undefined => { diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx index 5eb48cfab6cd5..c935ce847e40e 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx +++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx @@ -83,6 +83,7 @@ describe('PartitionVisComponent', function () { data: dataPluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), }, + hasOpenedOnAggBasedEditor: false, }; }); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.tsx index 94590ff164555..4ce300a7d9bb9 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.tsx +++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.tsx @@ -93,6 +93,7 @@ export type PartitionVisComponentProps = Omit< palettesRegistry: PaletteRegistry; services: Pick; columnCellValueActions: ColumnCellValueActions; + hasOpenedOnAggBasedEditor: boolean; }; const PartitionVisComponent = (props: PartitionVisComponentProps) => { @@ -105,6 +106,7 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => { syncColors, interactive, overrides, + hasOpenedOnAggBasedEditor, } = props; const visParams = useMemo(() => filterOutConfig(visType, preVisParams), [preVisParams, visType]); const chartTheme = props.chartsThemeService.useChartsTheme(); @@ -148,7 +150,7 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => { const [showLegend, setShowLegend] = useState(() => showLegendDefault()); const showToggleLegendElement = props.uiState !== undefined; - + const [chartIsLoaded, setChartIsLoaded] = useState(false); const [containerDimensions, setContainerDimensions] = useState< undefined | PieContainerDimensions >(); @@ -156,12 +158,14 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => { const parentRef = useRef(null); useEffect(() => { - if (parentRef && parentRef.current) { + // chart should be loaded to compute the dimensions + // otherwise the height is set to 0 + if (parentRef && parentRef.current && chartIsLoaded) { const parentHeight = parentRef.current!.getBoundingClientRect().height; const parentWidth = parentRef.current!.getBoundingClientRect().width; setContainerDimensions({ width: parentWidth, height: parentHeight }); } - }, [parentRef]); + }, [chartIsLoaded, parentRef]); useEffect(() => { const legendShow = showLegendDefault(); @@ -172,6 +176,7 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => { (isRendered: boolean = true) => { if (isRendered) { props.renderComplete(); + setChartIsLoaded(true); } }, [props] @@ -363,8 +368,16 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => { ) as Partial; const themeOverrides = useMemo( - () => getPartitionTheme(visType, visParams, chartTheme, containerDimensions, rescaleFactor), - [visType, visParams, chartTheme, containerDimensions, rescaleFactor] + () => + getPartitionTheme( + visType, + visParams, + chartTheme, + containerDimensions, + rescaleFactor, + hasOpenedOnAggBasedEditor + ), + [visType, visParams, chartTheme, containerDimensions, rescaleFactor, hasOpenedOnAggBasedEditor] ); const fixedViewPort = document.getElementById('app-fixed-viewport'); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx index 056ba6b7136ce..2379096796639 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx +++ b/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx @@ -21,7 +21,11 @@ import { withSuspense } from '@kbn/presentation-util-plugin/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { METRIC_TYPE } from '@kbn/analytics'; import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils'; -import { extractContainerType, extractVisualizationType } from '@kbn/chart-expressions-common'; +import { + extractContainerType, + extractVisualizationType, + isOnAggBasedEditor, +} from '@kbn/chart-expressions-common'; import { VisTypePieDependencies } from '../plugin'; import { PARTITION_VIS_RENDERER_NAME } from '../../common/constants'; import { CellValueAction, GetCompatibleCellValueActions } from '../types'; @@ -110,6 +114,8 @@ export const getPartitionVisRenderer: ( plugins.charts.palettes.getPalettes(), ]); + const hasOpenedOnAggBasedEditor = isOnAggBasedEditor(handlers.getExecutionContext()); + render( @@ -128,6 +134,7 @@ export const getPartitionVisRenderer: ( syncColors={syncColors} columnCellValueActions={columnCellValueActions} overrides={overrides} + hasOpenedOnAggBasedEditor={hasOpenedOnAggBasedEditor} /> diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.test.ts index 345d6ce068d0c..31d025ac0310f 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.test.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.test.ts @@ -144,9 +144,16 @@ const runPieDonutWaffleTestSuites = (chartType: ChartTypes, visParams: Partition }); }); - it('should return adjusted padding settings if dimensions are specified', () => { + it('should return adjusted padding settings if dimensions are specified and is on aggBased editor', () => { const specifiedDimensions = { width: 2000, height: 2000 }; - const theme = getPartitionTheme(chartType, visParams, chartTheme, specifiedDimensions); + const theme = getPartitionTheme( + chartType, + visParams, + chartTheme, + specifiedDimensions, + undefined, + true + ); expect(theme).toEqual({ ...getStaticThemeOptions(chartTheme, visParams), @@ -233,7 +240,6 @@ const runPieDonutWaffleTestSuites = (chartType: ChartTypes, visParams: Partition expect(theme).toEqual({ ...getStaticThemeOptions(chartTheme, visParams), - chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 }, partition: { ...getStaticThemePartition(chartTheme, visParams), outerSizeRatio: rescaleFactor, @@ -263,7 +269,6 @@ const runPieDonutWaffleTestSuites = (chartType: ChartTypes, visParams: Partition expect(theme).toEqual({ ...getStaticThemeOptions(chartTheme, vParams), - chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 }, partition: { ...getStaticThemePartition(chartTheme, vParams), outerSizeRatio: 0.5, @@ -285,7 +290,6 @@ const runPieDonutWaffleTestSuites = (chartType: ChartTypes, visParams: Partition expect(theme).toEqual({ ...getStaticThemeOptions(chartTheme, vParams), - chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 }, partition: { ...getStaticThemePartition(chartTheme, vParams), linkLabel: linkLabelWithEnoughSpace(vParams), @@ -420,7 +424,14 @@ const runTreemapMosaicTestSuites = (chartType: ChartTypes, visParams: PartitionV it('should return fullfilled padding settings if dimensions are specified', () => { const specifiedDimensions = { width: 2000, height: 2000 }; - const theme = getPartitionTheme(chartType, visParams, chartTheme, specifiedDimensions); + const theme = getPartitionTheme( + chartType, + visParams, + chartTheme, + specifiedDimensions, + undefined, + true + ); expect(theme).toEqual({ ...getStaticThemeOptions(chartTheme, visParams), diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.ts index edb1aaea64aad..3714cac911829 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.ts @@ -26,7 +26,8 @@ type GetThemeFn = ( visParams: PartitionVisParams, chartTheme: RecursivePartial, dimensions?: PieContainerDimensions, - rescaleFactor?: number + rescaleFactor?: number, + hasOpenedOnAggBasedEditor?: boolean ) => PartialTheme; type GetPieDonutWaffleThemeFn = ( @@ -118,12 +119,13 @@ export const getPartitionTheme: GetThemeFn = ( visParams, chartTheme, dimensions, - rescaleFactor = 1 + rescaleFactor = 1, + hasOpenedOnAggBasedEditor ) => { // On small multiples we want the labels to only appear inside const isSplitChart = Boolean(visParams.dimensions.splitColumn || visParams.dimensions.splitRow); const paddingProps: PartialTheme | null = - dimensions && !isSplitChart + dimensions && !isSplitChart && hasOpenedOnAggBasedEditor ? { chartPaddings: { top: ((1 - Math.min(1, MAX_SIZE / dimensions?.height)) / 2) * dimensions?.height, diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/bottom_bar/bottom_bar.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/bottom_bar/bottom_bar.tsx deleted file mode 100644 index b19c02669f818..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/bottom_bar/bottom_bar.tsx +++ /dev/null @@ -1,96 +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, { FC } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiBottomBar, - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiToolTip, -} from '@elastic/eui'; - -import { MODE as DATAVISUALIZER_MODE } from '../file_data_visualizer_view/constants'; - -interface BottomBarProps { - mode: DATAVISUALIZER_MODE; - onChangeMode: (mode: DATAVISUALIZER_MODE) => void; - onCancel: () => void; - disableImport?: boolean; -} - -/** - * Bottom bar component for Data Visualizer page. - */ -export const BottomBar: FC = ({ mode, onChangeMode, onCancel, disableImport }) => { - if (mode === DATAVISUALIZER_MODE.READ) { - return ( - - - - - ) : null - } - > - onChangeMode(DATAVISUALIZER_MODE.IMPORT)} - data-test-subj="dataVisualizerFileOpenImportPageButton" - > - - - - - - onCancel()}> - - - - - - ); - } else { - return ( - - - - onChangeMode(DATAVISUALIZER_MODE.READ)}> - - - - - onCancel()}> - - - - - - ); - } -}; diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js index b1378769efc92..dfa98a3713130 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js @@ -13,7 +13,6 @@ import { EuiSpacer } from '@elastic/eui'; import { isEqual } from 'lodash'; import { AboutPanel, LoadingPanel } from '../about_panel'; -import { BottomBar } from '../bottom_bar'; import { ResultsView } from '../results_view'; import { FileCouldNotBeRead, @@ -54,7 +53,6 @@ export class FileDataVisualizerView extends Component { mode: MODE.READ, isEditFlyoutVisible: false, isExplanationFlyoutVisible: false, - bottomBarVisible: false, hasPermissionToImport: false, fileCouldNotBeReadPermissionError: false, }; @@ -85,7 +83,6 @@ export class FileDataVisualizerView extends Component { this.setState( { loading: files.length > 0, - bottomBarVisible: files.length > 0, loaded: false, fileName: '', fileContents: '', @@ -213,30 +210,18 @@ export class FileDataVisualizerView extends Component { closeEditFlyout = () => { this.setState({ isEditFlyoutVisible: false }); - this.showBottomBar(); }; showEditFlyout = () => { this.setState({ isEditFlyoutVisible: true }); - this.hideBottomBar(); }; closeExplanationFlyout = () => { this.setState({ isExplanationFlyoutVisible: false }); - this.showBottomBar(); }; showExplanationFlyout = () => { this.setState({ isExplanationFlyoutVisible: true }); - this.hideBottomBar(); - }; - - showBottomBar = () => { - this.setState({ bottomBarVisible: true }); - }; - - hideBottomBar = () => { - this.setState({ bottomBarVisible: false }); }; setOverrides = (overrides) => { @@ -282,7 +267,6 @@ export class FileDataVisualizerView extends Component { mode, isEditFlyoutVisible, isExplanationFlyoutVisible, - bottomBarVisible, hasPermissionToImport, fileCouldNotBeReadPermissionError, } = this.state; @@ -333,6 +317,9 @@ export class FileDataVisualizerView extends Component { showEditFlyout={this.showEditFlyout} showExplanationFlyout={this.showExplanationFlyout} disableButtons={isEditFlyoutVisible || isExplanationFlyoutVisible} + onChangeMode={this.changeMode} + onCancel={this.onCancel} + disableImport={hasPermissionToImport === false} /> )} )} - - {bottomBarVisible && loaded && ( - <> - - - - )} )} {mode === MODE.IMPORT && ( @@ -369,23 +344,13 @@ export class FileDataVisualizerView extends Component { fileContents={fileContents} data={data} dataViewsContract={this.props.dataViewsContract} - showBottomBar={this.showBottomBar} - hideBottomBar={this.hideBottomBar} fileUpload={this.props.fileUpload} getAdditionalLinks={this.props.getAdditionalLinks} capabilities={this.props.capabilities} + mode={mode} + onChangeMode={this.changeMode} + onCancel={this.onCancel} /> - - {bottomBarVisible && ( - <> - - - - )} )} @@ -393,14 +358,3 @@ export class FileDataVisualizerView extends Component { ); } } - -function BottomPadding() { - // padding for the BottomBar - return ( - <> - - - - - ); -} diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js index eb42fde664850..db6929c7d4a66 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js @@ -10,12 +10,14 @@ import React, { Component } from 'react'; import { EuiButton, - EuiPage, EuiPageBody, EuiPageContentHeader_Deprecated as EuiPageContentHeader, EuiPanel, EuiSpacer, EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -32,6 +34,7 @@ import { addCombinedFieldsToMappings, getDefaultCombinedFields, } from '../../../common/components/combined_fields'; +import { MODE as DATAVISUALIZER_MODE } from '../file_data_visualizer_view/constants'; const DEFAULT_TIME_FIELD = '@timestamp'; const DEFAULT_INDEX_SETTINGS = { number_of_shards: 1 }; @@ -98,7 +101,7 @@ export class ImportView extends Component { // TODO - sort this function out. it's a mess async import() { - const { data, results, dataViewsContract, showBottomBar, fileUpload } = this.props; + const { data, results, dataViewsContract, fileUpload } = this.props; const { format } = results; let { timeFieldName } = this.state; @@ -149,7 +152,6 @@ export class ImportView extends Component { permissionCheckStatus: IMPORT_STATUS.COMPLETE, }, () => { - this.props.hideBottomBar(); setTimeout(async () => { let success = true; const createPipeline = pipelineString !== ''; @@ -309,8 +311,6 @@ export class ImportView extends Component { } } - showBottomBar(); - this.setState({ importing: false, imported: success, @@ -408,12 +408,10 @@ export class ImportView extends Component { showFilebeatFlyout = () => { this.setState({ isFilebeatFlyoutVisible: true }); - this.props.hideBottomBar(); }; closeFilebeatFlyout = () => { this.setState({ isFilebeatFlyoutVisible: false }); - this.props.showBottomBar(); }; async loadDataViewNames() { @@ -482,128 +480,172 @@ export class ImportView extends Component { checkingValidIndex === true; return ( - - - - -

{this.props.fileName}

-
-
+ + + +

{this.props.fileName}

+
+
+ + + +

+ +

+
+ + + - - -

- -

-
- - - + {(initialized === false || importing === true) && ( + + + + + + + + this.props.onChangeMode(DATAVISUALIZER_MODE.READ)} + isDisabled={importing} + > + + + + + this.props.onCancel()} isDisabled={importing}> + + + + + )} - {(initialized === false || importing === true) && ( - - - - )} - - {initialized === true && importing === false && ( - - - - )} -
- - {initialized === true && ( - - - - - - - {imported === true && ( - - - - + {initialized === true && importing === false && ( + + + + + + + + this.props.onChangeMode(DATAVISUALIZER_MODE.READ)} + isDisabled={importing} + > + + + + + this.props.onCancel()} isDisabled={importing}> + + + + + )} + - + {initialized === true && ( + + - + + + {imported === true && ( + + + + + + + + + + {isFilebeatFlyoutVisible && ( + + )} + + )} +
+ + )} + {errors.length > 0 && ( + + - {isFilebeatFlyoutVisible && ( - - )} - - )} - - - )} - {errors.length > 0 && ( - - - - - - )} -
-
+ + + )} + ); } } diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/results_view/results_view.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/results_view/results_view.tsx index 6977114954652..26a727a7a922e 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/results_view/results_view.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/results_view/results_view.tsx @@ -12,7 +12,6 @@ import { EuiButton, EuiButtonEmpty, EuiPageBody, - EuiPageContentHeader_Deprecated as EuiPageContentHeader, EuiPanel, EuiSpacer, EuiTitle, @@ -24,6 +23,7 @@ import { FindFileStructureResponse } from '@kbn/file-upload-plugin/common'; import { FileContents } from '../file_contents'; import { AnalysisSummary } from '../analysis_summary'; import { FieldsStatsGrid } from '../../../common/components/fields_stats_grid'; +import { MODE as DATAVISUALIZER_MODE } from '../file_data_visualizer_view/constants'; interface Props { data: string; @@ -32,6 +32,9 @@ interface Props { showEditFlyout(): void; showExplanationFlyout(): void; disableButtons: boolean; + onChangeMode: (mode: DATAVISUALIZER_MODE) => void; + onCancel: () => void; + disableImport?: boolean; } export const ResultsView: FC = ({ @@ -41,14 +44,32 @@ export const ResultsView: FC = ({ showEditFlyout, showExplanationFlyout, disableButtons, + onChangeMode, + onCancel, + disableImport, }) => { return ( - - -

{fileName}

-
-
+ + + +

{fileName}

+
+
+ + + + + +
+
@@ -67,6 +88,19 @@ export const ResultsView: FC = ({ + + onChangeMode(DATAVISUALIZER_MODE.IMPORT)} + data-test-subj="dataVisualizerFileOpenImportPageButton" + > + + + showEditFlyout()} disabled={disableButtons}> { created_at: null, custom_scheduling: {}, error: null, - index_name: 'index_name', + index_name: 'search-index_name', language: null, last_access_control_sync_status: null, last_seen: null, @@ -345,7 +349,7 @@ describe('startSync lib function', () => { configuration: {}, filtering: null, id: 'connectorId', - index_name: 'index_name', + index_name: `${CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX}index_name`, language: null, pipeline: null, service_type: null, diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts index ff9e0419e69b0..8d2ac4715e8df 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts @@ -7,18 +7,22 @@ import { IScopedClusterClient } from '@kbn/core/server'; -import { CONNECTORS_INDEX, CONNECTORS_JOBS_INDEX } from '../..'; +import { + CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX, + CONNECTORS_INDEX, + CONNECTORS_JOBS_INDEX, +} from '../..'; import { isConfigEntry } from '../../../common/connectors/is_category_entry'; import { ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE } from '../../../common/constants'; import { - ConnectorSyncConfiguration, ConnectorDocument, + ConnectorSyncConfiguration, ConnectorSyncJobDocument, + SyncJobType, SyncStatus, TriggerMethod, - SyncJobType, } from '../../../common/types/connectors'; import { ErrorCode } from '../../../common/types/error_codes'; @@ -63,6 +67,12 @@ export const startConnectorSync = async ( }); } + const indexNameWithoutSearchPrefix = index_name.replace('search-', ''); + const targetIndexName = + jobType === SyncJobType.ACCESS_CONTROL + ? `${CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX}${indexNameWithoutSearchPrefix}` + : index_name; + return await client.asCurrentUser.index({ document: { cancelation_requested_at: null, @@ -72,7 +82,7 @@ export const startConnectorSync = async ( configuration, filtering: filtering ? filtering[0]?.active ?? null : null, id: connectorId, - index_name, + index_name: targetIndexName, language, pipeline: pipeline ?? null, service_type, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/hooks/navigation.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/hooks/navigation.tsx index b908cb4908ade..097323962bc0c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/hooks/navigation.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/hooks/navigation.tsx @@ -4,9 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import { useCallback, useMemo, useEffect, useRef } from 'react'; -import { useHistory } from 'react-router-dom'; +import type { ApplicationStart } from '@kbn/core-application-browser'; +import { PLUGIN_ID } from '../../../../constants'; import { useStartServices, useLink, useIntraAppState } from '../../../../hooks'; import type { CreatePackagePolicyRouteState, @@ -67,7 +69,6 @@ export const useOnSaveNavigate = (params: UseOnSaveNavigateParams) => { const routeState = useIntraAppState(); const doOnSaveNavigation = useRef(true); const { getPath } = useLink(); - const history = useHistory(); const { application: { navigateToApp }, @@ -81,32 +82,46 @@ export const useOnSaveNavigate = (params: UseOnSaveNavigateParams) => { }, []); const onSaveNavigate = useCallback( - (policy?: PackagePolicy, paramsToApply: OnSaveQueryParamKeys[] = []) => { + (policy: PackagePolicy, paramsToApply: OnSaveQueryParamKeys[] = []) => { if (!doOnSaveNavigation.current) { return; } - const packagePolicyPath = getPath('policy_details', { policyId: packagePolicy.policy_id }); - if (routeState?.onSaveNavigateTo && policy) { - const [appId, options] = routeState.onSaveNavigateTo; - if (options?.path) { - const pathWithQueryString = appendOnSaveQueryParamsToPath({ - // In cases where we want to navigate back to a new/existing policy, we need to override the initial `path` - // value and navigate to the actual agent policy instead - path: queryParamsPolicyId ? packagePolicyPath : options.path, - policy, - mappingOptions: routeState.onSaveQueryParams, - paramsToApply, - }); - navigateToApp(appId, { ...options, path: pathWithQueryString }); - } else { - navigateToApp(...routeState.onSaveNavigateTo); - } + + const [onSaveNavigateTo, onSaveQueryParams]: [ + Parameters, + CreatePackagePolicyRouteState['onSaveQueryParams'] + ] = routeState?.onSaveNavigateTo + ? [routeState.onSaveNavigateTo, routeState?.onSaveQueryParams] + : [ + [ + PLUGIN_ID, + { + path: packagePolicyPath, + }, + ], + { + showAddAgentHelp: true, + openEnrollmentFlyout: true, + }, + ]; + + const [appId, options] = onSaveNavigateTo; + if (options?.path) { + const pathWithQueryString = appendOnSaveQueryParamsToPath({ + // In cases where we want to navigate back to a new/existing policy, we need to override the initial `path` + // value and navigate to the actual agent policy instead + path: queryParamsPolicyId ? packagePolicyPath : options.path, + policy, + mappingOptions: onSaveQueryParams, + paramsToApply, + }); + navigateToApp(appId, { ...options, path: pathWithQueryString }); } else { - history.push(packagePolicyPath); + navigateToApp(...onSaveNavigateTo); } }, - [packagePolicy.policy_id, getPath, navigateToApp, history, routeState, queryParamsPolicyId] + [packagePolicy.policy_id, getPath, navigateToApp, routeState, queryParamsPolicyId] ); return onSaveNavigate; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx index cc01cadccb788..83b476b774913 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx @@ -233,10 +233,10 @@ export function useOnSubmit({ queryParamsPolicyId, }); - const navigateAddAgent = (policy?: PackagePolicy) => + const navigateAddAgent = (policy: PackagePolicy) => onSaveNavigate(policy, ['openEnrollmentFlyout']); - const navigateAddAgentHelp = (policy?: PackagePolicy) => + const navigateAddAgentHelp = (policy: PackagePolicy) => onSaveNavigate(policy, ['showAddAgentHelp']); const onSubmit = useCallback( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx index e2f2ec5e908f7..458f4fab53384 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { useHistory } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; import React from 'react'; import { fireEvent, act, waitFor } from '@testing-library/react'; @@ -127,6 +126,8 @@ describe('when on the package policy create page', () => { mockApiCalls(testRenderer.startServices.http); testRenderer.mountHistory.push(createPageUrlPath); + jest.mocked(useStartServices().application.navigateToApp).mockReset(); + mockPackageInfo = { data: { item: { @@ -339,12 +340,15 @@ describe('when on the package policy create page', () => { test('should navigate to save navigate path with query param if set', async () => { const routeState = { onSaveNavigateTo: [PLUGIN_ID, { path: '/save/url/here' }], + onSaveQueryParams: { + openEnrollmentFlyout: true, + }, }; const queryParamsPolicyId = 'agent-policy-1'; await setupSaveNavigate(routeState, queryParamsPolicyId); expect(useStartServices().application.navigateToApp).toHaveBeenCalledWith(PLUGIN_ID, { - path: '/policies/agent-policy-1', + path: '/policies/agent-policy-1?openEnrollmentFlyout=true', }); }); @@ -357,10 +361,12 @@ describe('when on the package policy create page', () => { expect(useStartServices().application.navigateToApp).toHaveBeenCalledWith(PLUGIN_ID); }); - test('should set history if no routeState', async () => { + test('should navigate to agent policy if no route state is set', async () => { await setupSaveNavigate({}); - expect(useHistory().push).toHaveBeenCalledWith('/policies/agent-policy-1'); + expect(useStartServices().application.navigateToApp).toHaveBeenCalledWith(PLUGIN_ID, { + path: '/policies/agent-policy-1?openEnrollmentFlyout=true', + }); }); }); 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 81c1c518ccd4f..fd62addd08391 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 @@ -402,13 +402,16 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ onCancel={() => setFormState('VALID')} /> )} - {formState === 'SUBMITTED_NO_AGENTS' && agentPolicy && packageInfo && ( - navigateAddAgent(savedPackagePolicy)} - onCancel={() => navigateAddAgentHelp(savedPackagePolicy)} - /> - )} + {formState === 'SUBMITTED_NO_AGENTS' && + agentPolicy && + packageInfo && + savedPackagePolicy && ( + navigateAddAgent(savedPackagePolicy)} + onCancel={() => navigateAddAgentHelp(savedPackagePolicy)} + /> + )} {packageInfo && ( ({ mappings: { dynamic: false, properties: { - created_at: { type: 'date' }, policy_id: { type: 'keyword' }, token_plain: { type: 'keyword' }, }, diff --git a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts index 013f77f3005f3..40f0768161368 100644 --- a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts +++ b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts @@ -152,7 +152,7 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { latest: { top_hits: { size: 1, - sort: [{ [`${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.created_at`]: { order: 'desc' } }], + sort: [{ created_at: { order: 'desc' } }], }, }, }, diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index 499af010d076f..dc858ff5029a8 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -98,5 +98,6 @@ "@kbn/safer-lodash-set", "@kbn/shared-ux-file-types", "@kbn/core-http-router-server-mocks", + "@kbn/core-application-browser", ] } diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx index f5c2101317f01..ac3981026ea8e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx @@ -8,15 +8,17 @@ import React from 'react'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { LogViewReference } from '../../../../../../../common/log_views'; import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; interface LogsLinkToStreamProps { startTime: number; endTime: number; query: string; + logView: LogViewReference; } -export const LogsLinkToStream = ({ startTime, endTime, query }: LogsLinkToStreamProps) => { +export const LogsLinkToStream = ({ startTime, endTime, query, logView }: LogsLinkToStreamProps) => { const { services } = useKibanaContextForPlugin(); const { locators } = services; @@ -30,6 +32,7 @@ export const LogsLinkToStream = ({ startTime, endTime, query }: LogsLinkToStream endTime, }, filter: query, + logView, })} data-test-subj="hostsView-logs-link-to-stream-button" iconType="popout" diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx index db93a9a4617cc..55a8410618b28 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx @@ -5,9 +5,15 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { DEFAULT_LOG_VIEW } from '../../../../../../../common/log_views'; +import type { + LogIndexReference, + LogViewReference, +} from '../../../../../../../common/log_views/types'; +import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; import { InfraLoadingPanel } from '../../../../../../components/loading'; import { LogStream } from '../../../../../../components/log_stream'; import { useHostsViewContext } from '../../../hooks/use_hosts_view'; @@ -23,11 +29,57 @@ export const LogsTabContent = () => { const { from, to } = useMemo(() => getDateRangeAsTimestamp(), [getDateRangeAsTimestamp]); const { hostNodes, loading } = useHostsViewContext(); + const [logViewIndices, setLogViewIndices] = useState(); + + const { + services: { + logViews: { client }, + }, + } = useKibanaContextForPlugin(); + + useEffect(() => { + const getLogView = async () => { + const { attributes } = await client.getLogView(DEFAULT_LOG_VIEW); + setLogViewIndices(attributes.logIndices); + }; + getLogView(); + }, [client, setLogViewIndices]); + const hostsFilterQuery = useMemo( () => createHostsFilter(hostNodes.map((p) => p.name)), [hostNodes] ); + const logView: LogViewReference = useMemo(() => { + return { + type: 'log-view-inline', + id: 'hosts-logs-view', + attributes: { + name: 'Hosts Logs View', + description: 'Default view for hosts logs tab', + logIndices: logViewIndices!, + logColumns: [ + { + timestampColumn: { + id: '5e7f964a-be8a-40d8-88d2-fbcfbdca0e2f', + }, + }, + { + fieldColumn: { + id: 'eb9777a8-fcd3-420e-ba7d-172fff6da7a2', + field: 'host.name', + }, + }, + { + messageColumn: { + id: 'b645d6da-824b-4723-9a2a-e8cece1645c0', + }, + }, + ], + }, + }; + }, [logViewIndices]); + const logsLinkToStreamQuery = useMemo(() => { const hostsFilterQueryParam = createHostsFilterQueryParam(hostNodes.map((p) => p.name)); @@ -38,7 +90,7 @@ export const LogsTabContent = () => { return filterQuery.query || hostsFilterQueryParam; }, [filterQuery.query, hostNodes]); - if (loading) { + if (loading || !logViewIndices) { return ( @@ -64,14 +116,19 @@ export const LogsTabContent = () => { - + { + const url = new URL(HOSTS_FEEDBACK_LINK); + if (kibanaVersion) { + url.searchParams.append(KIBANA_VERSION_QUERY_PARAM, kibanaVersion); + } + + return url.href; +}; export const HostsPage = () => { const { isLoading, loadSourceFailureMessage, loadSource, source } = useSourceContext(); + const { + services: { kibanaVersion }, + } = useKibanaContextForPlugin(); useTrackPageview({ app: 'infra_metrics', path: 'hosts' }); useTrackPageview({ app: 'infra_metrics', path: 'hosts', delay: 15000 }); @@ -83,7 +98,7 @@ export const HostsPage = () => { rightSideItems: [ (() => ({})); constructor(context: PluginInitializerContext) { @@ -74,6 +75,7 @@ export class Plugin implements InfraClientPluginClass { this.metricsExplorerViews = new MetricsExplorerViewsService(); this.telemetry = new TelemetryService(); this.appTarget = this.config.logs.app_target; + this.kibanaVersion = context.env.packageInfo.version; } setup(core: InfraClientCoreSetup, pluginsSetup: InfraClientSetupDeps) { @@ -286,10 +288,15 @@ export class Plugin implements InfraClientPluginClass { deepLinks: infraDeepLinks, mount: async (params: AppMountParameters) => { // mount callback should not use setup dependencies, get start dependencies instead - const [coreStart, pluginsStart, pluginStart] = await core.getStartServices(); + const [coreStart, plugins, pluginStart] = await core.getStartServices(); const { renderApp } = await import('./apps/metrics_app'); - return renderApp(coreStart, pluginsStart, pluginStart, params); + return renderApp( + coreStart, + { ...plugins, kibanaVersion: this.kibanaVersion }, + pluginStart, + params + ); }, }); diff --git a/x-pack/plugins/infra/public/types.ts b/x-pack/plugins/infra/public/types.ts index fb1d2d4ab2a91..15d4f4ea5fb6e 100644 --- a/x-pack/plugins/infra/public/types.ts +++ b/x-pack/plugins/infra/public/types.ts @@ -93,6 +93,7 @@ export interface InfraClientStartDeps { dataViews: DataViewsPublicPluginStart; discover: DiscoverStart; embeddable?: EmbeddableStart; + kibanaVersion?: string; lens: LensPublicStart; ml: MlPluginStart; observability: ObservabilityPublicStart; diff --git a/x-pack/plugins/ml/common/types/storage.ts b/x-pack/plugins/ml/common/types/storage.ts index cb80b17bda583..a74bbea0e3aff 100644 --- a/x-pack/plugins/ml/common/types/storage.ts +++ b/x-pack/plugins/ml/common/types/storage.ts @@ -14,6 +14,7 @@ export const ML_GETTING_STARTED_CALLOUT_DISMISSED = 'ml.gettingStarted.isDismiss export const ML_FROZEN_TIER_PREFERENCE = 'ml.frozenDataTierPreference'; export const ML_ANOMALY_EXPLORER_PANELS = 'ml.anomalyExplorerPanels'; export const ML_NOTIFICATIONS_LAST_CHECKED_AT = 'ml.notificationsLastCheckedAt'; +export const ML_OVERVIEW_PANELS = 'ml.overviewPanels'; export type PartitionFieldConfig = | { @@ -52,6 +53,12 @@ export interface AnomalyExplorerPanelsState { mainPage: { size: number }; } +export interface OverviewPanelsState { + nodes: boolean; + adJobs: boolean; + dfaJobs: boolean; +} + export interface MlStorageRecord { [key: string]: unknown; [ML_ENTITY_FIELDS_CONFIG]: PartitionFieldsConfig; @@ -60,6 +67,7 @@ export interface MlStorageRecord { [ML_FROZEN_TIER_PREFERENCE]: FrozenTierPreference; [ML_ANOMALY_EXPLORER_PANELS]: AnomalyExplorerPanelsState | undefined; [ML_NOTIFICATIONS_LAST_CHECKED_AT]: number | undefined; + [ML_OVERVIEW_PANELS]: OverviewPanelsState; } export type MlStorage = Partial | null; @@ -78,6 +86,8 @@ export type TMlStorageMapped = T extends typeof ML_ENTIT ? AnomalyExplorerPanelsState | undefined : T extends typeof ML_NOTIFICATIONS_LAST_CHECKED_AT ? number | undefined + : T extends typeof ML_OVERVIEW_PANELS + ? OverviewPanelsState | undefined : null; export const ML_STORAGE_KEYS = [ @@ -87,4 +97,5 @@ export const ML_STORAGE_KEYS = [ ML_FROZEN_TIER_PREFERENCE, ML_ANOMALY_EXPLORER_PANELS, ML_NOTIFICATIONS_LAST_CHECKED_AT, + ML_OVERVIEW_PANELS, ] as const; diff --git a/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx b/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx new file mode 100644 index 0000000000000..53ed046423c2f --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx @@ -0,0 +1,137 @@ +/* + * 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, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiSplitPanel, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import React, { type FC } from 'react'; +import { css } from '@emotion/react/dist/emotion-react.cjs'; +import { useCurrentThemeVars } from '../../contexts/kibana'; + +export interface CollapsiblePanelProps { + isOpen: boolean; + onToggle: (isOpen: boolean) => void; + + header: React.ReactElement; + headerItems?: React.ReactElement[]; +} + +export const CollapsiblePanel: FC = ({ + isOpen, + onToggle, + children, + header, + headerItems, +}) => { + const { euiTheme } = useCurrentThemeVars(); + + return ( + + + + + + + { + onToggle(!isOpen); + }} + /> + + + +

{header}

+
+
+
+
+ {headerItems ? ( + + + {headerItems.map((item, i) => { + return ( + +
+ {item} +
+
+ ); + })} +
+
+ ) : null} +
+
+ {isOpen ? ( + + {children} + + ) : null} +
+ ); +}; + +export interface StatEntry { + label: string; + value: number; + 'data-test-subj'?: string; +} + +export interface OverviewStatsBarProps { + inputStats: StatEntry[]; + dataTestSub?: string; +} + +export const OverviewStatsBar: FC = ({ inputStats, dataTestSub }) => { + return ( + + {inputStats.map(({ value, label, 'data-test-subj': dataTestSubjValue }) => { + return ( + + + + {label}: + + + {value} + + + + ); + })} + + ); +}; diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/bottom_bar/index.ts b/x-pack/plugins/ml/public/application/components/collapsible_panel/index.ts similarity index 81% rename from x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/bottom_bar/index.ts rename to x-pack/plugins/ml/public/application/components/collapsible_panel/index.ts index 5291268824a62..d45a251f69ca9 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/bottom_bar/index.ts +++ b/x-pack/plugins/ml/public/application/components/collapsible_panel/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { BottomBar } from './bottom_bar'; +export { CollapsiblePanel } from './collapsible_panel'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/empty_prompt/empty_prompt.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/empty_prompt/empty_prompt.tsx index 23ce92dce1b91..14745812e3045 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/empty_prompt/empty_prompt.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/empty_prompt/empty_prompt.tsx @@ -6,16 +6,7 @@ */ import React, { FC } from 'react'; -import { - EuiButton, - EuiCallOut, - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiImage, - EuiLink, - EuiTitle, -} from '@elastic/eui'; +import { EuiButton, EuiEmptyPrompt, EuiImage, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import dfaImage from './data_frame_analytics_kibana.png'; @@ -26,10 +17,7 @@ import { usePermissionCheck } from '../../../../../capabilities/check_capabiliti export const AnalyticsEmptyPrompt: FC = () => { const { - services: { - docLinks, - http: { basePath }, - }, + services: { docLinks }, } = useMlKibana(); const [canCreateDataFrameAnalytics, canStartStopDataFrameAnalytics] = usePermissionCheck([ @@ -40,7 +28,6 @@ export const AnalyticsEmptyPrompt: FC = () => { const disabled = !mlNodesAvailable() || !canCreateDataFrameAnalytics || !canStartStopDataFrameAnalytics; - const transformsLink = `${basePath.get()}/app/management/data/transform`; const navigateToPath = useNavigateToPath(); const navigateToSourceSelection = async () => { @@ -57,16 +44,15 @@ export const AnalyticsEmptyPrompt: FC = () => { size="fullWidth" src={dfaImage} alt={i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptTitle', { - defaultMessage: 'Create your first data frame analytics job', + defaultMessage: 'Analyze your data with data frame analytics', })} /> } - color="subdued" title={

} @@ -78,39 +64,6 @@ export const AnalyticsEmptyPrompt: FC = () => { defaultMessage="Train outlier detection, regression, or classification machine learning models using data frame analytics." />

- - - - ), - sourcedata: ( - - - - ), - }} - /> - } - iconType="iInCircle" - /> } actions={[ @@ -118,37 +71,19 @@ export const AnalyticsEmptyPrompt: FC = () => { onClick={navigateToSourceSelection} isDisabled={disabled} color="primary" - iconType="plusInCircle" - fill data-test-subj="mlAnalyticsCreateFirstButton" > {i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptButtonText', { - defaultMessage: 'Create job', + defaultMessage: 'Create data frame analytics job', })}
, + + + , ]} - footer={ - - - -

- -

-
-
- - - - - -
- } data-test-subj="mlNoDataFrameAnalyticsFound" /> ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.test.ts index 613f9034e2ff4..949c8a47deb32 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.test.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.test.ts @@ -67,7 +67,7 @@ describe('get_analytics', () => { // act and assert expect(getAnalyticsJobsStats(mockResponse)).toEqual({ total: { - label: 'Total analytics jobs', + label: 'Total', value: 2, show: true, }, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts index 170a90b1fcba0..9a3ea0c9bef90 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts @@ -47,7 +47,7 @@ export function getInitialAnalyticsStats(): AnalyticStatsBarStats { return { total: { label: i18n.translate('xpack.ml.overview.statsBar.totalAnalyticsLabel', { - defaultMessage: 'Total analytics jobs', + defaultMessage: 'Total', }), value: 0, show: true, @@ -97,12 +97,18 @@ export function getAnalyticsJobsStats( ); resultStats.failed.show = resultStats.failed.value > 0; resultStats.total.value = analyticsStats.count; + + if (resultStats.total.value === 0) { + resultStats.started.show = false; + resultStats.stopped.show = false; + } + return resultStats; } export const getAnalyticsFactory = ( setAnalytics: React.Dispatch>, - setAnalyticsStats: React.Dispatch>, + setAnalyticsStats: (update: AnalyticStatsBarStats | undefined) => void, setErrorMessage: React.Dispatch< React.SetStateAction >, diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/anomaly_detection_empty_state/anomaly_detection_empty_state.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/anomaly_detection_empty_state/anomaly_detection_empty_state.tsx index b8dee1a7e6f60..171320dd7b781 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/anomaly_detection_empty_state/anomaly_detection_empty_state.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/anomaly_detection_empty_state/anomaly_detection_empty_state.tsx @@ -7,15 +7,7 @@ import React, { FC } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiButton, - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiImage, - EuiLink, - EuiTitle, -} from '@elastic/eui'; +import { EuiButton, EuiEmptyPrompt, EuiImage, EuiLink } from '@elastic/eui'; import adImage from './anomaly_detection_kibana.png'; import { ML_PAGES } from '../../../../../../common/constants/locator'; import { useMlKibana, useMlLocator, useNavigateToPath } from '../../../../contexts/kibana'; @@ -47,12 +39,11 @@ export const AnomalyDetectionEmptyState: FC = () => { hasBorder={false} hasShadow={false} icon={} - color="subdued" title={

} @@ -66,43 +57,25 @@ export const AnomalyDetectionEmptyState: FC = () => {

} - actions={ + actions={[ - - } - footer={ - - - -

- -

-
-
- - - - - -
- } +
, + + + , + ]} data-test-subj="mlAnomalyDetectionEmptyState" /> ); diff --git a/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/nodes_list.tsx b/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/nodes_list.tsx index a615e40c9e3ea..a8e5848aaef1a 100644 --- a/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/nodes_list.tsx +++ b/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/nodes_list.tsx @@ -200,15 +200,20 @@ export const NodesList: FC = ({ compactView = false }) => { return (
- - - {nodesStats && ( - - - - )} - - + {nodesStats && !compactView ? ( + <> + + + {nodesStats && ( + + + + )} + + + + ) : null} +
allowNeutralSort={false} diff --git a/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts b/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts index ea981a25d7ecb..c44c391a2fcfd 100644 --- a/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts +++ b/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts @@ -43,3 +43,7 @@ export function lazyMlNodesAvailable() { export function permissionToViewMlNodeCount() { return userHasPermissionToViewMlNodeCount; } + +export function getMlNodesCount(): number { + return mlNodeCount; +} diff --git a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx index b53860d9a3be6..41e9732461bb3 100644 --- a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx @@ -5,28 +5,32 @@ * 2.0. */ -import React, { FC, useEffect, useState } from 'react'; -import { - EuiButton, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner, - EuiPanel, - EuiSpacer, - EuiText, -} from '@elastic/eui'; +import React, { FC, useCallback, useEffect, useState } from 'react'; +import { EuiCallOut, EuiLink, EuiLoadingSpinner } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useStorage } from '@kbn/ml-local-storage'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { type AnalyticStatsBarStats } from '../../../components/stats_bar'; +import { + OverviewStatsBar, + type StatEntry, +} from '../../../components/collapsible_panel/collapsible_panel'; +import { + ML_OVERVIEW_PANELS, + MlStorageKey, + TMlStorageMapped, +} from '../../../../../common/types/storage'; import { AnalyticsTable } from './table'; import { getAnalyticsFactory } from '../../../data_frame_analytics/pages/analytics_management/services/analytics_service'; import { DataFrameAnalyticsListRow } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; -import { AnalyticStatsBarStats, StatsBar } from '../../../components/stats_bar'; import { useMlLink } from '../../../contexts/kibana'; import { ML_PAGES } from '../../../../../common/constants/locator'; import { useRefresh } from '../../../routing/use_refresh'; import type { GetDataFrameAnalyticsStatsResponseError } from '../../../services/ml_api_service/data_frame_analytics'; import { AnalyticsEmptyPrompt } from '../../../data_frame_analytics/pages/analytics_management/components/empty_prompt'; +import { overviewPanelDefaultState } from '../../overview_page'; +import { CollapsiblePanel } from '../../../components/collapsible_panel'; interface Props { setLazyJobCount: React.Dispatch>; @@ -35,9 +39,7 @@ export const AnalyticsPanel: FC = ({ setLazyJobCount }) => { const refresh = useRefresh(); const [analytics, setAnalytics] = useState([]); - const [analyticsStats, setAnalyticsStats] = useState( - undefined - ); + const [analyticsStats, setAnalyticsStats] = useState(undefined); const [errorMessage, setErrorMessage] = useState(); const [isInitialized, setIsInitialized] = useState(false); @@ -45,9 +47,24 @@ export const AnalyticsPanel: FC = ({ setLazyJobCount }) => { page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE, }); + const [panelsState, setPanelsState] = useStorage< + MlStorageKey, + TMlStorageMapped + >(ML_OVERVIEW_PANELS, overviewPanelDefaultState); + + const setAnalyticsStatsCustom = useCallback((stats: AnalyticStatsBarStats | undefined) => { + if (!stats) return; + + const result = Object.entries(stats) + .filter(([k, v]) => v.show) + .map(([k, v]) => v); + + setAnalyticsStats(result); + }, []); + const getAnalytics = getAnalyticsFactory( setAnalytics, - setAnalyticsStats, + setAnalyticsStatsCustom, setErrorMessage, setIsInitialized, setLazyJobCount, @@ -78,58 +95,40 @@ export const AnalyticsPanel: FC = ({ setLazyJobCount }) => { const noDFAJobs = errorMessage === undefined && isInitialized === true && analytics.length === 0; return ( - <> - {noDFAJobs ? ( - - ) : ( - - {typeof errorMessage !== 'undefined' ? errorDisplay : null} - {isInitialized === false && ( - - )} - - {isInitialized === true && analytics.length > 0 && ( - <> - - - -

- {i18n.translate('xpack.ml.overview.analyticsList.PanelTitle', { - defaultMessage: 'Analytics', - })} -

-
-
- - - {analyticsStats !== undefined ? ( - - - - ) : null} - - - {i18n.translate('xpack.ml.overview.analyticsList.manageJobsButtonText', { - defaultMessage: 'Manage jobs', - })} - - - - -
- - - - )} -
- )} - + { + setPanelsState({ ...panelsState, dfaJobs: update }); + }} + header={ + + } + headerItems={[ + ...(analyticsStats + ? [ + , + ] + : []), + + {i18n.translate('xpack.ml.overview.analyticsList.manageJobsButtonText', { + defaultMessage: 'Manage jobs', + })} + , + ]} + > + {noDFAJobs ? : null} + + {typeof errorMessage !== 'undefined' ? errorDisplay : null} + + {isInitialized === false && } + + {isInitialized === true && analytics.length > 0 ? : null} + ); }; diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index 1773531cb9aa2..30aa0f6d22dfb 100644 --- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -6,10 +6,20 @@ */ import React, { FC, Fragment, useEffect, useState } from 'react'; -import { EuiCallOut, EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; +import { EuiCallOut, EuiLink, EuiLoadingSpinner } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { zipObject } from 'lodash'; -import { useMlKibana } from '../../../contexts/kibana'; +import { zipObject, groupBy } from 'lodash'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useStorage } from '@kbn/ml-local-storage'; +import { + ML_OVERVIEW_PANELS, + MlStorageKey, + TMlStorageMapped, +} from '../../../../../common/types/storage'; +import { ML_PAGES } from '../../../../../common/constants/locator'; +import { OverviewStatsBar } from '../../../components/collapsible_panel/collapsible_panel'; +import { CollapsiblePanel } from '../../../components/collapsible_panel'; +import { useMlKibana, useMlLink } from '../../../contexts/kibana'; import { AnomalyDetectionTable } from './table'; import { ml } from '../../../services/ml_api_service'; import { getGroupsFromJobs, getStatsBarData } from './utils'; @@ -19,8 +29,8 @@ import { useRefresh } from '../../../routing/use_refresh'; import { useToastNotificationService } from '../../../services/toast_notification_service'; import { AnomalyTimelineService } from '../../../services/anomaly_timeline_service'; import type { OverallSwimlaneData } from '../../../explorer/explorer_utils'; -import { JobStatsBarStats } from '../../../components/stats_bar'; import { AnomalyDetectionEmptyState } from '../../../jobs/jobs_list/components/anomaly_detection_empty_state'; +import { overviewPanelDefaultState } from '../../overview_page'; export type GroupsDictionary = Dictionary; @@ -50,10 +60,21 @@ export const AnomalyDetectionPanel: FC = ({ anomalyTimelineService, setLa const refresh = useRefresh(); + const [panelsState, setPanelsState] = useStorage< + MlStorageKey, + TMlStorageMapped + >(ML_OVERVIEW_PANELS, overviewPanelDefaultState); + + const manageJobsLink = useMlLink({ + page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE, + }); + const [isLoading, setIsLoading] = useState(false); const [groups, setGroups] = useState({}); const [groupsCount, setGroupsCount] = useState(0); - const [statsBarData, setStatsBarData] = useState(); + const [statsBarData, setStatsBarData] = useState>(); + const [restStatsBarData, setRestStatsBarData] = + useState>(); const [errorMessage, setErrorMessage] = useState(); const loadJobs = async () => { @@ -71,9 +92,20 @@ export const AnomalyDetectionPanel: FC = ({ anomalyTimelineService, setLa }); const { groups: jobsGroups, count } = getGroupsFromJobs(jobsSummaryList); const stats = getStatsBarData(jobsSummaryList); + + const statGroups = groupBy( + Object.entries(stats) + .filter(([k, v]) => v.show) + .map(([k, v]) => v), + 'group' + ); + setIsLoading(false); setErrorMessage(undefined); - setStatsBarData(stats); + + setStatsBarData(statGroups[0]); + setRestStatsBarData(statGroups[1]); + setGroupsCount(count); setGroups(jobsGroups); loadOverallSwimLanes(jobsGroups); @@ -138,30 +170,52 @@ export const AnomalyDetectionPanel: FC = ({ anomalyTimelineService, setLa ); - const panelClass = isLoading ? 'mlOverviewPanel__isLoading' : 'mlOverviewPanel'; - const noAdJobs = !errorMessage && isLoading === false && typeof errorMessage === 'undefined' && groupsCount === 0; - if (noAdJobs) { - return ; - } - return ( - + { + setPanelsState({ ...panelsState, adJobs: update }); + }} + header={ + + } + headerItems={[ + ...(statsBarData + ? [] + : []), + ...(restStatsBarData + ? [ + , + ] + : []), + + {i18n.translate('xpack.ml.overview.anomalyDetection.manageJobsButtonText', { + defaultMessage: 'Manage jobs', + })} + , + ]} + > + {noAdJobs ? : null} + {typeof errorMessage !== 'undefined' && errorDisplay} - {isLoading && } + + {isLoading ? : null} {isLoading === false && typeof errorMessage === 'undefined' && groupsCount > 0 ? ( - + ) : null} - + ); }; diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx index 016261be7997e..de4a05c638ce2 100644 --- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx @@ -9,13 +9,8 @@ import React, { FC, useState } from 'react'; import { Direction, EuiBasicTableColumn, - EuiButton, - EuiFlexGroup, - EuiFlexItem, EuiIcon, EuiInMemoryTable, - EuiSpacer, - EuiText, EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -24,13 +19,10 @@ import { ChartsPluginStart } from '@kbn/charts-plugin/public'; import { formatHumanReadableDateTime } from '@kbn/ml-date-utils'; import { useGroupActions } from './actions'; import { Group, GroupsDictionary } from './anomaly_detection_panel'; -import { JobStatsBarStats, StatsBar } from '../../../components/stats_bar'; import { JobSelectorBadge } from '../../../components/job_selector/job_selector_badge'; import { toLocaleString } from '../../../util/string_utils'; import { SwimlaneContainer } from '../../../explorer/swimlane_container'; import { useTimeBuckets } from '../../../components/custom_hooks/use_time_buckets'; -import { ML_PAGES } from '../../../../../common/constants/locator'; -import { useMlLink } from '../../../contexts/kibana'; export enum AnomalyDetectionListColumns { id = 'id', @@ -44,11 +36,10 @@ export enum AnomalyDetectionListColumns { interface Props { items: GroupsDictionary; - statsBarData: JobStatsBarStats; chartsService: ChartsPluginStart; } -export const AnomalyDetectionTable: FC = ({ items, statsBarData, chartsService }) => { +export const AnomalyDetectionTable: FC = ({ items, chartsService }) => { const groupsList = Object.values(items); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -58,10 +49,6 @@ export const AnomalyDetectionTable: FC = ({ items, statsBarData, chartsSe const timeBuckets = useTimeBuckets(); - const manageJobsLink = useMlLink({ - page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE, - }); - const columns: Array> = [ { field: AnomalyDetectionListColumns.id, @@ -195,47 +182,19 @@ export const AnomalyDetectionTable: FC = ({ items, statsBarData, chartsSe }; return ( - <> - - - -

- {i18n.translate('xpack.ml.overview.anomalyDetection.panelTitle', { - defaultMessage: 'Anomaly Detection', - })} -

-
-
- - - - - - - - {i18n.translate('xpack.ml.overview.anomalyDetection.manageJobsButtonText', { - defaultMessage: 'Manage jobs', - })} - - - - -
- - - allowNeutralSort={false} - className="mlAnomalyDetectionTable" - columns={columns} - hasActions={true} - isExpandable={false} - isSelectable={false} - items={groupsList} - itemId={AnomalyDetectionListColumns.id} - onTableChange={onTableChange} - pagination={pagination} - sorting={sorting} - data-test-subj="mlOverviewTableAnomalyDetection" - /> - + + allowNeutralSort={false} + className="mlAnomalyDetectionTable" + columns={columns} + hasActions={true} + isExpandable={false} + isSelectable={false} + items={groupsList} + itemId={AnomalyDetectionListColumns.id} + onTableChange={onTableChange} + pagination={pagination} + sorting={sorting} + data-test-subj="mlOverviewTableAnomalyDetection" + /> ); }; diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts index fa2e83151b80f..a5a864e139eae 100644 --- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts +++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts @@ -76,40 +76,45 @@ export function getGroupsFromJobs(jobs: MlSummaryJobs): { export function getStatsBarData(jobsList: any) { const jobStats = { - activeNodes: { - label: i18n.translate('xpack.ml.overviewJobsList.statsBar.activeMLNodesLabel', { - defaultMessage: 'Active ML nodes', - }), - value: 0, - show: true, - }, total: { label: i18n.translate('xpack.ml.overviewJobsList.statsBar.totalJobsLabel', { - defaultMessage: 'Total jobs', + defaultMessage: 'Total', }), value: 0, show: true, + group: 0, }, open: { label: i18n.translate('xpack.ml.overviewJobsList.statsBar.openJobsLabel', { - defaultMessage: 'Open jobs', + defaultMessage: 'Open', }), value: 0, show: true, + group: 0, }, closed: { label: i18n.translate('xpack.ml.overviewJobsList.statsBar.closedJobsLabel', { - defaultMessage: 'Closed jobs', + defaultMessage: 'Closed', }), value: 0, show: true, + group: 0, }, failed: { label: i18n.translate('xpack.ml.overviewJobsList.statsBar.failedJobsLabel', { - defaultMessage: 'Failed jobs', + defaultMessage: 'Failed', }), value: 0, show: false, + group: 0, + }, + activeNodes: { + label: i18n.translate('xpack.ml.overviewJobsList.statsBar.activeMLNodesLabel', { + defaultMessage: 'Active ML nodes', + }), + value: 0, + show: true, + group: 1, }, activeDatafeeds: { label: i18n.translate('xpack.ml.jobsList.statsBar.activeDatafeedsLabel', { @@ -117,6 +122,7 @@ export function getStatsBarData(jobsList: any) { }), value: 0, show: true, + group: 1, }, }; @@ -158,5 +164,13 @@ export function getStatsBarData(jobsList: any) { jobStats.activeNodes.value = Object.keys(mlNodes).length; + if (jobStats.total.value === 0) { + for (const [statKey, val] of Object.entries(jobStats)) { + if (statKey !== 'total') { + val.show = false; + } + } + } + return jobStats; } diff --git a/x-pack/plugins/ml/public/application/overview/overview_page.tsx b/x-pack/plugins/ml/public/application/overview/overview_page.tsx index 4f7244c37f298..6772125bb9532 100644 --- a/x-pack/plugins/ml/public/application/overview/overview_page.tsx +++ b/x-pack/plugins/ml/public/application/overview/overview_page.tsx @@ -6,9 +6,15 @@ */ import React, { FC, useState } from 'react'; -import { EuiPanel, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiLink, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker'; +import { useStorage } from '@kbn/ml-local-storage'; +import { OverviewStatsBar } from '../components/collapsible_panel/collapsible_panel'; +import { ML_PAGES } from '../../../common/constants/locator'; +import { ML_OVERVIEW_PANELS, MlStorageKey, TMlStorageMapped } from '../../../common/types/storage'; +import { CollapsiblePanel } from '../components/collapsible_panel'; import { usePermissionCheck } from '../capabilities/check_capabilities'; import { mlNodesAvailable } from '../ml_nodes_check'; import { OverviewContent } from './components/content'; @@ -17,11 +23,18 @@ import { JobsAwaitingNodeWarning } from '../components/jobs_awaiting_node_warnin import { SavedObjectsWarning } from '../components/saved_objects_warning'; import { UpgradeWarning } from '../components/upgrade'; import { HelpMenu } from '../components/help_menu'; -import { useMlKibana } from '../contexts/kibana'; +import { useMlKibana, useMlLink } from '../contexts/kibana'; import { NodesList } from '../memory_usage/nodes_overview'; import { MlPageHeader } from '../components/page_header'; import { PageTitle } from '../components/page_title'; import { useIsServerless } from '../contexts/kibana/use_is_serverless'; +import { getMlNodesCount } from '../ml_nodes_check/check_ml_nodes'; + +export const overviewPanelDefaultState = Object.freeze({ + nodes: true, + adJobs: true, + dfaJobs: true, +}); export const OverviewPage: FC = () => { const serverless = useIsServerless(); @@ -33,11 +46,20 @@ export const OverviewPage: FC = () => { } = useMlKibana(); const helpLink = docLinks.links.ml.guide; + const viewNodesLink = useMlLink({ + page: ML_PAGES.MEMORY_USAGE, + }); + const timefilter = useTimefilter({ timeRangeSelector: true, autoRefreshSelector: true }); const [adLazyJobCount, setAdLazyJobCount] = useState(0); const [dfaLazyJobCount, setDfaLazyJobCount] = useState(0); + const [panelsState, setPanelsState] = useStorage< + MlStorageKey, + TMlStorageMapped + >(ML_OVERVIEW_PANELS, overviewPanelDefaultState); + return (
@@ -63,9 +85,36 @@ export const OverviewPage: FC = () => { {canViewMlNodes && serverless === false ? ( <> - + { + setPanelsState({ ...panelsState, nodes: update }); + }} + header={ + + } + headerItems={[ + , + + {i18n.translate('xpack.ml.overview.nodesPanel.viewNodeLink', { + defaultMessage: 'View nodes', + })} + , + ]} + > - + ) : null} diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.tsx b/x-pack/plugins/ml/public/application/routing/use_resolver.tsx index eb3586c15c05f..5c67ed9769426 100644 --- a/x-pack/plugins/ml/public/application/routing/use_resolver.tsx +++ b/x-pack/plugins/ml/public/application/routing/use_resolver.tsx @@ -35,7 +35,6 @@ export const useRouteResolver = ( ): { context: RouteResolverContext; results: ResolverResults; - component?: React.Component; } => { const requiredCapabilitiesRef = useRef(requiredCapabilities); const customResolversRef = useRef(customResolvers); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 84582899a6d4b..cd8cf5904c75e 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -11473,11 +11473,6 @@ "xpack.dataVisualizer.file.analysisSummary.hasHeaderRowTitle": "Possède une ligne d'en-tête", "xpack.dataVisualizer.file.analysisSummary.summaryTitle": "Résumé", "xpack.dataVisualizer.file.analysisSummary.timeFieldTitle": "Champ temporel", - "xpack.dataVisualizer.file.bottomBar.backButtonLabel": "Retour", - "xpack.dataVisualizer.file.bottomBar.cancelButtonLabel": "Annuler", - "xpack.dataVisualizer.file.bottomBar.missingImportPrivilegesMessage": "Vous devez avoir un rôle de ingest_admin pour activer l'importation des données", - "xpack.dataVisualizer.file.bottomBar.readMode.cancelButtonLabel": "Annuler", - "xpack.dataVisualizer.file.bottomBar.readMode.importButtonLabel": "Importer", "xpack.dataVisualizer.file.cannotCreateDataView.tooltip": "Vous devez disposer d'une autorisation pour créer des vues de données.", "xpack.dataVisualizer.file.editFlyout.applyOverrideSettingsButtonLabel": "Appliquer", "xpack.dataVisualizer.file.editFlyout.closeOverrideSettingsButtonLabel": "Fermer", @@ -22521,7 +22516,6 @@ "xpack.ml.notifications.newNotificationsMessage": "Il y a eu {newNotificationsCount, plural, one {# notification} many {# notifications} other {# notifications}} depuis {sinceDate}. Actualisez la page pour afficher les mises à jour.", "xpack.ml.notificationsIndicator.errorsAndWarningLabel": "Il y a eu {count, plural, one {# notification} many {# notifications} other {# notifications}} avec un niveau d'avertissement ou d'erreur depuis {lastCheckedAt}", "xpack.ml.notificationsIndicator.unreadLabel": "Vous avez des notifications non lues depuis {lastCheckedAt}", - "xpack.ml.overview.analyticsList.emptyPromptHelperText": "Avant de créer une tâche d'analyse du cadre de données, utilisez des {transforms} pour créer une {sourcedata}.", "xpack.ml.previewAlert.otherValuesLabel": "et {count, plural, one {# autre} many {# autres} other {# autres}}", "xpack.ml.previewAlert.previewMessage": "{alertsCount, plural, one {# anomalie a été trouvée} many {# anomalies ont été trouvées} other {# anomalies ont été trouvées}} au cours des dernières {interval}.", "xpack.ml.privilege.pleaseContactAdministratorTooltip": "{message} Veuillez contacter votre administrateur.", @@ -22898,7 +22892,6 @@ "xpack.ml.cases.anomalySwimLane.embeddableAddedEvent": "couloir d'anomalie ajouté", "xpack.ml.changePointDetection.pageHeader": "Modifier la détection du point", "xpack.ml.chrome.help.appName": "Machine Learning", - "xpack.ml.common.learnMoreQuestion": "Envie d'en savoir plus ?", "xpack.ml.common.readDocumentationLink": "Lire la documentation", "xpack.ml.components.colorRangeLegend.blueColorRangeLabel": "Bleu", "xpack.ml.components.colorRangeLegend.greenRedColorRangeLabel": "Vert – Rouge", @@ -24525,7 +24518,6 @@ "xpack.ml.overview.anomalyDetection.noAnomaliesFoundMessage": "Aucune anomalie n'a été trouvée", "xpack.ml.overview.anomalyDetection.noResultsFoundMessage": "Résultat introuvable", "xpack.ml.overview.anomalyDetection.overallScore": "Score général", - "xpack.ml.overview.anomalyDetection.panelTitle": "Détection des anomalies", "xpack.ml.overview.anomalyDetection.resultActions.openInJobManagementText": "Afficher les tâches", "xpack.ml.overview.anomalyDetection.resultActions.openJobsInAnomalyExplorerText": "Afficher dans l’Explorateur d'anomalies", "xpack.ml.overview.anomalyDetection.tableActionLabel": "Actions", @@ -24539,8 +24531,6 @@ "xpack.ml.overview.anomalyDetection.tableTypicalTooltip": "Valeurs typiques dans les résultats d'enregistrement des anomalies.", "xpack.ml.overview.anomalyDetection.viewJobsActionName": "Afficher les tâches", "xpack.ml.overview.anomalyDetection.viewResultsActionName": "Afficher dans l’Explorateur d'anomalies", - "xpack.ml.overview.gettingStartedSectionSourceData": "ensemble de données source centré sur les entités", - "xpack.ml.overview.gettingStartedSectionTransforms": "transformations", "xpack.ml.overview.notificationsLabel": "Notifications", "xpack.ml.overview.overviewLabel": "Aperçu", "xpack.ml.overview.statsBar.failedAnalyticsLabel": "Échoué", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3eece16c9098d..ef2cbdf13c2a6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -11472,11 +11472,6 @@ "xpack.dataVisualizer.file.analysisSummary.hasHeaderRowTitle": "ヘッダー行があります", "xpack.dataVisualizer.file.analysisSummary.summaryTitle": "まとめ", "xpack.dataVisualizer.file.analysisSummary.timeFieldTitle": "時間フィールド", - "xpack.dataVisualizer.file.bottomBar.backButtonLabel": "戻る", - "xpack.dataVisualizer.file.bottomBar.cancelButtonLabel": "キャンセル", - "xpack.dataVisualizer.file.bottomBar.missingImportPrivilegesMessage": "データインポートを有効にするには、ingest_adminロールが必要です", - "xpack.dataVisualizer.file.bottomBar.readMode.cancelButtonLabel": "キャンセル", - "xpack.dataVisualizer.file.bottomBar.readMode.importButtonLabel": "インポート", "xpack.dataVisualizer.file.cannotCreateDataView.tooltip": "データビューを作成する権限が必要です。", "xpack.dataVisualizer.file.editFlyout.applyOverrideSettingsButtonLabel": "適用", "xpack.dataVisualizer.file.editFlyout.closeOverrideSettingsButtonLabel": "閉じる", @@ -22512,7 +22507,6 @@ "xpack.ml.notifications.newNotificationsMessage": "{sinceDate}以降に{newNotificationsCount, plural, other {#件の通知があります}}。更新を表示するには、ページを更新してください。", "xpack.ml.notificationsIndicator.errorsAndWarningLabel": "{lastCheckedAt}以降にエラーまたは警告レベルの{count, plural, other {#件の通知があります}}", "xpack.ml.notificationsIndicator.unreadLabel": "{lastCheckedAt}以降に未読の通知があります", - "xpack.ml.overview.analyticsList.emptyPromptHelperText": "データフレーム分析ジョブを構築する前に、{transforms}を使用して{sourcedata}を作成してください。", "xpack.ml.previewAlert.otherValuesLabel": "および{count, plural, other {#個のその他}}", "xpack.ml.previewAlert.previewMessage": "過去{interval}に{alertsCount, plural, other {#個の異常}}が見つかりました。", "xpack.ml.privilege.pleaseContactAdministratorTooltip": "{message} 管理者にお問い合わせください。", @@ -22884,7 +22878,6 @@ "xpack.ml.cases.anomalySwimLane.embeddableAddedEvent": "追加された異常スイムレーン", "xpack.ml.changePointDetection.pageHeader": "変化点検出", "xpack.ml.chrome.help.appName": "機械学習", - "xpack.ml.common.learnMoreQuestion": "詳細について", "xpack.ml.common.readDocumentationLink": "ドキュメンテーションを表示", "xpack.ml.components.colorRangeLegend.blueColorRangeLabel": "青", "xpack.ml.components.colorRangeLegend.greenRedColorRangeLabel": "緑 - 赤", @@ -24511,7 +24504,6 @@ "xpack.ml.overview.anomalyDetection.noAnomaliesFoundMessage": "異常値が見つかりませんでした", "xpack.ml.overview.anomalyDetection.noResultsFoundMessage": "結果が見つかりませんでした", "xpack.ml.overview.anomalyDetection.overallScore": "全体スコア", - "xpack.ml.overview.anomalyDetection.panelTitle": "異常検知", "xpack.ml.overview.anomalyDetection.resultActions.openInJobManagementText": "ジョブを表示", "xpack.ml.overview.anomalyDetection.resultActions.openJobsInAnomalyExplorerText": "異常エクスプローラーで表示", "xpack.ml.overview.anomalyDetection.tableActionLabel": "アクション", @@ -24525,8 +24517,6 @@ "xpack.ml.overview.anomalyDetection.tableTypicalTooltip": "異常レコード結果の標準的な値。", "xpack.ml.overview.anomalyDetection.viewJobsActionName": "ジョブを表示", "xpack.ml.overview.anomalyDetection.viewResultsActionName": "異常エクスプローラーで表示", - "xpack.ml.overview.gettingStartedSectionSourceData": "エンティティ中心のソースデータセット", - "xpack.ml.overview.gettingStartedSectionTransforms": "トランスフォーム", "xpack.ml.overview.notificationsLabel": "通知", "xpack.ml.overview.overviewLabel": "概要", "xpack.ml.overview.statsBar.failedAnalyticsLabel": "失敗", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 41d6320f76e0a..3738c25103587 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -11472,11 +11472,6 @@ "xpack.dataVisualizer.file.analysisSummary.hasHeaderRowTitle": "包含标题行", "xpack.dataVisualizer.file.analysisSummary.summaryTitle": "摘要", "xpack.dataVisualizer.file.analysisSummary.timeFieldTitle": "时间字段", - "xpack.dataVisualizer.file.bottomBar.backButtonLabel": "返回", - "xpack.dataVisualizer.file.bottomBar.cancelButtonLabel": "取消", - "xpack.dataVisualizer.file.bottomBar.missingImportPrivilegesMessage": "您需要具有 ingest_admin 角色才能启用数据导入", - "xpack.dataVisualizer.file.bottomBar.readMode.cancelButtonLabel": "取消", - "xpack.dataVisualizer.file.bottomBar.readMode.importButtonLabel": "导入", "xpack.dataVisualizer.file.cannotCreateDataView.tooltip": "您需要权限以创建数据视图。", "xpack.dataVisualizer.file.editFlyout.applyOverrideSettingsButtonLabel": "应用", "xpack.dataVisualizer.file.editFlyout.closeOverrideSettingsButtonLabel": "关闭", @@ -22511,7 +22506,6 @@ "xpack.ml.notifications.newNotificationsMessage": "自 {sinceDate}以来有 {newNotificationsCount, plural, other {# 个通知}}。刷新页面以查看更新。", "xpack.ml.notificationsIndicator.errorsAndWarningLabel": "自 {lastCheckedAt}以来有 {count, plural, other {# 个通知}}包含错误或警告级别", "xpack.ml.notificationsIndicator.unreadLabel": "自 {lastCheckedAt}以来您有未计通知", - "xpack.ml.overview.analyticsList.emptyPromptHelperText": "构建数据帧分析作业之前,请使用 {transforms} 构造一个 {sourcedata}。", "xpack.ml.previewAlert.otherValuesLabel": "和{count, plural, other {另外 # 个}}", "xpack.ml.previewAlert.previewMessage": "在过去 {interval}找到 {alertsCount, plural, other {# 个异常}}。", "xpack.ml.privilege.pleaseContactAdministratorTooltip": "{message}请联系您的管理员。", @@ -22883,7 +22877,6 @@ "xpack.ml.cases.anomalySwimLane.embeddableAddedEvent": "已添加异常泳道", "xpack.ml.changePointDetection.pageHeader": "更改点检测", "xpack.ml.chrome.help.appName": "Machine Learning", - "xpack.ml.common.learnMoreQuestion": "希望了解详情?", "xpack.ml.common.readDocumentationLink": "阅读文档", "xpack.ml.components.colorRangeLegend.blueColorRangeLabel": "蓝", "xpack.ml.components.colorRangeLegend.greenRedColorRangeLabel": "绿 - 红", @@ -24510,7 +24503,6 @@ "xpack.ml.overview.anomalyDetection.noAnomaliesFoundMessage": "找不到异常", "xpack.ml.overview.anomalyDetection.noResultsFoundMessage": "找不到结果", "xpack.ml.overview.anomalyDetection.overallScore": "总分", - "xpack.ml.overview.anomalyDetection.panelTitle": "异常检测", "xpack.ml.overview.anomalyDetection.resultActions.openInJobManagementText": "查看作业", "xpack.ml.overview.anomalyDetection.resultActions.openJobsInAnomalyExplorerText": "在 Anomaly Explorer 中查看", "xpack.ml.overview.anomalyDetection.tableActionLabel": "操作", @@ -24524,8 +24516,6 @@ "xpack.ml.overview.anomalyDetection.tableTypicalTooltip": "异常记录结果中的典型值。", "xpack.ml.overview.anomalyDetection.viewJobsActionName": "查看作业", "xpack.ml.overview.anomalyDetection.viewResultsActionName": "在 Anomaly Explorer 中查看", - "xpack.ml.overview.gettingStartedSectionSourceData": "实体中心型源数据集", - "xpack.ml.overview.gettingStartedSectionTransforms": "转换", "xpack.ml.overview.notificationsLabel": "通知", "xpack.ml.overview.overviewLabel": "概览", "xpack.ml.overview.statsBar.failedAnalyticsLabel": "失败", diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index cc77c4626ba01..40ff5d9987432 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -447,6 +447,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should load the Logs tab section when clicking on it', async () => { await testSubjects.existOrFail('hostsView-logs'); }); + + it('should load the Logs tab with the right columns', async () => { + await retry.try(async () => { + const columnLabels = await pageObjects.infraHostsView.getLogsTableColumnHeaders(); + + expect(columnLabels).to.eql(['Timestamp', 'host.name', 'Message']); + }); + }); }); describe('Alerts Tab', () => { diff --git a/x-pack/test/functional/page_objects/infra_hosts_view.ts b/x-pack/test/functional/page_objects/infra_hosts_view.ts index b9219b53fda2d..c2982ae77f211 100644 --- a/x-pack/test/functional/page_objects/infra_hosts_view.ts +++ b/x-pack/test/functional/page_objects/infra_hosts_view.ts @@ -214,6 +214,13 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { return container.findAllByCssSelector('[data-test-subj*=streamEntry]'); }, + async getLogsTableColumnHeaders() { + const columnHeaderElements: WebElementWrapper[] = await testSubjects.findAll( + '~logColumnHeader' + ); + return await Promise.all(columnHeaderElements.map((element) => element.getVisibleText())); + }, + // Alerts Tab getAlertsTab() { return testSubjects.find('hostsView-tabs-alerts');