From 952a105aa0cfbdae046600a08bf42875959bfd9b Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Fri, 17 Jul 2020 12:12:31 -0400 Subject: [PATCH 1/8] making estypes and subtype available in indexPatterns --- .../exceptions/add_exception_modal/index.tsx | 53 +++-------- .../components/exceptions/builder/index.tsx | 17 ++-- .../exceptions/edit_exception_modal/index.tsx | 41 +++------ .../exceptions/viewer/index.test.tsx | 3 + .../components/exceptions/viewer/index.tsx | 10 ++- .../containers/source/index.gql_query.ts | 9 ++ .../public/common/containers/source/index.tsx | 2 +- .../alerts_table/default_config.tsx | 9 +- .../components/alerts_table/index.tsx | 26 +++--- .../detection_engine/rules/details/index.tsx | 1 + .../public/graphql/introspection.json | 89 +++++++++++++++++++ .../security_solution/public/graphql/types.ts | 42 +++++++++ .../graphql/source_status/schema.gql.ts | 14 +++ .../security_solution/server/graphql/types.ts | 78 ++++++++++++++++ .../server/lib/index_fields/types.ts | 4 +- 15 files changed, 299 insertions(+), 99 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index d5eeef0f1e768..db670805ee470 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -21,7 +21,6 @@ import { EuiCallOut, EuiText, } from '@elastic/eui'; -import { alertsIndexPattern } from '../../../../../common/endpoint/constants'; import { ExceptionListItemSchema, CreateExceptionListItemSchema, @@ -34,7 +33,6 @@ import { errorToToaster, displaySuccessToast, useStateToaster } from '../../toas import { ExceptionBuilder } from '../builder'; import { Loader } from '../../loader'; import { useAddOrUpdateException } from '../use_add_exception'; -import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; import { useFetchOrCreateRuleExceptionList } from '../use_fetch_or_create_rule_exception_list'; import { AddExceptionComments } from '../add_exception_comments'; import { @@ -47,20 +45,11 @@ import { } from '../helpers'; import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules'; -export interface AddExceptionOnClick { - ruleName: string; - ruleId: string; - exceptionListType: ExceptionListType; - alertData?: { - ecsData: Ecs; - nonEcsData: TimelineNonEcsData[]; - }; -} - -interface AddExceptionModalProps { +export interface AddExceptionModalProps { ruleName: string; ruleId: string; exceptionListType: ExceptionListType; + ruleIndices: string[]; alertData?: { ecsData: Ecs; nonEcsData: TimelineNonEcsData[]; @@ -76,10 +65,8 @@ const Modal = styled(EuiModal)` `; const ModalHeader = styled(EuiModalHeader)` - ${({ theme }) => css` - flex-direction: column; - align-items: flex-start; - `} + flex-direction: column; + align-items: flex-start; `; const ModalHeaderSubtitle = styled.div` @@ -101,6 +88,7 @@ const ModalBodySection = styled.section` export const AddExceptionModal = memo(function AddExceptionModal({ ruleName, ruleId, + ruleIndices, exceptionListType, alertData, onCancel, @@ -116,11 +104,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ >([]); const [fetchOrCreateListError, setFetchOrCreateListError] = useState(false); const [, dispatchToaster] = useStateToaster(); - const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); - - const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns( - signalIndexName !== null ? [signalIndexName] : [] - ); + const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns(ruleIndices); const onError = useCallback( (error: Error) => { @@ -180,19 +164,13 @@ export const AddExceptionModal = memo(function AddExceptionModal({ }, [alertData, exceptionListType, ruleExceptionList, ruleName]); useEffect(() => { - if (indexPatternLoading === false && isSignalIndexLoading === false) { + if (indexPatternLoading === false) { setShouldDisableBulkClose( entryHasListType(exceptionItemsToAdd) || entryHasNonEcsType(exceptionItemsToAdd, indexPatterns) ); } - }, [ - setShouldDisableBulkClose, - exceptionItemsToAdd, - indexPatternLoading, - isSignalIndexLoading, - indexPatterns, - ]); + }, [setShouldDisableBulkClose, exceptionItemsToAdd, indexPatternLoading, indexPatterns]); useEffect(() => { if (shouldDisableBulkClose === true) { @@ -252,8 +230,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ const onAddExceptionConfirm = useCallback(() => { if (addOrUpdateExceptionItems !== null) { const alertIdToClose = shouldCloseAlert && alertData ? alertData.ecsData._id : undefined; - const bulkCloseIndex = - shouldBulkCloseAlert && signalIndexName !== null ? [signalIndexName] : undefined; + const bulkCloseIndex = shouldBulkCloseAlert && ruleIndices !== null ? ruleIndices : undefined; addOrUpdateExceptionItems(enrichExceptionItems(), alertIdToClose, bulkCloseIndex); } }, [ @@ -262,7 +239,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ shouldCloseAlert, shouldBulkCloseAlert, alertData, - signalIndexName, + ruleIndices, ]); const isSubmitButtonDisabled = useCallback( @@ -270,13 +247,6 @@ export const AddExceptionModal = memo(function AddExceptionModal({ [fetchOrCreateListError, exceptionItemsToAdd] ); - const indexPatternConfig = useCallback(() => { - if (exceptionListType === 'endpoint') { - return [alertsIndexPattern]; - } - return signalIndexName ? [signalIndexName] : []; - }, [exceptionListType, signalIndexName]); - return ( @@ -296,7 +266,6 @@ export const AddExceptionModal = memo(function AddExceptionModal({ )} {fetchOrCreateListError === false && - !isSignalIndexLoading && !indexPatternLoading && !isLoadingExceptionList && ruleExceptionList && ( @@ -310,7 +279,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ listId={ruleExceptionList.list_id} listNamespaceType={ruleExceptionList.namespace_type} ruleName={ruleName} - indexPatternConfig={indexPatternConfig()} + indexPatterns={indexPatterns} isLoading={false} isOrDisabled={false} isAndDisabled={false} diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx index 6bff33afaf70c..3ce6ae15ea637 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx @@ -8,7 +8,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; import { ExceptionListItemComponent } from './exception_item'; -import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules/fetch_index_patterns'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; import { ExceptionListItemSchema, NamespaceType, @@ -51,7 +51,7 @@ interface ExceptionBuilderProps { listId: string; listNamespaceType: NamespaceType; ruleName: string; - indexPatternConfig: string[]; + indexPatterns: IIndexPattern; isLoading: boolean; isOrDisabled: boolean; isAndDisabled: boolean; @@ -64,7 +64,7 @@ export const ExceptionBuilder = ({ listId, listNamespaceType, ruleName, - indexPatternConfig, + indexPatterns, isLoading, isOrDisabled, isAndDisabled, @@ -75,9 +75,6 @@ export const ExceptionBuilder = ({ exceptionListItems ); const [exceptionsToDelete, setExceptionsToDelete] = useState([]); - const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns( - indexPatternConfig ?? [] - ); const handleCheckAndLogic = (items: ExceptionsBuilderExceptionItem[]): void => { setAndLogicIncluded((includesAnd: boolean): boolean => { @@ -175,7 +172,7 @@ export const ExceptionBuilder = ({ }, [exceptions]); // Filters index pattern fields by exceptionable fields if list type is endpoint - const filterIndexPatterns = useCallback(() => { + const filterIndexPatterns = useMemo((): IIndexPattern => { if (listType === 'endpoint') { return { ...indexPatterns, @@ -201,7 +198,7 @@ export const ExceptionBuilder = ({ return ( - {(isLoading || indexPatternLoading) && ( + {(isLoading || indexPatterns == null) && ( )} {exceptions.map((exceptionListItem, index) => ( @@ -229,8 +226,8 @@ export const ExceptionBuilder = ({ key={getExceptionListItemId(exceptionListItem, index)} exceptionItem={exceptionListItem} exceptionId={getExceptionListItemId(exceptionListItem, index)} - indexPattern={filterIndexPatterns()} - isLoading={indexPatternLoading} + indexPattern={filterIndexPatterns} + isLoading={indexPatterns.fields.length === 0} exceptionItemIndex={index} andLogicIncluded={andLogicIncluded} onCheckAndLogic={handleCheckAndLogic} diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx index 73933d483e2cb..f00fc3caa2285 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx @@ -20,9 +20,7 @@ import { EuiFormRow, EuiText, } from '@elastic/eui'; -import { alertsIndexPattern } from '../../../../../common/endpoint/constants'; import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules'; -import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; import { ExceptionListItemSchema, CreateExceptionListItemSchema, @@ -44,6 +42,7 @@ import { interface EditExceptionModalProps { ruleName: string; + ruleIndices: string[]; exceptionItem: ExceptionListItemSchema; exceptionListType: ExceptionListType; onCancel: () => void; @@ -57,10 +56,8 @@ const Modal = styled(EuiModal)` `; const ModalHeader = styled(EuiModalHeader)` - ${({ theme }) => css` - flex-direction: column; - align-items: flex-start; - `} + flex-direction: column; + align-items: flex-start; `; const ModalHeaderSubtitle = styled.div` @@ -81,6 +78,7 @@ const ModalBodySection = styled.section` export const EditExceptionModal = memo(function EditExceptionModal({ ruleName, + ruleIndices, exceptionItem, exceptionListType, onCancel, @@ -94,11 +92,7 @@ export const EditExceptionModal = memo(function EditExceptionModal({ Array >([]); const [, dispatchToaster] = useStateToaster(); - const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); - - const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns( - signalIndexName !== null ? [signalIndexName] : [] - ); + const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns(ruleIndices); const onError = useCallback( (error) => { @@ -121,19 +115,13 @@ export const EditExceptionModal = memo(function EditExceptionModal({ ); useEffect(() => { - if (indexPatternLoading === false && isSignalIndexLoading === false) { + if (indexPatternLoading === false) { setShouldDisableBulkClose( entryHasListType(exceptionItemsToAdd) || entryHasNonEcsType(exceptionItemsToAdd, indexPatterns) ); } - }, [ - setShouldDisableBulkClose, - exceptionItemsToAdd, - indexPatternLoading, - isSignalIndexLoading, - indexPatterns, - ]); + }, [setShouldDisableBulkClose, exceptionItemsToAdd, indexPatternLoading, indexPatterns]); useEffect(() => { if (shouldDisableBulkClose === true) { @@ -182,17 +170,10 @@ export const EditExceptionModal = memo(function EditExceptionModal({ const onEditExceptionConfirm = useCallback(() => { if (addOrUpdateExceptionItems !== null) { const bulkCloseIndex = - shouldBulkCloseAlert && signalIndexName !== null ? [signalIndexName] : undefined; + shouldBulkCloseAlert && ruleIndices.length > 0 ? ruleIndices : undefined; addOrUpdateExceptionItems(enrichExceptionItems(), undefined, bulkCloseIndex); } - }, [addOrUpdateExceptionItems, enrichExceptionItems, shouldBulkCloseAlert, signalIndexName]); - - const indexPatternConfig = useCallback(() => { - if (exceptionListType === 'endpoint') { - return [alertsIndexPattern]; - } - return signalIndexName ? [signalIndexName] : []; - }, [exceptionListType, signalIndexName]); + }, [addOrUpdateExceptionItems, enrichExceptionItems, shouldBulkCloseAlert, ruleIndices]); return ( @@ -204,7 +185,7 @@ export const EditExceptionModal = memo(function EditExceptionModal({ - {!isSignalIndexLoading && ( + {!indexPatternLoading && ( <> {i18n.EXCEPTION_BUILDER_INFO} @@ -221,7 +202,7 @@ export const EditExceptionModal = memo(function EditExceptionModal({ data-test-subj="edit-exception-modal-builder" id-aria="edit-exception-modal-builder" onChange={handleBuilderOnChange} - indexPatternConfig={indexPatternConfig()} + indexPatterns={indexPatterns} /> diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.test.tsx index f72008cbdffe1..986f27f6495ec 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.test.tsx @@ -67,6 +67,7 @@ describe('ExceptionsViewer', () => { ({ eui: euiLightVars, darkMode: false })}> { const wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> { const wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> { + }: UseExceptionListSuccess): void => { dispatch({ type: 'setExceptions', lists: newLists, @@ -253,10 +255,11 @@ const ExceptionsViewerComponent = ({ return ( <> {currentModal === 'editModal' && - exceptionToEdit !== null && - exceptionListTypeToEdit !== null && ( + exceptionToEdit != null && + exceptionListTypeToEdit != null && ( 0 ? { fields: fields.map((field) => - pick(['name', 'searchable', 'type', 'aggregatable'], field) + pick(['name', 'searchable', 'type', 'aggregatable', 'esTypes', 'subType'], field) ), title, } diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index 71cf5c10de764..a4ce6c0200eb3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -12,6 +12,7 @@ import { Dispatch } from 'redux'; import { EuiText } from '@elastic/eui'; import { RowRendererId } from '../../../../common/types/timeline'; +import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { Filter } from '../../../../../../../src/plugins/data/common/es_query'; import { @@ -38,7 +39,7 @@ import { UpdateTimelineLoading, } from './types'; import { Ecs, TimelineNonEcsData } from '../../../graphql/types'; -import { AddExceptionOnClick } from '../../../common/components/exceptions/add_exception_modal'; +import { AddExceptionModalBaseProps } from '../../../common/components/exceptions/add_exception_modal'; import { getMappedNonEcsValue } from '../../../common/components/exceptions/helpers'; export const buildAlertStatusFilter = (status: Status): Filter[] => [ @@ -225,7 +226,7 @@ interface AlertActionArgs { alertData, ruleName, ruleId, - }: AddExceptionOnClick) => void; + }: AddExceptionModalBaseProps) => void; } export const getAlertActions = ({ @@ -346,10 +347,12 @@ export const getAlertActions = ({ onClick: ({ ecsData, data }: TimelineRowActionOnClick) => { const [ruleName] = getMappedNonEcsValue({ data, fieldName: 'signal.rule.name' }); const [ruleId] = getMappedNonEcsValue({ data, fieldName: 'signal.rule.id' }); + const ruleIndices = getMappedNonEcsValue({ data, fieldName: 'signal.rule.index' }); if (ruleId !== undefined) { openAddExceptionModal({ ruleName: ruleName ?? '', ruleId, + ruleIndices: ruleIndices.length > 0 ? ruleIndices : DEFAULT_INDEX_PATTERN, exceptionListType: 'endpoint', alertData: { ecsData, @@ -369,10 +372,12 @@ export const getAlertActions = ({ onClick: ({ ecsData, data }: TimelineRowActionOnClick) => { const [ruleName] = getMappedNonEcsValue({ data, fieldName: 'signal.rule.name' }); const [ruleId] = getMappedNonEcsValue({ data, fieldName: 'signal.rule.id' }); + const ruleIndices = getMappedNonEcsValue({ data, fieldName: 'signal.rule.index' }); if (ruleId !== undefined) { openAddExceptionModal({ ruleName: ruleName ?? '', ruleId, + ruleIndices: ruleIndices.length > 0 ? ruleIndices : DEFAULT_INDEX_PATTERN, exceptionListType: 'detection', alertData: { ecsData, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 30cfe2d02354f..51dbf9e7f008a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -54,7 +54,7 @@ import { import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers'; import { AddExceptionModal, - AddExceptionOnClick, + AddExceptionModalBaseProps, } from '../../../common/components/exceptions/add_exception_modal'; interface OwnProps { @@ -73,9 +73,10 @@ interface OwnProps { type AlertsTableComponentProps = OwnProps & PropsFromRedux; -const addExceptionModalInitialState: AddExceptionOnClick = { +const addExceptionModalInitialState: AddExceptionModalBaseProps = { ruleName: '', ruleId: '', + ruleIndices: [], exceptionListType: 'detection', alertData: undefined, }; @@ -112,7 +113,7 @@ export const AlertsTableComponent: React.FC = ({ const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); const [shouldShowAddExceptionModal, setShouldShowAddExceptionModal] = useState(false); - const [addExceptionModalState, setAddExceptionModalState] = useState( + const [addExceptionModalState, setAddExceptionModalState] = useState( addExceptionModalInitialState ); const [{ browserFields, indexPatterns }] = useFetchIndexPatterns( @@ -216,12 +217,19 @@ export const AlertsTableComponent: React.FC = ({ ); const openAddExceptionModalCallback = useCallback( - ({ ruleName, ruleId, exceptionListType, alertData }: AddExceptionOnClick) => { + ({ + ruleName, + ruleIndices, + ruleId, + exceptionListType, + alertData, + }: AddExceptionModalBaseProps) => { if (alertData !== null && alertData !== undefined) { setShouldShowAddExceptionModal(true); setAddExceptionModalState({ ruleName, ruleId, + ruleIndices, exceptionListType, alertData, }); @@ -421,12 +429,9 @@ export const AlertsTableComponent: React.FC = ({ closeAddExceptionModal(); }, [closeAddExceptionModal]); - const onAddExceptionConfirm = useCallback( - (didCloseAlert: boolean) => { - closeAddExceptionModal(); - }, - [closeAddExceptionModal] - ); + const onAddExceptionConfirm = useCallback(() => closeAddExceptionModal(), [ + closeAddExceptionModal, + ]); if (loading || isEmpty(signalsIndex)) { return ( @@ -454,6 +459,7 @@ export const AlertsTableComponent: React.FC = ({ = ({ ; format?: Maybe; + /** the elastic type as mapped in the index */ + esTypes?: Maybe; + + subType?: Maybe; +} + +export interface IFieldSubType { + multi?: Maybe; + + nested?: Maybe; +} + +export interface IFieldSubTypeMulti { + parent?: Maybe; +} + +export interface IFieldSubTypeNested { + path?: Maybe; } export interface AuthenticationsData { @@ -2780,6 +2798,30 @@ export namespace SourceQuery { aggregatable: boolean; format: Maybe; + + esTypes: Maybe; + + subType: Maybe; + }; + + export type SubType = { + __typename?: 'IFieldSubType'; + + multi: Maybe; + + nested: Maybe; + }; + + export type Multi = { + __typename?: 'IFieldSubTypeMulti'; + + parent: Maybe; + }; + + export type Nested = { + __typename?: 'IFieldSubTypeNested'; + + path: Maybe; }; } diff --git a/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts index e484b60f8f364..6b3f57d497554 100644 --- a/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts +++ b/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts @@ -7,6 +7,17 @@ import gql from 'graphql-tag'; export const sourceStatusSchema = gql` + type IFieldSubTypeMulti { + parent: String + } + type IFieldSubTypeNested { + path: String + } + type IFieldSubType { + multi: IFieldSubTypeMulti + nested: IFieldSubTypeNested + } + "A descriptor of a field in an index" type IndexField { "Where the field belong" @@ -26,6 +37,9 @@ export const sourceStatusSchema = gql` "Description of the field" description: String format: String + "the elastic type as mapped in the index" + esTypes: [String!] + subType: IFieldSubType } extend type SourceStatus { diff --git a/x-pack/plugins/security_solution/server/graphql/types.ts b/x-pack/plugins/security_solution/server/graphql/types.ts index f8a614e86f28e..27f657923587a 100644 --- a/x-pack/plugins/security_solution/server/graphql/types.ts +++ b/x-pack/plugins/security_solution/server/graphql/types.ts @@ -629,6 +629,24 @@ export interface IndexField { description?: Maybe; format?: Maybe; + /** the elastic type as mapped in the index */ + esTypes?: Maybe; + + subType?: Maybe; +} + +export interface IFieldSubType { + multi?: Maybe; + + nested?: Maybe; +} + +export interface IFieldSubTypeMulti { + parent?: Maybe; +} + +export interface IFieldSubTypeNested { + path?: Maybe; } export interface AuthenticationsData { @@ -3579,6 +3597,10 @@ export namespace IndexFieldResolvers { description?: DescriptionResolver, TypeParent, TContext>; format?: FormatResolver, TypeParent, TContext>; + /** the elastic type as mapped in the index */ + esTypes?: EsTypesResolver, TypeParent, TContext>; + + subType?: SubTypeResolver, TypeParent, TContext>; } export type CategoryResolver = Resolver< @@ -3626,6 +3648,59 @@ export namespace IndexFieldResolvers { Parent = IndexField, TContext = SiemContext > = Resolver; + export type EsTypesResolver< + R = Maybe, + Parent = IndexField, + TContext = SiemContext + > = Resolver; + export type SubTypeResolver< + R = Maybe, + Parent = IndexField, + TContext = SiemContext + > = Resolver; +} + +export namespace IFieldSubTypeResolvers { + export interface Resolvers { + multi?: MultiResolver, TypeParent, TContext>; + + nested?: NestedResolver, TypeParent, TContext>; + } + + export type MultiResolver< + R = Maybe, + Parent = IFieldSubType, + TContext = SiemContext + > = Resolver; + export type NestedResolver< + R = Maybe, + Parent = IFieldSubType, + TContext = SiemContext + > = Resolver; +} + +export namespace IFieldSubTypeMultiResolvers { + export interface Resolvers { + parent?: ParentResolver, TypeParent, TContext>; + } + + export type ParentResolver< + R = Maybe, + Parent = IFieldSubTypeMulti, + TContext = SiemContext + > = Resolver; +} + +export namespace IFieldSubTypeNestedResolvers { + export interface Resolvers { + path?: PathResolver, TypeParent, TContext>; + } + + export type PathResolver< + R = Maybe, + Parent = IFieldSubTypeNested, + TContext = SiemContext + > = Resolver; } export namespace AuthenticationsDataResolvers { @@ -9349,6 +9424,9 @@ export type IResolvers = { SourceFields?: SourceFieldsResolvers.Resolvers; SourceStatus?: SourceStatusResolvers.Resolvers; IndexField?: IndexFieldResolvers.Resolvers; + IFieldSubType?: IFieldSubTypeResolvers.Resolvers; + IFieldSubTypeMulti?: IFieldSubTypeMultiResolvers.Resolvers; + IFieldSubTypeNested?: IFieldSubTypeNestedResolvers.Resolvers; AuthenticationsData?: AuthenticationsDataResolvers.Resolvers; AuthenticationsEdges?: AuthenticationsEdgesResolvers.Resolvers; AuthenticationItem?: AuthenticationItemResolvers.Resolvers; diff --git a/x-pack/plugins/security_solution/server/lib/index_fields/types.ts b/x-pack/plugins/security_solution/server/lib/index_fields/types.ts index 0c894c6980a31..b6141c79c9bf4 100644 --- a/x-pack/plugins/security_solution/server/lib/index_fields/types.ts +++ b/x-pack/plugins/security_solution/server/lib/index_fields/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexField } from '../../graphql/types'; +import { IndexField, IFieldSubType } from '../../graphql/types'; import { FrameworkRequest } from '../framework'; export interface FieldsAdapter { @@ -16,4 +16,6 @@ export interface IndexFieldDescriptor { type: string; searchable: boolean; aggregatable: boolean; + esTypes?: string[] | null; + subType?: IFieldSubType | null; } From 444873416afe3ae27e9b3aa889ebd6dd8863d5a7 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Fri, 17 Jul 2020 12:50:05 -0400 Subject: [PATCH 2/8] updating graphql types --- .../components/autocomplete/field_value_lists.tsx | 8 ++++++-- .../exceptions/add_exception_modal/index.tsx | 5 ++++- .../public/graphql/introspection.json | 10 +++++++--- .../security_solution/public/graphql/types.ts | 4 ++-- .../server/graphql/source_status/schema.gql.ts | 2 +- .../security_solution/server/graphql/types.ts | 14 +++++++------- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx index d8ce27e97874d..a9d85452651b5 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx @@ -36,8 +36,12 @@ export const AutocompleteFieldListsComponent: React.FC name, []); const optionsMemo = useMemo(() => { - if (selectedField != null) { - return lists.filter(({ type }) => type === selectedField.type); + if ( + selectedField != null && + selectedField.esTypes != null && + selectedField.esTypes.length > 0 + ) { + return lists.filter(({ type }) => selectedField.esTypes?.includes(type)); } else { return []; } diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index db670805ee470..0589c530b597a 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -45,7 +45,7 @@ import { } from '../helpers'; import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules'; -export interface AddExceptionModalProps { +export interface AddExceptionModalBaseProps { ruleName: string; ruleId: string; exceptionListType: ExceptionListType; @@ -54,6 +54,9 @@ export interface AddExceptionModalProps { ecsData: Ecs; nonEcsData: TimelineNonEcsData[]; }; +} + +export interface AddExceptionModalProps extends AddExceptionModalBaseProps { onCancel: () => void; onConfirm: (didCloseAlert: boolean) => void; } diff --git a/x-pack/plugins/security_solution/public/graphql/introspection.json b/x-pack/plugins/security_solution/public/graphql/introspection.json index 0d9ec58ddc234..a927c99d645f7 100644 --- a/x-pack/plugins/security_solution/public/graphql/introspection.json +++ b/x-pack/plugins/security_solution/public/graphql/introspection.json @@ -2675,12 +2675,16 @@ "description": "the elastic type as mapped in the index", "args": [], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } } }, "isDeprecated": false, diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts index 15ce40e812320..09c2e5b5e64de 100644 --- a/x-pack/plugins/security_solution/public/graphql/types.ts +++ b/x-pack/plugins/security_solution/public/graphql/types.ts @@ -628,7 +628,7 @@ export interface IndexField { format?: Maybe; /** the elastic type as mapped in the index */ - esTypes?: Maybe; + esTypes: string[]; subType?: Maybe; } @@ -2799,7 +2799,7 @@ export namespace SourceQuery { format: Maybe; - esTypes: Maybe; + esTypes: string[]; subType: Maybe; }; diff --git a/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts index 6b3f57d497554..a0558312db458 100644 --- a/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts +++ b/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts @@ -38,7 +38,7 @@ export const sourceStatusSchema = gql` description: String format: String "the elastic type as mapped in the index" - esTypes: [String!] + esTypes: [String!]! subType: IFieldSubType } diff --git a/x-pack/plugins/security_solution/server/graphql/types.ts b/x-pack/plugins/security_solution/server/graphql/types.ts index 27f657923587a..f53ac44075119 100644 --- a/x-pack/plugins/security_solution/server/graphql/types.ts +++ b/x-pack/plugins/security_solution/server/graphql/types.ts @@ -630,7 +630,7 @@ export interface IndexField { format?: Maybe; /** the elastic type as mapped in the index */ - esTypes?: Maybe; + esTypes: string[]; subType?: Maybe; } @@ -3598,7 +3598,7 @@ export namespace IndexFieldResolvers { format?: FormatResolver, TypeParent, TContext>; /** the elastic type as mapped in the index */ - esTypes?: EsTypesResolver, TypeParent, TContext>; + esTypes?: EsTypesResolver; subType?: SubTypeResolver, TypeParent, TContext>; } @@ -3648,11 +3648,11 @@ export namespace IndexFieldResolvers { Parent = IndexField, TContext = SiemContext > = Resolver; - export type EsTypesResolver< - R = Maybe, - Parent = IndexField, - TContext = SiemContext - > = Resolver; + export type EsTypesResolver = Resolver< + R, + Parent, + TContext + >; export type SubTypeResolver< R = Maybe, Parent = IndexField, From f059d70ace4b448770bc54ca109339ff6ce49f2a Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Fri, 17 Jul 2020 15:27:38 -0400 Subject: [PATCH 3/8] update source graphql type to match expected --- .../components/autocomplete/helpers.test.ts | 24 ++--- .../common/components/autocomplete/helpers.ts | 40 ++++---- .../components/exceptions/builder/index.tsx | 2 +- .../containers/source/index.gql_query.ts | 9 +- .../public/graphql/introspection.json | 77 ++-------------- .../security_solution/public/graphql/types.ts | 46 ++-------- .../server/graphql/ecs/resolvers.ts | 34 ++++++- .../server/graphql/ecs/schema.gql.ts | 1 + .../server/graphql/source_status/resolvers.ts | 39 ++++++++ .../graphql/source_status/schema.gql.ts | 15 +-- .../security_solution/server/graphql/types.ts | 92 +++++-------------- .../server/lib/index_fields/types.ts | 4 +- 12 files changed, 149 insertions(+), 234 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts index cfe23b9391ec0..c7e6e22d4ce71 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts @@ -55,49 +55,37 @@ describe('helpers', () => { describe('#validateParams', () => { test('returns true if value is undefined', () => { - const isValid = validateParams(undefined, 'date'); + const isValid = validateParams(undefined, getField('@timestamp')); expect(isValid).toBeTruthy(); }); test('returns true if value is empty string', () => { - const isValid = validateParams('', 'date'); + const isValid = validateParams('', getField('@timestamp')); expect(isValid).toBeTruthy(); }); test('returns true if type is "date" and value is valid', () => { - const isValid = validateParams('1994-11-05T08:15:30-05:00', 'date'); + const isValid = validateParams('1994-11-05T08:15:30-05:00', getField('@timestamp')); expect(isValid).toBeTruthy(); }); test('returns false if type is "date" and value is not valid', () => { - const isValid = validateParams('1593478826', 'date'); + const isValid = validateParams('1593478826', getField('@timestamp')); expect(isValid).toBeFalsy(); }); test('returns true if type is "ip" and value is valid', () => { - const isValid = validateParams('126.45.211.34', 'ip'); + const isValid = validateParams('126.45.211.34', getField('ip')); expect(isValid).toBeTruthy(); }); test('returns false if type is "ip" and value is not valid', () => { - const isValid = validateParams('hellooo', 'ip'); - - expect(isValid).toBeFalsy(); - }); - - test('returns true if type is "number" and value is valid', () => { - const isValid = validateParams('123', 'number'); - - expect(isValid).toBeTruthy(); - }); - - test('returns false if type is "number" and value is not valid', () => { - const isValid = validateParams('not a number', 'number'); + const isValid = validateParams('hellooo', getField('ip')); expect(isValid).toBeFalsy(); }); diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts index 483ca5d6d332e..3363f49ed750b 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts @@ -30,29 +30,33 @@ export const getOperators = (field: IFieldType | undefined): OperatorOption[] => } }; -export function validateParams(params: string | undefined, type: string) { +export const validateParams = ( + params: string | undefined, + field: IFieldType | undefined +): boolean => { // Box would show error state if empty otherwise if (params == null || params === '') { return true; } - switch (type) { - case 'date': - const moment = dateMath.parse(params); - return Boolean(moment && moment.isValid()); - case 'ip': - try { - return Boolean(new Ipv4Address(params)); - } catch (e) { - return false; - } - case 'number': - const val = parseFloat(params); - return typeof val === 'number' && !isNaN(val); - default: - return true; - } -} + const types = field != null && field.esTypes != null ? field.esTypes : []; + + return types.reduce((acc, type) => { + switch (type) { + case 'date': + const moment = dateMath.parse(params); + return Boolean(moment && moment.isValid()); + case 'ip': + try { + return Boolean(new Ipv4Address(params)); + } catch (e) { + return false; + } + default: + return acc; + } + }, true); +}; export function getGenericComboBoxProps({ options, diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx index 5fb94ac0e9cc0..3cc479fa8d287 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useState, useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; -import { ExceptionListItemComponent } from './exception_item'; +import { ExceptionListItemComponent } from './builder_exception_item'; import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; import { ExceptionListItemSchema, diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts b/x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts index 5929b1af58562..630515c5cbed4 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts @@ -23,14 +23,7 @@ export const sourceQuery = gql` aggregatable format esTypes - subType { - multi { - parent - } - nested { - path - } - } + subType } } } diff --git a/x-pack/plugins/security_solution/public/graphql/introspection.json b/x-pack/plugins/security_solution/public/graphql/introspection.json index a927c99d645f7..f40161ff9b4c2 100644 --- a/x-pack/plugins/security_solution/public/graphql/introspection.json +++ b/x-pack/plugins/security_solution/public/graphql/introspection.json @@ -2674,19 +2674,7 @@ "name": "esTypes", "description": "the elastic type as mapped in the index", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, + "type": { "kind": "SCALAR", "name": "ToStringArrayNoNullable", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, @@ -2694,34 +2682,7 @@ "name": "subType", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "IFieldSubType", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "IFieldSubType", - "description": "", - "fields": [ - { - "name": "multi", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "IFieldSubTypeMulti", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "nested", - "description": "", - "args": [], - "type": { "kind": "OBJECT", "name": "IFieldSubTypeNested", "ofType": null }, + "type": { "kind": "SCALAR", "name": "ToIFieldSubTypeNonNullable", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -2732,40 +2693,22 @@ "possibleTypes": null }, { - "kind": "OBJECT", - "name": "IFieldSubTypeMulti", + "kind": "SCALAR", + "name": "ToStringArrayNoNullable", "description": "", - "fields": [ - { - "name": "parent", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], + "fields": null, "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, { - "kind": "OBJECT", - "name": "IFieldSubTypeNested", + "kind": "SCALAR", + "name": "ToIFieldSubTypeNonNullable", "description": "", - "fields": [ - { - "name": "path", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], + "fields": null, "inputFields": null, - "interfaces": [], + "interfaces": null, "enumValues": null, "possibleTypes": null }, diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts index 09c2e5b5e64de..78ce8abce6cd0 100644 --- a/x-pack/plugins/security_solution/public/graphql/types.ts +++ b/x-pack/plugins/security_solution/public/graphql/types.ts @@ -428,6 +428,10 @@ export enum FlowDirection { biDirectional = 'biDirectional', } +export type ToStringArrayNoNullable = any; + +export type ToIFieldSubTypeNonNullable = any; + export type ToStringArray = string[]; export type Date = string; @@ -628,23 +632,9 @@ export interface IndexField { format?: Maybe; /** the elastic type as mapped in the index */ - esTypes: string[]; - - subType?: Maybe; -} - -export interface IFieldSubType { - multi?: Maybe; + esTypes?: Maybe; - nested?: Maybe; -} - -export interface IFieldSubTypeMulti { - parent?: Maybe; -} - -export interface IFieldSubTypeNested { - path?: Maybe; + subType?: Maybe; } export interface AuthenticationsData { @@ -2799,29 +2789,9 @@ export namespace SourceQuery { format: Maybe; - esTypes: string[]; + esTypes: Maybe; - subType: Maybe; - }; - - export type SubType = { - __typename?: 'IFieldSubType'; - - multi: Maybe; - - nested: Maybe; - }; - - export type Multi = { - __typename?: 'IFieldSubTypeMulti'; - - parent: Maybe; - }; - - export type Nested = { - __typename?: 'IFieldSubTypeNested'; - - path: Maybe; + subType: Maybe; }; } diff --git a/x-pack/plugins/security_solution/server/graphql/ecs/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/ecs/resolvers.ts index f30b7d192d05d..414e5b5d95bec 100644 --- a/x-pack/plugins/security_solution/server/graphql/ecs/resolvers.ts +++ b/x-pack/plugins/security_solution/server/graphql/ecs/resolvers.ts @@ -47,9 +47,41 @@ export const toStringArrayScalar = new GraphQLScalarType({ return null; }, }); - +export const toStringArrayNoNullableScalar = new GraphQLScalarType({ + name: 'StringArray', + description: 'Represents value in detail item from the timeline who wants to more than one type', + serialize(value): string[] | undefined { + if (value == null) { + return undefined; + } else if (Array.isArray(value)) { + return convertArrayToString(value) as string[]; + } else if (isBoolean(value) || isNumber(value) || isObject(value)) { + return [convertToString(value)]; + } + return [value]; + }, + parseValue(value) { + return value; + }, + parseLiteral(ast) { + switch (ast.kind) { + case Kind.INT: + return parseInt(ast.value, 10); + case Kind.FLOAT: + return parseFloat(ast.value); + case Kind.STRING: + return ast.value; + case Kind.LIST: + return ast.values; + case Kind.OBJECT: + return ast.fields; + } + return undefined; + }, +}); export const createScalarToStringArrayValueResolvers = () => ({ ToStringArray: toStringArrayScalar, + ToStringArrayNoNullable: toStringArrayNoNullableScalar, }); const convertToString = (value: object | number | boolean | string): string => { diff --git a/x-pack/plugins/security_solution/server/graphql/ecs/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/ecs/schema.gql.ts index 5b093a02b6514..bdc69f85d3542 100644 --- a/x-pack/plugins/security_solution/server/graphql/ecs/schema.gql.ts +++ b/x-pack/plugins/security_solution/server/graphql/ecs/schema.gql.ts @@ -8,6 +8,7 @@ import gql from 'graphql-tag'; export const ecsSchema = gql` scalar ToStringArray + scalar ToStringArrayNoNullable type EventEcsFields { action: ToStringArray diff --git a/x-pack/plugins/security_solution/server/graphql/source_status/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/source_status/resolvers.ts index 24589822f0250..8d55e645d6791 100644 --- a/x-pack/plugins/security_solution/server/graphql/source_status/resolvers.ts +++ b/x-pack/plugins/security_solution/server/graphql/source_status/resolvers.ts @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import { GraphQLScalarType, Kind } from 'graphql'; import { SourceStatusResolvers } from '../../graphql/types'; import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; import { IndexFields } from '../../lib/index_fields'; import { SourceStatus } from '../../lib/source_status'; import { QuerySourceResolver } from '../sources/resolvers'; +import { IFieldSubType } from '../../../../../../src/plugins/data/common/index_patterns/types'; export type SourceStatusIndicesExistResolver = ChildResolverOf< AppResolverOf, @@ -50,3 +52,40 @@ export const createSourceStatusResolvers = (libs: { }, }, }); + +export const toIFieldSubTypeNonNullableScalar = new GraphQLScalarType({ + name: 'IFieldSubType', + description: 'Represents value in index pattern field item', + serialize(value): IFieldSubType | undefined { + if (value == null) { + return undefined; + } + + return { + multi: value.multi ?? undefined, + nested: value.nested ?? undefined, + }; + }, + parseValue(value) { + return value; + }, + parseLiteral(ast) { + switch (ast.kind) { + case Kind.INT: + return undefined; + case Kind.FLOAT: + return undefined; + case Kind.STRING: + return undefined; + case Kind.LIST: + return undefined; + case Kind.OBJECT: + return ast; + } + return undefined; + }, +}); + +export const createScalarToIFieldSubTypeNonNullableScalarResolvers = () => ({ + ToIFieldSubTypeNonNullable: toIFieldSubTypeNonNullableScalar, +}); diff --git a/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts index a0558312db458..3062113f1b635 100644 --- a/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts +++ b/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts @@ -7,16 +7,7 @@ import gql from 'graphql-tag'; export const sourceStatusSchema = gql` - type IFieldSubTypeMulti { - parent: String - } - type IFieldSubTypeNested { - path: String - } - type IFieldSubType { - multi: IFieldSubTypeMulti - nested: IFieldSubTypeNested - } + scalar ToIFieldSubTypeNonNullable "A descriptor of a field in an index" type IndexField { @@ -38,8 +29,8 @@ export const sourceStatusSchema = gql` description: String format: String "the elastic type as mapped in the index" - esTypes: [String!]! - subType: IFieldSubType + esTypes: ToStringArrayNoNullable + subType: ToIFieldSubTypeNonNullable } extend type SourceStatus { diff --git a/x-pack/plugins/security_solution/server/graphql/types.ts b/x-pack/plugins/security_solution/server/graphql/types.ts index f53ac44075119..1e397a4e6bb6c 100644 --- a/x-pack/plugins/security_solution/server/graphql/types.ts +++ b/x-pack/plugins/security_solution/server/graphql/types.ts @@ -430,6 +430,10 @@ export enum FlowDirection { biDirectional = 'biDirectional', } +export type ToStringArrayNoNullable = any; + +export type ToIFieldSubTypeNonNullable = any; + export type ToStringArray = string[] | string; export type Date = string; @@ -630,23 +634,9 @@ export interface IndexField { format?: Maybe; /** the elastic type as mapped in the index */ - esTypes: string[]; - - subType?: Maybe; -} - -export interface IFieldSubType { - multi?: Maybe; + esTypes?: Maybe; - nested?: Maybe; -} - -export interface IFieldSubTypeMulti { - parent?: Maybe; -} - -export interface IFieldSubTypeNested { - path?: Maybe; + subType?: Maybe; } export interface AuthenticationsData { @@ -3598,9 +3588,9 @@ export namespace IndexFieldResolvers { format?: FormatResolver, TypeParent, TContext>; /** the elastic type as mapped in the index */ - esTypes?: EsTypesResolver; + esTypes?: EsTypesResolver, TypeParent, TContext>; - subType?: SubTypeResolver, TypeParent, TContext>; + subType?: SubTypeResolver, TypeParent, TContext>; } export type CategoryResolver = Resolver< @@ -3648,57 +3638,14 @@ export namespace IndexFieldResolvers { Parent = IndexField, TContext = SiemContext > = Resolver; - export type EsTypesResolver = Resolver< - R, - Parent, - TContext - >; - export type SubTypeResolver< - R = Maybe, + export type EsTypesResolver< + R = Maybe, Parent = IndexField, TContext = SiemContext > = Resolver; -} - -export namespace IFieldSubTypeResolvers { - export interface Resolvers { - multi?: MultiResolver, TypeParent, TContext>; - - nested?: NestedResolver, TypeParent, TContext>; - } - - export type MultiResolver< - R = Maybe, - Parent = IFieldSubType, - TContext = SiemContext - > = Resolver; - export type NestedResolver< - R = Maybe, - Parent = IFieldSubType, - TContext = SiemContext - > = Resolver; -} - -export namespace IFieldSubTypeMultiResolvers { - export interface Resolvers { - parent?: ParentResolver, TypeParent, TContext>; - } - - export type ParentResolver< - R = Maybe, - Parent = IFieldSubTypeMulti, - TContext = SiemContext - > = Resolver; -} - -export namespace IFieldSubTypeNestedResolvers { - export interface Resolvers { - path?: PathResolver, TypeParent, TContext>; - } - - export type PathResolver< - R = Maybe, - Parent = IFieldSubTypeNested, + export type SubTypeResolver< + R = Maybe, + Parent = IndexField, TContext = SiemContext > = Resolver; } @@ -9392,6 +9339,14 @@ export interface DeprecatedDirectiveArgs { reason?: string; } +export interface ToStringArrayNoNullableScalarConfig + extends GraphQLScalarTypeConfig { + name: 'ToStringArrayNoNullable'; +} +export interface ToIFieldSubTypeNonNullableScalarConfig + extends GraphQLScalarTypeConfig { + name: 'ToIFieldSubTypeNonNullable'; +} export interface ToStringArrayScalarConfig extends GraphQLScalarTypeConfig { name: 'ToStringArray'; } @@ -9424,9 +9379,6 @@ export type IResolvers = { SourceFields?: SourceFieldsResolvers.Resolvers; SourceStatus?: SourceStatusResolvers.Resolvers; IndexField?: IndexFieldResolvers.Resolvers; - IFieldSubType?: IFieldSubTypeResolvers.Resolvers; - IFieldSubTypeMulti?: IFieldSubTypeMultiResolvers.Resolvers; - IFieldSubTypeNested?: IFieldSubTypeNestedResolvers.Resolvers; AuthenticationsData?: AuthenticationsDataResolvers.Resolvers; AuthenticationsEdges?: AuthenticationsEdgesResolvers.Resolvers; AuthenticationItem?: AuthenticationItemResolvers.Resolvers; @@ -9568,6 +9520,8 @@ export type IResolvers = { EventsTimelineData?: EventsTimelineDataResolvers.Resolvers; OsFields?: OsFieldsResolvers.Resolvers; HostFields?: HostFieldsResolvers.Resolvers; + ToStringArrayNoNullable?: GraphQLScalarType; + ToIFieldSubTypeNonNullable?: GraphQLScalarType; ToStringArray?: GraphQLScalarType; Date?: GraphQLScalarType; ToNumberArray?: GraphQLScalarType; diff --git a/x-pack/plugins/security_solution/server/lib/index_fields/types.ts b/x-pack/plugins/security_solution/server/lib/index_fields/types.ts index b6141c79c9bf4..46f06f8ccfe97 100644 --- a/x-pack/plugins/security_solution/server/lib/index_fields/types.ts +++ b/x-pack/plugins/security_solution/server/lib/index_fields/types.ts @@ -16,6 +16,6 @@ export interface IndexFieldDescriptor { type: string; searchable: boolean; aggregatable: boolean; - esTypes?: string[] | null; - subType?: IFieldSubType | null; + esTypes?: string[]; + subType?: IFieldSubType; } From 42de9909175dbc44932750a338789ce36d92f99a Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Fri, 17 Jul 2020 15:51:07 -0400 Subject: [PATCH 4/8] cleanup --- .../common/components/autocomplete/field_value_match.tsx | 8 ++++---- .../components/autocomplete/field_value_match_any.tsx | 2 +- .../security_solution/server/lib/index_fields/types.ts | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx index 32a82af114bae..a082811920f88 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx @@ -79,10 +79,10 @@ export const AutocompleteFieldMatchComponent: React.FC validateParams(selectedValue, selectedField ? selectedField.type : ''), - [selectedField, selectedValue] - ); + const isValid = useMemo((): boolean => validateParams(selectedValue, selectedField), [ + selectedField, + selectedValue, + ]); return ( { const areAnyInvalid = selectedComboOptions.filter( - ({ label }) => !validateParams(label, selectedField ? selectedField.type : '') + ({ label }) => !validateParams(label, selectedField) ); return areAnyInvalid.length === 0; }, [selectedComboOptions, selectedField]); diff --git a/x-pack/plugins/security_solution/server/lib/index_fields/types.ts b/x-pack/plugins/security_solution/server/lib/index_fields/types.ts index 46f06f8ccfe97..67b3c254007e2 100644 --- a/x-pack/plugins/security_solution/server/lib/index_fields/types.ts +++ b/x-pack/plugins/security_solution/server/lib/index_fields/types.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexField, IFieldSubType } from '../../graphql/types'; +import { IndexField } from '../../graphql/types'; import { FrameworkRequest } from '../framework'; +import { IFieldSubType } from '../../../../../../src/plugins/data/common'; export interface FieldsAdapter { getIndexFields(req: FrameworkRequest, indices: string[]): Promise; From ebcb6d3c72b0bcb7031da5b93b4136e314a110b6 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Sat, 18 Jul 2020 23:19:51 -0400 Subject: [PATCH 5/8] tests --- .../field_value_lists.test.tsx.snap | 237 ++++++++++++++++++ .../autocomplete/field_value_lists.test.tsx | 155 ++++++++---- .../exceptions/add_exception_modal/index.tsx | 28 ++- .../exceptions/edit_exception_modal/index.tsx | 24 +- .../detection_engine/rules/details/index.tsx | 4 +- 5 files changed, 390 insertions(+), 58 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/autocomplete/__snapshots__/field_value_lists.test.tsx.snap diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/__snapshots__/field_value_lists.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/autocomplete/__snapshots__/field_value_lists.test.tsx.snap new file mode 100644 index 0000000000000..55a9cf35d7ceb --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/__snapshots__/field_value_lists.test.tsx.snap @@ -0,0 +1,237 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AutocompleteFieldListsComponent it correctly displays lists that match the selected "keyword" field esType 1`] = ` + + + +
+ + +
+
+
+

+ Placeholder text +

+ +
+ +
+
+ +
+ +
+ + + +
+
+
+
+ + +
+ + + +`; diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx index 7734344d193b8..2ec6ce9510cb3 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx @@ -8,14 +8,22 @@ import { ThemeProvider } from 'styled-components'; import { mount } from 'enzyme'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; +import { act } from 'react-dom/test-utils'; import { getField } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks.ts'; import { AutocompleteFieldListsComponent } from './field_value_lists'; import { getFoundListSchemaMock } from '../../../../../lists/common/schemas/response/found_list_schema.mock'; +import { getListResponseMock } from '../../../../../lists/common/schemas/response/list_schema.mock'; +import { wait } from '../../../common/lib/helpers'; +jest.mock('../../../common/lib/kibana'); const mockStart = jest.fn(); +const mockKeywordList = getListResponseMock(); +mockKeywordList.id = 'keyword_list'; +mockKeywordList.type = 'keyword'; +mockKeywordList.name = 'keyword list'; const mockResult = getFoundListSchemaMock(); -jest.mock('../../../common/lib/kibana'); +mockResult.data = [...mockResult.data, mockKeywordList]; jest.mock('../../../lists_plugin_deps', () => { const originalModule = jest.requireActual('../../../lists_plugin_deps'); @@ -31,78 +39,135 @@ jest.mock('../../../lists_plugin_deps', () => { }); describe('AutocompleteFieldListsComponent', () => { - test('it renders disabled if "isDisabled" is true', () => { - const wrapper = mount( - ({ eui: euiLightVars, darkMode: false })}> - - - ); + xtest('it renders disabled if "isDisabled" is true', async () => { + await act(async () => { + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + await wait(); + expect( + wrapper + .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] input`) + .prop('disabled') + ).toBeTruthy(); + }); + }); - expect( + xtest('it renders loading if "isLoading" is true', async () => { + await act(async () => { + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + await wait(); wrapper - .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] input`) - .prop('disabled') - ).toBeTruthy(); + .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] button`) + .at(0) + .simulate('click'); + expect( + wrapper + .find( + `EuiComboBoxOptionsList[data-test-subj="valuesAutocompleteComboBox listsComboxBox-optionsList"]` + ) + .prop('isLoading') + ).toBeTruthy(); + }); }); - test('it renders loading if "isLoading" is true', () => { + test('it allows user to clear values if "isClearable" is true', async () => { + await act(async () => { + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + await wait(); + expect( + wrapper + .find(`[data-test-subj="comboBoxInput"]`) + .hasClass('euiComboBox__inputWrap-isClearable') + ).toBeTruthy(); + }); + }); + + test('it correctly displays lists that match the selected "keyword" field esType', async () => { const wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> ); - wrapper - .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] button`) - .at(0) - .simulate('click'); + + await act(async () => { + wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click'); + }); + expect( - wrapper - .find( - `EuiComboBoxOptionsList[data-test-subj="valuesAutocompleteComboBox listsComboxBox-optionsList"]` - ) - .prop('isLoading') - ).toBeTruthy(); + wrapper.find('[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]').at(0).props() + .options + ).toEqual([{ label: 'keyword list' }]); }); - test('it allows user to clear values if "isClearable" is true', () => { + test('it correctly displays lists that match the selected "ip" field esType', async () => { const wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> ); + await act(async () => { + wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click'); + }); + expect( - wrapper - .find(`[data-test-subj="comboBoxInput"]`) - .hasClass('euiComboBox__inputWrap-isClearable') - ).toBeTruthy(); + wrapper.find('[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]').at(0).props() + .options + ).toEqual([{ label: 'some name' }]); }); - test('it correctly displays selected list', () => { + test('it correctly displays selected list', async () => { const wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> { ); - ((wrapper.find(EuiComboBox).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([{ label: 'some name' }]); + await act(async () => { + ((wrapper.find(EuiComboBox).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange([{ label: 'some name' }]); + }); expect(mockOnChange).toHaveBeenCalledWith({ created_at: '2020-04-20T15:25:31.830Z', diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index 9c020a7ef9f4a..7cbea4ab4cc04 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -44,6 +44,7 @@ import { getMappedNonEcsValue, } from '../helpers'; import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules'; +import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; export interface AddExceptionModalBaseProps { ruleName: string; @@ -106,9 +107,15 @@ export const AddExceptionModal = memo(function AddExceptionModal({ Array >([]); const [fetchOrCreateListError, setFetchOrCreateListError] = useState(false); - const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns(ruleIndices); const { addError, addSuccess } = useAppToasts(); + const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns(ruleIndices); + + const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); + const [ + { isLoading: signalIndexPatternLoading, indexPatterns: signalIndexPatterns }, + ] = useFetchIndexPatterns(signalIndexName !== null ? [signalIndexName] : []); + const onError = useCallback( (error: Error) => { addError(error, { title: i18n.ADD_EXCEPTION_ERROR }); @@ -167,13 +174,19 @@ export const AddExceptionModal = memo(function AddExceptionModal({ }, [alertData, exceptionListType, ruleExceptionList, ruleName]); useEffect(() => { - if (indexPatternLoading === false) { + if (signalIndexPatternLoading === false && isSignalIndexLoading === false) { setShouldDisableBulkClose( entryHasListType(exceptionItemsToAdd) || - entryHasNonEcsType(exceptionItemsToAdd, indexPatterns) + entryHasNonEcsType(exceptionItemsToAdd, signalIndexPatterns) ); } - }, [setShouldDisableBulkClose, exceptionItemsToAdd, indexPatternLoading, indexPatterns]); + }, [ + setShouldDisableBulkClose, + exceptionItemsToAdd, + signalIndexPatternLoading, + signalIndexPatterns, + isSignalIndexLoading, + ]); useEffect(() => { if (shouldDisableBulkClose === true) { @@ -233,7 +246,8 @@ export const AddExceptionModal = memo(function AddExceptionModal({ const onAddExceptionConfirm = useCallback(() => { if (addOrUpdateExceptionItems !== null) { const alertIdToClose = shouldCloseAlert && alertData ? alertData.ecsData._id : undefined; - const bulkCloseIndex = shouldBulkCloseAlert && ruleIndices !== null ? ruleIndices : undefined; + const bulkCloseIndex = + shouldBulkCloseAlert && signalIndexName !== null ? [signalIndexName] : undefined; addOrUpdateExceptionItems(enrichExceptionItems(), alertIdToClose, bulkCloseIndex); } }, [ @@ -242,7 +256,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ shouldCloseAlert, shouldBulkCloseAlert, alertData, - ruleIndices, + signalIndexName, ]); const isSubmitButtonDisabled = useCallback( @@ -269,6 +283,8 @@ export const AddExceptionModal = memo(function AddExceptionModal({ )} {fetchOrCreateListError === false && + !isSignalIndexLoading && + !signalIndexPatternLoading && !indexPatternLoading && !isLoadingExceptionList && ruleExceptionList && ( diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx index f3a484b9a4cf3..7c90f75690fc0 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx @@ -21,6 +21,7 @@ import { EuiText, } from '@elastic/eui'; import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules'; +import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; import { ExceptionListItemSchema, CreateExceptionListItemSchema, @@ -94,6 +95,11 @@ export const EditExceptionModal = memo(function EditExceptionModal({ const { addError, addSuccess } = useAppToasts(); const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns(ruleIndices); + const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); + const [ + { isLoading: signalIndexPatternLoading, indexPatterns: signalIndexPatterns }, + ] = useFetchIndexPatterns(signalIndexName !== null ? [signalIndexName] : []); + const onError = useCallback( (error) => { addError(error, { title: i18n.EDIT_EXCEPTION_ERROR }); @@ -115,13 +121,19 @@ export const EditExceptionModal = memo(function EditExceptionModal({ ); useEffect(() => { - if (indexPatternLoading === false) { + if (signalIndexPatternLoading === false && isSignalIndexLoading === false) { setShouldDisableBulkClose( entryHasListType(exceptionItemsToAdd) || - entryHasNonEcsType(exceptionItemsToAdd, indexPatterns) + entryHasNonEcsType(exceptionItemsToAdd, signalIndexPatterns) ); } - }, [setShouldDisableBulkClose, exceptionItemsToAdd, indexPatternLoading, indexPatterns]); + }, [ + setShouldDisableBulkClose, + exceptionItemsToAdd, + signalIndexPatternLoading, + signalIndexPatterns, + isSignalIndexLoading, + ]); useEffect(() => { if (shouldDisableBulkClose === true) { @@ -170,10 +182,10 @@ export const EditExceptionModal = memo(function EditExceptionModal({ const onEditExceptionConfirm = useCallback(() => { if (addOrUpdateExceptionItems !== null) { const bulkCloseIndex = - shouldBulkCloseAlert && ruleIndices.length > 0 ? ruleIndices : undefined; + shouldBulkCloseAlert && signalIndexName !== null ? [signalIndexName] : undefined; addOrUpdateExceptionItems(enrichExceptionItems(), undefined, bulkCloseIndex); } - }, [addOrUpdateExceptionItems, enrichExceptionItems, shouldBulkCloseAlert, ruleIndices]); + }, [addOrUpdateExceptionItems, enrichExceptionItems, shouldBulkCloseAlert, signalIndexName]); return ( @@ -185,7 +197,7 @@ export const EditExceptionModal = memo(function EditExceptionModal({ - {!indexPatternLoading && ( + {!indexPatternLoading && !isSignalIndexLoading && !signalIndexPatternLoading && ( <> {i18n.EXCEPTION_BUILDER_INFO} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 65c96ab11c19a..f913cafec9a96 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -81,7 +81,7 @@ import { SecurityPageName } from '../../../../../app/types'; import { LinkButton } from '../../../../../common/components/links'; import { useFormatUrl } from '../../../../../common/components/link_to'; import { ExceptionsViewer } from '../../../../../common/components/exceptions/viewer'; -import { FILTERS_GLOBAL_HEIGHT } from '../../../../../../common/constants'; +import { DEFAULT_INDEX_PATTERN, FILTERS_GLOBAL_HEIGHT } from '../../../../../../common/constants'; import { useFullScreen } from '../../../../../common/containers/use_full_screen'; import { Display } from '../../../../../hosts/pages/display'; import { ExceptionListTypeEnum, ExceptionIdentifiers } from '../../../../../lists_plugin_deps'; @@ -515,7 +515,7 @@ export const RuleDetailsPageComponent: FC = ({ Date: Sat, 18 Jul 2020 23:33:36 -0400 Subject: [PATCH 6/8] cleanup --- .../field_value_lists.test.tsx.snap | 237 ------------------ .../autocomplete/field_value_lists.test.tsx | 4 +- 2 files changed, 2 insertions(+), 239 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/autocomplete/__snapshots__/field_value_lists.test.tsx.snap diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/__snapshots__/field_value_lists.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/autocomplete/__snapshots__/field_value_lists.test.tsx.snap deleted file mode 100644 index 55a9cf35d7ceb..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/__snapshots__/field_value_lists.test.tsx.snap +++ /dev/null @@ -1,237 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AutocompleteFieldListsComponent it correctly displays lists that match the selected "keyword" field esType 1`] = ` - - - -
- - -
-
-
-

- Placeholder text -

- -
- -
-
- -
- -
- - - -
-
-
-
- - -
- - - -`; diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx index 2ec6ce9510cb3..449e72f47b6d7 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx @@ -39,7 +39,7 @@ jest.mock('../../../lists_plugin_deps', () => { }); describe('AutocompleteFieldListsComponent', () => { - xtest('it renders disabled if "isDisabled" is true', async () => { + test('it renders disabled if "isDisabled" is true', async () => { await act(async () => { const wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> @@ -63,7 +63,7 @@ describe('AutocompleteFieldListsComponent', () => { }); }); - xtest('it renders loading if "isLoading" is true', async () => { + test('it renders loading if "isLoading" is true', async () => { await act(async () => { const wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> From 760ed31ff3f2a49c994f771768ebff7fecf8527c Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Sun, 19 Jul 2020 21:31:03 -0400 Subject: [PATCH 7/8] updated tests, reverted changes to modals, to make sure they were still checking signals index for closing alerts functionality --- .../autocomplete/field_value_lists.test.tsx | 61 +++++++++---------- .../exceptions/add_exception_modal/index.tsx | 22 +++---- .../components/exceptions/builder/index.tsx | 6 -- .../exceptions/edit_exception_modal/index.tsx | 17 +++--- 4 files changed, 46 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx index 449e72f47b6d7..51a7fd6abf513 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx @@ -94,30 +94,27 @@ describe('AutocompleteFieldListsComponent', () => { }); test('it allows user to clear values if "isClearable" is true', async () => { - await act(async () => { - const wrapper = mount( - ({ eui: euiLightVars, darkMode: false })}> - - - ); - await wait(); - expect( - wrapper - .find(`[data-test-subj="comboBoxInput"]`) - .hasClass('euiComboBox__inputWrap-isClearable') - ).toBeTruthy(); - }); + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + expect( + wrapper + .find(`[data-test-subj="comboBoxInput"]`) + .hasClass('euiComboBox__inputWrap-isClearable') + ).toBeTruthy(); }); - test('it correctly displays lists that match the selected "keyword" field esType', async () => { + test('it correctly displays lists that match the selected "keyword" field esType', () => { const wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> { ); - await act(async () => { - wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click'); - }); + wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click'); expect( - wrapper.find('[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]').at(0).props() - .options + wrapper + .find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]') + .prop('options') ).toEqual([{ label: 'keyword list' }]); }); - test('it correctly displays lists that match the selected "ip" field esType', async () => { + test('it correctly displays lists that match the selected "ip" field esType', () => { const wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> { ); - await act(async () => { - wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click'); - }); + wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click'); expect( - wrapper.find('[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]').at(0).props() - .options + wrapper + .find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]') + .prop('options') ).toEqual([{ label: 'some name' }]); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index 7cbea4ab4cc04..17f52f5e56aba 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -33,6 +33,7 @@ import { useKibana } from '../../../lib/kibana'; import { ExceptionBuilder } from '../builder'; import { Loader } from '../../loader'; import { useAddOrUpdateException } from '../use_add_exception'; +import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; import { useFetchOrCreateRuleExceptionList } from '../use_fetch_or_create_rule_exception_list'; import { AddExceptionComments } from '../add_exception_comments'; import { @@ -44,7 +45,6 @@ import { getMappedNonEcsValue, } from '../helpers'; import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules'; -import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; export interface AddExceptionModalBaseProps { ruleName: string; @@ -108,14 +108,13 @@ export const AddExceptionModal = memo(function AddExceptionModal({ >([]); const [fetchOrCreateListError, setFetchOrCreateListError] = useState(false); const { addError, addSuccess } = useAppToasts(); - - const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns(ruleIndices); - const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); const [ - { isLoading: signalIndexPatternLoading, indexPatterns: signalIndexPatterns }, + { isLoading: isSignalIndexPatternLoading, indexPatterns: signalIndexPatterns }, ] = useFetchIndexPatterns(signalIndexName !== null ? [signalIndexName] : []); + const [{ isLoading: isIndexPatternLoading, indexPatterns }] = useFetchIndexPatterns(ruleIndices); + const onError = useCallback( (error: Error) => { addError(error, { title: i18n.ADD_EXCEPTION_ERROR }); @@ -174,7 +173,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ }, [alertData, exceptionListType, ruleExceptionList, ruleName]); useEffect(() => { - if (signalIndexPatternLoading === false && isSignalIndexLoading === false) { + if (isSignalIndexPatternLoading === false && isSignalIndexLoading === false) { setShouldDisableBulkClose( entryHasListType(exceptionItemsToAdd) || entryHasNonEcsType(exceptionItemsToAdd, signalIndexPatterns) @@ -183,9 +182,9 @@ export const AddExceptionModal = memo(function AddExceptionModal({ }, [ setShouldDisableBulkClose, exceptionItemsToAdd, - signalIndexPatternLoading, - signalIndexPatterns, + isSignalIndexPatternLoading, isSignalIndexLoading, + signalIndexPatterns, ]); useEffect(() => { @@ -265,7 +264,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ ); return ( - + {i18n.ADD_EXCEPTION} @@ -284,9 +283,9 @@ export const AddExceptionModal = memo(function AddExceptionModal({ )} {fetchOrCreateListError === false && !isSignalIndexLoading && - !signalIndexPatternLoading && - !indexPatternLoading && + !isSignalIndexPatternLoading && !isLoadingExceptionList && + !isIndexPatternLoading && ruleExceptionList && ( <> @@ -299,7 +298,6 @@ export const AddExceptionModal = memo(function AddExceptionModal({ listNamespaceType={ruleExceptionList.namespace_type} ruleName={ruleName} indexPatterns={indexPatterns} - isLoading={false} isOrDisabled={false} isAndDisabled={false} data-test-subj="alert-exception-builder" diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx index 3cc479fa8d287..f6feca591dc6d 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx @@ -22,7 +22,6 @@ import { AndOrBadge } from '../../and_or_badge'; import { BuilderButtonOptions } from './builder_button_options'; import { getNewExceptionItem, filterExceptionItems } from '../helpers'; import { ExceptionsBuilderExceptionItem, CreateExceptionListItemBuilderSchema } from '../types'; -import { Loader } from '../../loader'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import exceptionableFields from '../exceptionable_fields.json'; @@ -52,7 +51,6 @@ interface ExceptionBuilderProps { listNamespaceType: NamespaceType; ruleName: string; indexPatterns: IIndexPattern; - isLoading: boolean; isOrDisabled: boolean; isAndDisabled: boolean; onChange: (arg: OnChangeProps) => void; @@ -65,7 +63,6 @@ export const ExceptionBuilder = ({ listNamespaceType, ruleName, indexPatterns, - isLoading, isOrDisabled, isAndDisabled, onChange, @@ -193,9 +190,6 @@ export const ExceptionBuilder = ({ return ( - {(isLoading || indexPatterns == null) && ( - - )} {exceptions.map((exceptionListItem, index) => ( diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx index 7c90f75690fc0..b019a3c729f56 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx @@ -93,13 +93,13 @@ export const EditExceptionModal = memo(function EditExceptionModal({ Array >([]); const { addError, addSuccess } = useAppToasts(); - const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns(ruleIndices); - const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); const [ - { isLoading: signalIndexPatternLoading, indexPatterns: signalIndexPatterns }, + { isLoading: isSignalIndexPatternLoading, indexPatterns: signalIndexPatterns }, ] = useFetchIndexPatterns(signalIndexName !== null ? [signalIndexName] : []); + const [{ isLoading: isIndexPatternLoading, indexPatterns }] = useFetchIndexPatterns(ruleIndices); + const onError = useCallback( (error) => { addError(error, { title: i18n.EDIT_EXCEPTION_ERROR }); @@ -121,7 +121,7 @@ export const EditExceptionModal = memo(function EditExceptionModal({ ); useEffect(() => { - if (signalIndexPatternLoading === false && isSignalIndexLoading === false) { + if (isSignalIndexPatternLoading === false && isSignalIndexLoading === false) { setShouldDisableBulkClose( entryHasListType(exceptionItemsToAdd) || entryHasNonEcsType(exceptionItemsToAdd, signalIndexPatterns) @@ -130,9 +130,9 @@ export const EditExceptionModal = memo(function EditExceptionModal({ }, [ setShouldDisableBulkClose, exceptionItemsToAdd, - signalIndexPatternLoading, - signalIndexPatterns, + isSignalIndexPatternLoading, isSignalIndexLoading, + signalIndexPatterns, ]); useEffect(() => { @@ -188,7 +188,7 @@ export const EditExceptionModal = memo(function EditExceptionModal({ }, [addOrUpdateExceptionItems, enrichExceptionItems, shouldBulkCloseAlert, signalIndexName]); return ( - + {i18n.EDIT_EXCEPTION_TITLE} @@ -197,7 +197,7 @@ export const EditExceptionModal = memo(function EditExceptionModal({ - {!indexPatternLoading && !isSignalIndexLoading && !signalIndexPatternLoading && ( + {!isSignalIndexLoading && !isIndexPatternLoading && ( <> {i18n.EXCEPTION_BUILDER_INFO} @@ -208,7 +208,6 @@ export const EditExceptionModal = memo(function EditExceptionModal({ listId={exceptionItem.list_id} listNamespaceType={exceptionItem.namespace_type} ruleName={ruleName} - isLoading={false} isOrDisabled={false} isAndDisabled={false} data-test-subj="edit-exception-modal-builder" From f6677044f8713af6a4027a1328bb2fb12dfef971 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Mon, 20 Jul 2020 16:10:25 -0400 Subject: [PATCH 8/8] applying feedback --- .../autocomplete/field_value_lists.test.tsx | 93 ++++++++++--------- .../components/autocomplete/helpers.test.ts | 12 --- .../common/components/autocomplete/helpers.ts | 8 +- 3 files changed, 49 insertions(+), 64 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx index 51a7fd6abf513..1ff5d770521f3 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx @@ -8,21 +8,26 @@ import { ThemeProvider } from 'styled-components'; import { mount } from 'enzyme'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { act } from 'react-dom/test-utils'; +// we don't have the types for waitFor just yet, so using "as waitFor" until when we do +import { wait as waitFor } from '@testing-library/react'; import { getField } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks.ts'; -import { AutocompleteFieldListsComponent } from './field_value_lists'; +import { ListSchema } from '../../../lists_plugin_deps'; import { getFoundListSchemaMock } from '../../../../../lists/common/schemas/response/found_list_schema.mock'; import { getListResponseMock } from '../../../../../lists/common/schemas/response/list_schema.mock'; -import { wait } from '../../../common/lib/helpers'; +import { DATE_NOW } from '../../../../../lists/common/constants.mock'; + +import { AutocompleteFieldListsComponent } from './field_value_lists'; jest.mock('../../../common/lib/kibana'); const mockStart = jest.fn(); -const mockKeywordList = getListResponseMock(); -mockKeywordList.id = 'keyword_list'; -mockKeywordList.type = 'keyword'; -mockKeywordList.name = 'keyword list'; -const mockResult = getFoundListSchemaMock(); +const mockKeywordList: ListSchema = { + ...getListResponseMock(), + id: 'keyword_list', + type: 'keyword', + name: 'keyword list', +}; +const mockResult = { ...getFoundListSchemaMock() }; mockResult.data = [...mockResult.data, mockKeywordList]; jest.mock('../../../lists_plugin_deps', () => { const originalModule = jest.requireActual('../../../lists_plugin_deps'); @@ -40,21 +45,21 @@ jest.mock('../../../lists_plugin_deps', () => { describe('AutocompleteFieldListsComponent', () => { test('it renders disabled if "isDisabled" is true', async () => { - await act(async () => { - const wrapper = mount( - ({ eui: euiLightVars, darkMode: false })}> - - - ); - await wait(); + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + await waitFor(() => { expect( wrapper .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] input`) @@ -64,21 +69,21 @@ describe('AutocompleteFieldListsComponent', () => { }); test('it renders loading if "isLoading" is true', async () => { - await act(async () => { - const wrapper = mount( - ({ eui: euiLightVars, darkMode: false })}> - - - ); - await wait(); + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + await waitFor(() => { wrapper .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] button`) .at(0) @@ -201,14 +206,12 @@ describe('AutocompleteFieldListsComponent', () => { ); - await act(async () => { - ((wrapper.find(EuiComboBox).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([{ label: 'some name' }]); - }); + ((wrapper.find(EuiComboBox).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange([{ label: 'some name' }]); expect(mockOnChange).toHaveBeenCalledWith({ - created_at: '2020-04-20T15:25:31.830Z', + created_at: DATE_NOW, created_by: 'some user', description: 'some description', id: 'some-list-id', @@ -216,7 +219,7 @@ describe('AutocompleteFieldListsComponent', () => { name: 'some name', tie_breaker_id: '6a76b69d-80df-4ab2-8c3e-85f466b06a0e', type: 'ip', - updated_at: '2020-04-20T15:25:31.830Z', + updated_at: DATE_NOW, updated_by: 'some user', }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts index c7e6e22d4ce71..cb07d99913107 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts @@ -77,18 +77,6 @@ describe('helpers', () => { expect(isValid).toBeFalsy(); }); - - test('returns true if type is "ip" and value is valid', () => { - const isValid = validateParams('126.45.211.34', getField('ip')); - - expect(isValid).toBeTruthy(); - }); - - test('returns false if type is "ip" and value is not valid', () => { - const isValid = validateParams('hellooo', getField('ip')); - - expect(isValid).toBeFalsy(); - }); }); describe('#getGenericComboBoxProps', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts index 3363f49ed750b..16659593784db 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts @@ -7,7 +7,7 @@ import dateMath from '@elastic/datemath'; import { EuiComboBoxOptionOption } from '@elastic/eui'; -import { IFieldType, Ipv4Address } from '../../../../../../../src/plugins/data/common'; +import { IFieldType } from '../../../../../../../src/plugins/data/common'; import { EXCEPTION_OPERATORS, @@ -46,12 +46,6 @@ export const validateParams = ( case 'date': const moment = dateMath.parse(params); return Boolean(moment && moment.isValid()); - case 'ip': - try { - return Boolean(new Ipv4Address(params)); - } catch (e) { - return false; - } default: return acc; }