[Filters] Remove any from Filter['meta']['params'] in kbn/es-query (#148681)

## Summary
This pr removes the the type from
https://github.com/elastic/kibana/blob/main/packages/kbn-es-query/src/filters/build_filters/types.ts#L67
to make interoperability of the security solution DataProvider type
https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/data_provider.ts#L35
easier with the Filter type used throughout Kibana. With the addition of
nested Filters, these two types are functionally equivalent. No logic
changes were intended in this pr, so if something looks wrong, please
let me know.

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Kevin Qualters 2023-02-01 13:15:51 -05:00 committed by GitHub
parent 37ef9a274d
commit beeec00a17
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 353 additions and 189 deletions

View file

@ -6,25 +6,26 @@
* Side Public License, v 1.
*/
import type { PhrasesFilter } from './phrases_filter';
import type { PhraseFilter } from './phrase_filter';
import type { RangeFilter } from './range_filter';
import { Filter, FILTERS } from './types';
import { isPhrasesFilter, PhrasesFilter } from './phrases_filter';
import { isPhraseFilter } from './phrase_filter';
import { isRangeFilter } from './range_filter';
import { Filter } from './types';
/**
* @internal used only by the filter bar to create filter pills.
*/
export function getFilterParams(filter: Filter) {
switch (filter.meta.type) {
case FILTERS.PHRASE:
return (filter as PhraseFilter).meta.params.query;
case FILTERS.PHRASES:
return (filter as PhrasesFilter).meta.params;
case FILTERS.RANGE:
const { gte, gt, lte, lt } = (filter as RangeFilter).meta.params;
return {
from: gte ?? gt,
to: lt ?? lte,
};
export function getFilterParams(filter: Filter): Filter['meta']['params'] {
if (isPhraseFilter(filter)) {
return filter.meta.params?.query;
} else if (isPhrasesFilter(filter)) {
return (filter as PhrasesFilter).meta.params;
} else if (isRangeFilter(filter) && filter.meta.params) {
const { gte, gt, lte, lt } = filter.meta.params;
return {
from: gte ?? gt,
to: lt ?? lte,
};
} else {
return filter.meta.params;
}
}

View file

@ -7,10 +7,11 @@
*/
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { SerializableRecord } from '@kbn/utility-types';
import { has } from 'lodash';
import type { Filter, FilterMeta } from './types';
export interface MatchAllFilterMeta extends FilterMeta {
export interface MatchAllFilterMeta extends FilterMeta, SerializableRecord {
field: string;
formattedValue: string;
}

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { SerializableRecord } from '@kbn/utility-types';
import { get, has, isPlainObject } from 'lodash';
import type { Filter, FilterMeta } from './types';
import type { DataViewFieldBase, DataViewBase } from '../../es_query';
@ -14,10 +15,12 @@ import { hasRangeKeys } from './range_filter';
export type PhraseFilterValue = string | number | boolean;
export interface PhraseFilterMetaParams extends SerializableRecord {
query: PhraseFilterValue; // The unformatted value
}
export type PhraseFilterMeta = FilterMeta & {
params?: {
query: PhraseFilterValue; // The unformatted value
};
params?: PhraseFilterMetaParams;
field?: string;
index?: string;
};

View file

@ -7,7 +7,9 @@
*/
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { map, reduce, mapValues, has, get, keys, pickBy } from 'lodash';
import type { Filter, FilterMeta } from './types';
import type { SerializableRecord } from '@kbn/utility-types';
import type { Filter, FilterMeta, FilterMetaParams } from './types';
import { FILTERS } from './types';
import type { DataViewBase, DataViewFieldBase } from '../../es_query';
const OPERANDS_IN_RANGE = 2;
@ -37,7 +39,7 @@ const dateComparators = {
* It is similar, but not identical to estypes.QueryDslRangeQuery
* @public
*/
export interface RangeFilterParams {
export interface RangeFilterParams extends SerializableRecord {
from?: number | string;
to?: number | string;
gt?: number | string;
@ -53,9 +55,10 @@ export const hasRangeKeys = (params: RangeFilterParams) =>
);
export type RangeFilterMeta = FilterMeta & {
params: RangeFilterParams;
params?: RangeFilterParams;
field?: string;
formattedValue?: string;
type: 'range';
};
export type ScriptedRangeFilter = Filter & {
@ -90,7 +93,16 @@ export type RangeFilter = Filter & {
*
* @public
*/
export const isRangeFilter = (filter?: Filter): filter is RangeFilter => has(filter, 'query.range');
export function isRangeFilter(filter?: Filter): filter is RangeFilter {
if (filter?.meta?.type) return filter.meta.type === FILTERS.RANGE;
return has(filter, 'query.range');
}
export function isRangeFilterParams(
params: FilterMetaParams | undefined
): params is RangeFilterParams {
return typeof params === 'object' && get(params, 'type', '') === 'range';
}
/**
*
@ -140,7 +152,9 @@ export const buildRangeFilter = (
const totalInfinite = ['gt', 'lt'].reduce((acc, op) => {
const key = op in params ? op : `${op}e`;
const isInfinite = Math.abs(get(params, key)) === Infinity;
const value = get(params, key);
const numericValue = typeof value === 'number' ? value : 0;
const isInfinite = Math.abs(numericValue) === Infinity;
if (isInfinite) {
acc++;
@ -152,7 +166,7 @@ export const buildRangeFilter = (
return acc;
}, 0);
const meta: RangeFilterMeta = {
const meta = {
index: indexPattern?.id,
params: {},
field: field.name,

View file

@ -7,10 +7,10 @@
*/
import { ExistsFilter } from './exists_filter';
import { PhrasesFilter } from './phrases_filter';
import { PhraseFilter } from './phrase_filter';
import { RangeFilter } from './range_filter';
import { MatchAllFilter } from './match_all_filter';
import { PhrasesFilter, PhrasesFilterMeta } from './phrases_filter';
import { PhraseFilter, PhraseFilterMeta, PhraseFilterMetaParams } from './phrase_filter';
import { RangeFilter, RangeFilterMeta, RangeFilterParams } from './range_filter';
import { MatchAllFilter, MatchAllFilterMeta } from './match_all_filter';
/**
* A common type for filters supported by this package
@ -50,6 +50,22 @@ export enum FilterStateStore {
GLOBAL_STATE = 'globalState',
}
export type FilterMetaParams =
| Filter
| Filter[]
| RangeFilterMeta
| RangeFilterParams
| PhraseFilterMeta
| PhraseFilterMetaParams
| PhrasesFilterMeta
| MatchAllFilterMeta
| string
| string[]
| boolean
| boolean[]
| number
| number[];
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type FilterMeta = {
alias?: string | null;
@ -64,7 +80,7 @@ export type FilterMeta = {
isMultiIndex?: boolean;
type?: string;
key?: string;
params?: any;
params?: FilterMetaParams;
value?: string;
};

View file

@ -6,16 +6,14 @@
* Side Public License, v 1.
*/
import { identity, pickBy } from 'lodash';
import type { Filter, FilterMeta, RangeFilterParams } from '..';
type FilterOperator = Pick<FilterMeta, 'type' | 'negate'>;
import { get } from 'lodash';
import { isRangeFilterParams } from '../build_filters/range_filter';
import type { Filter, FilterMeta } from '..';
export const updateFilter = (
filter: Filter,
field?: string,
operator?: FilterOperator,
operator?: FilterMeta,
params?: Filter['meta']['params'],
fieldType?: string
) => {
@ -52,7 +50,7 @@ function updateField(filter: Filter, field?: string) {
};
}
function updateWithExistsOperator(filter: Filter, operator?: FilterOperator) {
function updateWithExistsOperator(filter: Filter, operator?: FilterMeta) {
return {
...filter,
meta: {
@ -68,76 +66,114 @@ function updateWithExistsOperator(filter: Filter, operator?: FilterOperator) {
function updateWithIsOperator(
filter: Filter,
operator?: FilterOperator,
operator?: FilterMeta,
params?: Filter['meta']['params'],
fieldType?: string
) {
const safeParams = fieldType === 'number' && !params ? 0 : params;
return {
...filter,
meta: {
...filter.meta,
negate: operator?.negate,
type: operator?.type,
params: { ...filter.meta.params, query: params },
value: undefined,
},
query: { match_phrase: { [filter.meta.key!]: safeParams ?? '' } },
};
if (typeof filter.meta.params === 'object') {
return {
...filter,
meta: {
...filter.meta,
negate: operator?.negate,
type: operator?.type,
params: { ...filter.meta.params, query: params },
value: undefined,
},
query: { match_phrase: { [filter.meta.key!]: safeParams ?? '' } },
};
} else {
return {
...filter,
meta: {
...filter.meta,
negate: operator?.negate,
type: operator?.type,
params: { query: params },
value: undefined,
},
query: { match_phrase: { [filter.meta.key!]: safeParams ?? '' } },
};
}
}
function updateWithRangeOperator(
filter: Filter,
operator: FilterOperator,
rawParams: RangeFilterParams,
operator: FilterMeta,
rawParams: Filter['meta']['params'] | undefined,
field: string
) {
const rangeParams = {
...pickBy(rawParams, identity),
};
const params = {
gte: rangeParams?.from,
lt: rangeParams?.to,
};
const updatedFilter = {
...filter,
meta: {
...filter.meta,
negate: operator?.negate,
type: operator?.type,
params,
},
query: {
range: {
[field]: params,
if (isRangeFilterParams(rawParams)) {
const { from, to } = rawParams;
const params = {
gte: from,
lt: to,
};
const updatedFilter = {
...filter,
meta: {
...filter.meta,
negate: operator?.negate,
type: operator?.type,
params,
},
},
};
query: {
range: {
[field]: params,
},
},
};
return updatedFilter;
return updatedFilter;
} else {
const from = get(rawParams, 'from', undefined);
const to = get(rawParams, 'to', undefined);
const params = {
gte: from,
lt: to,
};
const updatedFilter = {
...filter,
meta: {
...filter.meta,
negate: operator?.negate,
type: operator?.type,
params,
},
query: {
range: {
[field]: params,
},
},
};
return updatedFilter;
}
}
function updateWithIsOneOfOperator(
filter: Filter,
operator?: FilterOperator,
params?: Array<Filter['meta']['params']>
operator?: FilterMeta,
params?: Filter['meta']['params']
) {
return {
...filter,
meta: {
...filter.meta,
negate: operator?.negate,
type: operator?.type,
params,
},
query: {
bool: {
minimum_should_match: 1,
...filter!.query?.should,
should: params?.map((param) => ({ match_phrase: { [filter.meta.key!]: param } })),
if (Array.isArray(params)) {
return {
...filter,
meta: {
...filter.meta,
negate: operator?.negate,
type: operator?.type,
params,
},
},
};
query: {
bool: {
minimum_should_match: 1,
...filter!.query?.should,
should: params?.map((param) => ({ match_phrase: { [filter.meta.key!]: param } })),
},
},
};
} else {
return filter;
}
}

View file

@ -6,9 +6,9 @@
* Side Public License, v 1.
*/
import { PhraseFilter, FilterStateStore } from '..';
import { FilterStateStore } from '..';
export const phraseFilter: PhraseFilter = {
export const phraseFilter = {
meta: {
negate: false,
index: 'logstash-*',
@ -24,5 +24,7 @@ export const phraseFilter: PhraseFilter = {
$state: {
store: FilterStateStore.APP_STATE,
},
query: {},
query: {
match_phrase: {},
},
};

View file

@ -6,9 +6,9 @@
* Side Public License, v 1.
*/
import { RangeFilter, FilterStateStore } from '..';
import { FilterStateStore } from '..';
export const rangeFilter: RangeFilter = {
export const rangeFilter = {
meta: {
index: 'logstash-*',
negate: false,

View file

@ -114,7 +114,7 @@ describe('SearchSource', () => {
});
describe('#getActiveIndexFilter()', () => {
test('pase _index from query', () => {
test('pass _index from query', () => {
searchSource.setField('query', {
language: 'kuery',
query: `_INDEX : fakebeat and _index : "mybeat-*"`,
@ -122,7 +122,7 @@ describe('SearchSource', () => {
expect(searchSource.getActiveIndexFilter()).toMatchObject(['mybeat-*']);
});
test('pase _index from filter', () => {
test('pass _index from filter', () => {
const filter = [
{
query: { match_phrase: { _index: 'auditbeat-*' } },
@ -163,7 +163,7 @@ describe('SearchSource', () => {
expect(searchSource.getActiveIndexFilter()).toMatchObject(['auditbeat-*']);
});
test('pase _index from query and filter with negate equals to true', () => {
test('pass _index from query and filter with negate equals to true', () => {
const filter = [
{
query: {
@ -189,7 +189,7 @@ describe('SearchSource', () => {
expect(searchSource.getActiveIndexFilter()).toMatchObject([]);
});
test('pase _index from query and filter with negate equals to true and disabled equals to true', () => {
test('pass _index from query and filter with negate equals to true and disabled equals to true', () => {
const filter = [
{
query: {

View file

@ -59,7 +59,17 @@
*/
import { setWith } from '@kbn/safer-lodash-set';
import { difference, isEqual, isFunction, isObject, keyBy, pick, uniqueId, uniqWith } from 'lodash';
import {
difference,
isEqual,
isFunction,
isObject,
keyBy,
pick,
uniqueId,
uniqWith,
concat,
} from 'lodash';
import {
catchError,
finalize,
@ -72,7 +82,7 @@ import {
} from 'rxjs/operators';
import { defer, EMPTY, from, lastValueFrom, Observable } from 'rxjs';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { buildEsQuery, Filter, isOfQueryType } from '@kbn/es-query';
import { buildEsQuery, Filter, isOfQueryType, isPhraseFilter } from '@kbn/es-query';
import { fieldWildcardFilter } from '@kbn/kibana-utils-plugin/common';
import { getHighlightRequest } from '@kbn/field-formats-plugin/common';
import type { DataView } from '@kbn/data-views-plugin/common';
@ -81,7 +91,6 @@ import {
buildExpression,
buildExpressionFunction,
} from '@kbn/expressions-plugin/common';
import _ from 'lodash';
import { normalizeSortRequest } from './normalize_sort_request';
import { AggConfigSerialized, DataViewField, SerializedSearchSourceFields } from '../..';
@ -251,7 +260,6 @@ export class SearchSource {
getActiveIndexFilter() {
const { filter: originalFilters, query } = this.getFields();
let filters: Filter[] = [];
if (originalFilters) {
filters = this.getFilters(originalFilters);
@ -270,16 +278,16 @@ export class SearchSource {
return acc.concat(this.parseActiveIndexPatternFromQueryString(currStr));
}, []) ?? [];
const activeIndexPattern: string[] = filters?.reduce<string[]>((acc, f) => {
if (f.meta.key === '_index' && f.meta.disabled === false) {
if (f.meta.negate === false) {
return _.concat(acc, f.meta.params.query ?? f.meta.params);
} else {
if (Array.isArray(f.meta.params)) {
return _.difference(acc, f.meta.params);
const activeIndexPattern = filters?.reduce((acc, f) => {
if (isPhraseFilter(f)) {
if (f.meta.key === '_index' && f.meta.disabled === false) {
if (f.meta.negate === false) {
return concat(acc, f.meta.params?.query ?? f.meta.params);
} else {
return _.difference(acc, [f.meta.params.query]);
return difference(acc, [f.meta.params?.query]);
}
} else {
return acc;
}
} else {
return acc;

View file

@ -25,7 +25,7 @@ export function getPhraseDisplayValue(
formatter?: FieldFormat,
fieldType?: string
): string {
const value = filter.meta.value ?? filter.meta.params.query;
const value = filter.meta.value ?? filter.meta.params?.query;
const updatedValue = fieldType === 'number' && !value ? 0 : value;
if (formatter?.convert) {
return formatter.convert(updatedValue);

View file

@ -11,7 +11,7 @@ import { FieldFormat } from '@kbn/field-formats-plugin/common';
export function getPhrasesDisplayValue(filter: PhrasesFilter, formatter?: FieldFormat) {
return filter.meta.params
.map((v: string) => {
.map((v) => {
return formatter?.convert(v) ?? v;
})
.join(', ');

View file

@ -21,8 +21,8 @@ export function getRangeDisplayValue(
{ meta: { params } }: RangeFilter | ScriptedRangeFilter,
formatter?: FieldFormat
) {
const left = params.gte ?? params.gt ?? -Infinity;
const right = params.lte ?? params.lt ?? Infinity;
const left = params?.gte ?? params?.gt ?? -Infinity;
const right = params?.lte ?? params?.lt ?? Infinity;
if (!formatter) return `${left} to ${right}`;
const convert = formatter.getConverterFor('text');
return `${convert(left)} to ${convert(right)}`;

View file

@ -9,6 +9,7 @@
import React from 'react';
import { getDisplayValueFromFilter, getFieldDisplayValueFromFilter } from '@kbn/data-plugin/public';
import type { Filter, DataViewBase } from '@kbn/es-query';
import { isCombinedFilter } from '@kbn/es-query';
import { EuiTextColor } from '@elastic/eui';
import { FilterBadgeGroup } from './filter_badge_group';
import { FilterContent } from './filter_content';
@ -54,9 +55,10 @@ export function FilterExpressionBadge({
dataViews,
filterLabelStatus,
}: FilterBadgeExpressionProps) {
const isCombined = isCombinedFilter(filter);
const conditionalOperationType = getBooleanRelationType(filter);
return conditionalOperationType ? (
return conditionalOperationType && isCombined ? (
<>
{shouldShowBrackets && (
<span>

View file

@ -32,6 +32,7 @@ import {
buildEmptyFilter,
filterToQueryDsl,
getFilterParams,
isCombinedFilter,
} from '@kbn/es-query';
import { merge } from 'lodash';
import React, { Component } from 'react';
@ -390,9 +391,10 @@ class FilterEditorComponent extends Component<FilterEditorProps, State> {
};
private isUnknownFilterType() {
const { type, params } = this.props.filter.meta;
if (params && type === 'combined') {
return this.hasCombinedFilterCustomType(params);
const { type } = this.props.filter.meta;
if (isCombinedFilter(this.props.filter)) {
const { params } = this.props.filter.meta;
return params && this.hasCombinedFilterCustomType(params);
}
return !!type && !['phrase', 'phrases', 'range', 'exists', 'combined'].includes(type);
}

View file

@ -17,7 +17,7 @@ import {
useEuiPaddingSize,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { type Filter, BooleanRelation } from '@kbn/es-query';
import { BooleanRelation, type Filter } from '@kbn/es-query';
import { cx } from '@emotion/css';
import type { Path } from './types';
import { getBooleanRelationType } from '../utils';
@ -85,9 +85,17 @@ export const FilterGroup = ({
const orDisabled = hideOr || (isDepthReached && booleanRelation === BooleanRelation.AND);
const andDisabled = isDepthReached && booleanRelation === BooleanRelation.OR;
const removeDisabled = pathInArray.length <= 1 && filters.length === 1;
const removeDisabled =
pathInArray.length <= 1 &&
filters !== undefined &&
Array.isArray(filters) &&
filters.length === 1;
const shouldNormalizeFirstLevel =
!path && filters.length === 1 && getBooleanRelationType(filters[0]);
!path &&
filters &&
Array.isArray(filters) &&
filters.length === 1 &&
getBooleanRelationType(filters[0]);
if (shouldNormalizeFirstLevel) {
reverseBackground = true;
@ -96,38 +104,41 @@ export const FilterGroup = ({
const color = reverseBackground ? 'plain' : 'subdued';
const renderedFilters = filters.map((filter, index, arrayRef) => {
const showDelimiter = booleanRelation && index + 1 < arrayRef.length;
return (
<EuiFlexGroup
key={index}
direction="column"
gutterSize={shouldNormalizeFirstLevel ? 'none' : 'xs'}
responsive={false}
>
<EuiFlexItem>
<FilterItem
filter={filter}
draggable={arrayRef.length !== 1}
path={`${path}${path ? '.' : ''}${index}`}
reverseBackground={reverseBackground}
disableOr={orDisabled}
disableAnd={andDisabled}
disableRemove={removeDisabled}
color={color}
index={index}
renderedLevel={renderedLevel}
/>
</EuiFlexItem>
{showDelimiter && (
const renderedFilters =
filters &&
Array.isArray(filters) &&
filters.map((filter, index, arrayRef) => {
const showDelimiter = booleanRelation && index + 1 < arrayRef.length;
return (
<EuiFlexGroup
key={index}
direction="column"
gutterSize={shouldNormalizeFirstLevel ? 'none' : 'xs'}
responsive={false}
>
<EuiFlexItem>
<Delimiter color={color} booleanRelation={booleanRelation} />
<FilterItem
filter={filter}
draggable={arrayRef.length !== 1}
path={`${path}${path ? '.' : ''}${index}`}
reverseBackground={reverseBackground}
disableOr={orDisabled}
disableAnd={andDisabled}
disableRemove={removeDisabled}
color={color}
index={index}
renderedLevel={renderedLevel}
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
);
});
{showDelimiter && (
<EuiFlexItem>
<Delimiter color={color} booleanRelation={booleanRelation} />
</EuiFlexItem>
)}
</EuiFlexGroup>
);
});
return shouldNormalizeFirstLevel ? (
<>{renderedFilters}</>

View file

@ -33,6 +33,7 @@ import { FilterGroup } from '../filter_group';
import type { Path } from '../types';
import { getFieldFromFilter, getOperatorFromFilter } from '../../filter_bar/filter_editor';
import { Operator } from '../../filter_bar/filter_editor';
import { getGroupedFilters } from '../utils/filters_builder';
import {
cursorAddCss,
cursorOrCss,
@ -101,7 +102,7 @@ export function FilterItem({
const { euiTheme } = useEuiTheme();
let field: DataViewField | undefined;
let operator: Operator | undefined;
let params: Filter['meta']['params'] | undefined;
let params: Filter['meta']['params'];
const isMaxNesting = isMaxFilterNesting(path);
if (!conditionalOperationType) {
field = getFieldFromFilter(filter, dataView!);
@ -132,7 +133,7 @@ export function FilterItem({
);
const onHandleParamsChange = useCallback(
(selectedParams: unknown) => {
(selectedParams: Filter['meta']['params']) => {
dispatch({
type: 'updateFilter',
payload: { dest: { path, index }, field, operator, params: selectedParams },
@ -146,7 +147,12 @@ export function FilterItem({
const paramsValues = Array.isArray(params) ? params : [];
dispatch({
type: 'updateFilter',
payload: { dest: { path, index }, field, operator, params: [...paramsValues, value] },
payload: {
dest: { path, index },
field,
operator,
params: [...paramsValues, value] as Filter['meta']['params'],
},
});
},
[dispatch, path, index, field, operator, params]
@ -192,7 +198,7 @@ export function FilterItem({
<FilterGroup
path={path}
booleanRelation={conditionalOperationType}
filters={Array.isArray(filter) ? filter : filter.meta?.params}
filters={getGroupedFilters(filter)}
reverseBackground={!reverseBackground}
renderedLevel={renderedLevel + 1}
/>

View file

@ -8,6 +8,7 @@
import React, { useCallback, useContext } from 'react';
import { DataView, DataViewField } from '@kbn/data-views-plugin/common';
import type { Filter } from '@kbn/es-query';
import { EuiToolTip, EuiFormRow } from '@elastic/eui';
import type { Operator } from '../../filter_bar/filter_editor';
import { getFieldValidityAndErrorMessage } from '../../filter_bar/filter_editor/lib';
@ -17,8 +18,8 @@ import { ParamsEditorInput } from './params_editor_input';
interface ParamsEditorProps {
dataView: DataView;
params: unknown;
onHandleParamsChange: (params: unknown) => void;
onHandleParamsUpdate: (value: unknown) => void;
onHandleParamsChange: (params: Filter['meta']['params']) => void;
onHandleParamsUpdate: (value: string) => void;
timeRangeForSuggestionsOverride?: boolean;
field?: DataViewField;
operator?: Operator;

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { buildEmptyFilter, type Filter, BooleanRelation } from '@kbn/es-query';
import { buildEmptyFilter, type Filter, isCombinedFilter, BooleanRelation } from '@kbn/es-query';
import { DataView } from '@kbn/data-views-plugin/common';
import {
getFilterByPath,
@ -188,7 +188,19 @@ describe('filters_builder', () => {
beforeAll(() => {
filter = filters[0];
filtersWithOrRelationships = filters[1];
groupOfFilters = filters[1].meta.params[1];
if (Array.isArray(filters[1].meta.params)) {
const secondFilter = filters[1].meta.params[1];
if (
typeof secondFilter !== 'number' &&
typeof secondFilter !== 'string' &&
typeof secondFilter !== 'boolean' &&
isCombinedFilter(secondFilter)
) {
groupOfFilters = secondFilter;
}
} else {
groupOfFilters = filters[0];
}
});
test('should return correct ConditionalOperationType', () => {

View file

@ -19,8 +19,14 @@ const PATH_SEPARATOR = '.';
export const getPathInArray = (path: Path) => path.split(PATH_SEPARATOR).map(Number);
const getGroupedFilters = (filter: Filter): Filter[] =>
Array.isArray(filter) ? filter : filter?.meta?.params ?? [];
export const getGroupedFilters = (filter: Filter): Filter[] => {
const isCombined = isCombinedFilter(filter);
if (isCombined) {
return filter?.meta?.params ?? [];
} else {
return [];
}
};
const doForFilterByPath = <T>(filters: Filter[], path: Path, action: (filter: Filter) => T) => {
const [first, ...restPath] = getPathInArray(path);

View file

@ -32,9 +32,21 @@ function reducer(state: ReducerState, action: ReducerAction) {
selectedDatasets: action.payload.datasets,
};
case 'updateDatasetsFilters':
const datasetsToAdd = action.payload.filters
.filter((filter) => !state.selectedDatasets.includes(filter.meta.params.query))
.map((filter) => filter.meta.params.query);
const datasetsToAdd = action.payload.filters.reduce(
(prevFilters: string[], nextFilter: Filter) => {
const query =
typeof nextFilter.meta.params === 'object' &&
'query' in nextFilter.meta.params &&
nextFilter.meta.params?.query;
const queryString = query ? String(query) : '';
if (!state.selectedDatasets.includes(queryString)) {
prevFilters.push(queryString);
}
return prevFilters;
},
[]
);
return {
...state,
selectedDatasets: [...state.selectedDatasets, ...datasetsToAdd],
@ -77,11 +89,16 @@ export const useDatasetFiltering = () => {
// be re-added via the embeddable as it will be seen as a duplicate to the FilterManager,
// and no update will be emitted.
useEffect(() => {
const filtersToRemove = reducerState.selectedDatasetsFilters.filter(
(filter) => !reducerState.selectedDatasets.includes(filter.meta.params.query)
);
const filtersToRemove = reducerState.selectedDatasetsFilters.filter((filter: Filter) => {
const query =
typeof filter.meta.params === 'object' &&
'query' in filter.meta.params &&
filter.meta.params?.query;
const queryString = query ? String(query) : '';
return !reducerState.selectedDatasets.includes(queryString);
});
if (filtersToRemove.length > 0) {
filtersToRemove.forEach((filter) => {
filtersToRemove.forEach((filter: Filter) => {
services.data.query.filterManager.removeFilter(filter);
});
}

View file

@ -7,7 +7,7 @@
import { cloneDeep } from 'lodash';
import moment from 'moment';
import rison, { RisonValue } from '@kbn/rison';
import rison from '@kbn/rison';
import React, { FC, useEffect, useMemo, useState } from 'react';
import { APP_ID as MAPS_APP_ID } from '@kbn/maps-plugin/common';
import {
@ -561,13 +561,15 @@ export const LinksMenuUI = (props: LinksMenuProps) => {
},
});
const appStateProps: RisonValue = {
const appStateProps = {
index: dataViewId,
filters: getFiltersForDSLQuery(job.datafeed_config.query, dataViewId, job.job_id),
...(query !== null
? {
query,
}
: {}),
};
if (query !== null) {
appStateProps.query = query;
}
const _a = rison.encode(appStateProps);
// Need to encode the _a parameter as it will contain characters such as '+' if using the regex.

View file

@ -78,7 +78,11 @@ export function createApplyEntityFieldFiltersAction(
const filter = filterManager
.getFilters()
.find(
(f) => f.meta.key === field.fieldName && f.meta.params.query === field.fieldValue
(f) =>
f.meta.key === field.fieldName &&
typeof f.meta.params === 'object' &&
'query' in f.meta.params &&
f.meta.params.query === field.fieldValue
);
if (filter) {
filterManager.removeFilter(filter);

View file

@ -5,20 +5,25 @@
* 2.0.
*/
import { Filter } from '@kbn/es-query';
import { Filter, PhraseFilter } from '@kbn/es-query';
import { FilterIn, FilterOut, updateFiltersArray } from '.';
describe('updateFiltersArray', () => {
it('should add new filter', () => {
const existingFilters: Filter[] = [];
const existingFilters: PhraseFilter[] = [];
const key: string = 'key';
const value: string = 'value';
const filterType: boolean = FilterIn;
const newFilters = updateFiltersArray(existingFilters, key, value, filterType);
const newFilters = updateFiltersArray(
existingFilters,
key,
value,
filterType
) as PhraseFilter[];
expect(newFilters).toHaveLength(1);
expect(newFilters[0].meta.key).toEqual(key);
expect(newFilters[0].meta.params.query).toEqual(value);
expect(newFilters[0].meta.params?.query).toEqual(value);
expect(newFilters[0].meta.negate).toEqual(!filterType);
});
@ -40,10 +45,15 @@ describe('updateFiltersArray', () => {
];
const filterType: boolean = FilterOut;
const newFilters = updateFiltersArray(existingFilters, key, value, filterType);
const newFilters = updateFiltersArray(
existingFilters,
key,
value,
filterType
) as PhraseFilter[];
expect(newFilters).toHaveLength(1);
expect(newFilters[0].meta.key).toEqual(key);
expect(newFilters[0].meta.params.query).toEqual(value);
expect(newFilters[0].meta.params?.query).toEqual(value);
expect(newFilters[0].meta.negate).toEqual(!filterType);
});
@ -64,11 +74,15 @@ describe('updateFiltersArray', () => {
},
];
const filterType: boolean = FilterIn;
const newFilters = updateFiltersArray(existingFilters, key, value, filterType);
const newFilters = updateFiltersArray(
existingFilters,
key,
value,
filterType
) as PhraseFilter[];
expect(newFilters).toHaveLength(1);
expect(newFilters[0].meta.key).toEqual(key);
expect(newFilters[0].meta.params.query).toEqual(value);
expect(newFilters[0].meta.params?.query).toEqual(value);
expect(newFilters[0].meta.negate).toEqual(!filterType);
});
});

View file

@ -41,7 +41,13 @@ const filterExistsInFiltersArray = (
key: string,
value: string
): Filter | undefined =>
filters.find((f: Filter) => f.meta.key === key && f.meta.params.query === value);
filters.find(
(f: Filter) =>
f.meta.key === key &&
typeof f.meta.params === 'object' &&
'query' in f.meta.params &&
f.meta.params?.query === value
);
/**
* Returns true if the filter exists and should be removed, false otherwise (depending on a FilterIn or FilterOut action)

View file

@ -7,7 +7,7 @@
import { encode } from '@kbn/rison';
import React, { FunctionComponent, useState, useEffect } from 'react';
import { buildPhrasesFilter, PhraseFilter } from '@kbn/es-query';
import { buildPhrasesFilter, PhrasesFilter } from '@kbn/es-query';
import { FormattedMessage } from '@kbn/i18n-react';
import { METRIC_TYPE } from '@kbn/analytics';
@ -81,7 +81,7 @@ const DiscoverAppLink: FunctionComponent<Props> = ({ checkpoint }) => {
const dataView = await getDeprecationDataView(dataService);
const field = dataView.getFieldByName(DEPRECATION_LOGS_ORIGIN_FIELD);
let filters: PhraseFilter[] = [];
let filters: PhrasesFilter[] = [];
if (field !== undefined) {
const filter = buildPhrasesFilter(field!, [...APPS_WITH_DEPRECATION_LOGS], dataView);