Skip to content

Commit

Permalink
Merge pull request elastic#7 from abdulloh76/save-filter-expression
Browse files Browse the repository at this point in the history
Save filter expression and Create filter set with given label name from add filter modal
  • Loading branch information
stratoula authored Feb 2, 2022
2 parents 664e49f + 338550b commit b36fd9e
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 10 deletions.
37 changes: 36 additions & 1 deletion src/plugins/data/public/ui/filter_bar/filter_bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import { SavedQueriesItem } from './saved_queries_item';
import { FilterExpressionItem } from './filter_expression_item';

import { UI_SETTINGS } from '../../../common';
import { SavedQueryMeta } from '../saved_query_form';
import { SavedQueryService } from '../..';

interface Props {
filters: Filter[];
Expand All @@ -46,6 +48,9 @@ interface Props {
selectedSavedQueries?: SavedQuery[];
removeSelectedSavedQuery: (savedQuery: SavedQuery) => void;
onMultipleFiltersUpdated?: (filters: Filter[]) => void;
savedQueryService: SavedQueryService;
onFilterSave: (savedQueryMeta: SavedQueryMeta, saveAsNew?: boolean) => Promise<void>;
onFilterBadgeSave: (groupId: number, alias: string) => void;
}

const FilterBarUI = React.memo(function FilterBarUI(props: Props) {
Expand Down Expand Up @@ -98,7 +103,13 @@ const FilterBarUI = React.memo(function FilterBarUI(props: Props) {
}

function renderMultipleFilters() {
const firstDepthGroupedFilters = groupBy(props.multipleFilters, 'groupId');
const groupedByAlias = groupBy(props.multipleFilters, 'meta.alias');
const filtersWithoutLabel = groupedByAlias.null || groupedByAlias.undefined;
const labels = Object.keys(groupedByAlias).filter(
(key) => key !== 'null' && key !== 'undefined'
);

const firstDepthGroupedFilters = groupBy(filtersWithoutLabel, 'groupId');
const GroupBadge: JSX.Element[] = [];
for (const [groupId, groupedFilters] of Object.entries(firstDepthGroupedFilters)) {
const badge = (
Expand All @@ -110,10 +121,34 @@ const FilterBarUI = React.memo(function FilterBarUI(props: Props) {
onRemove={onRemoveFilterGroup}
onUpdate={onUpdateFilterGroup}
filtersGroupsCount={Object.entries(firstDepthGroupedFilters).length}
savedQueryService={props.savedQueryService}
onFilterSave={props.onFilterSave}
onFilterBadgeSave={props.onFilterBadgeSave}
/>
);
GroupBadge.push(badge);
}

let groupId: string;
labels.map((label) => {
// we should have same groupIds on our labeled filters group
groupId = (groupedByAlias[label][0] as any).groupId;
groupedByAlias[label].forEach((filter) => ((filter as any).groupId = groupId));
const labelBadge = (
<FilterExpressionItem
groupId={groupId}
groupedFilters={groupedByAlias[label]}
indexPatterns={props?.indexPatterns}
onClick={() => {}}
onRemove={onRemoveFilterGroup}
onUpdate={onUpdateFilterGroup}
filtersGroupsCount={1}
customLabel={label}
/>
);
GroupBadge.push(labelBadge);
});

return GroupBadge;
}

Expand Down
71 changes: 67 additions & 4 deletions src/plugins/data/public/ui/filter_bar/filter_expression_item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
EuiTextColor,
EuiPopover,
EuiContextMenu,
EuiIcon,
EuiContextMenuPanelDescriptor,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { groupBy } from 'lodash';
Expand All @@ -22,6 +24,8 @@ import { FILTERS } from '../../../common';
import { existsOperator, isOneOfOperator } from './filter_editor/lib/filter_operators';
import { IIndexPattern } from '../..';
import { getDisplayValueFromFilter, getIndexPatternFromFilter } from '../../query';
import { SavedQueryMeta, SaveQueryForm } from '../saved_query_form';
import { SavedQueryService } from '../..';

const FILTER_ITEM_OK = '';
const FILTER_ITEM_WARNING = 'warn';
Expand All @@ -46,6 +50,10 @@ interface Props {
groupId: string;
filtersGroupsCount: number;
onUpdate?: (filters: Filter[], groupId: string, toggleNegate: boolean) => void;
savedQueryService?: SavedQueryService;
onFilterSave?: (savedQueryMeta: SavedQueryMeta, saveAsNew?: boolean) => Promise<void>;
customLabel?: string;
onFilterBadgeSave?: (groupId: number, alias: string) => void;
}

export const FilterExpressionItem: FC<Props> = ({
Expand All @@ -56,8 +64,17 @@ export const FilterExpressionItem: FC<Props> = ({
groupId,
filtersGroupsCount,
onUpdate,
savedQueryService,
onFilterSave,
customLabel,
onFilterBadgeSave,
}: Props) => {
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
const filters: Filter[] = groupedFilters.map((filter: Filter) => ({
$state: filter.$state,
meta: filter.meta,
query: filter.query,
}));
function handleBadgeClick() {
// if (e.shiftKey) {
// onToggleDisabled();
Expand Down Expand Up @@ -149,7 +166,7 @@ export const FilterExpressionItem: FC<Props> = ({
},
];

return [
const panels: EuiContextMenuPanelDescriptor[] = [
{
id: 0,
items: mainPanelItems,
Expand All @@ -172,6 +189,45 @@ export const FilterExpressionItem: FC<Props> = ({
// ),
// },
];

if (!customLabel && savedQueryService && onFilterSave && onFilterBadgeSave) {
const saveAsFilterPanelItem = {
name: i18n.translate('data.filter.filterBar.saveAsFilterButtonLabel', {
defaultMessage: `Save as filter`,
}),
icon: 'save',
panel: 2,
'data-test-subj': 'saveAsFilter',
};

const saveAsFilterPanelContent = {
id: 2,
title: i18n.translate('data.filter.filterBar.saveAsFilterButtonLabel', {
defaultMessage: `Save as filter`,
}),
content: (
<div style={{ padding: 16 }}>
<SaveQueryForm
savedQueryService={savedQueryService}
onSave={(savedQueryMeta) => {
onFilterSave(savedQueryMeta, true);
setIsPopoverOpen(false);
}}
onClose={() => setIsPopoverOpen(false)}
showTimeFilterOption={false}
showFilterOption={false}
filters={filters}
onFilterBadgeSave={(alias: string) => onFilterBadgeSave(Number(groupId), alias)}
/>
</div>
),
};

mainPanelItems.splice(mainPanelItems.length - 1, 0, saveAsFilterPanelItem);
panels.push(saveAsFilterPanelContent);
}

return panels;
}
/**
* Checks if filter field exists in any of the index patterns provided,
Expand Down Expand Up @@ -406,9 +462,16 @@ export const FilterExpressionItem: FC<Props> = ({
onClick={handleBadgeClick}
>
<div ref={ref}>
{filterExpression.map((expression) => {
return <>{expression}</>;
})}
{customLabel ? (
<>
<EuiIcon type="save" />
{customLabel}
</>
) : (
filterExpression.map((expression) => {
return <>{expression}</>;
})
)}
</div>
</EuiBadge>
</EuiFlexItem>
Expand Down
19 changes: 19 additions & 0 deletions src/plugins/data/public/ui/query_string_input/add_filter_modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { GenericComboBox } from '../filter_bar/filter_editor/generic_combo_box';
import { PhraseValueInput } from '../filter_bar/filter_editor/phrase_value_input';
import { PhrasesValuesInput } from '../filter_bar/filter_editor/phrases_values_input';
import { RangeValueInput } from '../filter_bar/filter_editor/range_value_input';
import { SavedQueryMeta } from '../saved_query_form';

import { IIndexPattern, IFieldType } from '../..';

Expand Down Expand Up @@ -95,6 +96,7 @@ export function AddFilterModal({
timeRangeForSuggestionsOverride,
savedQueryManagement,
initialAddFilterMode,
saveFilters,
}: {
onSubmit: (filters: Filter[]) => void;
onMultipleFiltersSubmit: (filters: FilterGroup[], buildFilters: Filter[]) => void;
Expand All @@ -105,6 +107,7 @@ export function AddFilterModal({
timeRangeForSuggestionsOverride?: boolean;
savedQueryManagement?: JSX.Element;
initialAddFilterMode?: string;
saveFilters: (savedQueryMeta: SavedQueryMeta) => void;
}) {
const [selectedIndexPattern, setSelectedIndexPattern] = useState(
getIndexPatternFromFilter(filter, indexPatterns)
Expand Down Expand Up @@ -370,6 +373,13 @@ export function AddFilterModal({
$state.store
);
onSubmit([builtCustomFilter]);
saveFilters({
title: customLabel,
description: '',
shouldIncludeFilters: false,
shouldIncludeTimefilter: false,
filters: [builtCustomFilter],
});
} else if (addFilterMode === 'quick_form' && selectedIndexPattern) {
const builtFilters = localFilters.map((localFilter) => {
if (localFilter.field && localFilter.operator) {
Expand All @@ -391,6 +401,15 @@ export function AddFilterModal({
) as Filter[];
// onSubmit(finalFilters);
onMultipleFiltersSubmit(localFilters, finalFilters);
if (alias) {
saveFilters({
title: customLabel,
description: '',
shouldIncludeFilters: false,
shouldIncludeTimefilter: false,
filters: finalFilters,
});
}
}
} else if (addFilterMode === 'saved_filters') {
applySavedQueries();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { NoDataPopover } from './no_data_popover';
import { shallowEqual } from '../../utils/shallow_equal';
import { SavedQuery } from '../..';
import { AddFilterModal, FilterGroup } from './add_filter_modal';
import { SavedQueryMeta } from '../saved_query_form';

const SuperDatePicker = React.memo(
EuiSuperDatePicker as any
Expand All @@ -49,6 +50,7 @@ const QueryStringInput = withKibana(QueryStringInputUI);
// @internal
export interface QueryBarTopRowProps {
filters: Filter[];
multipleFilters: Filter[];
onFiltersUpdated?: (filters: Filter[]) => void;
onMultipleFiltersUpdated?: (filters: Filter[]) => void;
applySelectedSavedQueries?: () => void;
Expand Down Expand Up @@ -85,6 +87,7 @@ export interface QueryBarTopRowProps {
toggleAddFilterModal?: (value: boolean) => void;
isAddFilterModalOpen?: boolean;
addFilterMode?: string;
onNewFiltersSave: (savedQueryMeta: SavedQueryMeta) => void;
}

const SharingMetaFields = React.memo(function SharingMetaFields({
Expand Down Expand Up @@ -394,18 +397,30 @@ export const QueryBarTopRow = React.memo(
}

function onAddMultipleFiltersANDOR(selectedFilters: FilterGroup[], buildFilters: Filter[]) {
const lastFilter: any = props.multipleFilters[props.multipleFilters.length - 1];
const mappedFilters = mapAndFlattenFilters(buildFilters);
if (lastFilter !== undefined) lastFilter.relationship = 'AND';
const mergedFilters = mappedFilters.map((filter, idx) => {
let groupId = selectedFilters[idx].groupId;
let id = selectedFilters[idx].id;
// groupId starts from 1; id starts from 0

if (lastFilter !== undefined) {
groupId += lastFilter.groupId;
id += lastFilter.id + 1;
}

return {
...filter,
groupId: selectedFilters[idx].groupId,
id: selectedFilters[idx].id,
groupId,
id,
relationship: selectedFilters[idx].relationship,
subGroupId: selectedFilters[idx].subGroupId,
};
});
props.toggleAddFilterModal?.(false);
props?.onMultipleFiltersUpdated?.(mergedFilters);
props?.onMultipleFiltersUpdated?.([...props.multipleFilters, ...mergedFilters]);
// props?.onMultipleFiltersUpdated?.(mergedFilters);

const filters = [...props.filters, ...buildFilters];
props?.onFiltersUpdated?.(filters);
Expand Down Expand Up @@ -448,6 +463,7 @@ export const QueryBarTopRow = React.memo(
timeRangeForSuggestionsOverride={props.timeRangeForSuggestionsOverride}
savedQueryManagement={props.savedQueryManagement}
initialAddFilterMode={props.addFilterMode}
saveFilters={props.onNewFiltersSave}
/>
)}
</EuiFlexItem>
Expand Down
10 changes: 10 additions & 0 deletions src/plugins/data/public/ui/saved_query_form/save_query_form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import React, { useEffect, useState, useCallback } from 'react';
import { EuiButton, EuiForm, EuiFormRow, EuiFieldText, EuiSwitch, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { Filter } from '@kbn/es-query';
import { sortBy, isEqual } from 'lodash';
import { SavedQuery, SavedQueryService } from '../..';

Expand All @@ -19,6 +20,8 @@ interface Props {
onClose: () => void;
showFilterOption: boolean | undefined;
showTimeFilterOption: boolean | undefined;
filters?: Filter[];
onFilterBadgeSave?: (alias: string) => void;
}

export interface SavedQueryMeta {
Expand All @@ -27,6 +30,7 @@ export interface SavedQueryMeta {
description: string;
shouldIncludeFilters: boolean;
shouldIncludeTimefilter: boolean;
filters?: Filter[];
}

export function SaveQueryForm({
Expand All @@ -36,6 +40,8 @@ export function SaveQueryForm({
onClose,
showFilterOption = true,
showTimeFilterOption = true,
filters,
onFilterBadgeSave,
}: Props) {
const [title, setTitle] = useState(savedQuery?.attributes.title ?? '');
const [enabledSaveButton, setEnabledSaveButton] = useState(Boolean(savedQuery));
Expand Down Expand Up @@ -111,7 +117,9 @@ export function SaveQueryForm({
description,
shouldIncludeFilters,
shouldIncludeTimefilter,
filters,
});
if (onFilterBadgeSave) onFilterBadgeSave(title);
}
}, [
validate,
Expand All @@ -121,6 +129,8 @@ export function SaveQueryForm({
description,
shouldIncludeFilters,
shouldIncludeTimefilter,
filters,
onFilterBadgeSave,
]);

const onInputChange = useCallback((event) => {
Expand Down
Loading

0 comments on commit b36fd9e

Please sign in to comment.