diff --git a/x-pack/packages/ml/field_stats_flyout/eui_combo_box_with_field_stats.tsx b/x-pack/packages/ml/field_stats_flyout/eui_combo_box_with_field_stats.tsx
deleted file mode 100644
index a09710da8e398..0000000000000
--- a/x-pack/packages/ml/field_stats_flyout/eui_combo_box_with_field_stats.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import type { FC } from 'react';
-import React, { useMemo } from 'react';
-import type { EuiComboBoxProps } from '@elastic/eui/src/components/combo_box/combo_box';
-import type { EuiComboBoxOptionOption } from '@elastic/eui';
-import { EuiComboBox } from '@elastic/eui';
-import { css } from '@emotion/react';
-import { useFieldStatsTrigger } from './use_field_stats_trigger';
-
-export const optionCss = css`
- .euiComboBoxOption__enterBadge {
- display: none;
- }
- .euiFlexGroup {
- gap: 0px;
- }
- .euiComboBoxOption__content {
- margin-left: 2px;
- }
-`;
-
-/**
- * Props for the EuiComboBoxWithFieldStats component.
- */
-export type EuiComboBoxWithFieldStatsProps = EuiComboBoxProps<
- string | number | string[] | undefined
->;
-
-/**
- * React component that wraps the EuiComboBox component and adds field statistics functionality.
- *
- * @component
- * @example
- * ```tsx
- *
- * ```
- * @param {EuiComboBoxWithFieldStatsProps} props - The component props.
- */
-export const EuiComboBoxWithFieldStats: FC = (props) => {
- const { options, ...restProps } = props;
- const { renderOption } = useFieldStatsTrigger();
- const comboBoxOptions: EuiComboBoxOptionOption[] = useMemo(
- () =>
- Array.isArray(options)
- ? options.map((o) => ({
- ...o,
- css: optionCss,
- }))
- : [],
- [options]
- );
-
- return (
-
- );
-};
diff --git a/x-pack/packages/ml/field_stats_flyout/field_stats_flyout_provider.tsx b/x-pack/packages/ml/field_stats_flyout/field_stats_flyout_provider.tsx
index 9dd947f0872f3..678dec7d36f42 100644
--- a/x-pack/packages/ml/field_stats_flyout/field_stats_flyout_provider.tsx
+++ b/x-pack/packages/ml/field_stats_flyout/field_stats_flyout_provider.tsx
@@ -142,6 +142,7 @@ export const FieldStatsFlyoutProvider: FC = (prop
// Get all field names for each returned doc and flatten it
// to a list of unique field names used across all docs.
const fieldsWithData = new Set(docs.map(Object.keys).flat(1));
+
manager.set(cacheKey, fieldsWithData);
if (!unmounted) {
setPopulatedFields(fieldsWithData);
diff --git a/x-pack/packages/ml/field_stats_flyout/field_stats_info_button.tsx b/x-pack/packages/ml/field_stats_flyout/field_stats_info_button.tsx
index 936f9550cdda1..7863f358708d6 100644
--- a/x-pack/packages/ml/field_stats_flyout/field_stats_info_button.tsx
+++ b/x-pack/packages/ml/field_stats_flyout/field_stats_info_button.tsx
@@ -88,6 +88,7 @@ export const FieldStatsInfoButton: FC = (props) => {
defaultMessage: '(no data found in 1000 sample records)',
})
: '';
+
return (
@@ -135,14 +136,15 @@ export const FieldStatsInfoButton: FC = (props) => {
grow={false}
css={{
paddingRight: themeVars.euiTheme.euiSizeXS,
- paddingBottom: themeVars.euiTheme.euiSizeXS,
}}
>
-
+ {!hideTrigger ? (
+
+ ) : null}
= (props) => {
aria-label={label}
title={label}
className="euiComboBoxOption__content"
- css={{ paddingBottom: themeVars.euiTheme.euiSizeXS }}
>
{label}
diff --git a/x-pack/packages/ml/field_stats_flyout/index.ts b/x-pack/packages/ml/field_stats_flyout/index.ts
index db4d3c5ee7b15..53ed8c7ce877b 100644
--- a/x-pack/packages/ml/field_stats_flyout/index.ts
+++ b/x-pack/packages/ml/field_stats_flyout/index.ts
@@ -21,7 +21,6 @@ export {
type FieldStatsInfoButtonProps,
} from './field_stats_info_button';
export { useFieldStatsTrigger } from './use_field_stats_trigger';
-export {
- EuiComboBoxWithFieldStats,
- type EuiComboBoxWithFieldStatsProps,
-} from './eui_combo_box_with_field_stats';
+
+export { OptionListWithFieldStats } from './options_list_with_stats/option_list_with_stats';
+export type { DropDownLabel } from './options_list_with_stats/types';
diff --git a/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_popover.tsx b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_popover.tsx
new file mode 100644
index 0000000000000..77b5f8a0d8b15
--- /dev/null
+++ b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_popover.tsx
@@ -0,0 +1,158 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import type { FC } from 'react';
+import React, { useMemo, useState, useEffect } from 'react';
+import { isDefined } from '@kbn/ml-is-defined';
+import type {
+ EuiComboBoxOptionOption,
+ EuiComboBoxSingleSelectionShape,
+ EuiSelectableOption,
+} from '@elastic/eui';
+import { EuiFlexItem, EuiSelectable, htmlIdGenerator } from '@elastic/eui';
+import { css } from '@emotion/react';
+import { type DropDownLabel } from './types';
+import { useFieldStatsFlyoutContext } from '../use_field_stats_flyout_context';
+import { OptionsListPopoverFooter } from './option_list_popover_footer';
+
+interface OptionsListPopoverProps {
+ options: DropDownLabel[];
+ renderOption: (option: DropDownLabel) => React.ReactNode;
+ singleSelection?: boolean | EuiComboBoxSingleSelectionShape;
+ onChange?:
+ | ((newSuggestions: DropDownLabel[]) => void)
+ | ((
+ newSuggestions: Array>
+ ) => void);
+ setPopoverOpen: (open: boolean) => void;
+ isLoading?: boolean;
+}
+
+interface OptionsListPopoverSuggestionsProps {
+ options: DropDownLabel[];
+ renderOption: (option: DropDownLabel) => React.ReactNode;
+ singleSelection?: boolean | EuiComboBoxSingleSelectionShape;
+ onChange?:
+ | ((newSuggestions: DropDownLabel[]) => void)
+ | ((
+ newSuggestions: Array>
+ ) => void);
+ setPopoverOpen: (open: boolean) => void;
+}
+const OptionsListPopoverSuggestions: FC = ({
+ options,
+ renderOption,
+ singleSelection,
+ onChange,
+ setPopoverOpen,
+}) => {
+ const [selectableOptions, setSelectableOptions] = useState([]); // will be set in following useEffect
+ useEffect(() => {
+ /* This useEffect makes selectableOptions responsive to search, show only selected, and clear selections */
+ const _selectableOptions = (options ?? []).map((suggestion) => {
+ const key = suggestion.label ?? suggestion.field?.id;
+ return {
+ ...suggestion,
+ key,
+ checked: undefined,
+ 'data-test-subj': `optionsListControlSelection-${key}`,
+ };
+ });
+ setSelectableOptions(_selectableOptions);
+ }, [options]);
+
+ return (
+ >}
+ renderOption={renderOption}
+ listProps={{ onFocusBadge: false }}
+ onChange={(opts, _, changedOption) => {
+ const option = changedOption as DropDownLabel;
+ if (singleSelection) {
+ if (onChange) {
+ onChange([option as EuiComboBoxOptionOption]);
+ setPopoverOpen(false);
+ }
+ } else {
+ if (onChange) {
+ onChange([option as EuiComboBoxOptionOption]);
+ setPopoverOpen(false);
+ }
+ }
+ }}
+ >
+ {(list, search) => (
+ <>
+ {search}
+ {list}
+ >
+ )}
+
+ );
+};
+
+export const OptionsListPopover = ({
+ options,
+ renderOption,
+ singleSelection,
+ onChange,
+ setPopoverOpen,
+ isLoading,
+}: OptionsListPopoverProps) => {
+ const { populatedFields } = useFieldStatsFlyoutContext();
+
+ const [showEmptyFields, setShowEmptyFields] = useState(false);
+ const id = useMemo(() => htmlIdGenerator()(), []);
+
+ const filteredOptions = useMemo(() => {
+ return showEmptyFields
+ ? options
+ : options.filter((option) => {
+ if (isDefined(option['data-is-empty'])) {
+ return !option['data-is-empty'];
+ }
+ if (
+ Object.hasOwn(option, 'isGroupLabel') ||
+ Object.hasOwn(option, 'isGroupLabelOption')
+ ) {
+ const key = option.key ?? option.searchableLabel;
+ return key ? populatedFields?.has(key) : false;
+ }
+ if (option.field) {
+ return populatedFields?.has(option.field.id);
+ }
+ return true;
+ });
+ }, [options, showEmptyFields, populatedFields]);
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_popover_footer.tsx b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_popover_footer.tsx
new file mode 100644
index 0000000000000..0bed94223b0c5
--- /dev/null
+++ b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_popover_footer.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React from 'react';
+import type { FC } from 'react';
+import { EuiPopoverFooter, EuiSwitch, EuiProgress, useEuiBackgroundColor } from '@elastic/eui';
+import { css } from '@emotion/react';
+import { i18n } from '@kbn/i18n';
+import { euiThemeVars } from '@kbn/ui-theme';
+
+export const OptionsListPopoverFooter: FC<{
+ showEmptyFields: boolean;
+ setShowEmptyFields: (showEmptyFields: boolean) => void;
+ isLoading?: boolean;
+}> = ({ showEmptyFields, setShowEmptyFields, isLoading }) => {
+ return (
+
+ {isLoading ? (
+ // @ts-expect-error css should be ok
+
+
+
+ ) : null}
+
+ setShowEmptyFields(e.target.checked)}
+ />
+
+ );
+};
diff --git a/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_with_stats.tsx b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_with_stats.tsx
new file mode 100644
index 0000000000000..244b2d6a511a9
--- /dev/null
+++ b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/option_list_with_stats.tsx
@@ -0,0 +1,146 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { FC } from 'react';
+import React, { useMemo, useState } from 'react';
+import type { EuiComboBoxOptionOption, EuiComboBoxSingleSelectionShape } from '@elastic/eui';
+import { EuiInputPopover, htmlIdGenerator, EuiFormControlLayout, EuiFieldText } from '@elastic/eui';
+import { css } from '@emotion/react';
+import { i18n } from '@kbn/i18n';
+import { useFieldStatsTrigger } from '../use_field_stats_trigger';
+import { OptionsListPopover } from './option_list_popover';
+import type { DropDownLabel } from './types';
+
+const MIN_POPOVER_WIDTH = 400;
+
+export const optionCss = css`
+ display: flex;
+ align-items: center;
+ .euiComboBoxOption__enterBadge {
+ display: none;
+ }
+ .euiFlexGroup {
+ gap: 0px;
+ }
+ .euiComboBoxOption__content {
+ margin-left: 2px;
+ }
+`;
+
+interface OptionListWithFieldStatsProps {
+ options: DropDownLabel[];
+ placeholder?: string;
+ 'aria-label'?: string;
+ singleSelection?: boolean | EuiComboBoxSingleSelectionShape;
+ onChange:
+ | ((newSuggestions: DropDownLabel[]) => void)
+ | ((newSuggestions: EuiComboBoxOptionOption[]) => void);
+ selectedOptions?: Array<{ label: string }>;
+ fullWidth?: boolean;
+ isDisabled?: boolean;
+ isLoading?: boolean;
+ isClearable?: boolean;
+ isInvalid?: boolean;
+ 'data-test-subj'?: string;
+}
+
+export const OptionListWithFieldStats: FC = ({
+ options,
+ placeholder,
+ singleSelection = false,
+ onChange,
+ selectedOptions,
+ fullWidth,
+ isDisabled,
+ isLoading,
+ isClearable = true,
+ 'aria-label': ariaLabel,
+ 'data-test-subj': dataTestSubj,
+}) => {
+ const { renderOption } = useFieldStatsTrigger();
+ const [isPopoverOpen, setPopoverOpen] = useState(false);
+
+ const popoverId = useMemo(() => htmlIdGenerator()(), []);
+ const comboBoxOptions: DropDownLabel[] = useMemo(
+ () =>
+ Array.isArray(options)
+ ? options.map(({ isEmpty, hideTrigger: hideInspectButton, ...o }) => ({
+ ...o,
+ css: optionCss,
+ // Change data-is-empty- because EUI is passing all props to dom element
+ // so isEmpty is invalid, but we need this info to render option correctly
+ 'data-is-empty': isEmpty,
+ 'data-hide-inspect': hideInspectButton,
+ }))
+ : [],
+ [options]
+ );
+ const hasSelections = useMemo(() => selectedOptions?.length ?? 0 > 0, [selectedOptions]);
+
+ const value = singleSelection && selectedOptions?.[0]?.label ? selectedOptions?.[0]?.label : '';
+ return (
+
+ {}}
+ value={value}
+ />
+
+ }
+ hasArrow={false}
+ repositionOnScroll
+ isOpen={isPopoverOpen}
+ panelPaddingSize="none"
+ panelMinWidth={MIN_POPOVER_WIDTH}
+ initialFocus={'[data-test-subj=optionsList-control-search-input]'}
+ closePopover={setPopoverOpen.bind(null, false)}
+ panelProps={{
+ 'aria-label': i18n.translate('xpack.ml.controls.optionsList.popover.ariaLabel', {
+ defaultMessage: 'Popover for {ariaLabel}',
+ values: { ariaLabel },
+ }),
+ }}
+ >
+ {isPopoverOpen ? (
+
+ ) : null}
+
+ );
+};
diff --git a/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/types.ts b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/types.ts
new file mode 100644
index 0000000000000..ef95daa38ea03
--- /dev/null
+++ b/x-pack/packages/ml/field_stats_flyout/options_list_with_stats/types.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { EuiComboBoxOptionOption, EuiSelectableOption } from '@elastic/eui';
+import type { Aggregation, Field } from '@kbn/ml-anomaly-utils';
+
+interface BaseOption {
+ key?: string;
+ label: string | React.ReactNode;
+ isEmpty?: boolean;
+ hideTrigger?: boolean;
+ 'data-is-empty'?: boolean;
+ 'data-hide-inspect'?: boolean;
+ isGroupLabelOption?: boolean;
+ isGroupLabel?: boolean;
+ field?: Field;
+ agg?: Aggregation;
+ searchableLabel?: string;
+}
+export type SelectableOption = EuiSelectableOption>;
+export type DropDownLabel =
+ | (EuiComboBoxOptionOption & BaseOption)
+ | SelectableOption;
+
+export function isSelectableOption(option: unknown): option is SelectableOption {
+ return typeof option === 'object' && option !== null && Object.hasOwn(option, 'label');
+}
diff --git a/x-pack/packages/ml/field_stats_flyout/tsconfig.json b/x-pack/packages/ml/field_stats_flyout/tsconfig.json
index b0920fac0ad2a..0010d79432e34 100644
--- a/x-pack/packages/ml/field_stats_flyout/tsconfig.json
+++ b/x-pack/packages/ml/field_stats_flyout/tsconfig.json
@@ -32,5 +32,6 @@
"@kbn/ml-query-utils",
"@kbn/ml-is-defined",
"@kbn/field-types",
+ "@kbn/ui-theme",
]
}
diff --git a/x-pack/packages/ml/field_stats_flyout/use_field_stats_trigger.tsx b/x-pack/packages/ml/field_stats_flyout/use_field_stats_trigger.tsx
index 78c2f6772049a..546dc36ce9e4b 100644
--- a/x-pack/packages/ml/field_stats_flyout/use_field_stats_trigger.tsx
+++ b/x-pack/packages/ml/field_stats_flyout/use_field_stats_trigger.tsx
@@ -7,13 +7,27 @@
import type { ReactNode } from 'react';
import React, { useCallback } from 'react';
-import type { EuiComboBoxOptionOption } from '@elastic/eui';
+import { type EuiComboBoxOptionOption } from '@elastic/eui';
import type { Field } from '@kbn/ml-anomaly-utils';
-import { optionCss } from './eui_combo_box_with_field_stats';
+import { css } from '@emotion/react';
+import { EVENT_RATE_FIELD_ID } from '@kbn/ml-anomaly-utils/fields';
+import type { DropDownLabel } from '.';
import { useFieldStatsFlyoutContext } from '.';
import type { FieldForStats } from './field_stats_info_button';
import { FieldStatsInfoButton } from './field_stats_info_button';
+import { isSelectableOption } from './options_list_with_stats/types';
+export const optionCss = css`
+ .euiComboBoxOption__enterBadge {
+ display: none;
+ }
+ .euiFlexGroup {
+ gap: 0px;
+ }
+ .euiComboBoxOption__content {
+ margin-left: 2px;
+ }
+`;
interface Option extends EuiComboBoxOptionOption {
field: Field;
}
@@ -30,7 +44,7 @@ interface Option extends EuiComboBoxOptionOption {
* - `optionCss`: CSS styles for the options in the combo box.
* - `populatedFields`: A set of populated fields.
*/
-export const useFieldStatsTrigger = () => {
+export function useFieldStatsTrigger() {
const { setIsFlyoutVisible, setFieldName, populatedFields } = useFieldStatsFlyoutContext();
const closeFlyout = useCallback(() => setIsFlyoutVisible(false), [setIsFlyoutVisible]);
@@ -46,18 +60,26 @@ export const useFieldStatsTrigger = () => {
);
const renderOption = useCallback(
- (option: EuiComboBoxOptionOption, searchValue: string): ReactNode => {
- const field = (option as Option).field;
- return option.isGroupLabelOption || !field ? (
- option.label
- ) : (
-
- );
+ (option: T): ReactNode => {
+ if (isSelectableOption(option)) {
+ const field = (option as Option).field;
+ const isInternalEventRateFieldId = field?.id === EVENT_RATE_FIELD_ID;
+ const isEmpty = isInternalEventRateFieldId
+ ? false
+ : !populatedFields?.has(field?.id ?? field?.name);
+ const shouldHideInpectButton = option.hideTrigger ?? option['data-hide-inspect'];
+ return option.isGroupLabel || !field ? (
+ option.label
+ ) : (
+
+ );
+ }
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[handleFieldStatsButtonClick, populatedFields?.size]
@@ -71,4 +93,4 @@ export const useFieldStatsTrigger = () => {
optionCss,
populatedFields,
};
-};
+}
diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/partitions_selector.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/partitions_selector.tsx
index 2034930913d6c..dddada9cd83db 100644
--- a/x-pack/plugins/aiops/public/components/change_point_detection/partitions_selector.tsx
+++ b/x-pack/plugins/aiops/public/components/change_point_detection/partitions_selector.tsx
@@ -16,7 +16,7 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { type SearchRequest } from '@elastic/elasticsearch/lib/api/types';
-import type { EuiComboBoxOptionOption } from '@elastic/eui/src/components/combo_box/types';
+import type { EuiComboBoxOptionOption } from '@elastic/eui';
import { debounce } from 'lodash';
import usePrevious from 'react-use/lib/usePrevious';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx
index b2b60e95dceda..3212eba8b2ddd 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx
@@ -30,11 +30,12 @@ import {
import { DataGrid } from '@kbn/ml-data-grid';
import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils';
import {
- EuiComboBoxWithFieldStats,
+ OptionListWithFieldStats,
FieldStatsFlyoutProvider,
type FieldForStats,
} from '@kbn/ml-field-stats-flyout';
+import type { DropDownLabel } from '../../../../../jobs/new_job/pages/components/pick_fields_step/components/agg_select';
import { useMlApi, useMlKibana } from '../../../../../contexts/kibana';
import { useNewJobCapsServiceAnalytics } from '../../../../../services/new_job_capabilities/new_job_capabilities_service_analytics';
import { useDataSource } from '../../../../../contexts/ml';
@@ -665,7 +666,7 @@ export const ConfigurationStepForm: FC = ({
: []),
]}
>
- = ({
singleSelection={true}
options={dependentVariableOptions}
selectedOptions={dependentVariable ? [{ label: dependentVariable }] : []}
- onChange={(selectedOptions) => {
+ onChange={(selectedOptions: DropDownLabel[]) => {
setFormState({
dependentVariable: selectedOptions[0].label || '',
});
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx
index 667840a6ca486..51146ee8992dc 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx
@@ -11,12 +11,12 @@ import React, { Fragment, useState, useContext, useEffect } from 'react';
import type { EuiComboBoxOptionOption } from '@elastic/eui';
import {
- EuiComboBox,
EuiFlexItem,
EuiFlexGroup,
EuiFlexGrid,
EuiHorizontalRule,
EuiTextArea,
+ EuiComboBox,
} from '@elastic/eui';
import {
@@ -25,7 +25,7 @@ import {
EVENT_RATE_FIELD_ID,
mlCategory,
} from '@kbn/ml-anomaly-utils';
-import { useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout';
+import { OptionListWithFieldStats, useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout';
import { JobCreatorContext } from '../../../job_creator_context';
import type { AdvancedJobCreator } from '../../../../../common/job_creator';
@@ -261,14 +261,13 @@ export const AdvancedDetectorModal: FC = ({
-
@@ -277,53 +276,49 @@ export const AdvancedDetectorModal: FC = ({
-
-
-
-
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/agg_select/agg_select.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/agg_select/agg_select.tsx
index 257b075ed4511..1d112a68fdf8a 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/agg_select/agg_select.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/agg_select/agg_select.tsx
@@ -8,34 +8,25 @@
import type { FC } from 'react';
import React, { useContext, useState, useEffect, useMemo } from 'react';
import type { EuiComboBoxOptionOption } from '@elastic/eui';
-import { EuiComboBox, EuiFormRow } from '@elastic/eui';
-import type { Field, Aggregation, AggFieldPair } from '@kbn/ml-anomaly-utils';
+import { EuiFormRow } from '@elastic/eui';
+import type { Field, AggFieldPair } from '@kbn/ml-anomaly-utils';
import { EVENT_RATE_FIELD_ID } from '@kbn/ml-anomaly-utils';
-import { useFieldStatsTrigger, FieldStatsInfoButton } from '@kbn/ml-field-stats-flyout';
+import { i18n } from '@kbn/i18n';
+import { omit } from 'lodash';
+import type { DropDownLabel } from '@kbn/ml-field-stats-flyout';
+import {
+ OptionListWithFieldStats,
+ FieldStatsInfoButton,
+ useFieldStatsTrigger,
+} from '@kbn/ml-field-stats-flyout';
import { JobCreatorContext } from '../../../job_creator_context';
-
-// The display label used for an aggregation e.g. sum(bytes).
-export type Label = string;
-
-// Label object structured for EUI's ComboBox.
-export interface DropDownLabel {
- label: Label;
- agg: Aggregation;
- field: Field;
-}
-
-// Label object structure for EUI's ComboBox with support for nesting.
-export interface DropDownOption extends EuiComboBoxOptionOption {
- label: Label;
- options: DropDownLabel[];
-}
-
+export type { DropDownLabel };
export type DropDownProps = DropDownLabel[] | EuiComboBoxOptionOption[];
interface Props {
fields: Field[];
- changeHandler(d: EuiComboBoxOptionOption[]): void;
- selectedOptions: EuiComboBoxOptionOption[];
+ changeHandler(d: DropDownLabel[]): void;
+ selectedOptions: DropDownLabel[];
removeOptions: AggFieldPair[];
}
@@ -47,40 +38,51 @@ export const AggSelect: FC = ({ fields, changeHandler, selectedOptions, r
const removeLabels = removeOptions.map(createLabel);
const { handleFieldStatsButtonClick, populatedFields } = useFieldStatsTrigger();
- const options: EuiComboBoxOptionOption[] = useMemo(
- () =>
- fields.map((f) => {
- const aggOption: DropDownOption = {
- isGroupLabelOption: true,
+ const options: DropDownLabel[] = useMemo(
+ () => {
+ const opts: DropDownLabel[] = [];
+ fields.forEach((f) => {
+ const isEmpty = f.id === EVENT_RATE_FIELD_ID ? false : !populatedFields?.has(f.name);
+ const aggOption: DropDownLabel = {
+ isGroupLabel: true,
key: f.name,
+ searchableLabel: f.name,
+ isEmpty,
// @ts-ignore Purposefully passing label as element instead of string
// for more robust rendering
label: (
),
- options: [],
};
- if (typeof f.aggs !== 'undefined') {
- aggOption.options = f.aggs
- .filter((a) => a.dslName !== null) // don't include aggs which have no ES equivalent
- .map(
- (a) =>
- ({
- label: `${a.title}(${f.name})`,
- agg: a,
- field: f,
- } as DropDownLabel)
- )
- .filter((o) => removeLabels.includes(o.label) === false);
+ if (typeof f.aggs !== 'undefined' && f.aggs.length > 0) {
+ opts.push(aggOption);
+
+ f.aggs.forEach((a) => {
+ const label = `${a.title}(${f.name})`;
+ if (removeLabels.includes(label) === true) return;
+ if (a.dslName !== null) {
+ const agg: DropDownLabel = {
+ key: label,
+ isEmpty,
+ hideTrigger: true,
+ isGroupLabel: false,
+ label,
+ agg: omit(a, 'fields'),
+ field: omit(f, 'aggs'),
+ };
+ opts.push(agg);
+ }
+ });
}
- return aggOption;
- }),
+ });
+ return opts;
+ },
// eslint-disable-next-line react-hooks/exhaustive-deps
[handleFieldStatsButtonClick, fields, removeLabels, populatedFields?.size]
);
@@ -96,8 +98,11 @@ export const AggSelect: FC = ({ fields, changeHandler, selectedOptions, r
isInvalid={validation.valid === false}
data-test-subj="mlJobWizardAggSelection"
>
- = ({ fields, changeHandler, selectedField }) => {
const { jobCreator, jobCreatorUpdated } = useContext(JobCreatorContext);
- const { renderOption, optionCss } = useFieldStatsTrigger();
+ const { optionCss } = useFieldStatsTrigger();
const options: EuiComboBoxOptionOption[] = useMemo(
() =>
@@ -51,14 +50,13 @@ export const CategorizationFieldSelect: FC = ({ fields, changeHandler, se
);
return (
-
);
};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_input.tsx
index 237688b215511..33a7a04fc5640 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_input.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_partition_field/categorization_per_partition_input.tsx
@@ -8,10 +8,9 @@
import type { FC } from 'react';
import React, { useCallback, useContext, useMemo } from 'react';
import type { EuiComboBoxOptionOption } from '@elastic/eui';
-import { EuiComboBox } from '@elastic/eui';
import type { Field } from '@kbn/ml-anomaly-utils';
-import { useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout';
+import { OptionListWithFieldStats, useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout';
import { JobCreatorContext } from '../../../job_creator_context';
import { createFieldOptions } from '../../../../../common/job_creator/util/general';
@@ -27,7 +26,7 @@ export const CategorizationPerPartitionFieldSelect: FC = ({
selectedField,
}) => {
const { jobCreator, jobCreatorUpdated } = useContext(JobCreatorContext);
- const { renderOption, optionCss } = useFieldStatsTrigger();
+ const { optionCss } = useFieldStatsTrigger();
const options: EuiComboBoxOptionOption[] = useMemo(
() =>
@@ -54,14 +53,13 @@ export const CategorizationPerPartitionFieldSelect: FC = ({
);
return (
-
);
};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/geo_field/geo_field_select.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/geo_field/geo_field_select.tsx
index f40e28f2fea28..3c866d6605083 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/geo_field/geo_field_select.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/geo_field/geo_field_select.tsx
@@ -8,9 +8,8 @@
import type { FC } from 'react';
import React, { useCallback, useMemo } from 'react';
import type { EuiComboBoxOptionOption } from '@elastic/eui';
-import { EuiComboBox } from '@elastic/eui';
import type { Field } from '@kbn/ml-anomaly-utils';
-import { useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout';
+import { OptionListWithFieldStats, useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout';
interface DropDownLabel {
label: string;
@@ -24,7 +23,7 @@ interface Props {
}
export const GeoFieldSelect: FC = ({ fields, changeHandler, selectedField }) => {
- const { renderOption, optionCss } = useFieldStatsTrigger();
+ const { optionCss } = useFieldStatsTrigger();
const options: EuiComboBoxOptionOption[] = useMemo(
() =>
@@ -60,14 +59,13 @@ export const GeoFieldSelect: FC = ({ fields, changeHandler, selectedField
);
return (
-
);
};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers_select.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers_select.tsx
index 4c0946657d8e6..b667c8667eeee 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers_select.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/influencers/influencers_select.tsx
@@ -42,6 +42,7 @@ export const InfluencersSelect: FC = ({ fields, changeHandler, selectedIn
return (
= ({ setIsValid }) => {
function addDetector(selectedOptionsIn: DropDownLabel[]) {
if (selectedOptionsIn !== null && selectedOptionsIn.length) {
const option = selectedOptionsIn[0] as DropDownLabel;
- if (typeof option !== 'undefined') {
- const newPair = { agg: option.agg, field: option.field };
+ if (typeof option !== 'undefined' && isPopulatedObject(option, ['agg', 'field'])) {
+ const newPair = {
+ agg: option.agg as Aggregation,
+ field: option.field as Field,
+ };
setAggFieldPairList([...aggFieldPairList, newPair]);
setSelectedOptions([]);
} else {
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx
index 81dd83b9e157c..f193c0c49bbc9 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx
@@ -8,8 +8,9 @@
import type { FC } from 'react';
import React, { Fragment, useContext, useEffect, useState, useReducer, useMemo } from 'react';
import { EuiHorizontalRule } from '@elastic/eui';
-import type { Field, AggFieldPair } from '@kbn/ml-anomaly-utils';
+import type { Field, AggFieldPair, Aggregation } from '@kbn/ml-anomaly-utils';
+import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import { useUiSettings } from '../../../../../../../contexts/kibana';
import { JobCreatorContext } from '../../../job_creator_context';
import type { PopulationJobCreator } from '../../../../../common/job_creator';
@@ -72,9 +73,13 @@ export const PopulationDetectors: FC = ({ setIsValid }) => {
function addDetector(selectedOptionsIn: DropDownLabel[]) {
if (selectedOptionsIn !== null && selectedOptionsIn.length) {
- const option = selectedOptionsIn[0] as DropDownLabel;
- if (typeof option !== 'undefined') {
- const newPair = { agg: option.agg, field: option.field, by: { field: null, value: null } };
+ const option = selectedOptionsIn[0] as DropDownLabel & { field: Field };
+ if (typeof option !== 'undefined' && isPopulatedObject(option, ['agg', 'field'])) {
+ const newPair: AggFieldPair = {
+ agg: option.agg as Aggregation,
+ field: option.field,
+ by: { field: null, value: null },
+ };
setAggFieldPairList([...aggFieldPairList, newPair]);
setSelectedOptions([]);
} else {
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/rare_field_select.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/rare_field_select.tsx
index a834a0d3bbdd4..e4a2f7588496c 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/rare_field_select.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/rare_field_select.tsx
@@ -8,9 +8,8 @@
import type { FC } from 'react';
import React from 'react';
import type { EuiComboBoxOptionOption } from '@elastic/eui';
-import { EuiComboBox } from '@elastic/eui';
import type { Field, SplitField } from '@kbn/ml-anomaly-utils';
-import { useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout';
+import { OptionListWithFieldStats, useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout';
interface DropDownLabel {
label: string;
@@ -32,7 +31,7 @@ export const RareFieldSelect: FC = ({
testSubject,
placeholder,
}) => {
- const { renderOption, optionCss } = useFieldStatsTrigger();
+ const { optionCss } = useFieldStatsTrigger();
const options: EuiComboBoxOptionOption[] = fields.map(
(f) =>
@@ -58,7 +57,7 @@ export const RareFieldSelect: FC = ({
}
return (
- = ({
placeholder={placeholder}
data-test-subj={testSubject}
isClearable={false}
- renderOption={renderOption}
/>
);
};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx
index e37b1722e9bc2..dcf325adf41d1 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx
@@ -7,8 +7,9 @@
import type { FC } from 'react';
import React, { Fragment, useContext, useEffect, useState, useMemo } from 'react';
-import type { AggFieldPair } from '@kbn/ml-anomaly-utils';
+import type { AggFieldPair, Aggregation, Field } from '@kbn/ml-anomaly-utils';
+import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import { useUiSettings } from '../../../../../../../contexts/kibana';
import { JobCreatorContext } from '../../../job_creator_context';
import type { SingleMetricJobCreator } from '../../../../../common/job_creator';
@@ -58,9 +59,13 @@ export const SingleMetricDetectors: FC = ({ setIsValid }) => {
function detectorChangeHandler(selectedOptionsIn: DropDownLabel[]) {
setSelectedOptions(selectedOptionsIn);
if (selectedOptionsIn.length) {
- const option = selectedOptionsIn[0];
- if (typeof option !== 'undefined') {
- setAggFieldPair({ agg: option.agg, field: option.field });
+ const option = selectedOptionsIn[0] as DropDownLabel;
+ if (typeof option !== 'undefined' && isPopulatedObject(option, ['agg', 'field'])) {
+ setAggFieldPair({
+ agg: option.agg as Aggregation,
+ field: option.field as Field,
+ by: { field: null, value: null },
+ });
} else {
setAggFieldPair(null);
}
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field_select/split_field_select.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field_select/split_field_select.tsx
index d621e85b3f56a..72c236d690979 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field_select/split_field_select.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field_select/split_field_select.tsx
@@ -8,15 +8,10 @@
import type { FC } from 'react';
import React from 'react';
import type { EuiComboBoxOptionOption } from '@elastic/eui';
-import { EuiComboBox } from '@elastic/eui';
import type { Field, SplitField } from '@kbn/ml-anomaly-utils';
-import { useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout';
-
-interface DropDownLabel {
- label: string;
- field: Field;
-}
+import type { DropDownLabel } from '@kbn/ml-field-stats-flyout';
+import { OptionListWithFieldStats, useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout';
interface Props {
fields: Field[];
@@ -35,8 +30,8 @@ export const SplitFieldSelect: FC = ({
testSubject,
placeholder,
}) => {
- const { renderOption, optionCss } = useFieldStatsTrigger();
- const options: EuiComboBoxOptionOption[] = fields.map(
+ const { optionCss } = useFieldStatsTrigger();
+ const options: DropDownLabel[] = fields.map(
(f) =>
({
label: f.name,
@@ -45,14 +40,14 @@ export const SplitFieldSelect: FC = ({
} as DropDownLabel)
);
- const selection: EuiComboBoxOptionOption[] = [];
+ const selection: DropDownLabel[] = [];
if (selectedField !== null) {
selection.push({ label: selectedField.name, field: selectedField } as DropDownLabel);
}
function onChange(selectedOptions: EuiComboBoxOptionOption[]) {
const option = selectedOptions[0] as DropDownLabel;
- if (typeof option !== 'undefined') {
+ if (typeof option?.field !== 'undefined') {
changeHandler(option.field);
} else {
changeHandler(null);
@@ -60,15 +55,14 @@ export const SplitFieldSelect: FC = ({
}
return (
-
);
};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/summary_count_field/summary_count_field_select.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/summary_count_field/summary_count_field_select.tsx
index 8fc91b74d1a2c..59c761a46e75a 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/summary_count_field/summary_count_field_select.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/summary_count_field/summary_count_field_select.tsx
@@ -8,10 +8,8 @@
import type { FC } from 'react';
import React, { useContext } from 'react';
import type { EuiComboBoxOptionOption } from '@elastic/eui';
-import { EuiComboBox } from '@elastic/eui';
-
import type { Field } from '@kbn/ml-anomaly-utils';
-import { useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout';
+import { OptionListWithFieldStats, useFieldStatsTrigger } from '@kbn/ml-field-stats-flyout';
import { JobCreatorContext } from '../../../job_creator_context';
import {
@@ -27,7 +25,7 @@ interface Props {
export const SummaryCountFieldSelect: FC = ({ fields, changeHandler, selectedField }) => {
const { jobCreator } = useContext(JobCreatorContext);
- const { renderOption, optionCss } = useFieldStatsTrigger();
+ const { optionCss } = useFieldStatsTrigger();
const options: EuiComboBoxOptionOption[] = [
...createDocCountFieldOption(jobCreator.aggregationFields.length > 0),
@@ -49,14 +47,13 @@ export const SummaryCountFieldSelect: FC = ({ fields, changeHandler, sele
}
return (
-
);
};
diff --git a/x-pack/test/functional/services/ml/common_ui.ts b/x-pack/test/functional/services/ml/common_ui.ts
index 7c12e406227d6..735c061419b14 100644
--- a/x-pack/test/functional/services/ml/common_ui.ts
+++ b/x-pack/test/functional/services/ml/common_ui.ts
@@ -451,5 +451,48 @@ export function MachineLearningCommonUIProvider({
async toggleSwitchIfNeeded(testSubj: string, targetState: boolean) {
await testSubjects.setEuiSwitch(testSubj, targetState ? 'check' : 'uncheck');
},
+
+ /** Set value for OptionListWithFieldStats component */
+ async setOptionsListWithFieldStatsValue(selector: string, value: string) {
+ await testSubjects.click(selector);
+ await testSubjects.existOrFail('optionsListControlAvailableOptions');
+
+ await retry.tryForTime(1000, async () => {
+ const enabled =
+ (await testSubjects.getAttribute(`optionsListIncludeEmptyFields`, 'aria-checked')) ===
+ 'true';
+ if (!enabled) {
+ await testSubjects.click(`optionsListIncludeEmptyFields`);
+ expect(
+ (await testSubjects.getAttribute(`optionsListIncludeEmptyFields`, 'aria-checked')) ===
+ 'true'
+ ).to.eql(true, `Expected optionsListIncludeEmptyFields to be enabled.`);
+ }
+ });
+ await retry.tryForTime(5 * 1000, async () => {
+ await testSubjects.find('optionsListFilterInput');
+
+ await testSubjects.setValue('optionsListFilterInput', value);
+ await testSubjects.click(`optionsListControlSelection-${value}`);
+ });
+ },
+
+ async assertOptionsListWithFieldStatsValue(
+ selector: string,
+ expectedIdentifiers?: string[] | string,
+ label?: string
+ ) {
+ const expectedValue =
+ (Array.isArray(expectedIdentifiers) ? expectedIdentifiers.join('') : expectedIdentifiers) ??
+ '';
+ const actualValue = await testSubjects.getAttribute(
+ `${selector} > comboBoxSearchInput`,
+ 'value'
+ );
+ expect(actualValue).to.eql(
+ expectedValue,
+ `Expected ${label ?? selector} value should be '${expectedValue}' (got '${actualValue}')`
+ );
+ },
};
}
diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts
index bda9bb2b350d1..4292480c9c61c 100644
--- a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts
+++ b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts
@@ -397,7 +397,7 @@ export function MachineLearningDataFrameAnalyticsCreationProvider(
async selectDependentVariable(dependentVariable: string) {
await this.waitForDependentVariableInputLoaded();
- await comboBox.set(
+ await mlCommonUI.setOptionsListWithFieldStatsValue(
'~mlAnalyticsCreateJobWizardDependentVariableSelect > comboBoxInput',
dependentVariable
);
diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts
index 7249a493368d9..d62dfe921f69c 100644
--- a/x-pack/test/functional/services/ml/index.ts
+++ b/x-pack/test/functional/services/ml/index.ts
@@ -134,6 +134,7 @@ export function MachineLearningProvider(context: FtrProviderContext) {
const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context, commonUI);
const jobWizardCategorization = MachineLearningJobWizardCategorizationProvider(
context,
+ commonUI,
commonFieldStatsFlyout
);
const jobWizardRecognizer = MachineLearningJobWizardRecognizerProvider(context, commonUI);
@@ -143,13 +144,15 @@ export function MachineLearningProvider(context: FtrProviderContext) {
customUrls,
commonFieldStatsFlyout
);
- const jobWizardGeo = MachineLearningJobWizardGeoProvider(context);
+ const jobWizardGeo = MachineLearningJobWizardGeoProvider(context, commonUI);
const jobWizardMultiMetric = MachineLearningJobWizardMultiMetricProvider(
context,
+ commonUI,
commonFieldStatsFlyout
);
const jobWizardPopulation = MachineLearningJobWizardPopulationProvider(
context,
+ commonUI,
commonFieldStatsFlyout
);
diff --git a/x-pack/test/functional/services/ml/job_wizard_advanced.ts b/x-pack/test/functional/services/ml/job_wizard_advanced.ts
index 317001efd75f8..a629494f3d6a2 100644
--- a/x-pack/test/functional/services/ml/job_wizard_advanced.ts
+++ b/x-pack/test/functional/services/ml/job_wizard_advanced.ts
@@ -7,8 +7,8 @@
import expect from '@kbn/expect';
-import { FtrProviderContext } from '../../ftr_provider_context';
-import { MlCommonUI } from './common_ui';
+import type { FtrProviderContext } from '../../ftr_provider_context';
+import type { MlCommonUI } from './common_ui';
export function MachineLearningJobWizardAdvancedProvider(
{ getService }: FtrProviderContext,
@@ -125,17 +125,16 @@ export function MachineLearningJobWizardAdvancedProvider(
},
async assertCategorizationFieldSelection(expectedIdentifier: string[]) {
- const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
- 'mlCategorizationFieldNameSelect > comboBoxInput'
- );
- expect(comboBoxSelectedOptions).to.eql(
+ await mlCommonUI.assertOptionsListWithFieldStatsValue(
+ 'mlCategorizationFieldNameSelect > comboBoxInput',
expectedIdentifier,
- `Expected categorization field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')`
+ 'categorization field selection'
);
},
async selectCategorizationField(identifier: string) {
- await comboBox.set('mlCategorizationFieldNameSelect > comboBoxInput', identifier);
+ const selector = 'mlCategorizationFieldNameSelect > comboBoxInput';
+ await mlCommonUI.setOptionsListWithFieldStatsValue(selector, identifier);
await this.assertCategorizationFieldSelection([identifier]);
},
@@ -144,18 +143,19 @@ export function MachineLearningJobWizardAdvancedProvider(
},
async assertSummaryCountFieldSelection(expectedIdentifier: string[]) {
- const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
- 'mlSummaryCountFieldNameSelect > comboBoxInput'
- );
- expect(comboBoxSelectedOptions).to.eql(
+ await mlCommonUI.assertOptionsListWithFieldStatsValue(
+ 'mlSummaryCountFieldNameSelect > comboBoxInput',
expectedIdentifier,
- `Expected summary count field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')`
+ 'summary count field selection'
);
},
async selectSummaryCountField(identifier: string) {
await retry.tryForTime(15 * 1000, async () => {
- await comboBox.set('mlSummaryCountFieldNameSelect > comboBoxInput', identifier);
+ await mlCommonUI.setOptionsListWithFieldStatsValue(
+ 'mlSummaryCountFieldNameSelect > comboBoxInput',
+ identifier
+ );
await this.assertSummaryCountFieldSelection([identifier]);
});
},
@@ -199,17 +199,18 @@ export function MachineLearningJobWizardAdvancedProvider(
},
async assertDetectorFieldSelection(expectedIdentifier: string[]) {
- const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
- 'mlAdvancedFieldSelect > comboBoxInput'
- );
- expect(comboBoxSelectedOptions).to.eql(
+ await mlCommonUI.assertOptionsListWithFieldStatsValue(
+ 'mlAdvancedFieldSelect > comboBoxInput',
expectedIdentifier,
- `Expected detector field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')`
+ 'detector field selection'
);
},
async selectDetectorField(identifier: string) {
- await comboBox.set('mlAdvancedFieldSelect > comboBoxInput', identifier);
+ await mlCommonUI.setOptionsListWithFieldStatsValue(
+ 'mlAdvancedFieldSelect > comboBoxInput',
+ identifier
+ );
await this.assertDetectorFieldSelection([identifier]);
},
@@ -218,17 +219,18 @@ export function MachineLearningJobWizardAdvancedProvider(
},
async assertDetectorByFieldSelection(expectedIdentifier: string[]) {
- const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
- 'mlAdvancedByFieldSelect > comboBoxInput'
- );
- expect(comboBoxSelectedOptions).to.eql(
+ await mlCommonUI.assertOptionsListWithFieldStatsValue(
+ 'mlAdvancedByFieldSelect > comboBoxInput',
expectedIdentifier,
- `Expected detector by field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')`
+ 'detector by field selection'
);
},
async selectDetectorByField(identifier: string) {
- await comboBox.set('mlAdvancedByFieldSelect > comboBoxInput', identifier);
+ await mlCommonUI.setOptionsListWithFieldStatsValue(
+ 'mlAdvancedByFieldSelect > comboBoxInput',
+ identifier
+ );
await this.assertDetectorByFieldSelection([identifier]);
},
@@ -237,17 +239,18 @@ export function MachineLearningJobWizardAdvancedProvider(
},
async assertDetectorOverFieldSelection(expectedIdentifier: string[]) {
- const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
- 'mlAdvancedOverFieldSelect > comboBoxInput'
- );
- expect(comboBoxSelectedOptions).to.eql(
+ await mlCommonUI.assertOptionsListWithFieldStatsValue(
+ 'mlAdvancedOverFieldSelect > comboBoxInput',
expectedIdentifier,
- `Expected detector over field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')`
+ 'detector over field selection'
);
},
async selectDetectorOverField(identifier: string) {
- await comboBox.set('mlAdvancedOverFieldSelect > comboBoxInput', identifier);
+ await mlCommonUI.setOptionsListWithFieldStatsValue(
+ 'mlAdvancedOverFieldSelect > comboBoxInput',
+ identifier
+ );
await this.assertDetectorOverFieldSelection([identifier]);
},
@@ -256,17 +259,18 @@ export function MachineLearningJobWizardAdvancedProvider(
},
async assertDetectorPartitionFieldSelection(expectedIdentifier: string[]) {
- const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
- 'mlAdvancedPartitionFieldSelect > comboBoxInput'
- );
- expect(comboBoxSelectedOptions).to.eql(
+ await mlCommonUI.assertOptionsListWithFieldStatsValue(
+ 'mlAdvancedPartitionFieldSelect > comboBoxInput',
expectedIdentifier,
- `Expected detector partition field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')`
+ 'detector partition field selection'
);
},
async selectDetectorPartitionField(identifier: string) {
- await comboBox.set('mlAdvancedPartitionFieldSelect > comboBoxInput', identifier);
+ await mlCommonUI.setOptionsListWithFieldStatsValue(
+ 'mlAdvancedPartitionFieldSelect > comboBoxInput',
+ identifier
+ );
await this.assertDetectorPartitionFieldSelection([identifier]);
},
@@ -275,17 +279,18 @@ export function MachineLearningJobWizardAdvancedProvider(
},
async assertDetectorExcludeFrequentSelection(expectedIdentifier: string[]) {
- const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
- 'mlAdvancedExcludeFrequentSelect > comboBoxInput'
- );
- expect(comboBoxSelectedOptions).to.eql(
+ await mlCommonUI.assertOptionsListWithFieldStatsValue(
+ 'mlAdvancedExcludeFrequentSelect > comboBoxInput',
expectedIdentifier,
- `Expected detector exclude frequent selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')`
+ 'detector exclude frequent selection'
);
},
async selectDetectorExcludeFrequent(identifier: string) {
- await comboBox.set('mlAdvancedExcludeFrequentSelect > comboBoxInput', identifier);
+ await mlCommonUI.setOptionsListWithFieldStatsValue(
+ 'mlAdvancedExcludeFrequentSelect > comboBoxInput',
+ identifier
+ );
await this.assertDetectorExcludeFrequentSelection([identifier]);
},
diff --git a/x-pack/test/functional/services/ml/job_wizard_categorization.ts b/x-pack/test/functional/services/ml/job_wizard_categorization.ts
index 8c4e4a6386da7..7a24df49e7c05 100644
--- a/x-pack/test/functional/services/ml/job_wizard_categorization.ts
+++ b/x-pack/test/functional/services/ml/job_wizard_categorization.ts
@@ -10,9 +10,11 @@ import expect from '@kbn/expect';
import { CATEGORY_EXAMPLES_VALIDATION_STATUS } from '@kbn/ml-category-validator';
import type { FtrProviderContext } from '../../ftr_provider_context';
import type { MlCommonFieldStatsFlyout } from './field_stats_flyout';
+import type { MlCommonUI } from './common_ui';
export function MachineLearningJobWizardCategorizationProvider(
{ getService }: FtrProviderContext,
+ mlCommonUI: MlCommonUI,
mlCommonFieldStatsFlyout: MlCommonFieldStatsFlyout
) {
const comboBox = getService('comboBox');
@@ -50,7 +52,10 @@ export function MachineLearningJobWizardCategorizationProvider(
},
async selectCategorizationField(identifier: string) {
- await comboBox.set('mlCategorizationFieldNameSelect > comboBoxInput', identifier);
+ await mlCommonUI.setOptionsListWithFieldStatsValue(
+ 'mlCategorizationFieldNameSelect > comboBoxInput',
+ identifier
+ );
await this.assertCategorizationFieldSelection([identifier]);
},
diff --git a/x-pack/test/functional/services/ml/job_wizard_common.ts b/x-pack/test/functional/services/ml/job_wizard_common.ts
index b1671626f191f..6dd2aaf1d1826 100644
--- a/x-pack/test/functional/services/ml/job_wizard_common.ts
+++ b/x-pack/test/functional/services/ml/job_wizard_common.ts
@@ -127,7 +127,10 @@ export function MachineLearningJobWizardCommonProvider(
},
async selectAggAndField(identifier: string, isIdentifierKeptInField: boolean) {
- await comboBox.set('mlJobWizardAggSelection > comboBoxInput', identifier);
+ await mlCommonUI.setOptionsListWithFieldStatsValue(
+ 'mlJobWizardAggSelection > comboBoxInput',
+ identifier
+ );
await this.assertAggAndFieldSelection(isIdentifierKeptInField ? [identifier] : []);
},
diff --git a/x-pack/test/functional/services/ml/job_wizard_geo.ts b/x-pack/test/functional/services/ml/job_wizard_geo.ts
index be2848985a6a2..3e4717942e990 100644
--- a/x-pack/test/functional/services/ml/job_wizard_geo.ts
+++ b/x-pack/test/functional/services/ml/job_wizard_geo.ts
@@ -7,10 +7,14 @@
import expect from '@kbn/expect';
-import { FtrProviderContext } from '../../ftr_provider_context';
-
-export function MachineLearningJobWizardGeoProvider({ getService }: FtrProviderContext) {
- const comboBox = getService('comboBox');
+import type { FtrProviderContext } from '../../ftr_provider_context';
+import type { MlCommonUI } from './common_ui';
+
+export function MachineLearningJobWizardGeoProvider(
+ { getService }: FtrProviderContext,
+ mlCommonUI: MlCommonUI
+) {
+ const retry = getService('retry');
const testSubjects = getService('testSubjects');
return {
@@ -19,18 +23,21 @@ export function MachineLearningJobWizardGeoProvider({ getService }: FtrProviderC
},
async assertGeoFieldSelection(expectedIdentifier: string[]) {
- const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
- 'mlGeoFieldNameSelect > comboBoxInput'
- );
- expect(comboBoxSelectedOptions).to.eql(
+ await mlCommonUI.assertOptionsListWithFieldStatsValue(
+ 'mlGeoFieldNameSelect > comboBoxInput',
expectedIdentifier,
- `Expected geo field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')`
+ 'geo field selection'
);
},
async selectGeoField(identifier: string) {
- await comboBox.set('mlGeoFieldNameSelect > comboBoxInput', identifier);
- await this.assertGeoFieldSelection([identifier]);
+ await retry.tryForTime(5 * 1000, async () => {
+ await mlCommonUI.setOptionsListWithFieldStatsValue(
+ 'mlGeoFieldNameSelect > comboBoxInput',
+ identifier
+ );
+ await this.assertGeoFieldSelection([identifier]);
+ });
},
async assertSplitCardWithMapExampleExists() {
@@ -40,13 +47,15 @@ export function MachineLearningJobWizardGeoProvider({ getService }: FtrProviderC
async assertDetectorPreviewExists(detectorDescription: string) {
await testSubjects.existOrFail('mlGeoMap > mlDetectorTitle');
const actualDetectorTitle = await testSubjects.getVisibleText('mlGeoMap > mlDetectorTitle');
- expect(actualDetectorTitle).to.eql(
- detectorDescription,
- `Expected detector title to be '${detectorDescription}' (got '${actualDetectorTitle}')`
- );
-
- await testSubjects.existOrFail('mlGeoJobWizardMap');
- await testSubjects.existOrFail('mapContainer');
+ await retry.tryForTime(5 * 1000, async () => {
+ expect(actualDetectorTitle).to.eql(
+ detectorDescription,
+ `Expected detector title to be '${detectorDescription}' (got '${actualDetectorTitle}')`
+ );
+
+ await testSubjects.existOrFail('mlGeoJobWizardMap');
+ await testSubjects.existOrFail('mapContainer');
+ });
},
};
}
diff --git a/x-pack/test/functional/services/ml/job_wizard_multi_metric.ts b/x-pack/test/functional/services/ml/job_wizard_multi_metric.ts
index f5c347ad697b2..0390f716ddc04 100644
--- a/x-pack/test/functional/services/ml/job_wizard_multi_metric.ts
+++ b/x-pack/test/functional/services/ml/job_wizard_multi_metric.ts
@@ -9,9 +9,11 @@ import expect from '@kbn/expect';
import type { FtrProviderContext } from '../../ftr_provider_context';
import type { MlCommonFieldStatsFlyout } from './field_stats_flyout';
+import type { MlCommonUI } from './common_ui';
export function MachineLearningJobWizardMultiMetricProvider(
{ getService }: FtrProviderContext,
+ mlCommonUI: MlCommonUI,
mlCommonFieldStatsFlyout: MlCommonFieldStatsFlyout
) {
const comboBox = getService('comboBox');
@@ -46,7 +48,11 @@ export function MachineLearningJobWizardMultiMetricProvider(
},
async selectSplitField(identifier: string) {
- await comboBox.set('mlSplitFieldSelect > comboBoxInput', identifier);
+ await mlCommonUI.setOptionsListWithFieldStatsValue(
+ 'mlSplitFieldSelect > comboBoxInput',
+ identifier
+ );
+
await this.assertSplitFieldSelection([identifier]);
},
diff --git a/x-pack/test/functional/services/ml/job_wizard_population.ts b/x-pack/test/functional/services/ml/job_wizard_population.ts
index 418369bbf905f..0039138e94ce1 100644
--- a/x-pack/test/functional/services/ml/job_wizard_population.ts
+++ b/x-pack/test/functional/services/ml/job_wizard_population.ts
@@ -9,9 +9,11 @@ import expect from '@kbn/expect';
import type { FtrProviderContext } from '../../ftr_provider_context';
import type { MlCommonFieldStatsFlyout } from './field_stats_flyout';
+import type { MlCommonUI } from './common_ui';
export function MachineLearningJobWizardPopulationProvider(
{ getService }: FtrProviderContext,
+ mlCommonUI: MlCommonUI,
mlCommonFieldStatsFlyout: MlCommonFieldStatsFlyout
) {
const comboBox = getService('comboBox');
@@ -46,7 +48,10 @@ export function MachineLearningJobWizardPopulationProvider(
},
async selectPopulationField(identifier: string) {
- await comboBox.set('mlPopulationSplitFieldSelect > comboBoxInput', identifier);
+ await mlCommonUI.setOptionsListWithFieldStatsValue(
+ 'mlPopulationSplitFieldSelect > comboBoxInput',
+ identifier
+ );
await this.assertPopulationFieldSelection([identifier]);
},
@@ -70,7 +75,7 @@ export function MachineLearningJobWizardPopulationProvider(
},
async selectDetectorSplitField(detectorPosition: number, identifier: string) {
- await comboBox.set(
+ await mlCommonUI.setOptionsListWithFieldStatsValue(
`mlDetector ${detectorPosition} > mlByFieldSelect > comboBoxInput`,
identifier
);