mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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:
parent
37ef9a274d
commit
beeec00a17
26 changed files with 353 additions and 189 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: {},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(', ');
|
||||
|
|
|
@ -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)}`;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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}</>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue