Skip to content

Commit

Permalink
142435 add is one of operator (elastic#144988)
Browse files Browse the repository at this point in the history
## Summary

This PR adds support for an is one of operator allowing users to filter
multiple values for one field.

[Some investigation
](https://discuss.elastic.co/t/passing-multiple-values-in-kibana-add-filter-is-one-of/232694/2)by
@andrew-goldstein revealed that since the underlying engine uses Lucene,
we can add support for multiple values by using an OR query:

`kibana.alert.workflow_status: ("open" OR "closed" OR "acknowledged")`
is equivalent to
```
"terms": {
      "kibana.alert.workflow_status": [ "open", "closed", "acknowledged"]
    }
```
Where the former is usable in our `DataProviders` used by timeline and
other components that navigate a user to a pre-populated timeline.

As an enhancement to the timeline view, users can also use this `is one
of` operator by interacting with the `Add field` button and selecting
the new operator.

<img width="433" alt="image"
src="https://user-images.githubusercontent.com/28942857/193487154-769005b6-3e5a-40bf-9476-8dd3f3bcb8ee.png">

### Checklist

Delete any items that are not applicable to this PR.

- [X] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [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


## Known issues
This operator does not support timeline templates at this time so usage
there disables the ability for conversion to template field but a better
approach should be implemented to notify users.
elastic#142437. For now I have added a
template message and prevented users from creating templates with this
operator:

<img width="374" alt="image"
src="https://user-images.githubusercontent.com/28942857/201157676-80017c6c-9f5b-4cd7-ba0b-ee2e43a884cb.png">



## Testing
Create a new timeline or visit an existing one. 
Click 'Add field' button on Timeline in OR query section
add any field ( preferably one that can have many values- consider
`kibana.alerts.workflow_status` but this requires alerts.
Select the `is one of` or `is not one of operator`
Add or remove values in the value section.
Click save.

Co-authored-by: Kristof-Pierre Cummings <kristofpierre.cummings@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
3 people authored and benakansara committed Nov 17, 2022
1 parent 1adf0ab commit a48a5a1
Show file tree
Hide file tree
Showing 41 changed files with 1,535 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ const SavedColumnHeaderRuntimeType = runtimeTypes.partial({
const SavedDataProviderQueryMatchBasicRuntimeType = runtimeTypes.partial({
field: unionWithNullType(runtimeTypes.string),
displayField: unionWithNullType(runtimeTypes.string),
value: unionWithNullType(runtimeTypes.string),
value: runtimeTypes.union([
runtimeTypes.null,
runtimeTypes.string,
runtimeTypes.array(runtimeTypes.string),
]),
displayValue: unionWithNullType(runtimeTypes.string),
operator: unionWithNullType(runtimeTypes.string),
});
Expand Down Expand Up @@ -652,7 +656,7 @@ export interface DataProviderResult {
export interface QueryMatchResult {
field?: Maybe<string>;
displayField?: Maybe<string>;
value?: Maybe<string>;
value?: Maybe<string | string[]>;
displayValue?: Maybe<string>;
operator?: Maybe<string>;
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import {
} from '../../../../timelines/components/timeline/body/renderers/constants';
import { BYTES_FORMAT } from '../../../../timelines/components/timeline/body/renderers/bytes';
import { EVENT_DURATION_FIELD_NAME } from '../../../../timelines/components/duration';
import { getDisplayValue } from '../../../../timelines/components/timeline/data_providers/helpers';
import { PORT_NAMES } from '../../../../network/components/port/helpers';
import { INDICATOR_REFERENCE } from '../../../../../common/cti/constants';
import type { BrowserField } from '../../../containers/source';
import type { DataProvider } from '../../../../../common/types';
import type { DataProvider, QueryOperator } from '../../../../../common/types';
import { IS_OPERATOR } from '../../../../../common/types';

export interface UseActionCellDataProvider {
Expand All @@ -48,7 +49,12 @@ export interface ActionCellValuesAndDataProvider {
dataProviders: DataProvider[];
}

export const getDataProvider = (field: string, id: string, value: string): DataProvider => ({
export const getDataProvider = (
field: string,
id: string,
value: string | string[],
operator: QueryOperator = IS_OPERATOR
): DataProvider => ({
and: [],
enabled: true,
id: escapeDataProviderId(id),
Expand All @@ -58,7 +64,8 @@ export const getDataProvider = (field: string, id: string, value: string): DataP
queryMatch: {
field,
value,
operator: IS_OPERATOR,
operator,
displayValue: getDisplayValue(value),
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,20 @@ export const useHoverActions = ({

const { closeTopN, toggleTopN, isShowingTopN } = useTopNPopOver(handleClosePopOverTrigger);

const values = useMemo(() => {
const val = dataProvider.queryMatch.value;

if (typeof val === 'number') {
return val.toString();
}

if (Array.isArray(val)) {
return val.map((item) => String(item));
}

return val;
}, [dataProvider.queryMatch.value]);

const hoverContent = useMemo(() => {
// display links as additional content in the hover menu to enable keyboard
// navigation of links (when the draggable contains them):
Expand Down Expand Up @@ -110,11 +124,7 @@ export const useHoverActions = ({
showTopN={isShowingTopN}
scopeId={id}
toggleTopN={toggleTopN}
values={
typeof dataProvider.queryMatch.value !== 'number'
? dataProvider.queryMatch.value
: `${dataProvider.queryMatch.value}`
}
values={values}
/>
);
}, [
Expand All @@ -131,6 +141,7 @@ export const useHoverActions = ({
onFilterAdded,
id,
toggleTopN,
values,
]);

const setContainerRef = useCallback((e: HTMLDivElement) => {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,22 @@ export const mocksSource = {
},
},
},
{
aggregatable: false,
category: 'nestedField',
description: '',
example: '',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'nestedField.thirdAttributes',
searchable: true,
type: 'date',
subType: {
nested: {
path: 'nestedField',
},
},
},
],
};

Expand Down Expand Up @@ -952,6 +968,22 @@ export const mockBrowserFields: BrowserFields = {
},
},
},
'nestedField.thirdAttributes': {
aggregatable: false,
category: 'nestedField',
description: '',
example: '',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'nestedField.thirdAttributes',
searchable: true,
type: 'date',
subType: {
nested: {
path: 'nestedField',
},
},
},
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ export const dataProviderWithOneFilter = [
{
and: [],
enabled: true,
id: '',
id: 'mock-id',
name: 'host.hostname',
excluded: false,
kqlQuery: '',
queryMatch: {
field: 'host.hostname',
value: 'Host-u6ou715rzy',
displayValue: 'Host-u6ou715rzy',
operator: ':' as QueryOperator,
},
},
Expand All @@ -49,25 +50,27 @@ export const dataProviderWithAndFilters = [
and: [],
enabled: true,
excluded: false,
id: '',
id: 'mock-id',
kqlQuery: '',
name: 'kibana.alerts.workflow_status',
queryMatch: {
field: 'kibana.alerts.workflow_status',
operator: ':' as QueryOperator,
value: 'open',
displayValue: 'open',
},
},
],

enabled: true,
id: '',
id: 'mock-id',
name: 'host.hostname',
excluded: false,
kqlQuery: '',
queryMatch: {
field: 'host.hostname',
value: 'Host-u6ou715rzy',
displayValue: 'Host-u6ou715rzy',
operator: ':' as QueryOperator,
},
},
Expand All @@ -79,25 +82,27 @@ export const dataProviderWithOrFilters = [
{
and: [],
enabled: true,
id: '',
id: 'mock-id',
name: 'kibana.alerts.workflow_status',
excluded: false,
kqlQuery: '',
queryMatch: {
field: 'kibana.alerts.workflow_status',
value: 'open',
displayValue: 'open',
operator: ':' as QueryOperator,
},
},
],
enabled: true,
id: '',
id: 'mock-id',
name: 'host.hostname',
excluded: false,
kqlQuery: '',
queryMatch: {
field: 'host.hostname',
value: 'Host-u6ou715rzy',
displayValue: 'Host-u6ou715rzy',
operator: ':' as QueryOperator,
},
},
Expand All @@ -106,25 +111,27 @@ export const dataProviderWithOrFilters = [
{
and: [],
enabled: true,
id: '',
id: 'mock-id',
name: 'kibana.alerts.workflow_status',
excluded: false,
kqlQuery: '',
queryMatch: {
field: 'kibana.alerts.workflow_status',
value: 'closed',
displayValue: 'closed',
operator: ':' as QueryOperator,
},
},
],
enabled: true,
id: '',
id: 'mock-id',
name: 'host.hostname',
excluded: false,
kqlQuery: '',
queryMatch: {
field: 'host.hostname',
value: 'Host-u6ou715rzy',
displayValue: 'Host-u6ou715rzy',
operator: ':' as QueryOperator,
},
},
Expand All @@ -133,25 +140,27 @@ export const dataProviderWithOrFilters = [
{
and: [],
enabled: true,
id: '',
id: 'mock-id',
name: 'kibana.alerts.workflow_status',
excluded: false,
kqlQuery: '',
queryMatch: {
field: 'kibana.alerts.workflow_status',
value: 'acknowledged',
displayValue: 'acknowledged',
operator: ':' as QueryOperator,
},
},
],
enabled: true,
id: '',
id: 'mock-id',
name: 'host.hostname',
excluded: false,
kqlQuery: '',
queryMatch: {
field: 'host.hostname',
value: 'Host-u6ou715rzy',
displayValue: 'Host-u6ou715rzy',
operator: ':' as QueryOperator,
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ jest.mock('react-redux', () => {
};
});

jest.mock('uuid', () => ({
v4: () => 'mock-id',
}));

const id = 'timeline-1';
const renderUseNavigatgeToTimeline = () => renderHook(() => useNavigateToTimeline());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@

import { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { v4 as uuid } from 'uuid';

import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
import { sourcererActions } from '../../../../common/store/sourcerer';
import { getDataProvider } from '../../../../common/components/event_details/table/use_action_cell_data_provider';
import type { DataProvider } from '../../../../../common/types/timeline';
import type { DataProvider, QueryOperator } from '../../../../../common/types/timeline';
import { TimelineId, TimelineType } from '../../../../../common/types/timeline';
import { useCreateTimeline } from '../../../../timelines/components/timeline/properties/use_create_timeline';
import { updateProviders } from '../../../../timelines/store/timeline/actions';
Expand All @@ -21,7 +22,8 @@ import type { TimeRange } from '../../../../common/store/inputs/model';

export interface Filter {
field: string;
value: string;
value: string | string[];
operator?: QueryOperator;
}

export const useNavigateToTimeline = () => {
Expand Down Expand Up @@ -79,10 +81,17 @@ export const useNavigateToTimeline = () => {
const mainFilter = orFilterGroup[0];

if (mainFilter) {
const dataProvider = getDataProvider(mainFilter.field, '', mainFilter.value);
const dataProvider = getDataProvider(
mainFilter.field,
uuid(),
mainFilter.value,
mainFilter.operator
);

for (const filter of orFilterGroup.slice(1)) {
dataProvider.and.push(getDataProvider(filter.field, '', filter.value));
dataProvider.and.push(
getDataProvider(filter.field, uuid(), filter.value, filter.operator)
);
}
dataProviders.push(dataProvider);
}
Expand Down
Loading

0 comments on commit a48a5a1

Please sign in to comment.