Skip to content

Commit

Permalink
[data views] clarify field subtype typescript types (#112499)
Browse files Browse the repository at this point in the history
* separate out multi and nested subTypes

* separate out multi and nested subTypes

* add undefined checks

* remove expect error statements

* use helper functions in es-query

* simplify changes with helper functions

* checking existence instead of getting value x2

* simplify types and revert discover changes

* update discover sidebar with helper methods

* try helpers with group_fields file

* try different helper with group_fields file

* revert group field changes, try nested field helpers

* revert nested field changes, try field_name.tsx helpers

* fix maps jest test

* use helpers in discover instead of setting types

* fix field_name.tsx

* Update index_pattern_util.test.ts

* lint  fix

* fix common exports

* reduce data_views plugin bundle size

* reduce data_views plugin bundle size

* remove discover reliance on es-query package

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
mattkime and kibanamachine authored Oct 7, 2021
1 parent a67eef4 commit 202980e
Show file tree
Hide file tree
Showing 30 changed files with 230 additions and 100 deletions.
7 changes: 5 additions & 2 deletions packages/kbn-es-query/src/es_query/handle_nested_filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { getFilterField, cleanFilter, Filter } from '../filters';
import { IndexPatternBase } from './types';
import { getDataViewFieldSubtypeNested } from '../utils';

/** @internal */
export const handleNestedFilter = (filter: Filter, indexPattern?: IndexPatternBase) => {
Expand All @@ -21,7 +22,9 @@ export const handleNestedFilter = (filter: Filter, indexPattern?: IndexPatternBa
const field = indexPattern.fields.find(
(indexPatternField) => indexPatternField.name === fieldName
);
if (!field || !field.subType || !field.subType.nested || !field.subType.nested.path) {

const subTypeNested = field && getDataViewFieldSubtypeNested(field);
if (!subTypeNested) {
return filter;
}

Expand All @@ -31,7 +34,7 @@ export const handleNestedFilter = (filter: Filter, indexPattern?: IndexPatternBa
meta: filter.meta,
query: {
nested: {
path: field.subType.nested.path,
path: subTypeNested.nested.path,
query: query.query || query,
},
},
Expand Down
2 changes: 2 additions & 0 deletions packages/kbn-es-query/src/es_query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ export {
BoolQuery,
DataViewBase,
DataViewFieldBase,
IFieldSubTypeMulti,
IFieldSubTypeNested,
} from './types';
15 changes: 14 additions & 1 deletion packages/kbn-es-query/src/es_query/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,24 @@ import type { estypes } from '@elastic/elasticsearch';
* A field's sub type
* @public
*/
export interface IFieldSubType {
export type IFieldSubType = IFieldSubTypeMultiOptional | IFieldSubTypeNestedOptional;

export interface IFieldSubTypeMultiOptional {
multi?: { parent: string };
}

export interface IFieldSubTypeMulti {
multi: { parent: string };
}

export interface IFieldSubTypeNestedOptional {
nested?: { path: string };
}

export interface IFieldSubTypeNested {
nested: { path: string };
}

/**
* A base interface for an index pattern field
* @public
Expand Down
6 changes: 6 additions & 0 deletions packages/kbn-es-query/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@
export * from './es_query';
export * from './filters';
export * from './kuery';
export {
isDataViewFieldSubtypeMulti,
isDataViewFieldSubtypeNested,
getDataViewFieldSubtypeMulti,
getDataViewFieldSubtypeNested,
} from './utils';
13 changes: 5 additions & 8 deletions packages/kbn-es-query/src/kuery/functions/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
* Side Public License, v 1.
*/

import { get, isUndefined } from 'lodash';
import { isUndefined } from 'lodash';
import { estypes } from '@elastic/elasticsearch';
import { getPhraseScript } from '../../filters';
import { getFields } from './utils/get_fields';
import { getTimeZoneFromSettings } from '../../utils';
import { getTimeZoneFromSettings, getDataViewFieldSubtypeNested } from '../../utils';
import { getFullFieldNameNode } from './utils/get_full_field_name_node';
import { IndexPatternBase, KueryNode, IndexPatternFieldBase, KueryQueryOptions } from '../..';

Expand Down Expand Up @@ -105,16 +105,13 @@ export function toElasticsearchQuery(
const wrapWithNestedQuery = (query: any) => {
// Wildcards can easily include nested and non-nested fields. There isn't a good way to let
// users handle this themselves so we automatically add nested queries in this scenario.
if (
!(fullFieldNameArg.type === 'wildcard') ||
!get(field, 'subType.nested') ||
context?.nested
) {
const subTypeNested = getDataViewFieldSubtypeNested(field);
if (!(fullFieldNameArg.type === 'wildcard') || !subTypeNested?.nested || context?.nested) {
return query;
} else {
return {
nested: {
path: field.subType!.nested!.path,
path: subTypeNested.nested.path,
query,
score_mode: 'none',
},
Expand Down
19 changes: 8 additions & 11 deletions packages/kbn-es-query/src/kuery/functions/range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@
* Side Public License, v 1.
*/

import _ from 'lodash';
import { pick, map, mapValues } from 'lodash';
import { estypes } from '@elastic/elasticsearch';
import { nodeTypes } from '../node_types';
import * as ast from '../ast';
import { getRangeScript, RangeFilterParams } from '../../filters';
import { getFields } from './utils/get_fields';
import { getTimeZoneFromSettings } from '../../utils';
import { getTimeZoneFromSettings, getDataViewFieldSubtypeNested } from '../../utils';
import { getFullFieldNameNode } from './utils/get_full_field_name_node';
import { IndexPatternBase, KueryNode, KueryQueryOptions } from '../..';

export function buildNodeParams(fieldName: string, params: RangeFilterParams) {
const paramsToMap = _.pick(params, 'gt', 'lt', 'gte', 'lte', 'format');
const paramsToMap = pick(params, 'gt', 'lt', 'gte', 'lte', 'format');
const fieldNameArg =
typeof fieldName === 'string'
? ast.fromLiteralExpression(fieldName)
: nodeTypes.literal.buildNode(fieldName);

const args = _.map(paramsToMap, (value: number | string, key: string) => {
const args = map(paramsToMap, (value: number | string, key: string) => {
return nodeTypes.namedArg.buildNode(key, value);
});

Expand All @@ -46,7 +46,7 @@ export function toElasticsearchQuery(
);
const fields = indexPattern ? getFields(fullFieldNameArg, indexPattern) : [];
const namedArgs = extractArguments(args);
const queryParams = _.mapValues(namedArgs, (arg: KueryNode) => {
const queryParams = mapValues(namedArgs, (arg: KueryNode) => {
return ast.toElasticsearchQuery(arg);
});

Expand All @@ -67,16 +67,13 @@ export function toElasticsearchQuery(
const wrapWithNestedQuery = (query: any) => {
// Wildcards can easily include nested and non-nested fields. There isn't a good way to let
// users handle this themselves so we automatically add nested queries in this scenario.
if (
!(fullFieldNameArg.type === 'wildcard') ||
!_.get(field, 'subType.nested') ||
context!.nested
) {
const subTypeNested = getDataViewFieldSubtypeNested(field);
if (!(fullFieldNameArg.type === 'wildcard') || !subTypeNested?.nested || context!.nested) {
return query;
} else {
return {
nested: {
path: field.subType!.nested!.path,
path: subTypeNested.nested.path,
query,
score_mode: 'none',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { getFields } from './get_fields';
import { IndexPatternBase, IndexPatternFieldBase, KueryNode } from '../../..';
import { getDataViewFieldSubtypeNested } from '../../../utils';

export function getFullFieldNameNode(
rootNameNode: any,
Expand All @@ -28,8 +29,8 @@ export function getFullFieldNameNode(
const fields = getFields(fullFieldNameNode, indexPattern);

const errors = fields!.reduce((acc: any, field: IndexPatternFieldBase) => {
const nestedPathFromField =
field.subType && field.subType.nested ? field.subType.nested.path : undefined;
const subTypeNested = getDataViewFieldSubtypeNested(field);
const nestedPathFromField = subTypeNested?.nested.path;

if (nestedPath && !nestedPathFromField) {
return [
Expand All @@ -48,11 +49,7 @@ export function getFullFieldNameNode(
if (nestedPathFromField !== nestedPath) {
return [
...acc,
`Nested field ${
field.name
} is being queried with the incorrect nested path. The correct path is ${
field.subType!.nested!.path
}.`,
`Nested field ${field.name} is being queried with the incorrect nested path. The correct path is ${subTypeNested?.nested.path}.`,
];
}

Expand Down
21 changes: 21 additions & 0 deletions packages/kbn-es-query/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,31 @@
*/

import moment from 'moment-timezone';
import { DataViewFieldBase, IFieldSubTypeNested, IFieldSubTypeMulti } from './es_query';

/** @internal */
export function getTimeZoneFromSettings(dateFormatTZ: string) {
const detectedTimezone = moment.tz.guess();

return dateFormatTZ === 'Browser' ? detectedTimezone : dateFormatTZ;
}

type HasSubtype = Pick<DataViewFieldBase, 'subType'>;

export function isDataViewFieldSubtypeNested(field: HasSubtype) {
const subTypeNested = field?.subType as IFieldSubTypeNested;
return !!subTypeNested?.nested?.path;
}

export function getDataViewFieldSubtypeNested(field: HasSubtype) {
return isDataViewFieldSubtypeNested(field) ? (field.subType as IFieldSubTypeNested) : undefined;
}

export function isDataViewFieldSubtypeMulti(field: HasSubtype) {
const subTypeNested = field?.subType as IFieldSubTypeMulti;
return !!subTypeNested?.multi?.parent;
}

export function getDataViewFieldSubtypeMulti(field: HasSubtype) {
return isDataViewFieldSubtypeMulti(field) ? (field.subType as IFieldSubTypeMulti) : undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
import { useEffect, useRef, useState } from 'react';
import { debounce } from 'lodash';
import { ListOperatorTypeEnum as OperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query';
import {
IndexPatternBase,
IndexPatternFieldBase,
getDataViewFieldSubtypeNested,
} from '@kbn/es-query';

// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/100715
// import { AutocompleteStart } from '../../../../../../../../src/plugins/data/public';
Expand Down Expand Up @@ -68,14 +72,13 @@ export const useFieldValueAutocomplete = ({
}

setIsLoading(true);

const field =
fieldSelected.subType != null && fieldSelected.subType.nested != null
? {
...fieldSelected,
name: `${fieldSelected.subType.nested.path}.${fieldSelected.name}`,
}
: fieldSelected;
const subTypeNested = getDataViewFieldSubtypeNested(fieldSelected);
const field = subTypeNested
? {
...fieldSelected,
name: `${subTypeNested.nested.path}.${fieldSelected.name}`,
}
: fieldSelected;

const newSuggestions = await autocompleteService.getValueSuggestions({
field,
Expand Down
23 changes: 12 additions & 11 deletions packages/kbn-securitysolution-list-utils/src/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ import {
exceptionListItemSchema,
nestedEntryItem,
} from '@kbn/securitysolution-io-ts-list-types';
import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query';
import {
IndexPatternBase,
IndexPatternFieldBase,
getDataViewFieldSubtypeNested,
isDataViewFieldSubtypeNested,
} from '@kbn/es-query';

import {
EXCEPTION_OPERATORS,
Expand Down Expand Up @@ -297,11 +302,11 @@ export const getFilteredIndexPatterns = (
...indexPatterns,
fields: indexPatterns.fields
.filter((indexField) => {
const subTypeNested = getDataViewFieldSubtypeNested(indexField);
const fieldHasCommonParentPath =
indexField.subType != null &&
indexField.subType.nested != null &&
subTypeNested &&
item.parent != null &&
indexField.subType.nested.path === item.parent.parent.field;
subTypeNested.nested.path === item.parent.parent.field;

return fieldHasCommonParentPath;
})
Expand All @@ -317,9 +322,7 @@ export const getFilteredIndexPatterns = (
// when user selects to add a nested entry, only nested fields are shown as options
return {
...indexPatterns,
fields: indexPatterns.fields.filter(
(field) => field.subType != null && field.subType.nested != null
),
fields: indexPatterns.fields.filter((field) => isDataViewFieldSubtypeNested(field)),
};
} else {
return indexPatterns;
Expand All @@ -346,10 +349,8 @@ export const getEntryOnFieldChange = (
// a user selects "exists", as soon as they make a selection
// we can now identify the 'parent' and 'child' this is where
// we first convert the entry into type "nested"
const newParentFieldValue =
newField.subType != null && newField.subType.nested != null
? newField.subType.nested.path
: '';
const subTypeNested = getDataViewFieldSubtypeNested(newField);
const newParentFieldValue = subTypeNested?.nested.path || '';

return {
index: entryIndex,
Expand Down
5 changes: 4 additions & 1 deletion src/plugins/data/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ export {
DATA_VIEW_SAVED_OBJECT_TYPE,
INDEX_PATTERN_SAVED_OBJECT_TYPE,
isFilterable,
isNestedField,
fieldList,
DataViewField,
IndexPatternField,
Expand All @@ -75,4 +74,8 @@ export {
DuplicateDataViewError,
DataViewSavedObjectConflictError,
getIndexPatternLoadMeta,
isNestedField,
isMultiField,
getFieldSubtypeMulti,
getFieldSubtypeNested,
} from '../../data_views/common';
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,9 @@ export const setupGetFieldSuggestions: KqlQuerySuggestionProvider<QuerySuggestio
);
const search = `${prefix}${suffix}`.trim().toLowerCase();
const matchingFields = allFields.filter((field) => {
const subTypeNested = indexPatternsUtils.getFieldSubtypeNested(field);
return (
(!nestedPath ||
(nestedPath &&
field.subType &&
field.subType.nested &&
field.subType.nested.path.includes(nestedPath))) &&
(!nestedPath || (nestedPath && subTypeNested?.nested.path.includes(nestedPath))) &&
field.name.toLowerCase().includes(search)
);
});
Expand Down
11 changes: 10 additions & 1 deletion src/plugins/data/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,13 @@ export const exporters = {
* Index patterns:
*/

import { isNestedField, isFilterable } from '../common';
import {
isNestedField,
isFilterable,
isMultiField,
getFieldSubtypeNested,
getFieldSubtypeMulti,
} from '../common';

import {
ILLEGAL_CHARACTERS_KEY,
Expand All @@ -59,6 +65,9 @@ export const indexPatterns = {
ILLEGAL_CHARACTERS,
isFilterable,
isNestedField,
isMultiField,
getFieldSubtypeMulti,
getFieldSubtypeNested,
validate: validateDataView,
flattenHitWrapper,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { QueryLanguageSwitcher } from './language_switcher';
import { PersistedLog, getQueryLog, matchPairs, toUser, fromUser } from '../../query';
import { SuggestionsListSize } from '../typeahead/suggestions_component';
import { SuggestionsComponent } from '..';
import { KIBANA_USER_QUERY_LANGUAGE_KEY } from '../../../common';
import { KIBANA_USER_QUERY_LANGUAGE_KEY, getFieldSubtypeNested } from '../../../common';

export interface QueryStringInputProps {
indexPatterns: Array<IIndexPattern | string>;
Expand Down Expand Up @@ -425,10 +425,10 @@ export default class QueryStringInputUI extends Component<Props, State> {
};

private handleNestedFieldSyntaxNotification = (suggestion: QuerySuggestion) => {
const subTypeNested = 'field' in suggestion && getFieldSubtypeNested(suggestion.field);
if (
'field' in suggestion &&
suggestion.field.subType &&
suggestion.field.subType.nested &&
subTypeNested &&
subTypeNested.nested &&
!this.services.storage.get('kibana.KQLNestedQuerySyntaxInfoOptOut')
) {
const { notifications, docLinks } = this.services;
Expand Down
Loading

0 comments on commit 202980e

Please sign in to comment.