Skip to content

Commit 3fff743

Browse files
committed
feat(ml): remove intermediate data view or discover session picker step in Data Drift
- Introduced `DataDriftDataSourcePicker` component for improved data source selection in the Data Drift page. - Removed the previous page header and integrated the new picker for a streamlined user experience. - Updated routing to reflect changes in breadcrumb navigation and deep links for the Data Drift feature. - Cleaned up unused imports and components to enhance code maintainability.
1 parent aa4fb69 commit 3fff743

14 files changed

Lines changed: 309 additions & 210 deletions

File tree

x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_app_state.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
import type { FC } from 'react';
8+
import type { FC, ReactNode } from 'react';
99
import React from 'react';
1010
import { pick } from 'lodash';
1111

@@ -40,6 +40,8 @@ export interface DataDriftDetectionAppStateProps {
4040
dataView: DataView;
4141
/** The saved search to analyze. */
4242
savedSearch: SavedSearch | null;
43+
/** Optional content rendered on the left side of the time controls row. */
44+
headerContent?: ReactNode;
4345
}
4446

4547
export type DataDriftSpec = typeof DataDriftDetectionAppState;
@@ -57,6 +59,7 @@ const getStr = (arg: string | string[] | null, fallbackStr?: string): string =>
5759
export const DataDriftDetectionAppState: FC<DataDriftDetectionAppStateProps> = ({
5860
dataView,
5961
savedSearch,
62+
headerContent,
6063
}) => {
6164
if (!(dataView || savedSearch)) {
6265
throw Error('No data view or saved search available.');
@@ -147,7 +150,7 @@ export const DataDriftDetectionAppState: FC<DataDriftDetectionAppStateProps> = (
147150
comparison: comparisonStateManager,
148151
}}
149152
>
150-
<DataDriftPage initialSettings={initialSettings} />
153+
<DataDriftPage initialSettings={initialSettings} headerContent={headerContent} />
151154
</DataDriftStateManagerContext.Provider>
152155
</DatePickerContextProvider>
153156
</StorageContextProvider>

x-pack/platform/plugins/private/data_visualizer/public/application/data_drift/data_drift_page.tsx

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
import type { FC } from 'react';
8+
import type { FC, ReactNode } from 'react';
99
import React, { useCallback, useEffect, useState, useMemo, useRef } from 'react';
1010

1111
import type { estypes } from '@elastic/elasticsearch';
@@ -16,7 +16,6 @@ import {
1616
EuiPageSection,
1717
EuiPanel,
1818
EuiSpacer,
19-
EuiPageHeader,
2019
EuiHorizontalRule,
2120
EuiBadge,
2221
} from '@elastic/eui';
@@ -35,7 +34,6 @@ import {
3534
useTimefilter,
3635
} from '@kbn/ml-date-picker';
3736
import moment from 'moment';
38-
import { css } from '@emotion/react';
3937
import type { SearchQueryLanguage } from '@kbn/ml-query-utils';
4038
import { i18n } from '@kbn/i18n';
4139
import { cloneDeep } from 'lodash';
@@ -57,15 +55,12 @@ import { useSearch } from '../common/hooks/use_search';
5755
import { DocumentCountWithBrush } from './document_count_with_brush';
5856
import { useDataDriftColors } from './use_data_drift_colors';
5957

60-
const dataViewTitleHeader = css({
61-
minWidth: '300px',
62-
});
63-
6458
interface PageHeaderProps {
6559
onRefresh: () => void;
6660
needsUpdate: boolean;
61+
headerContent?: ReactNode;
6762
}
68-
export const PageHeader: FC<PageHeaderProps> = ({ onRefresh, needsUpdate }) => {
63+
export const PageHeader: FC<PageHeaderProps> = ({ onRefresh, needsUpdate, headerContent }) => {
6964
const [, setGlobalState] = useUrlState('_g');
7065
const { dataView } = useDataSource();
7166

@@ -101,37 +96,41 @@ export const PageHeader: FC<PageHeaderProps> = ({ onRefresh, needsUpdate }) => {
10196
);
10297

10398
return (
104-
<EuiPageHeader
105-
pageTitle={
106-
<div data-test-subj={'mlDataDriftPageDataViewTitle'} css={dataViewTitleHeader}>
107-
{dataView.getName()}
108-
</div>
109-
}
110-
rightSideGroupProps={{
111-
gutterSize: 's',
112-
'data-test-subj': 'dataComparisonTimeRangeSelectorSection',
113-
}}
114-
rightSideItems={[
115-
<DatePickerWrapper
116-
isAutoRefreshOnly={!hasValidTimeField}
117-
showRefresh={!hasValidTimeField}
118-
width="full"
119-
onRefresh={onRefresh}
120-
needsUpdate={needsUpdate}
121-
/>,
122-
hasValidTimeField && (
123-
<FullTimeRangeSelector
124-
frozenDataPreference={frozenDataPreference}
125-
setFrozenDataPreference={setFrozenDataPreference}
126-
dataView={dataView}
127-
query={undefined}
128-
disabled={false}
129-
timefilter={timefilter}
130-
callback={updateTimeState}
131-
/>
132-
),
133-
].filter(Boolean)}
134-
/>
99+
<EuiFlexGroup
100+
gutterSize="s"
101+
alignItems="center"
102+
justifyContent="spaceBetween"
103+
responsive={false}
104+
data-test-subj="dataComparisonTimeRangeSelectorSection"
105+
>
106+
<EuiFlexItem grow={false}>{headerContent ?? null}</EuiFlexItem>
107+
<EuiFlexItem grow={false}>
108+
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
109+
{hasValidTimeField && (
110+
<EuiFlexItem grow={false}>
111+
<FullTimeRangeSelector
112+
frozenDataPreference={frozenDataPreference}
113+
setFrozenDataPreference={setFrozenDataPreference}
114+
dataView={dataView}
115+
query={undefined}
116+
disabled={false}
117+
timefilter={timefilter}
118+
callback={updateTimeState}
119+
/>
120+
</EuiFlexItem>
121+
)}
122+
<EuiFlexItem grow={false}>
123+
<DatePickerWrapper
124+
isAutoRefreshOnly={!hasValidTimeField}
125+
showRefresh={!hasValidTimeField}
126+
width="full"
127+
onRefresh={onRefresh}
128+
needsUpdate={needsUpdate}
129+
/>
130+
</EuiFlexItem>
131+
</EuiFlexGroup>
132+
</EuiFlexItem>
133+
</EuiFlexGroup>
135134
);
136135
};
137136

@@ -147,12 +146,13 @@ const getDataDriftDataLabel = (label: string, indexPattern?: string) => (
147146
);
148147
interface Props {
149148
initialSettings: InitialSettings;
149+
headerContent?: ReactNode;
150150
}
151151

152152
const isBarBetween = (start: number, end: number, min: number, max: number) => {
153153
return start >= min && end <= max;
154154
};
155-
export const DataDriftPage: FC<Props> = ({ initialSettings }) => {
155+
export const DataDriftPage: FC<Props> = ({ initialSettings, headerContent }) => {
156156
const {
157157
services: { data: dataService, uiSettings, cps },
158158
} = useDataVisualizerKibana();
@@ -404,7 +404,11 @@ export const DataDriftPage: FC<Props> = ({ initialSettings }) => {
404404

405405
return (
406406
<EuiPageBody data-test-subj="dataComparisonDataDriftPage" paddingSize="none" panelled={false}>
407-
<PageHeader onRefresh={handleRefresh} needsUpdate={queryNeedsUpdate} />
407+
<PageHeader
408+
onRefresh={handleRefresh}
409+
needsUpdate={queryNeedsUpdate}
410+
headerContent={headerContent}
411+
/>
408412
<EuiSpacer size="m" />
409413
<EuiPageSection paddingSize="none">
410414
<EuiFlexGroup gutterSize="m" direction="column">

x-pack/platform/plugins/private/translations/translations/de-DE.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27043,7 +27043,6 @@
2704327043
"xpack.ml.dataDrift.indexPatternsEditor.timestampFieldLabel": "Zeitstempel-Feld",
2704427044
"xpack.ml.dataDrift.indexPatternsEditor.timestampFieldOptions": "Wählen Sie ein optionales Zeitstempelfeld aus",
2704527045
"xpack.ml.dataDrift.indexPatternsEditor.timestampSelectAriaLabel": "Zeitstempel-Feld",
27046-
"xpack.ml.dataDruiftWithDocCount.pageHeader": "Datendrift",
2704727046
"xpack.ml.dataframe.analytics.classificationExploration.classificationDocsLink": "Dokumente zur Klassifizierungsbewertung",
2704827047
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixActualLabel": "Eigentliche Klasse",
2704927048
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixEntireHelpText": "Normalisierte Konfusionsmatrix für den gesamten Datensatz",

x-pack/platform/plugins/private/translations/translations/fr-FR.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27072,7 +27072,6 @@
2707227072
"xpack.ml.dataDrift.indexPatternsEditor.timestampFieldLabel": "Champ d'horodatage",
2707327073
"xpack.ml.dataDrift.indexPatternsEditor.timestampFieldOptions": "Sélectionner un champ d'horodatage différent",
2707427074
"xpack.ml.dataDrift.indexPatternsEditor.timestampSelectAriaLabel": "Champ d'horodatage",
27075-
"xpack.ml.dataDruiftWithDocCount.pageHeader": "Dérive de données",
2707627075
"xpack.ml.dataframe.analytics.classificationExploration.classificationDocsLink": "Documents d'évaluation de classification",
2707727076
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixActualLabel": "Classe réelle",
2707827077
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixEntireHelpText": "Matrice de confusion normalisée pour l'ensemble de données entier",

x-pack/platform/plugins/private/translations/translations/ja-JP.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27151,7 +27151,6 @@
2715127151
"xpack.ml.dataDrift.indexPatternsEditor.timestampFieldLabel": "タイムスタンプフィールド",
2715227152
"xpack.ml.dataDrift.indexPatternsEditor.timestampFieldOptions": "任意のタイムスタンプフィールドを選択",
2715327153
"xpack.ml.dataDrift.indexPatternsEditor.timestampSelectAriaLabel": "タイムスタンプフィールド",
27154-
"xpack.ml.dataDruiftWithDocCount.pageHeader": "データドリフト",
2715527154
"xpack.ml.dataframe.analytics.classificationExploration.classificationDocsLink": "分類評価ドキュメント",
2715627155
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixActualLabel": "実際のクラス",
2715727156
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixEntireHelpText": "データセット全体で正規化された混同行列",

x-pack/platform/plugins/private/translations/translations/zh-CN.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27151,7 +27151,6 @@
2715127151
"xpack.ml.dataDrift.indexPatternsEditor.timestampFieldLabel": "时间戳字段",
2715227152
"xpack.ml.dataDrift.indexPatternsEditor.timestampFieldOptions": "选择可选的时间戳字段",
2715327153
"xpack.ml.dataDrift.indexPatternsEditor.timestampSelectAriaLabel": "时间戳字段",
27154-
"xpack.ml.dataDruiftWithDocCount.pageHeader": "数据偏移",
2715527154
"xpack.ml.dataframe.analytics.classificationExploration.classificationDocsLink": "分类评估文档",
2715627155
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixActualLabel": "实际类",
2715727156
"xpack.ml.dataframe.analytics.classificationExploration.confusionMatrixEntireHelpText": "整个数据集的标准化混淆矩阵",
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { FC } from 'react';
9+
import React, { useState } from 'react';
10+
import { css } from '@emotion/react';
11+
import {
12+
EuiButton,
13+
EuiFlexGroup,
14+
EuiFlexItem,
15+
EuiFormControlButton,
16+
EuiFormControlLayout,
17+
EuiPopover,
18+
} from '@elastic/eui';
19+
import { i18n } from '@kbn/i18n';
20+
import { FormattedMessage } from '@kbn/i18n-react';
21+
import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public';
22+
import type { DataView } from '@kbn/data-views-plugin/common';
23+
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
24+
import type { FinderAttributes, SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common';
25+
26+
import { createPath } from '../../routing/router';
27+
import { ML_PAGES } from '../../../../common/constants/locator';
28+
import { useMlKibana, useNavigateToPath } from '../../contexts/kibana';
29+
30+
type SavedObject = SavedObjectCommon<FinderAttributes & { isTextBasedQuery?: boolean }>;
31+
32+
const pickerPanelCss = css({ width: 600, maxHeight: '70vh', overflow: 'auto' });
33+
34+
export interface DataDriftDataSourcePickerProps {
35+
currentDataView: DataView | null;
36+
currentSavedSearch: SavedSearch | null;
37+
}
38+
39+
export const DataDriftDataSourcePicker: FC<DataDriftDataSourcePickerProps> = ({
40+
currentDataView,
41+
currentSavedSearch,
42+
}) => {
43+
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
44+
const navigateToPath = useNavigateToPath();
45+
const { contentManagement, uiSettings, dataViewEditor } = useMlKibana().services;
46+
47+
const canEditDataView = dataViewEditor?.userPermissions.editDataView();
48+
49+
const onChoose = (id: string, type: string) => {
50+
setIsPopoverOpen(false);
51+
const param = type === 'index-pattern' ? 'index' : 'savedSearchId';
52+
navigateToPath(`/data_drift?${param}=${encodeURIComponent(id)}`);
53+
};
54+
55+
const triggerLabel = currentSavedSearch?.title
56+
? currentSavedSearch.title
57+
: currentDataView?.getName() ?? '';
58+
59+
const prepend = currentSavedSearch?.title
60+
? i18n.translate('xpack.ml.dataDrift.dataSourcePicker.discoverSessionLabel', {
61+
defaultMessage: 'Discover session',
62+
})
63+
: currentDataView
64+
? i18n.translate('xpack.ml.dataDrift.dataSourcePicker.dataViewLabel', {
65+
defaultMessage: 'Data view',
66+
})
67+
: undefined;
68+
69+
const triggerButton = (
70+
<EuiFormControlButton
71+
compressed
72+
aria-expanded={isPopoverOpen}
73+
data-test-subj="mlDataDriftPageDataViewSelectorButton"
74+
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
75+
>
76+
<EuiFlexGroup
77+
component="span"
78+
alignItems="center"
79+
gutterSize="s"
80+
responsive={false}
81+
css={{ maxWidth: '100%' }}
82+
>
83+
<span className="eui-textTruncate" data-test-subj="mlDataDriftPageDataViewTitle">
84+
{triggerLabel ||
85+
i18n.translate('xpack.ml.dataDrift.dataSourcePicker.placeholderLabel', {
86+
defaultMessage: 'Select data view or saved Discover session',
87+
})}
88+
</span>
89+
</EuiFlexGroup>
90+
</EuiFormControlButton>
91+
);
92+
93+
const popover = (
94+
<EuiPopover
95+
ownFocus
96+
button={triggerButton}
97+
isOpen={isPopoverOpen}
98+
closePopover={() => setIsPopoverOpen(false)}
99+
panelPaddingSize="none"
100+
panelProps={{ css: pickerPanelCss }}
101+
aria-label={i18n.translate('xpack.ml.dataDrift.dataSourcePicker.popoverAriaLabel', {
102+
defaultMessage: 'Data source selector',
103+
})}
104+
>
105+
<SavedObjectFinder
106+
id="mlDataDriftDataViewsPicker"
107+
key="dataDriftSavedObjectFinder"
108+
onChoose={onChoose}
109+
showFilter
110+
noItemsMessage={i18n.translate('xpack.ml.dataDrift.dataSourcePicker.notFoundLabel', {
111+
defaultMessage: 'No matching data views or saved Discover sessions found.',
112+
})}
113+
savedObjectMetaData={[
114+
{
115+
type: 'search',
116+
getIconForSavedObject: () => 'discoverApp',
117+
name: i18n.translate(
118+
'xpack.ml.dataDrift.dataSourcePicker.savedObjectType.discoverSession',
119+
{ defaultMessage: 'Discover session' }
120+
),
121+
showSavedObject: (savedObject: SavedObject) =>
122+
savedObject.attributes.isTextBasedQuery !== true,
123+
},
124+
{
125+
type: 'index-pattern',
126+
getIconForSavedObject: () => 'indexPatternApp',
127+
name: i18n.translate('xpack.ml.dataDrift.dataSourcePicker.savedObjectType.dataView', {
128+
defaultMessage: 'Data view',
129+
}),
130+
},
131+
]}
132+
fixedPageSize={20}
133+
services={{
134+
contentClient: contentManagement.client,
135+
uiSettings,
136+
}}
137+
>
138+
<EuiButton
139+
size="m"
140+
fill
141+
iconType="plusCircle"
142+
onClick={() => navigateToPath(createPath(ML_PAGES.DATA_DRIFT_CUSTOM))}
143+
disabled={!canEditDataView}
144+
data-test-subj="dataDriftCreateDataViewButton"
145+
>
146+
<FormattedMessage
147+
id="xpack.ml.dataDrift.createDataViewButton"
148+
defaultMessage="Create a data view"
149+
/>
150+
</EuiButton>
151+
</SavedObjectFinder>
152+
</EuiPopover>
153+
);
154+
155+
if (prepend) {
156+
return (
157+
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
158+
<EuiFlexItem grow={false} css={{ minWidth: 0 }}>
159+
<EuiFormControlLayout compressed isDropdown prepend={prepend}>
160+
{popover}
161+
</EuiFormControlLayout>
162+
</EuiFlexItem>
163+
</EuiFlexGroup>
164+
);
165+
}
166+
167+
return popover;
168+
};

0 commit comments

Comments
 (0)