From d70287a3ca115ef88f852cb091e3fbfb995553ec Mon Sep 17 00:00:00 2001 From: Dzmitry Tamashevich Date: Mon, 18 Apr 2022 20:04:00 +0500 Subject: [PATCH 1/8] [Discover] add initial setup --- .../components/data_view_select_popover.tsx | 108 ++++++++++++++++++ .../filters_list.tsx} | 20 ++-- .../public/alert_types/es_query/constants.ts | 15 +++ .../es_query/expression/expression.tsx | 22 +--- .../expression/search_source_expression.tsx | 82 +++++++------ .../public/alert_types/es_query/types.ts | 14 ++- .../public/alert_types/es_query/util.ts | 8 ++ .../public/alert_types/es_query/validation.ts | 16 +-- 8 files changed, 210 insertions(+), 75 deletions(-) create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/components/data_view_select_popover.tsx rename x-pack/plugins/stack_alerts/public/alert_types/{es_query/expression/read_only_filter_items.tsx => components/filters_list.tsx} (75%) diff --git a/x-pack/plugins/stack_alerts/public/alert_types/components/data_view_select_popover.tsx b/x-pack/plugins/stack_alerts/public/alert_types/components/data_view_select_popover.tsx new file mode 100644 index 0000000000000..84dacabf28846 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/components/data_view_select_popover.tsx @@ -0,0 +1,108 @@ +/* + * 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, { useCallback, useEffect, useMemo, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButtonIcon, + EuiComboBox, + EuiExpression, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiPopover, + EuiPopoverTitle, +} from '@elastic/eui'; +import { useTriggersAndActionsUiDeps } from '../es_query/util'; +import { DataViewOption } from '../es_query/types'; + +interface DataViewSelectPopoverProps { + selectedDataViewTitle: string; + onSelectDataView: (options: DataViewOption[]) => void; +} + +export const DataViewSelectPopover: React.FunctionComponent = ({ + selectedDataViewTitle, + onSelectDataView, +}) => { + const { data } = useTriggersAndActionsUiDeps(); + + const [dataViewPopoverOpen, setDataViewPopoverOpen] = useState(false); + const [dataViewOptions, setDataViewOptions] = useState(); + const selectedOption = useMemo(() => ({ label: selectedDataViewTitle }), [selectedDataViewTitle]); + + useEffect(() => { + const initDataViews = async () => { + const dataViewItems = await data.dataViews.getIdsWithTitle(); + setDataViewOptions(dataViewItems.map(({ id, title }) => ({ label: title, value: id }))); + }; + initDataViews(); + }, [data.dataViews]); + + const closeDataViewPopover = useCallback(() => setDataViewPopoverOpen(false), []); + + return ( + { + setDataViewPopoverOpen(true); + }} + isInvalid={!selectedDataViewTitle} + /> + } + isOpen={dataViewPopoverOpen} + closePopover={closeDataViewPopover} + ownFocus + anchorPosition="downLeft" + zIndex={8000} + display="block" + > +
+ + + + {i18n.translate('xpack.stackAlerts.components.ui.alertParams.dataViewButtonLabel', { + defaultMessage: 'Data view', + })} + + + + + + + + + +
+
+ ); +}; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx b/x-pack/plugins/stack_alerts/public/alert_types/components/filters_list.tsx similarity index 75% rename from x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/components/filters_list.tsx index e753fee71d44b..4abb6c1e1d9aa 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/read_only_filter_items.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/components/filters_list.tsx @@ -5,30 +5,30 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { injectI18n } from '@kbn/i18n-react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; import { getDisplayValueFromFilter } from '@kbn/data-plugin/public'; import { Filter } from '@kbn/data-plugin/common'; import { DataView } from '@kbn/data-views-plugin/public'; import { FilterItem } from '@kbn/unified-search-plugin/public'; +import { useTriggersAndActionsUiDeps } from '../es_query/util'; -const FilterItemComponent = injectI18n(FilterItem); - -interface ReadOnlyFilterItemsProps { +interface FiltersListProps { + dataView: DataView; filters: Filter[]; - indexPatterns: DataView[]; } const noOp = () => {}; +const FilterItemComponent = injectI18n(FilterItem); -export const ReadOnlyFilterItems = ({ filters, indexPatterns }: ReadOnlyFilterItemsProps) => { - const { uiSettings } = useKibana().services; +export const FiltersList = ({ filters, dataView }: FiltersListProps) => { + const { uiSettings } = useTriggersAndActionsUiDeps(); + const dataViews = useMemo(() => [dataView], [dataView]); const filterList = filters.map((filter, index) => { - const filterValue = getDisplayValueFromFilter(filter, indexPatterns); + const filterValue = getDisplayValueFromFilter(filter, dataViews); return ( diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/constants.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/constants.ts index bceb39ba08cf9..da85c878f3281 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/constants.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/constants.ts @@ -6,6 +6,7 @@ */ import { COMPARATORS } from '@kbn/triggers-actions-ui-plugin/public'; +import { ErrorKey } from './types'; export const DEFAULT_VALUES = { THRESHOLD_COMPARATOR: COMPARATORS.GREATER_THAN, @@ -19,3 +20,17 @@ export const DEFAULT_VALUES = { TIME_WINDOW_UNIT: 'm', THRESHOLD: [1000], }; + +export const EXPRESSION_ERRORS = { + index: new Array(), + size: new Array(), + timeField: new Array(), + threshold0: new Array(), + threshold1: new Array(), + esQuery: new Array(), + thresholdComparator: new Array(), + timeWindowSize: new Array(), + searchConfiguration: new Array(), +}; + +export const EXPRESSION_ERROR_KEYS = Object.keys(EXPRESSION_ERRORS) as ErrorKey[]; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx index df44a8923183c..d7b7e2888ef85 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx @@ -12,22 +12,11 @@ import 'brace/theme/github'; import { EuiSpacer, EuiCallOut } from '@elastic/eui'; import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; -import { EsQueryAlertParams } from '../types'; +import { ErrorKey, EsQueryAlertParams } from '../types'; import { SearchSourceExpression } from './search_source_expression'; import { EsQueryExpression } from './es_query_expression'; import { isSearchSourceAlert } from '../util'; - -const expressionFieldsWithValidation = [ - 'index', - 'size', - 'timeField', - 'threshold0', - 'threshold1', - 'timeWindowSize', - 'searchType', - 'esQuery', - 'searchConfiguration', -]; +import { EXPRESSION_ERROR_KEYS } from '../constants'; export const EsQueryAlertTypeExpression: React.FunctionComponent< RuleTypeParamsExpressionProps @@ -35,11 +24,11 @@ export const EsQueryAlertTypeExpression: React.FunctionComponent< const { ruleParams, errors } = props; const isSearchSource = isSearchSourceAlert(ruleParams); - const hasExpressionErrors = !!Object.keys(errors).find((errorKey) => { + const hasExpressionErrors = Object.keys(errors).some((errorKey) => { return ( - expressionFieldsWithValidation.includes(errorKey) && + EXPRESSION_ERROR_KEYS.includes(errorKey as ErrorKey) && errors[errorKey].length >= 1 && - ruleParams[errorKey as keyof EsQueryAlertParams] !== undefined + ruleParams[errorKey] !== undefined ); }); @@ -54,7 +43,6 @@ export const EsQueryAlertTypeExpression: React.FunctionComponent< <> {hasExpressionErrors && ( <> - diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx index 1d54609223aaf..a3ecf6c18af34 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useCallback, useEffect, useState } from 'react'; +import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'; import './search_source_expression.scss'; import { FormattedMessage } from '@kbn/i18n-react'; import { @@ -24,15 +24,16 @@ import { ThresholdExpression, ValueExpression, } from '@kbn/triggers-actions-ui-plugin/public'; -import { EsQueryAlertParams, SearchType } from '../types'; +import { DataViewOption, EsQueryAlertParams, SearchType } from '../types'; import { DEFAULT_VALUES } from '../constants'; -import { ReadOnlyFilterItems } from './read_only_filter_items'; +import { DataViewSelectPopover } from '../../components/data_view_select_popover'; +import { FiltersList } from '../../components/filters_list'; +import { useTriggersAndActionsUiDeps } from '../util'; export const SearchSourceExpression = ({ ruleParams, setRuleParams, setRuleProperty, - data, errors, }: RuleTypeParamsExpressionProps>) => { const { @@ -43,8 +44,10 @@ export const SearchSourceExpression = ({ timeWindowUnit, size, } = ruleParams; - const [usedSearchSource, setUsedSearchSource] = useState(); - const [paramsError, setParamsError] = useState(); + const { data } = useTriggersAndActionsUiDeps(); + + const searchSourceRef = useRef(); + const [paramsError, setParamsError] = useState(); const [currentAlertParams, setCurrentAlertParams] = useState< EsQueryAlertParams @@ -69,44 +72,59 @@ export const SearchSourceExpression = ({ [setRuleParams] ); - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(() => setRuleProperty('params', currentAlertParams), []); + useEffect(() => { + setRuleProperty('params', currentAlertParams); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); useEffect(() => { - async function initSearchSource() { - try { - const loadedSearchSource = await data.search.searchSource.create(searchConfiguration); - setUsedSearchSource(loadedSearchSource); - } catch (error) { - setParamsError(error); - } - } - if (searchConfiguration) { - initSearchSource(); - } - }, [data.search.searchSource, searchConfiguration]); + const initSearchSource = () => + data.search.searchSource + .create(searchConfiguration) + .then((searchSource) => (searchSourceRef.current = searchSource)) + .catch(setParamsError); + + initSearchSource(); + }, [data.search.searchSource, searchConfiguration, data.dataViews]); + + if (!searchSourceRef.current) { + return } />; + } if (paramsError) { return ( <> -

{paramsError.message}

+

+ {paramsError || ( + + )} +

); } - if (!usedSearchSource) { - return } />; - } - + const usedSearchSource = searchSourceRef.current; const dataView = usedSearchSource.getField('index')!; const query = usedSearchSource.getField('query')!; - const filters = (usedSearchSource.getField('filter') as Filter[]).filter( + const filters = ((usedSearchSource.getField('filter') as Filter[]) || []).filter( ({ meta }) => !meta.disabled ); - const dataViews = [dataView]; + + const onSelectDataView = ([selected]: DataViewOption[]) => { + // type casting is safe, since id was set to ComboBox value + data.dataViews.get(selected.value!).then((newDataView) => { + const newSearchSource = usedSearchSource.createCopy(); + newSearchSource.setField('index', newDataView); + setParam('searchConfiguration', newSearchSource); + }); + }; + return ( @@ -129,11 +147,9 @@ export const SearchSourceExpression = ({ iconType="iInCircle" /> - {query.query !== '' && ( } + value={} display="columns" /> )} diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts index bccf6ed4ced43..616964ba422a4 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts @@ -7,6 +7,8 @@ import { RuleTypeParams } from '@kbn/alerting-plugin/common'; import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; +import { EuiComboBoxOptionOption } from '@elastic/eui'; +import { EXPRESSION_ERRORS } from './constants'; export interface Comparator { text: string; @@ -19,7 +21,7 @@ export enum SearchType { searchSource = 'searchSource', } -export interface CommonAlertParams extends RuleTypeParams { +export interface CommonAlertParams extends RuleTypeParams { size: number; thresholdComparator?: string; threshold: number[]; @@ -28,8 +30,8 @@ export interface CommonAlertParams extends RuleTypeParams } export type EsQueryAlertParams = T extends SearchType.searchSource - ? CommonAlertParams & OnlySearchSourceAlertParams - : CommonAlertParams & OnlyEsQueryAlertParams; + ? CommonAlertParams & OnlySearchSourceAlertParams + : CommonAlertParams & OnlyEsQueryAlertParams; export interface OnlyEsQueryAlertParams { esQuery: string; @@ -40,3 +42,9 @@ export interface OnlySearchSourceAlertParams { searchType: 'searchSource'; searchConfiguration: SerializedSearchSourceFields; } + +export type DataViewOption = EuiComboBoxOptionOption; + +export type ExpressionErrors = typeof EXPRESSION_ERRORS; + +export type ErrorKey = keyof ExpressionErrors & unknown; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/util.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/util.ts index 5b70da7cb3e80..ab8a24588800e 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/util.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/util.ts @@ -5,10 +5,18 @@ * 2.0. */ +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; import { EsQueryAlertParams, SearchType } from './types'; +export interface TriggersAndActionsUiDeps { + data: DataPublicPluginStart; +} + export const isSearchSourceAlert = ( ruleParams: EsQueryAlertParams ): ruleParams is EsQueryAlertParams => { return ruleParams.searchType === 'searchSource'; }; + +export const useTriggersAndActionsUiDeps = () => useKibana().services; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts index 914dd6a4f5f9f..8a1135e75492f 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts @@ -5,25 +5,17 @@ * 2.0. */ +import { defaultsDeep } from 'lodash'; import { i18n } from '@kbn/i18n'; import { ValidationResult, builtInComparators } from '@kbn/triggers-actions-ui-plugin/public'; -import { EsQueryAlertParams } from './types'; +import { EsQueryAlertParams, ExpressionErrors } from './types'; import { isSearchSourceAlert } from './util'; +import { EXPRESSION_ERRORS } from './constants'; export const validateExpression = (alertParams: EsQueryAlertParams): ValidationResult => { const { size, threshold, timeWindowSize, thresholdComparator } = alertParams; const validationResult = { errors: {} }; - const errors = { - index: new Array(), - timeField: new Array(), - esQuery: new Array(), - size: new Array(), - threshold0: new Array(), - threshold1: new Array(), - thresholdComparator: new Array(), - timeWindowSize: new Array(), - searchConfiguration: new Array(), - }; + const errors: ExpressionErrors = defaultsDeep({}, EXPRESSION_ERRORS); validationResult.errors = errors; if (!threshold || threshold.length === 0 || threshold[0] === undefined) { errors.threshold0.push( From e02187d0290abe258646e2d105edba3e1aa61e33 Mon Sep 17 00:00:00 2001 From: Dzmitry Tamashevich Date: Tue, 19 Apr 2022 20:05:45 +0500 Subject: [PATCH 2/8] [Discover] add filters edit --- .../public/filter_bar/filter_add.tsx | 90 +++++++ .../public/filter_bar/filter_bar.tsx | 73 +----- .../public/filter_bar/filter_editor/index.tsx | 3 + .../public/filter_bar/filter_item.tsx | 23 +- .../public/filter_bar/filter_view/index.tsx | 67 +++--- .../public/filter_bar/index.tsx | 7 + src/plugins/unified_search/public/index.ts | 2 +- .../alert_types/components/filters_list.tsx | 20 +- .../es_query/expression/expression.tsx | 21 +- .../expression/search_source_expression.tsx | 221 +++--------------- .../search_source_expression_form.tsx | 200 ++++++++++++++++ 11 files changed, 421 insertions(+), 306 deletions(-) create mode 100644 src/plugins/unified_search/public/filter_bar/filter_add.tsx create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx diff --git a/src/plugins/unified_search/public/filter_bar/filter_add.tsx b/src/plugins/unified_search/public/filter_bar/filter_add.tsx new file mode 100644 index 0000000000000..faac634c2dff7 --- /dev/null +++ b/src/plugins/unified_search/public/filter_bar/filter_add.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiButtonEmpty, EuiFlexItem, EuiPopover } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { buildEmptyFilter, Filter } from '@kbn/es-query'; +import React, { useState } from 'react'; + +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import { IDataPluginServices } from '@kbn/data-plugin/public'; +import { DataView } from '@kbn/data-views-plugin/public'; +import { FILTER_EDITOR_WIDTH } from './filter_item'; +import { FilterEditor } from './filter_editor'; + +export interface Props { + dataViews: DataView[]; + onAdd: (filter: Filter) => void; + timeRangeForSuggestionsOverride?: boolean; +} + +export const FilterAdd = (props: Props) => { + const kibana = useKibana(); + const [isAddFilterPopoverOpen, setIsAddFilterPopoverOpen] = useState(false); + const { uiSettings } = kibana.services; + + const isPinned = uiSettings!.get(UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT); + const [indexPattern] = props.dataViews; + const index = indexPattern && indexPattern.id; + const newFilter = buildEmptyFilter(isPinned, index); + + const onAddFilter = (filter: Filter) => { + setIsAddFilterPopoverOpen(false); + props.onAdd(filter); + }; + + const onAddFilterClick = () => setIsAddFilterPopoverOpen(!isAddFilterPopoverOpen); + + const button = ( + + +{' '} + + + ); + + return ( + + setIsAddFilterPopoverOpen(false)} + anchorPosition="downLeft" + panelPaddingSize="none" + initialFocus=".filterEditor__hiddenItem" + ownFocus + repositionOnScroll + > + +
+ setIsAddFilterPopoverOpen(false)} + key={JSON.stringify(newFilter)} + timeRangeForSuggestionsOverride={props.timeRangeForSuggestionsOverride} + /> +
+
+
+
+ ); +}; + +// eslint-disable-next-line import/no-default-export +export default FilterAdd; diff --git a/src/plugins/unified_search/public/filter_bar/filter_bar.tsx b/src/plugins/unified_search/public/filter_bar/filter_bar.tsx index 43b511b2c9f7d..7d93a1d968f03 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_bar.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_bar.tsx @@ -6,10 +6,9 @@ * Side Public License, v 1. */ -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n-react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { InjectedIntl, injectI18n } from '@kbn/i18n-react'; import { - buildEmptyFilter, Filter, enableFilter, disableFilter, @@ -19,16 +18,15 @@ import { unpinFilter, } from '@kbn/es-query'; import classNames from 'classnames'; -import React, { useState, useRef } from 'react'; +import React, { useRef } from 'react'; import { METRIC_TYPE } from '@kbn/analytics'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { IDataPluginServices } from '@kbn/data-plugin/public'; import { DataView } from '@kbn/data-views-plugin/public'; import { FilterOptions } from './filter_options'; -import { FILTER_EDITOR_WIDTH, FilterItem } from './filter_item'; -import { FilterEditor } from './filter_editor'; +import { FilterItem } from './filter_item'; +import { FilterAdd } from './filter_add'; export interface Props { filters: Filter[]; @@ -42,7 +40,6 @@ export interface Props { const FilterBarUI = React.memo(function FilterBarUI(props: Props) { const groupRef = useRef(null); - const [isAddFilterPopoverOpen, setIsAddFilterPopoverOpen] = useState(false); const kibana = useKibana(); const { appName, usageCollection, uiSettings } = kibana.services; if (!uiSettings) return null; @@ -55,8 +52,6 @@ const FilterBarUI = React.memo(function FilterBarUI(props: Props) { } } - const onAddFilterClick = () => setIsAddFilterPopoverOpen(!isAddFilterPopoverOpen); - function renderItems() { return props.filters.map((filter, i) => ( @@ -74,60 +69,8 @@ const FilterBarUI = React.memo(function FilterBarUI(props: Props) { )); } - function renderAddFilter() { - const isPinned = uiSettings!.get(UI_SETTINGS.FILTERS_PINNED_BY_DEFAULT); - const [indexPattern] = props.indexPatterns; - const index = indexPattern && indexPattern.id; - const newFilter = buildEmptyFilter(isPinned, index); - - const button = ( - - +{' '} - - - ); - - return ( - - setIsAddFilterPopoverOpen(false)} - anchorPosition="downLeft" - panelPaddingSize="none" - initialFocus=".filterEditor__hiddenItem" - ownFocus - repositionOnScroll - > - -
- setIsAddFilterPopoverOpen(false)} - key={JSON.stringify(newFilter)} - timeRangeForSuggestionsOverride={props.timeRangeForSuggestionsOverride} - /> -
-
-
-
- ); - } - function onAdd(filter: Filter) { reportUiCounter?.(METRIC_TYPE.CLICK, `filter:added`); - setIsAddFilterPopoverOpen(false); const filters = [...props.filters, filter]; onFiltersUpdated(filters); @@ -221,7 +164,11 @@ const FilterBarUI = React.memo(function FilterBarUI(props: Props) { tabIndex={-1} > {renderItems()} - {renderAddFilter()} +
diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx index 08b0b761ff5ac..310bc27a4cde3 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx @@ -526,3 +526,6 @@ function OperatorComboBox(props: GenericComboBoxProps) { } export const FilterEditor = injectI18n(FilterEditorUI); + +// eslint-disable-next-line import/no-default-export +export default FilterEditor; diff --git a/src/plugins/unified_search/public/filter_bar/filter_item.tsx b/src/plugins/unified_search/public/filter_bar/filter_item.tsx index 2f48a4ea4c758..7eb12e576881e 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_item.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_item.tsx @@ -20,9 +20,9 @@ import React, { MouseEvent, useState, useEffect, HTMLAttributes } from 'react'; import { IUiSettingsClient } from '@kbn/core/public'; import { DataView } from '@kbn/data-views-plugin/public'; import { getIndexPatternFromFilter, getDisplayValueFromFilter } from '@kbn/data-plugin/public'; -import { FilterEditor } from './filter_editor'; import { FilterView } from './filter_view'; import { getIndexPatterns } from '../services'; +import { FilterEditor } from './filter_editor'; type PanelOptions = 'pinFilter' | 'editFilter' | 'negateFilter' | 'disableFilter' | 'deleteFilter'; @@ -37,7 +37,7 @@ export interface FilterItemProps { uiSettings: IUiSettingsClient; hiddenPanelOptions?: PanelOptions[]; timeRangeForSuggestionsOverride?: boolean; - readonly?: boolean; + editOnly?: boolean; } type FilterPopoverProps = HTMLAttributes & EuiPopoverProps; @@ -361,7 +361,6 @@ export function FilterItem(props: FilterItemProps) { iconOnClick: props.onRemove, onClick: handleBadgeClick, 'data-test-subj': getDataTestSubj(valueLabelConfig), - readonly: props.readonly, }; const popoverProps: FilterPopoverProps = { @@ -376,14 +375,18 @@ export function FilterItem(props: FilterItemProps) { panelPaddingSize: 'none', }; - if (props.readonly) { + if (props.editOnly) { return ( - - + + { + setIsPopoverOpen(false); + }} + timeRangeForSuggestionsOverride={props.timeRangeForSuggestionsOverride} + /> ); } diff --git a/src/plugins/unified_search/public/filter_bar/filter_view/index.tsx b/src/plugins/unified_search/public/filter_bar/filter_view/index.tsx index 974b94e95b0e0..fe711105d28c0 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_view/index.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_view/index.tsx @@ -13,12 +13,11 @@ import { Filter, isFilterPinned } from '@kbn/es-query'; import { FilterLabel } from '..'; import type { FilterLabelStatus } from '../filter_item'; -interface Props { +export interface Props { filter: Filter; valueLabel: string; filterLabelStatus: FilterLabelStatus; errorMessage?: string; - readonly?: boolean; hideAlias?: boolean; [propName: string]: any; } @@ -30,7 +29,6 @@ export const FilterView: FC = ({ valueLabel, errorMessage, filterLabelStatus, - readonly, hideAlias, ...rest }: Props) => { @@ -54,45 +52,29 @@ export const FilterView: FC = ({ })} ${title}`; } - const badgeProps: EuiBadgeProps = readonly - ? { - title, - color: 'hollow', - onClick, - onClickAriaLabel: i18n.translate( - 'unifiedSearch.filter.filterBar.filterItemReadOnlyBadgeAriaLabel', - { - defaultMessage: 'Filter entry', - } - ), - iconOnClick, + const badgeProps: EuiBadgeProps = { + title, + color: 'hollow', + iconType: 'cross', + iconSide: 'right', + closeButtonProps: { + // Removing tab focus on close button because the same option can be obtained through the context menu + // Also, we may want to add a `DEL` keyboard press functionality + tabIndex: -1, + }, + iconOnClick, + iconOnClickAriaLabel: i18n.translate( + 'unifiedSearch.filter.filterBar.filterItemBadgeIconAriaLabel', + { + defaultMessage: 'Delete {filter}', + values: { filter: innerText }, } - : { - title, - color: 'hollow', - iconType: 'cross', - iconSide: 'right', - closeButtonProps: { - // Removing tab focus on close button because the same option can be obtained through the context menu - // Also, we may want to add a `DEL` keyboard press functionality - tabIndex: -1, - }, - iconOnClick, - iconOnClickAriaLabel: i18n.translate( - 'unifiedSearch.filter.filterBar.filterItemBadgeIconAriaLabel', - { - defaultMessage: 'Delete {filter}', - values: { filter: innerText }, - } - ), - onClick, - onClickAriaLabel: i18n.translate( - 'unifiedSearch.filter.filterBar.filterItemBadgeAriaLabel', - { - defaultMessage: 'Filter actions', - } - ), - }; + ), + onClick, + onClickAriaLabel: i18n.translate('unifiedSearch.filter.filterBar.filterItemBadgeAriaLabel', { + defaultMessage: 'Filter actions', + }), + }; return ( @@ -107,3 +89,6 @@ export const FilterView: FC = ({ ); }; + +// eslint-disable-next-line import/no-default-export +export default FilterView; diff --git a/src/plugins/unified_search/public/filter_bar/index.tsx b/src/plugins/unified_search/public/filter_bar/index.tsx index 70a108f359790..eac0d81c6fd55 100644 --- a/src/plugins/unified_search/public/filter_bar/index.tsx +++ b/src/plugins/unified_search/public/filter_bar/index.tsx @@ -30,3 +30,10 @@ export const FilterItem = (props: React.ComponentProps) = ); + +const LazyFilterAdd = React.lazy(() => import('./filter_add')); +export const FilterAdd = (props: React.ComponentProps) => ( + }> + + +); diff --git a/src/plugins/unified_search/public/index.ts b/src/plugins/unified_search/public/index.ts index ff020ef1e8f94..ca9a311e5afad 100755 --- a/src/plugins/unified_search/public/index.ts +++ b/src/plugins/unified_search/public/index.ts @@ -15,7 +15,7 @@ export { QueryStringInput } from './query_string_input'; export type { StatefulSearchBarProps, SearchBarProps } from './search_bar'; export type { UnifiedSearchPublicPluginStart } from './types'; export { SearchBar } from './search_bar'; -export { FilterLabel, FilterItem } from './filter_bar'; +export { FilterLabel, FilterItem, FilterView, FilterAdd } from './filter_bar'; export type { ApplyGlobalFilterActionContext } from './actions'; export { ACTION_GLOBAL_APPLY_FILTER } from './actions'; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/components/filters_list.tsx b/x-pack/plugins/stack_alerts/public/alert_types/components/filters_list.tsx index 4abb6c1e1d9aa..db52f7a65854e 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/components/filters_list.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/components/filters_list.tsx @@ -18,15 +18,25 @@ import { useTriggersAndActionsUiDeps } from '../es_query/util'; interface FiltersListProps { dataView: DataView; filters: Filter[]; + onUpdateFilters: (filters: Filter[]) => void; } -const noOp = () => {}; const FilterItemComponent = injectI18n(FilterItem); -export const FiltersList = ({ filters, dataView }: FiltersListProps) => { +export const FiltersList = ({ filters, dataView, onUpdateFilters }: FiltersListProps) => { const { uiSettings } = useTriggersAndActionsUiDeps(); const dataViews = useMemo(() => [dataView], [dataView]); + const onUpdate = (newFilter: Filter, index: number) => { + const newFilters = [...filters]; + newFilters[index] = newFilter; + onUpdateFilters(newFilters); + }; + + const onRemove = (index: number) => { + onUpdateFilters(filters.filter((_, currentIndex) => currentIndex !== index)); + }; + const filterList = filters.map((filter, index) => { const filterValue = getDisplayValueFromFilter(filter, dataViews); return ( @@ -35,11 +45,11 @@ export const FiltersList = ({ filters, dataView }: FiltersListProps) => { key={`${filter.meta.key}${filterValue}`} id={`${index}`} filter={filter} - onUpdate={noOp} - onRemove={noOp} + onUpdate={(newFilter) => onUpdate(newFilter, index)} + onRemove={() => onRemove(index)} indexPatterns={dataViews} uiSettings={uiSettings!} - readonly + editOnly />
); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx index d7b7e2888ef85..96cccba1bc843 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx @@ -5,19 +5,34 @@ * 2.0. */ -import React from 'react'; +import React, { memo, PropsWithChildren } from 'react'; import { i18n } from '@kbn/i18n'; +import { isEqual } from 'lodash'; import 'brace/theme/github'; import { EuiSpacer, EuiCallOut } from '@elastic/eui'; import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; import { ErrorKey, EsQueryAlertParams } from '../types'; -import { SearchSourceExpression } from './search_source_expression'; +import { SearchSourceExpression, SearchSourceExpressionProps } from './search_source_expression'; import { EsQueryExpression } from './es_query_expression'; import { isSearchSourceAlert } from '../util'; import { EXPRESSION_ERROR_KEYS } from '../constants'; +function areSearchSourceExpressionPropsEqual( + prevProps: Readonly>, + nextProps: Readonly> +) { + const areErrorsEqual = isEqual(prevProps.errors, nextProps.errors); + const areRuleParamsEqual = isEqual(prevProps.ruleParams, nextProps.ruleParams); + return areErrorsEqual && areRuleParamsEqual; +} + +const SearchSourceExpressionMemoized = memo( + SearchSourceExpression, + areSearchSourceExpressionPropsEqual +); + export const EsQueryAlertTypeExpression: React.FunctionComponent< RuleTypeParamsExpressionProps > = (props) => { @@ -49,7 +64,7 @@ export const EsQueryAlertTypeExpression: React.FunctionComponent< )} {isSearchSource ? ( - + ) : ( )} diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx index a3ecf6c18af34..e56082a61a777 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx @@ -5,37 +5,26 @@ * 2.0. */ -import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import './search_source_expression.scss'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiSpacer, - EuiTitle, - EuiExpression, - EuiLoadingSpinner, - EuiEmptyPrompt, - EuiCallOut, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { Filter, ISearchSource } from '@kbn/data-plugin/common'; -import { - ForLastExpression, - RuleTypeParamsExpressionProps, - ThresholdExpression, - ValueExpression, -} from '@kbn/triggers-actions-ui-plugin/public'; -import { DataViewOption, EsQueryAlertParams, SearchType } from '../types'; -import { DEFAULT_VALUES } from '../constants'; -import { DataViewSelectPopover } from '../../components/data_view_select_popover'; -import { FiltersList } from '../../components/filters_list'; +import { EuiSpacer, EuiLoadingSpinner, EuiEmptyPrompt, EuiCallOut } from '@elastic/eui'; +import { ISearchSource } from '@kbn/data-plugin/common'; +import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { EsQueryAlertParams, SearchType } from '../types'; import { useTriggersAndActionsUiDeps } from '../util'; +import { SearchSourceExpressionForm } from './search_source_expression_form'; +import { DEFAULT_VALUES } from '../constants'; + +export type SearchSourceExpressionProps = RuleTypeParamsExpressionProps< + EsQueryAlertParams +>; export const SearchSourceExpression = ({ ruleParams, + errors, setRuleParams, setRuleProperty, - errors, -}: RuleTypeParamsExpressionProps>) => { +}: SearchSourceExpressionProps) => { const { searchConfiguration, thresholdComparator, @@ -49,188 +38,54 @@ export const SearchSourceExpression = ({ const searchSourceRef = useRef(); const [paramsError, setParamsError] = useState(); - const [currentAlertParams, setCurrentAlertParams] = useState< - EsQueryAlertParams - >({ - searchConfiguration, - searchType: SearchType.searchSource, - timeWindowSize: timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE, - timeWindowUnit: timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT, - threshold: threshold ?? DEFAULT_VALUES.THRESHOLD, - thresholdComparator: thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR, - size: size ?? DEFAULT_VALUES.SIZE, - }); - const setParam = useCallback( - (paramField: string, paramValue: unknown) => { - setCurrentAlertParams((currentParams) => ({ - ...currentParams, - [paramField]: paramValue, - })); - setRuleParams(paramField, paramValue); - }, + (paramField: string, paramValue: unknown) => setRuleParams(paramField, paramValue), [setRuleParams] ); - useEffect(() => { - setRuleProperty('params', currentAlertParams); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - useEffect(() => { const initSearchSource = () => data.search.searchSource .create(searchConfiguration) - .then((searchSource) => (searchSourceRef.current = searchSource)) + .then((searchSource) => { + searchSourceRef.current = searchSource; + setRuleProperty('params', { + searchConfiguration, + searchType: SearchType.searchSource, + timeWindowSize: timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE, + timeWindowUnit: timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT, + threshold: threshold ?? DEFAULT_VALUES.THRESHOLD, + thresholdComparator: thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR, + size: size ?? DEFAULT_VALUES.SIZE, + }); + }) .catch(setParamsError); initSearchSource(); - }, [data.search.searchSource, searchConfiguration, data.dataViews]); - - if (!searchSourceRef.current) { - return } />; - } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data.search.searchSource, data.dataViews, searchConfiguration]); if (paramsError) { return ( <> -

- {paramsError || ( - - )} -

+

{paramsError.message}

); } - const usedSearchSource = searchSourceRef.current; - const dataView = usedSearchSource.getField('index')!; - const query = usedSearchSource.getField('query')!; - const filters = ((usedSearchSource.getField('filter') as Filter[]) || []).filter( - ({ meta }) => !meta.disabled - ); - - const onSelectDataView = ([selected]: DataViewOption[]) => { - // type casting is safe, since id was set to ComboBox value - data.dataViews.get(selected.value!).then((newDataView) => { - const newSearchSource = usedSearchSource.createCopy(); - newSearchSource.setField('index', newDataView); - setParam('searchConfiguration', newSearchSource); - }); - }; + if (!searchSourceRef.current) { + return } />; + } return ( - - -
- -
-
- - - } - iconType="iInCircle" - /> - - - {query.query !== '' && ( - - )} - {filters.length > 0 && ( - } - display="columns" - /> - )} - - - -
- -
-
- - - setParam('threshold', selectedThresholds) - } - onChangeSelectedThresholdComparator={(selectedThresholdComparator) => - setParam('thresholdComparator', selectedThresholdComparator) - } - /> - - setParam('timeWindowSize', selectedWindowSize) - } - onChangeWindowUnit={(selectedWindowUnit: string) => - setParam('timeWindowUnit', selectedWindowUnit) - } - /> - - -
- -
-
- - { - setParam('size', updatedValue); - }} - /> - -
+ ); }; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx new file mode 100644 index 0000000000000..1fc8619cc4a56 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx @@ -0,0 +1,200 @@ +/* + * 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, { Fragment, useCallback, useMemo, useReducer } from 'react'; +import './search_source_expression.scss'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiSpacer, EuiTitle, EuiExpression, EuiCallOut } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Filter, DataView, Query, ISearchSource } from '@kbn/data-plugin/common'; +import { + ForLastExpression, + IErrorObject, + ThresholdExpression, + ValueExpression, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { FilterAdd } from '@kbn/unified-search-plugin/public'; +import { DataViewOption, EsQueryAlertParams, SearchType } from '../types'; +import { DEFAULT_VALUES } from '../constants'; +import { DataViewSelectPopover } from '../../components/data_view_select_popover'; +import { useTriggersAndActionsUiDeps } from '../util'; +import { FiltersList } from '../../components/filters_list'; + +interface SearchSourceParamsState { + index: DataView; + filter: Filter[]; + query: Query; +} + +interface SearchSourceAction { + type: 'index' | 'filter' | 'query'; + payload: DataView | Filter | Query; +} + +type SearchSourceStateReducer = ( + prevState: SearchSourceParamsState, + action: SearchSourceAction +) => SearchSourceParamsState; + +interface SearchSourceExpressionFormProps { + searchSource: ISearchSource; + ruleParams: EsQueryAlertParams; + errors: IErrorObject; + setParam: (paramField: string, paramValue: unknown) => void; +} + +export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProps) => { + const { searchSource, ruleParams, errors, setParam } = props; + const { thresholdComparator, threshold, timeWindowSize, timeWindowUnit, size } = ruleParams; + const { data } = useTriggersAndActionsUiDeps(); + + const [{ index: dataView, query, filter: filters }, dispatch] = + useReducer( + (currentState, action) => { + searchSource.setParent(undefined).setField(action.type, action.payload); + setParam('searchConfiguration', searchSource.getSerializedFields()); + return { ...currentState, [action.type]: action.payload }; + }, + { + index: searchSource.getField('index')!, + query: searchSource.getField('query')!, + filter: (searchSource.getField('filter') as Filter[]).filter(({ meta }) => !meta.disabled), + } + ); + const dataViews = useMemo(() => [dataView], [dataView]); + + const onSelectDataView = useCallback( + ([selected]: DataViewOption[]) => + // type casting is safe, since id was set to ComboBox value + data.dataViews + .get(selected.value!) + .then((newDataView) => dispatch({ type: 'index', payload: newDataView })), + [data.dataViews] + ); + + const onUpdateFilters = useCallback( + (newFilters) => { + data.query.filterManager.setFilters(newFilters, false); + dispatch({ type: 'filter', payload: newFilters }); + }, + [data.query.filterManager] + ); + + return ( + + +
+ +
+
+ + + } + iconType="iInCircle" + /> + + + {query.query !== '' && ( + + )} + + 0 ? ( + + ) : ( + {}} /> + ) + } + display="columns" + /> + + + +
+ +
+
+ + + setParam('threshold', selectedThresholds) + } + onChangeSelectedThresholdComparator={(selectedThresholdComparator) => + setParam('thresholdComparator', selectedThresholdComparator) + } + /> + + setParam('timeWindowSize', selectedWindowSize) + } + onChangeWindowUnit={(selectedWindowUnit: string) => + setParam('timeWindowUnit', selectedWindowUnit) + } + /> + + +
+ +
+
+ + { + setParam('size', updatedValue); + }} + /> + +
+ ); +}; From 48bb6f6dab9674e1bd84eaae271f0ad263419c1f Mon Sep 17 00:00:00 2001 From: Dzmitry Tamashevich Date: Tue, 19 Apr 2022 20:12:30 +0500 Subject: [PATCH 3/8] [Discover] clean up imports, add comment --- .../unified_search/public/filter_bar/filter_editor/index.tsx | 3 --- .../unified_search/public/filter_bar/filter_view/index.tsx | 3 --- src/plugins/unified_search/public/index.ts | 2 +- .../es_query/expression/search_source_expression_form.tsx | 1 + 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx index 310bc27a4cde3..08b0b761ff5ac 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx @@ -526,6 +526,3 @@ function OperatorComboBox(props: GenericComboBoxProps) { } export const FilterEditor = injectI18n(FilterEditorUI); - -// eslint-disable-next-line import/no-default-export -export default FilterEditor; diff --git a/src/plugins/unified_search/public/filter_bar/filter_view/index.tsx b/src/plugins/unified_search/public/filter_bar/filter_view/index.tsx index fe711105d28c0..1547ac4a5bc96 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_view/index.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_view/index.tsx @@ -89,6 +89,3 @@ export const FilterView: FC = ({ ); }; - -// eslint-disable-next-line import/no-default-export -export default FilterView; diff --git a/src/plugins/unified_search/public/index.ts b/src/plugins/unified_search/public/index.ts index ca9a311e5afad..e53494c22a4a1 100755 --- a/src/plugins/unified_search/public/index.ts +++ b/src/plugins/unified_search/public/index.ts @@ -15,7 +15,7 @@ export { QueryStringInput } from './query_string_input'; export type { StatefulSearchBarProps, SearchBarProps } from './search_bar'; export type { UnifiedSearchPublicPluginStart } from './types'; export { SearchBar } from './search_bar'; -export { FilterLabel, FilterItem, FilterView, FilterAdd } from './filter_bar'; +export { FilterLabel, FilterItem, FilterAdd } from './filter_bar'; export type { ApplyGlobalFilterActionContext } from './actions'; export { ACTION_GLOBAL_APPLY_FILTER } from './actions'; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx index 1fc8619cc4a56..66e49bc458a36 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx @@ -78,6 +78,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp const onUpdateFilters = useCallback( (newFilters) => { + // mutates new filter properties for migration purpose and further proper usage data.query.filterManager.setFilters(newFilters, false); dispatch({ type: 'filter', payload: newFilters }); }, From 485b2098ecf69ebbaa35741ef404ecc8b881b2b3 Mon Sep 17 00:00:00 2001 From: Dzmitry Tamashevich Date: Wed, 20 Apr 2022 15:25:21 +0500 Subject: [PATCH 4/8] [Discover] add query edit option --- .../public/filter_bar/filter_add.tsx | 22 +---- .../public/filter_bar/filter_bar.tsx | 20 ++++- .../components/data_view_select_popover.tsx | 2 +- .../alert_types/components/query_popover.tsx | 81 +++++++++++++++++++ .../expression/search_source_expression.scss | 4 + .../search_source_expression_form.tsx | 58 ++++++------- 6 files changed, 138 insertions(+), 49 deletions(-) create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/components/query_popover.tsx diff --git a/src/plugins/unified_search/public/filter_bar/filter_add.tsx b/src/plugins/unified_search/public/filter_bar/filter_add.tsx index faac634c2dff7..b490fd9df54fc 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_add.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_add.tsx @@ -6,8 +6,7 @@ * Side Public License, v 1. */ -import { EuiButtonEmpty, EuiFlexItem, EuiPopover } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiFlexItem, EuiPopover } from '@elastic/eui'; import { buildEmptyFilter, Filter } from '@kbn/es-query'; import React, { useState } from 'react'; @@ -20,6 +19,7 @@ import { FilterEditor } from './filter_editor'; export interface Props { dataViews: DataView[]; + renderButton: (onClick: () => void) => JSX.Element; onAdd: (filter: Filter) => void; timeRangeForSuggestionsOverride?: boolean; } @@ -41,31 +41,17 @@ export const FilterAdd = (props: Props) => { const onAddFilterClick = () => setIsAddFilterPopoverOpen(!isAddFilterPopoverOpen); - const button = ( - - +{' '} - - - ); - return ( setIsAddFilterPopoverOpen(false)} anchorPosition="downLeft" panelPaddingSize="none" initialFocus=".filterEditor__hiddenItem" + display="block" ownFocus repositionOnScroll > diff --git a/src/plugins/unified_search/public/filter_bar/filter_bar.tsx b/src/plugins/unified_search/public/filter_bar/filter_bar.tsx index 7d93a1d968f03..d15b9b9cd4d57 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_bar.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_bar.tsx @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { InjectedIntl, injectI18n } from '@kbn/i18n-react'; +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n-react'; import { Filter, enableFilter, @@ -134,6 +134,21 @@ const FilterBarUI = React.memo(function FilterBarUI(props: Props) { const classes = classNames('globalFilterBar', props.className); + const renderOpenPopoverButton = (onClick: () => void) => ( + + +{' '} + + + ); + return ( {renderItems()} - {i18n.translate('xpack.stackAlerts.components.ui.alertParams.dataViewButtonLabel', { + {i18n.translate('xpack.stackAlerts.components.ui.alertParams.dataViewPopoverTitle', { defaultMessage: 'Data view', })} diff --git a/x-pack/plugins/stack_alerts/public/alert_types/components/query_popover.tsx b/x-pack/plugins/stack_alerts/public/alert_types/components/query_popover.tsx new file mode 100644 index 0000000000000..51282894b95b5 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/components/query_popover.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { + EuiButtonIcon, + EuiExpression, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiPopover, + EuiPopoverTitle, +} from '@elastic/eui'; +import { Query, DataView } from '@kbn/data-plugin/common'; +import { i18n } from '@kbn/i18n'; +import { QueryStringInput } from '@kbn/unified-search-plugin/public'; + +interface QueryPopoverProps { + query: Query; + dataViews: DataView[]; + onChangeQuery: (query: Query) => void; +} + +export const QueryPopover = ({ query, dataViews, onChangeQuery }: QueryPopoverProps) => { + const [queryPopoverOpen, setQueryPopoverOpen] = useState(false); + + const openPopover = () => setQueryPopoverOpen(true); + const closeQueryPopover = () => setQueryPopoverOpen(false); + + return ( + + } + isOpen={queryPopoverOpen} + closePopover={closeQueryPopover} + ownFocus + anchorPosition="downLeft" + zIndex={4000} + display="block" + > +
+ + + + {i18n.translate('xpack.stackAlerts.components.ui.alertParams.queryPopoverTitle', { + defaultMessage: 'Query', + })} + + + + + + + + + +
+
+ ); +}; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.scss b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.scss index 418449eb792c1..4bf4a7f69d4e7 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.scss +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.scss @@ -1,3 +1,7 @@ +html { + overflow: hidden; +} + .searchSourceAlertFilters { .euiExpression__value { width: 80%; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx index 66e49bc458a36..a73eef803a766 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx @@ -18,11 +18,13 @@ import { ValueExpression, } from '@kbn/triggers-actions-ui-plugin/public'; import { FilterAdd } from '@kbn/unified-search-plugin/public'; +import { mapAndFlattenFilters } from '@kbn/data-plugin/public'; import { DataViewOption, EsQueryAlertParams, SearchType } from '../types'; import { DEFAULT_VALUES } from '../constants'; import { DataViewSelectPopover } from '../../components/data_view_select_popover'; import { useTriggersAndActionsUiDeps } from '../util'; import { FiltersList } from '../../components/filters_list'; +import { QueryPopover } from '../../components/query_popover'; interface SearchSourceParamsState { index: DataView; @@ -32,7 +34,7 @@ interface SearchSourceParamsState { interface SearchSourceAction { type: 'index' | 'filter' | 'query'; - payload: DataView | Filter | Query; + payload: DataView | Filter[] | Query; } type SearchSourceStateReducer = ( @@ -77,14 +79,29 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp ); const onUpdateFilters = useCallback( - (newFilters) => { - // mutates new filter properties for migration purpose and further proper usage - data.query.filterManager.setFilters(newFilters, false); - dispatch({ type: 'filter', payload: newFilters }); - }, - [data.query.filterManager] + (newFilters) => dispatch({ type: 'filter', payload: mapAndFlattenFilters(newFilters) }), + [] ); + const onAddFilter = (newFilter: Filter) => onUpdateFilters([...filters, newFilter]); + + const onChangeQuery = (newQuery: Query) => dispatch({ type: 'query', payload: newQuery }); + + const renderAddFilterPopoverButton = (onClick: () => void) => { + return ( + + } + display="columns" + onClick={onClick} + /> + ); + }; + return ( @@ -111,29 +128,14 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp selectedDataViewTitle={dataView.title} onSelectDataView={onSelectDataView} /> - {query.query !== '' && ( - - )} - 0 ? ( - - ) : ( - {}} /> - ) - } - display="columns" - /> + +
From 4f68aacf7f6797bd7cb26f2524696c25e994a054 Mon Sep 17 00:00:00 2001 From: Dzmitry Tamashevich Date: Mon, 25 Apr 2022 10:26:29 +0500 Subject: [PATCH 5/8] [Discover] change flyout, add saved query functionality --- src/plugins/discover/kibana.json | 1 + src/plugins/discover/public/build_services.ts | 3 + src/plugins/discover/public/plugin.tsx | 2 + .../public/filter_bar/filter_add.tsx | 22 ++++- .../public/filter_bar/filter_bar.tsx | 20 +---- .../public/search_bar/search_bar.tsx | 2 +- .../alert_types/components/filters_list.tsx | 7 +- .../alert_types/components/query_popover.tsx | 81 ------------------- .../es_query/expression/expression.tsx | 6 +- .../search_source_expression_form.tsx | 81 +++++++++++-------- .../public/alert_types/es_query/util.ts | 2 + 11 files changed, 81 insertions(+), 146 deletions(-) delete mode 100644 x-pack/plugins/stack_alerts/public/alert_types/components/query_popover.tsx diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json index f9802782f8e48..5d5db1677525f 100644 --- a/src/plugins/discover/kibana.json +++ b/src/plugins/discover/kibana.json @@ -12,6 +12,7 @@ "urlForwarding", "navigation", "uiActions", + "unifiedSearch", "savedObjects", "dataViewFieldEditor", "dataViewEditor" diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index d77bc5dde2660..3953b9b36408d 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -42,6 +42,7 @@ import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; import type { SpacesApi } from '@kbn/spaces-plugin/public'; import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; +import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { DiscoverAppLocator } from './locator'; import { getHistory } from './kibana_services'; import { DiscoverStartPlugins } from './plugin'; @@ -81,6 +82,7 @@ export interface DiscoverServices { spaces?: SpacesApi; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; locator: DiscoverAppLocator; + unifiedSearch: UnifiedSearchPublicPluginStart; } export const buildServices = memoize(function ( @@ -125,5 +127,6 @@ export const buildServices = memoize(function ( dataViewEditor: plugins.dataViewEditor, triggersActionsUi: plugins.triggersActionsUi, locator, + unifiedSearch: plugins.unifiedSearch, }; }); diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 8330751cf13f0..cf165eb95706c 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -35,6 +35,7 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; +import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { DocViewInput, DocViewInputFn } from './services/doc_views/doc_views_types'; import { DocViewsRegistry } from './services/doc_views/doc_views_registry'; import { @@ -172,6 +173,7 @@ export interface DiscoverStartPlugins { dataViewFieldEditor: IndexPatternFieldEditorStart; spaces?: SpacesPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; + unifiedSearch: UnifiedSearchPublicPluginStart; } /** diff --git a/src/plugins/unified_search/public/filter_bar/filter_add.tsx b/src/plugins/unified_search/public/filter_bar/filter_add.tsx index b490fd9df54fc..056f9e0ba9830 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_add.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_add.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { EuiFlexItem, EuiPopover } from '@elastic/eui'; +import { EuiButtonEmpty, EuiFlexItem, EuiPopover } from '@elastic/eui'; import { buildEmptyFilter, Filter } from '@kbn/es-query'; import React, { useState } from 'react'; @@ -14,12 +14,12 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { IDataPluginServices } from '@kbn/data-plugin/public'; import { DataView } from '@kbn/data-views-plugin/public'; +import { FormattedMessage } from '@kbn/i18n-react'; import { FILTER_EDITOR_WIDTH } from './filter_item'; import { FilterEditor } from './filter_editor'; export interface Props { dataViews: DataView[]; - renderButton: (onClick: () => void) => JSX.Element; onAdd: (filter: Filter) => void; timeRangeForSuggestionsOverride?: boolean; } @@ -41,17 +41,31 @@ export const FilterAdd = (props: Props) => { const onAddFilterClick = () => setIsAddFilterPopoverOpen(!isAddFilterPopoverOpen); + const openPopoverButton = ( + + +{' '} + + + ); + return ( setIsAddFilterPopoverOpen(false)} anchorPosition="downLeft" panelPaddingSize="none" initialFocus=".filterEditor__hiddenItem" - display="block" ownFocus repositionOnScroll > diff --git a/src/plugins/unified_search/public/filter_bar/filter_bar.tsx b/src/plugins/unified_search/public/filter_bar/filter_bar.tsx index d15b9b9cd4d57..7d93a1d968f03 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_bar.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_bar.tsx @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n-react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { InjectedIntl, injectI18n } from '@kbn/i18n-react'; import { Filter, enableFilter, @@ -134,21 +134,6 @@ const FilterBarUI = React.memo(function FilterBarUI(props: Props) { const classes = classNames('globalFilterBar', props.className); - const renderOpenPopoverButton = (onClick: () => void) => ( - - +{' '} - - - ); - return ( {renderItems()} { isLoading={this.props.isLoading} fillSubmitButton={this.props.fillSubmitButton || false} prepend={ - this.props.showFilterBar && this.state.query + this.state.query ? this.renderSavedQueryManagement( this.props.onClearSavedQuery, this.props.showSaveQuery, diff --git a/x-pack/plugins/stack_alerts/public/alert_types/components/filters_list.tsx b/x-pack/plugins/stack_alerts/public/alert_types/components/filters_list.tsx index db52f7a65854e..68cbf9273b9b2 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/components/filters_list.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/components/filters_list.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { injectI18n } from '@kbn/i18n-react'; @@ -16,16 +16,15 @@ import { FilterItem } from '@kbn/unified-search-plugin/public'; import { useTriggersAndActionsUiDeps } from '../es_query/util'; interface FiltersListProps { - dataView: DataView; filters: Filter[]; + dataViews: DataView[]; onUpdateFilters: (filters: Filter[]) => void; } const FilterItemComponent = injectI18n(FilterItem); -export const FiltersList = ({ filters, dataView, onUpdateFilters }: FiltersListProps) => { +export const FiltersList = ({ filters, dataViews, onUpdateFilters }: FiltersListProps) => { const { uiSettings } = useTriggersAndActionsUiDeps(); - const dataViews = useMemo(() => [dataView], [dataView]); const onUpdate = (newFilter: Filter, index: number) => { const newFilters = [...filters]; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/components/query_popover.tsx b/x-pack/plugins/stack_alerts/public/alert_types/components/query_popover.tsx deleted file mode 100644 index 51282894b95b5..0000000000000 --- a/x-pack/plugins/stack_alerts/public/alert_types/components/query_popover.tsx +++ /dev/null @@ -1,81 +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, { useState } from 'react'; -import { - EuiButtonIcon, - EuiExpression, - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - EuiPopover, - EuiPopoverTitle, -} from '@elastic/eui'; -import { Query, DataView } from '@kbn/data-plugin/common'; -import { i18n } from '@kbn/i18n'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; - -interface QueryPopoverProps { - query: Query; - dataViews: DataView[]; - onChangeQuery: (query: Query) => void; -} - -export const QueryPopover = ({ query, dataViews, onChangeQuery }: QueryPopoverProps) => { - const [queryPopoverOpen, setQueryPopoverOpen] = useState(false); - - const openPopover = () => setQueryPopoverOpen(true); - const closeQueryPopover = () => setQueryPopoverOpen(false); - - return ( - - } - isOpen={queryPopoverOpen} - closePopover={closeQueryPopover} - ownFocus - anchorPosition="downLeft" - zIndex={4000} - display="block" - > -
- - - - {i18n.translate('xpack.stackAlerts.components.ui.alertParams.queryPopoverTitle', { - defaultMessage: 'Query', - })} - - - - - - - - - -
-
- ); -}; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx index 96cccba1bc843..3b5e978b999c8 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx @@ -7,7 +7,7 @@ import React, { memo, PropsWithChildren } from 'react'; import { i18n } from '@kbn/i18n'; -import { isEqual } from 'lodash'; +import deepEqual from 'fast-deep-equal'; import 'brace/theme/github'; @@ -23,8 +23,8 @@ function areSearchSourceExpressionPropsEqual( prevProps: Readonly>, nextProps: Readonly> ) { - const areErrorsEqual = isEqual(prevProps.errors, nextProps.errors); - const areRuleParamsEqual = isEqual(prevProps.ruleParams, nextProps.ruleParams); + const areErrorsEqual = deepEqual(prevProps.errors, nextProps.errors); + const areRuleParamsEqual = deepEqual(prevProps.ruleParams, nextProps.ruleParams); return areErrorsEqual && areRuleParamsEqual; } diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx index a73eef803a766..1d22246d19d6c 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx @@ -5,10 +5,10 @@ * 2.0. */ -import React, { Fragment, useCallback, useMemo, useReducer } from 'react'; +import React, { Fragment, useCallback, useMemo, useReducer, useState } from 'react'; import './search_source_expression.scss'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiSpacer, EuiTitle, EuiExpression, EuiCallOut } from '@elastic/eui'; +import { EuiSpacer, EuiTitle, EuiExpression } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Filter, DataView, Query, ISearchSource } from '@kbn/data-plugin/common'; import { @@ -18,13 +18,12 @@ import { ValueExpression, } from '@kbn/triggers-actions-ui-plugin/public'; import { FilterAdd } from '@kbn/unified-search-plugin/public'; -import { mapAndFlattenFilters } from '@kbn/data-plugin/public'; +import { mapAndFlattenFilters, SavedQuery } from '@kbn/data-plugin/public'; import { DataViewOption, EsQueryAlertParams, SearchType } from '../types'; import { DEFAULT_VALUES } from '../constants'; import { DataViewSelectPopover } from '../../components/data_view_select_popover'; import { useTriggersAndActionsUiDeps } from '../util'; import { FiltersList } from '../../components/filters_list'; -import { QueryPopover } from '../../components/query_popover'; interface SearchSourceParamsState { index: DataView; @@ -50,10 +49,13 @@ interface SearchSourceExpressionFormProps { } export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProps) => { + const { data, unifiedSearch } = useTriggersAndActionsUiDeps(); const { searchSource, ruleParams, errors, setParam } = props; const { thresholdComparator, threshold, timeWindowSize, timeWindowUnit, size } = ruleParams; - const { data } = useTriggersAndActionsUiDeps(); + const [savedQuery, setSavedQuery] = useState(); + const SearchBar = unifiedSearch.ui.SearchBar; + // part of alert rule params const [{ index: dataView, query, filter: filters }, dispatch] = useReducer( (currentState, action) => { @@ -85,21 +87,14 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp const onAddFilter = (newFilter: Filter) => onUpdateFilters([...filters, newFilter]); - const onChangeQuery = (newQuery: Query) => dispatch({ type: 'query', payload: newQuery }); + const onChangeQuery = ({ query: newQuery }: { query?: Query }) => + dispatch({ type: 'query', payload: newQuery || { ...query, query: '' } }); - const renderAddFilterPopoverButton = (onClick: () => void) => { - return ( - - } - display="columns" - onClick={onClick} - /> - ); + const onSavedQueryUpdated = (newSavedQuery: SavedQuery) => setSavedQuery(newSavedQuery); + + const onClearSavedQuery = () => { + setSavedQuery(undefined); + dispatch({ type: 'query', payload: { ...query, query: '' } }); }; return ( @@ -112,30 +107,46 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp />
- - - } - iconType="iInCircle" - /> + - + - } /> + + } + value={ + + } + display="columns" + /> +
diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/util.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/util.ts index ab8a24588800e..0d35ddf846240 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/util.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/util.ts @@ -7,10 +7,12 @@ import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { EsQueryAlertParams, SearchType } from './types'; export interface TriggersAndActionsUiDeps { data: DataPublicPluginStart; + unifiedSearch: UnifiedSearchPublicPluginStart; } export const isSearchSourceAlert = ( From 5beaab5b86633eed5269ad0c80dff955ed0a79a1 Mon Sep 17 00:00:00 2001 From: Dzmitry Tamashevich Date: Mon, 25 Apr 2022 12:27:25 +0500 Subject: [PATCH 6/8] [Discover] improve saved query usage --- .../components/top_nav/get_top_nav_links.tsx | 1 + .../top_nav/open_alerts_popover.tsx | 14 +++++++++++-- .../expression/search_source_expression.tsx | 9 +++++++++ .../search_source_expression_form.tsx | 20 +++++++++++++++---- .../public/alert_types/es_query/types.ts | 1 + 5 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx index f2ac0d2bfa060..ee35e10b6631a 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_links.tsx @@ -74,6 +74,7 @@ export const getTopNavLinks = ({ anchorElement, searchSource: savedSearch.searchSource, services, + savedQueryId: state.appStateContainer.getState().savedQuery, }); }, testId: 'discoverAlertsButton', diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx index d414919e567f9..71a0ef3df1b8c 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx @@ -26,9 +26,15 @@ interface AlertsPopoverProps { onClose: () => void; anchorElement: HTMLElement; searchSource: ISearchSource; + savedQueryId?: string; } -export function AlertsPopover({ searchSource, anchorElement, onClose }: AlertsPopoverProps) { +export function AlertsPopover({ + searchSource, + anchorElement, + savedQueryId, + onClose, +}: AlertsPopoverProps) { const dataView = searchSource.getField('index')!; const services = useDiscoverServices(); const { triggersActionsUi } = services; @@ -49,8 +55,9 @@ export function AlertsPopover({ searchSource, anchorElement, onClose }: AlertsPo return { searchType: 'searchSource', searchConfiguration: nextSearchSource.getSerializedFields(), + savedQueryId, }; - }, [searchSource, services]); + }, [savedQueryId, searchSource, services]); const SearchThresholdAlertFlyout = useMemo(() => { if (!alertFlyoutVisible) { @@ -156,11 +163,13 @@ export function openAlertsPopover({ anchorElement, searchSource, services, + savedQueryId, }: { I18nContext: I18nStart['Context']; anchorElement: HTMLElement; searchSource: ISearchSource; services: DiscoverServices; + savedQueryId?: string; }) { if (isOpen) { closeAlertsPopover(); @@ -177,6 +186,7 @@ export function openAlertsPopover({ onClose={closeAlertsPopover} anchorElement={anchorElement} searchSource={searchSource} + savedQueryId={savedQueryId} /> diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx index e56082a61a777..f7cf7aed06959 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx @@ -10,6 +10,7 @@ import './search_source_expression.scss'; import { EuiSpacer, EuiLoadingSpinner, EuiEmptyPrompt, EuiCallOut } from '@elastic/eui'; import { ISearchSource } from '@kbn/data-plugin/common'; import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { SavedQuery } from '@kbn/data-plugin/public'; import { EsQueryAlertParams, SearchType } from '../types'; import { useTriggersAndActionsUiDeps } from '../util'; import { SearchSourceExpressionForm } from './search_source_expression_form'; @@ -36,6 +37,7 @@ export const SearchSourceExpression = ({ const { data } = useTriggersAndActionsUiDeps(); const searchSourceRef = useRef(); + const [savedQuery, setSavedQuery] = useState(); const [paramsError, setParamsError] = useState(); const setParam = useCallback( @@ -65,6 +67,12 @@ export const SearchSourceExpression = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [data.search.searchSource, data.dataViews, searchConfiguration]); + useEffect(() => { + if (ruleParams.savedQueryId) { + data.query.savedQueries.getSavedQuery(ruleParams.savedQueryId).then(setSavedQuery); + } + }, [data.query.savedQueries, ruleParams.savedQueryId]); + if (paramsError) { return ( <> @@ -85,6 +93,7 @@ export const SearchSourceExpression = ({ searchSource={searchSourceRef.current} errors={errors} ruleParams={ruleParams} + initialSavedQuery={savedQuery} setParam={setParam} /> ); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx index 1d22246d19d6c..1182b7ff20388 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useCallback, useMemo, useReducer, useState } from 'react'; +import React, { Fragment, useCallback, useEffect, useMemo, useReducer, useState } from 'react'; import './search_source_expression.scss'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiSpacer, EuiTitle, EuiExpression } from '@elastic/eui'; @@ -45,17 +45,20 @@ interface SearchSourceExpressionFormProps { searchSource: ISearchSource; ruleParams: EsQueryAlertParams; errors: IErrorObject; + initialSavedQuery?: SavedQuery; setParam: (paramField: string, paramValue: unknown) => void; } export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProps) => { const { data, unifiedSearch } = useTriggersAndActionsUiDeps(); - const { searchSource, ruleParams, errors, setParam } = props; + const { searchSource, ruleParams, errors, initialSavedQuery, setParam } = props; const { thresholdComparator, threshold, timeWindowSize, timeWindowUnit, size } = ruleParams; const [savedQuery, setSavedQuery] = useState(); const SearchBar = unifiedSearch.ui.SearchBar; - // part of alert rule params + useEffect(() => setSavedQuery(initialSavedQuery), [initialSavedQuery]); + + // part of alert search source rule params const [{ index: dataView, query, filter: filters }, dispatch] = useReducer( (currentState, action) => { @@ -90,7 +93,13 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp const onChangeQuery = ({ query: newQuery }: { query?: Query }) => dispatch({ type: 'query', payload: newQuery || { ...query, query: '' } }); - const onSavedQueryUpdated = (newSavedQuery: SavedQuery) => setSavedQuery(newSavedQuery); + const onSavedQueryUpdated = (newSavedQuery: SavedQuery) => { + setSavedQuery(newSavedQuery); + const newFilters = newSavedQuery.attributes.filters; + if (newFilters) { + dispatch({ type: 'filter', payload: newFilters }); + } + }; const onClearSavedQuery = () => { setSavedQuery(undefined); @@ -126,6 +135,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp indexPatterns={dataViews} onQueryChange={onChangeQuery} savedQueryId={savedQuery?.id} + filters={filters} onClearSavedQuery={onClearSavedQuery} onSavedQueryUpdated={onSavedQueryUpdated} showSaveQuery={true} @@ -135,6 +145,8 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp showDatePicker={false} showAutoRefreshOnly={false} customSubmitButton={<>} + dateRangeFrom={undefined} + dateRangeTo={undefined} /> ; From dc0a760d0feeeed01342b0ffc451f25873590710 Mon Sep 17 00:00:00 2001 From: Dzmitry Tamashevich Date: Mon, 25 Apr 2022 12:57:54 +0500 Subject: [PATCH 7/8] [Discover] add suggestions size --- .../public/query_string_input/query_bar_top_row.tsx | 3 +++ src/plugins/unified_search/public/search_bar/search_bar.tsx | 4 ++++ .../es_query/expression/search_source_expression.scss | 4 ---- .../es_query/expression/search_source_expression_form.tsx | 1 + 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx index de1fa659aa133..96e24cf3c35cb 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -37,6 +37,7 @@ import { UI_SETTINGS } from '@kbn/data-plugin/common'; import QueryStringInputUI from './query_string_input'; import { NoDataPopover } from './no_data_popover'; import { shallowEqual } from '../utils'; +import type { SuggestionsListSize } from '../typeahead/suggestions_component'; const SuperDatePicker = React.memo( EuiSuperDatePicker as any @@ -78,6 +79,7 @@ export interface QueryBarTopRowProps { showAutoRefreshOnly?: boolean; timeHistory?: TimeHistoryContract; timeRangeForSuggestionsOverride?: boolean; + suggestionsSize?: SuggestionsListSize; } const SharingMetaFields = React.memo(function SharingMetaFields({ @@ -363,6 +365,7 @@ export const QueryBarTopRow = React.memo( nonKqlMode={props.nonKqlMode} nonKqlModeHelpText={props.nonKqlModeHelpText} timeRangeForSuggestionsOverride={props.timeRangeForSuggestionsOverride} + size={props.suggestionsSize} /> ); diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx index b0af40b72ef78..ab983f8064fb7 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -27,6 +27,7 @@ import { FilterBar } from '../filter_bar'; import QueryBarTopRow from '../query_string_input/query_bar_top_row'; import { SavedQueryMeta, SaveQueryForm } from '../saved_query_form'; import { SavedQueryManagementComponent } from '../saved_query_management'; +import { SuggestionsListSize } from '../typeahead/suggestions_component'; export interface SearchBarInjectedDeps { kibana: KibanaReactContextValue; @@ -82,6 +83,8 @@ export interface SearchBarOwnProps { displayStyle?: 'inPage' | 'detached'; // super update button background fill control fillSubmitButton?: boolean; + // defines size of suggestions query popover + suggestionsSize?: SuggestionsListSize; } export type SearchBarProps = SearchBarOwnProps & SearchBarInjectedDeps; @@ -394,6 +397,7 @@ class SearchBarUI extends Component { nonKqlMode={this.props.nonKqlMode} nonKqlModeHelpText={this.props.nonKqlModeHelpText} timeRangeForSuggestionsOverride={timeRangeForSuggestionsOverride} + suggestionsSize={this.props.suggestionsSize} /> ); } diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.scss b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.scss index 4bf4a7f69d4e7..418449eb792c1 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.scss +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.scss @@ -1,7 +1,3 @@ -html { - overflow: hidden; -} - .searchSourceAlertFilters { .euiExpression__value { width: 80%; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx index 1182b7ff20388..8d1f8fec410d8 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx @@ -136,6 +136,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp onQueryChange={onChangeQuery} savedQueryId={savedQuery?.id} filters={filters} + suggestionsSize="s" onClearSavedQuery={onClearSavedQuery} onSavedQueryUpdated={onSavedQueryUpdated} showSaveQuery={true} From 754fdde1955838019f5ec21c1e42e0e06e27e35e Mon Sep 17 00:00:00 2001 From: Dzmitry Tamashevich Date: Mon, 25 Apr 2022 12:59:46 +0500 Subject: [PATCH 8/8] [Discover] add type import --- src/plugins/unified_search/public/search_bar/search_bar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx index ab983f8064fb7..4a20b2d8c0e1a 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -27,7 +27,7 @@ import { FilterBar } from '../filter_bar'; import QueryBarTopRow from '../query_string_input/query_bar_top_row'; import { SavedQueryMeta, SaveQueryForm } from '../saved_query_form'; import { SavedQueryManagementComponent } from '../saved_query_management'; -import { SuggestionsListSize } from '../typeahead/suggestions_component'; +import type { SuggestionsListSize } from '../typeahead/suggestions_component'; export interface SearchBarInjectedDeps { kibana: KibanaReactContextValue;