Skip to content

Commit

Permalink
Adds override logic to rule execution, integrates AutocompleteField, …
Browse files Browse the repository at this point in the history
…and fixes EditAboutStep rehydration
  • Loading branch information
spong committed Jul 13, 2020
1 parent 3c82c83 commit fc1ebb5
Show file tree
Hide file tree
Showing 20 changed files with 478 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ export const severity_mapping_item = t.exact(
severity,
})
);
export type SeverityMappingItem = t.TypeOf<typeof severity_mapping_item>;

export const severity_mapping = t.array(severity_mapping_item);
export type SeverityMapping = t.TypeOf<typeof severity_mapping>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface OperatorProps {
isLoading: boolean;
isDisabled: boolean;
isClearable: boolean;
fieldTypeFilter?: string[];
fieldInputWidth?: number;
onChange: (a: IFieldType[]) => void;
}
Expand All @@ -28,13 +29,22 @@ export const FieldComponent: React.FC<OperatorProps> = ({
isLoading = false,
isDisabled = false,
isClearable = false,
fieldTypeFilter = [],
fieldInputWidth = 190,
onChange,
}): JSX.Element => {
const getLabel = useCallback((field): string => field.name, []);
const optionsMemo = useMemo((): IFieldType[] => (indexPattern ? indexPattern.fields : []), [
indexPattern,
]);
const optionsMemo = useMemo((): IFieldType[] => {
if (indexPattern != null) {
if (fieldTypeFilter.length > 0) {
return indexPattern.fields.filter((f) => fieldTypeFilter.includes(f.type));
} else {
return indexPattern.fields;
}
} else {
return [];
}
}, [fieldTypeFilter, indexPattern]);
const selectedOptionsMemo = useMemo((): IFieldType[] => (selectedField ? [selectedField] : []), [
selectedField,
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ describe('AlertsUtilityBar', () => {
currentFilter="closed"
selectAll={jest.fn()}
showClearSelection={true}
showBuildingBlockAlerts={false}
onShowBuildingBlockAlertsChanged={jest.fn()}
updateAlertsStatus={jest.fn()}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ describe('AlertsTableComponent', () => {
clearEventsLoading={jest.fn()}
setEventsDeleted={jest.fn()}
clearEventsDeleted={jest.fn()}
showBuildingBlockAlerts={false}
onShowBuildingBlockAlertsChanged={jest.fn()}
updateTimelineIsLoading={jest.fn()}
updateTimeline={jest.fn()}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useCallback, useMemo } from 'react';
import { EuiFormRow } from '@elastic/eui';
import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
import { FieldComponent } from '../../../../common/components/autocomplete/field';
import { IFieldType } from '../../../../../../../../src/plugins/data/common/index_patterns/fields';
import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns';

interface AutocompleteFieldProps {
dataTestSubj: string;
field: FieldHook;
idAria: string;
indices: IIndexPattern;
isDisabled: boolean;
fieldType: string;
placeholder?: string;
}

export const AutocompleteField = ({
dataTestSubj,
field,
idAria,
indices,
isDisabled,
fieldType,
placeholder,
}: AutocompleteFieldProps) => {
const handleFieldChange = useCallback(
([newField]: IFieldType[]): void => {
// TODO: Update onChange type in FieldComponent as newField can be undefined
field.setValue(newField?.name ?? '');
},
[field]
);

const selectedField = useMemo(() => {
const existingField = (field.value as string) ?? '';
const [newSelectedField] = indices.fields.filter(
({ name }) => existingField != null && existingField === name
);
return newSelectedField;
}, [field.value, indices]);

const fieldTypeFilter = useMemo(() => [fieldType], [fieldType]);

return (
<EuiFormRow
data-test-subj={dataTestSubj}
describedByIds={idAria ? [idAria] : undefined}
fullWidth
helpText={field.helpText}
label={field.label}
labelAppend={field.labelAppend}
>
<FieldComponent
placeholder={placeholder ?? ''}
indexPattern={indices}
selectedField={selectedField}
fieldTypeFilter={fieldTypeFilter}
isLoading={false}
isDisabled={isDisabled}
isClearable={false}
onChange={handleFieldChange}
data-test-subj={dataTestSubj}
aria-label={idAria}
fieldInputWidth={500}
/>
</EuiFormRow>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import {
EuiFormRow,
EuiFieldText,
EuiCheckbox,
EuiText,
EuiFlexGroup,
Expand All @@ -15,12 +14,15 @@ import {
EuiIcon,
EuiSpacer,
} from '@elastic/eui';
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import * as i18n from './translations';
import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
import { CommonUseField } from '../../../../cases/components/create';
import { AboutStepRiskScore } from '../../../pages/detection_engine/rules/types';
import { FieldComponent } from '../../../../common/components/autocomplete/field';
import { IFieldType } from '../../../../../../../../src/plugins/data/common/index_patterns/fields';
import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns';

const NestedContent = styled.div`
margin-left: 24px;
Expand All @@ -38,20 +40,47 @@ interface RiskScoreFieldProps {
dataTestSubj: string;
field: FieldHook;
idAria: string;
indices: string[];
indices: IIndexPattern;
placeholder?: string;
}

export const RiskScoreField = ({ dataTestSubj, field, idAria, indices }: RiskScoreFieldProps) => {
export const RiskScoreField = ({
dataTestSubj,
field,
idAria,
indices,
placeholder,
}: RiskScoreFieldProps) => {
const [isRiskScoreMappingSelected, setIsRiskScoreMappingSelected] = useState(false);
const [initialFieldCheck, setInitialFieldCheck] = useState(true);

const updateRiskScoreMapping = useCallback(
(event) => {
const fieldTypeFilter = useMemo(() => ['number'], []);

useEffect(() => {
if (
!isRiskScoreMappingSelected &&
initialFieldCheck &&
(field.value as AboutStepRiskScore).mapping?.length > 0
) {
setIsRiskScoreMappingSelected(true);
setInitialFieldCheck(false);
}
}, [
field,
initialFieldCheck,
isRiskScoreMappingSelected,
setIsRiskScoreMappingSelected,
setInitialFieldCheck,
]);

const handleFieldChange = useCallback(
([newField]: IFieldType[]): void => {
const values = field.value as AboutStepRiskScore;
field.setValue({
value: values.value,
mapping: [
{
field: event.target.value,
field: newField?.name ?? '',
operator: 'equals',
value: '',
},
Expand All @@ -61,7 +90,19 @@ export const RiskScoreField = ({ dataTestSubj, field, idAria, indices }: RiskSco
[field]
);

const severityLabel = useMemo(() => {
const handleRiskScoreMappingSelected = useCallback(() => {
setIsRiskScoreMappingSelected(!isRiskScoreMappingSelected);
}, [isRiskScoreMappingSelected, setIsRiskScoreMappingSelected]);

const selectedField = useMemo(() => {
const existingField = (field.value as AboutStepRiskScore).mapping?.[0]?.field ?? '';
const [newSelectedField] = indices.fields.filter(
({ name }) => existingField != null && existingField === name
);
return newSelectedField;
}, [field.value, indices]);

const riskScoreLabel = useMemo(() => {
return (
<div>
<EuiFlexGroup gutterSize="s">
Expand All @@ -73,19 +114,15 @@ export const RiskScoreField = ({ dataTestSubj, field, idAria, indices }: RiskSco
);
}, []);

const severityMappingLabel = useMemo(() => {
const riskScoreMappingLabel = useMemo(() => {
return (
<div>
<EuiFlexGroup
alignItems="center"
gutterSize="s"
onClick={() => setIsRiskScoreMappingSelected(!isRiskScoreMappingSelected)}
>
<EuiFlexGroup alignItems="center" gutterSize="s" onClick={handleRiskScoreMappingSelected}>
<EuiFlexItem grow={false}>
<EuiCheckbox
id={`risk_score-mapping-override`}
checked={isRiskScoreMappingSelected}
onChange={(e) => setIsRiskScoreMappingSelected(e.target.checked)}
onChange={handleRiskScoreMappingSelected}
/>
</EuiFlexItem>
<EuiFlexItem>{i18n.RISK_SCORE_MAPPING}</EuiFlexItem>
Expand All @@ -96,13 +133,13 @@ export const RiskScoreField = ({ dataTestSubj, field, idAria, indices }: RiskSco
</NestedContent>
</div>
);
}, [isRiskScoreMappingSelected, setIsRiskScoreMappingSelected]);
}, [handleRiskScoreMappingSelected, isRiskScoreMappingSelected]);

return (
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label={severityLabel}
label={riskScoreLabel}
labelAppend={field.labelAppend}
helpText={field.helpText}
error={'errorMessage'}
Expand Down Expand Up @@ -130,7 +167,7 @@ export const RiskScoreField = ({ dataTestSubj, field, idAria, indices }: RiskSco
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label={severityMappingLabel}
label={riskScoreMappingLabel}
labelAppend={field.labelAppend}
helpText={
isRiskScoreMappingSelected ? (
Expand Down Expand Up @@ -164,12 +201,18 @@ export const RiskScoreField = ({ dataTestSubj, field, idAria, indices }: RiskSco
<EuiFlexItem>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem>
<EuiFieldText
data-test-subj={'detectionEngineStepAboutRuleRiskScoreMappingValue'}
aria-label={'detectionEngineStepAboutRuleRiskScoreMappingValu'}
disabled={false}
onChange={updateRiskScoreMapping.bind(null)}
value={(field.value as AboutStepRiskScore).mapping?.[0]?.field ?? ''}
<FieldComponent
placeholder={placeholder ?? ''}
indexPattern={indices}
selectedField={selectedField}
fieldTypeFilter={fieldTypeFilter}
isLoading={false}
isClearable={false}
isDisabled={false}
onChange={handleFieldChange}
data-test-subj={dataTestSubj}
aria-label={idAria}
fieldInputWidth={230}
/>
</EuiFlexItem>
<EuiFlexItemIconColumn grow={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ import {
EuiIcon,
EuiSpacer,
} from '@elastic/eui';
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import * as i18n from './translations';
import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
import { SeverityOptionItem } from '../step_about_rule/data';
import { CommonUseField } from '../../../../cases/components/create';
import { AboutStepSeverity } from '../../../pages/detection_engine/rules/types';
import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns';

const NestedContent = styled.div`
margin-left: 24px;
Expand All @@ -39,18 +40,36 @@ interface SeverityFieldProps {
dataTestSubj: string;
field: FieldHook;
idAria: string;
indices: string[];
indices: IIndexPattern;
options: SeverityOptionItem[];
}

export const SeverityField = ({
dataTestSubj,
field,
idAria,
indices, // TODO: To be used with autocomplete fields once https://github.com/elastic/kibana/pull/67013 is merged
indices,
options,
}: SeverityFieldProps) => {
const [isSeverityMappingChecked, setIsSeverityMappingChecked] = useState(false);
const [initialFieldCheck, setInitialFieldCheck] = useState(true);

useEffect(() => {
if (
!isSeverityMappingChecked &&
initialFieldCheck &&
(field.value as AboutStepSeverity).mapping?.length > 0
) {
setIsSeverityMappingChecked(true);
setInitialFieldCheck(false);
}
}, [
field,
initialFieldCheck,
isSeverityMappingChecked,
setIsSeverityMappingChecked,
setInitialFieldCheck,
]);

const updateSeverityMapping = useCallback(
(index: number, severity: string, mappingField: string, event) => {
Expand Down Expand Up @@ -168,7 +187,7 @@ export const SeverityField = ({
</EuiFlexItem>
<EuiFlexItemIconColumn grow={false} />
<EuiFlexItemSeverityColumn grow={false}>
<EuiFormLabel>{i18n.SEVERITY}</EuiFormLabel>
<EuiFormLabel>{i18n.DEFAULT_SEVERITY}</EuiFormLabel>
</EuiFlexItemSeverityColumn>
</EuiFlexGroup>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ export const SEVERITY = i18n.translate(
}
);

export const DEFAULT_SEVERITY = i18n.translate(
'xpack.securitySolution.alerts.severityMapping.defaultSeverityTitle',
{
defaultMessage: 'Severity',
}
);

export const SOURCE_FIELD = i18n.translate(
'xpack.securitySolution.alerts.severityMapping.sourceFieldTitle',
{
Expand Down
Loading

0 comments on commit fc1ebb5

Please sign in to comment.