Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] [Platform] Adds state to remember what was in data view or index pattern selection when switching between the two #136448

Merged
merged 23 commits into from
Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2d2221b
adds state to remember what was in data view or index pattern selecti…
dhurley14 Jul 14, 2022
865e231
cleanup
dhurley14 Jul 14, 2022
b2be33b
remove console log
dhurley14 Jul 14, 2022
53b9115
Merge branch 'main' into dv-bug-fixes
vitaliidm Jul 18, 2022
a63f9a1
Keep index and dataVew on form
nkhristinin Jul 19, 2022
93b2c7b
Merge branch 'main' into dv-bug-fixes
kibanamachine Jul 19, 2022
5e216aa
fix type errors and fixes failure in validation when dataViewId was e…
dhurley14 Jul 19, 2022
8ac0f45
update jest test
dhurley14 Jul 20, 2022
ee98001
Fix for eql
nkhristinin Jul 20, 2022
f73fa29
More check for eql
nkhristinin Jul 20, 2022
b785fc9
Fix validation
nkhristinin Jul 20, 2022
38be325
Remove unused code and fix defaultValue
nkhristinin Jul 20, 2022
26440e2
[CI] Auto-commit changed files from 'node scripts/precommit_hook.js -…
kibanamachine Jul 20, 2022
6a55019
Show dataViewLabel
nkhristinin Jul 21, 2022
aea2f33
Merge branch 'main' into dv-bug-fixes
kibanamachine Jul 21, 2022
6aeb934
Update x-pack/plugins/security_solution/public/detections/components/…
nkhristinin Jul 21, 2022
354066d
Update x-pack/plugins/security_solution/public/detections/components/…
nkhristinin Jul 21, 2022
2bdc67f
Update x-pack/plugins/security_solution/public/detections/components/…
nkhristinin Jul 21, 2022
0ae898f
Update x-pack/plugins/security_solution/public/detections/components/…
nkhristinin Jul 21, 2022
a33441e
add new helper function for writing data source to rule form
dhurley14 Jul 21, 2022
af27f0d
Merge branch 'main' into dv-bug-fixes
dhurley14 Jul 25, 2022
2d2afcb
merge with main
dhurley14 Jul 25, 2022
8fda3cf
merge with main 2
dhurley14 Jul 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,17 @@ import type { EuiComboBoxOptionOption } from '@elastic/eui';
import { EuiCallOut, EuiComboBox, EuiFormRow, EuiSpacer } from '@elastic/eui';

import type { DataViewListItem } from '@kbn/data-views-plugin/common';
import type { DataViewBase } from '@kbn/es-query';
import type { FieldHook } from '../../../../shared_imports';
import { getFieldValidityAndErrorMessage } from '../../../../shared_imports';
import * as i18n from './translations';
import { useKibana } from '../../../../common/lib/kibana';
import type { DefineStepRule } from '../../../pages/detection_engine/rules/types';

interface DataViewSelectorProps {
kibanaDataViews: { [x: string]: DataViewListItem };
kibanaDataViews: { [dataViewId: string]: DataViewListItem };
nkhristinin marked this conversation as resolved.
Show resolved Hide resolved
field: FieldHook<DefineStepRule['dataViewId']>;
setIndexPattern: (indexPattern: DataViewBase) => void;
}

export const DataViewSelector = ({
kibanaDataViews,
field,
setIndexPattern,
}: DataViewSelectorProps) => {
const { data } = useKibana().services;

export const DataViewSelector = ({ kibanaDataViews, field }: DataViewSelectorProps) => {
nkhristinin marked this conversation as resolved.
Show resolved Hide resolved
let isInvalid;
let errorMessage;
let dataViewId: string | null | undefined;
Expand Down Expand Up @@ -62,7 +53,15 @@ export const DataViewSelector = ({
: []
);

const [selectedDataView, setSelectedDataView] = useState<DataViewListItem>();
useEffect(() => {
if (!selectedDataViewNotFound && dataViewId != null && dataViewId !== '') {
nkhristinin marked this conversation as resolved.
Show resolved Hide resolved
setSelectedOption([
{ id: kibanaDataViews[dataViewId].id, label: kibanaDataViews[dataViewId].title },
]);
} else {
setSelectedOption([]);
}
}, [dataViewId, field, kibanaDataViews, selectedDataViewNotFound]);

// TODO: optimize this, pass down array of data view ids
// at the same time we grab the data views in the top level form component
Expand All @@ -75,17 +74,6 @@ export const DataViewSelector = ({
: [];
}, [kibanaDataViewsDefined, kibanaDataViews]);

useEffect(() => {
const fetchSingleDataView = async () => {
if (selectedDataView != null) {
const dv = await data.dataViews.get(selectedDataView.id);
setIndexPattern(dv);
}
};

fetchSingleDataView();
}, [data.dataViews, selectedDataView, setIndexPattern]);

const onChangeDataViews = (options: Array<EuiComboBoxOptionOption<string>>) => {
const selectedDataViewOption = options;

Expand All @@ -96,10 +84,9 @@ export const DataViewSelector = ({
selectedDataViewOption.length > 0 &&
selectedDataViewOption[0].id != null
) {
setSelectedDataView(kibanaDataViews[selectedDataViewOption[0].id]);
field?.setValue(selectedDataViewOption[0].id);
const selectedDataViewId = selectedDataViewOption[0].id;
field?.setValue(selectedDataViewId);
} else {
setSelectedDataView(undefined);
field?.setValue(undefined);
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { FieldHook, ValidationError, ValidationFunc } from '../../../../sha
import { isEqlRule } from '../../../../../common/detection_engine/utils';
import { KibanaServices } from '../../../../common/lib/kibana';
import type { DefineStepRule } from '../../../pages/detection_engine/rules/types';
import { DataSourceType } from '../../../pages/detection_engine/rules/types';
import { validateEql } from '../../../../common/hooks/eql/api';
import type { FieldValueQueryBar } from '../query_bar';
import * as i18n from './translations';
Expand Down Expand Up @@ -69,7 +70,11 @@ export const eqlValidator = async (
const { data } = KibanaServices.get();
let dataViewTitle = index?.join();
let runtimeMappings = {};
if (dataViewId != null) {
if (
dataViewId != null &&
dataViewId !== '' &&
formData.dataSourceType === DataSourceType.DataView
) {
const dataView = await data.dataViews.get(dataViewId);

dataViewTitle = dataView.title;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type {
RuleStep,
DefineStepRule,
} from '../../../pages/detection_engine/rules/types';
import { DataSourceType } from '../../../pages/detection_engine/rules/types';
import { fillEmptySeverityMappings } from '../../../pages/detection_engine/rules/helpers';
import { TestProviders } from '../../../../common/mock';

Expand Down Expand Up @@ -54,6 +55,7 @@ export const stepDefineStepMLRule: DefineStepRule = {
threatMapping: [],
timeline: { id: null, title: null },
eqlOptions: {},
dataSourceType: DataSourceType.IndexPatterns,
};

describe('StepAboutRuleComponent', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import usePrevious from 'react-use/lib/usePrevious';

import type { DataViewBase, DataViewFieldBase } from '@kbn/es-query';
import { FormattedMessage } from '@kbn/i18n-react';
import omit from 'lodash/omit';
import {
DEFAULT_INDEX_KEY,
DEFAULT_THREAT_INDEX_KEY,
Expand All @@ -36,11 +37,11 @@ import { isMlRule } from '../../../../../common/machine_learning/helpers';
import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions';
import { hasMlLicense } from '../../../../../common/machine_learning/has_ml_license';
import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities';
import { useUiSetting$ } from '../../../../common/lib/kibana';
import { useUiSetting$, useKibana } from '../../../../common/lib/kibana';
import type { EqlOptionsSelected, FieldsEqlOptions } from '../../../../../common/search_strategy';
import { filterRuleFieldsForType } from '../../../pages/detection_engine/rules/create/helpers';
import type { DefineStepRule, RuleStepProps } from '../../../pages/detection_engine/rules/types';
import { RuleStep } from '../../../pages/detection_engine/rules/types';
import { RuleStep, DataSourceType } from '../../../pages/detection_engine/rules/types';
import { StepRuleDescription } from '../description_step';
import { QueryBarDefineRule } from '../query_bar';
import { SelectRuleType } from '../select_rule_type';
Expand Down Expand Up @@ -75,9 +76,6 @@ import { RulePreview } from '../rule_preview';
import { getIsRulePreviewDisabled } from '../rule_preview/helpers';
import { DocLink } from '../../../../common/components/links_to_docs/doc_link';

const DATA_VIEW_SELECT_ID = 'dataView';
const INDEX_PATTERN_SELECT_ID = 'indexPatterns';

const CommonUseField = getUseField({ component: Field });

const StyledButtonGroup = styled(EuiButtonGroup)`
Expand All @@ -91,6 +89,10 @@ const StyledButtonGroup = styled(EuiButtonGroup)`
const StyledFlexGroup = styled(EuiFlexGroup)`
margin-bottom: -21px;
`;

const StyledVisibleContainer = styled.div<{ isVisible: boolean }>`
display: ${(props) => (props.isVisible ? 'block' : 'none')};
`;
interface StepDefineRuleProps extends RuleStepProps {
defaultValues?: DefineStepRule;
}
Expand Down Expand Up @@ -127,6 +129,7 @@ export const stepDefineDefaultValue: DefineStepRule = {
title: DEFAULT_TIMELINE_TITLE,
},
eqlOptions: {},
dataSourceType: DataSourceType.IndexPatterns,
};

/**
Expand Down Expand Up @@ -180,6 +183,7 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
const [openTimelineSearch, setOpenTimelineSearch] = useState(false);
const [indexModified, setIndexModified] = useState(false);
const [threatIndexModified, setThreatIndexModified] = useState(false);
const [dataViewTitle, setDataViewTitle] = useState<string>();

const [indicesConfig] = useUiSetting$<string[]>(DEFAULT_INDEX_KEY);
const [threatIndicesConfig] = useUiSetting$<string[]>(DEFAULT_THREAT_INDEX_KEY);
Expand Down Expand Up @@ -208,6 +212,7 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
threatMapping: formThreatMapping,
machineLearningJobId: formMachineLearningJobId,
anomalyThreshold: formAnomalyThreshold,
dataSourceType: formdataSourceType,
nkhristinin marked this conversation as resolved.
Show resolved Hide resolved
},
] = useFormData<DefineStepRule>({
form,
Expand All @@ -225,6 +230,7 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
'threatMapping',
'machineLearningJobId',
'anomalyThreshold',
'dataSourceType',
],
});

Expand All @@ -236,31 +242,59 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
const machineLearningJobId = formMachineLearningJobId ?? initialState.machineLearningJobId;
const anomalyThreshold = formAnomalyThreshold ?? initialState.anomalyThreshold;
const ruleType = formRuleType || initialState.ruleType;
const dataSourceType = formdataSourceType || initialState.dataSourceType;
nkhristinin marked this conversation as resolved.
Show resolved Hide resolved

// if 'index' is selected, use these browser fields
// otherwise use the dataview browserfields
const previousRuleType = usePrevious(ruleType);
const [optionsSelected, setOptionsSelected] = useState<EqlOptionsSelected>(
defaultValues?.eqlOptions || {}
);
const [initIsIndexPatternLoading, { browserFields, indexPatterns: initIndexPattern }] =
useFetchIndex(index, false);
const [indexPattern, setIndexPattern] = useState<DataViewBase>(initIndexPattern);
const [isIndexPatternLoading, setIsIndexPatternLoading] = useState(initIsIndexPatternLoading);
const [dataSourceRadioIdSelected, setDataSourceRadioIdSelected] = useState(
dataView == null || dataView === '' ? INDEX_PATTERN_SELECT_ID : DATA_VIEW_SELECT_ID
const [isIndexPatternLoading, { browserFields, indexPatterns: initIndexPattern }] = useFetchIndex(
index,
false
);
const [indexPattern, setIndexPattern] = useState<DataViewBase>(initIndexPattern);

const { data } = useKibana().services;

// Why do we need this? to ensure the query bar auto-suggest gets the latest updates
// when the index pattern changes
// when we select new dataView
// when we choose some other dataSourceType
useEffect(() => {
if (dataSourceRadioIdSelected === INDEX_PATTERN_SELECT_ID) {
setIndexPattern(initIndexPattern);
if (dataSourceType === DataSourceType.IndexPatterns) {
if (!isIndexPatternLoading) {
setIndexPattern(initIndexPattern);
}
}
}, [initIndexPattern, dataSourceRadioIdSelected]);

if (dataSourceType === DataSourceType.DataView) {
const fetchDataView = async () => {
if (dataView != null) {
const dv = await data.dataViews.get(dataView);
setDataViewTitle(dv.title);
setIndexPattern(dv);
}
};

fetchDataView();
}
Comment on lines +273 to +283
Copy link
Contributor

@banderror banderror Jul 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using react-query might help for keeping dataView parameter (it's a name or an id, right?) in sync with the data view saved object that needs to be fetched. We used this library for the Rules Table and for fetching rule execution results and events in #126063. It simplifies code in such cases and makes it clean and robust.

}, [dataSourceType, isIndexPatternLoading, data, dataView, initIndexPattern]);

// Callback for when user toggles between Data Views and Index Patterns
const onChangeDataSource = (optionId: string) => {
setDataSourceRadioIdSelected(optionId);
};
const onChangeDataSource = useCallback(
(optionId: string) => {
form.setFieldValue('dataSourceType', optionId);
form.getFields().index.reset({
resetValue: false,
});
form.getFields().dataViewId.reset({
resetValue: false,
});
},
[form]
);

const [aggFields, setAggregatableFields] = useState<DataViewFieldBase[]>([]);

Expand Down Expand Up @@ -433,28 +467,26 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
const dataViewIndexPatternToggleButtonOptions: EuiButtonGroupOptionProps[] = useMemo(
() => [
{
id: INDEX_PATTERN_SELECT_ID,
id: DataSourceType.IndexPatterns,
label: i18nCore.translate(
'xpack.securitySolution.ruleDefine.indexTypeSelect.indexPattern',
{
defaultMessage: 'Index Patterns',
}
),
iconType:
dataSourceRadioIdSelected === INDEX_PATTERN_SELECT_ID ? 'checkInCircleFilled' : 'empty',
'data-test-subj': `rule-index-toggle-${INDEX_PATTERN_SELECT_ID}`,
iconType: dataSourceType === DataSourceType.IndexPatterns ? 'checkInCircleFilled' : 'empty',
'data-test-subj': `rule-index-toggle-${DataSourceType.IndexPatterns}`,
},
{
id: DATA_VIEW_SELECT_ID,
id: DataSourceType.DataView,
label: i18nCore.translate('xpack.securitySolution.ruleDefine.indexTypeSelect.dataView', {
defaultMessage: 'Data View',
}),
iconType:
dataSourceRadioIdSelected === DATA_VIEW_SELECT_ID ? 'checkInCircleFilled' : 'empty',
'data-test-subj': `rule-index-toggle-${DATA_VIEW_SELECT_ID}`,
iconType: dataSourceType === DataSourceType.DataView ? 'checkInCircleFilled' : 'empty',
'data-test-subj': `rule-index-toggle-${DataSourceType.DataView}`,
},
],
[dataSourceRadioIdSelected]
[dataSourceType]
);

const DataViewSelectorMemo = useMemo(() => {
Expand All @@ -465,8 +497,6 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
component={DataViewSelector}
componentProps={{
kibanaDataViews,
setIndexPattern,
setIsIndexPatternLoading,
}}
/>
);
Expand All @@ -481,7 +511,7 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
<StyledButtonGroup
legend="Rule index pattern or data view selector"
data-test-subj="dataViewIndexPatternButtonGroup"
idSelected={dataSourceRadioIdSelected}
idSelected={dataSourceType}
onChange={onChangeDataSource}
options={dataViewIndexPatternToggleButtonOptions}
color="primary"
Expand Down Expand Up @@ -513,9 +543,10 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
</EuiFlexItem>

<EuiFlexItem>
{dataSourceRadioIdSelected === DATA_VIEW_SELECT_ID ? (
DataViewSelectorMemo
) : (
<StyledVisibleContainer isVisible={dataSourceType === DataSourceType.DataView}>
{DataViewSelectorMemo}
</StyledVisibleContainer>
<StyledVisibleContainer isVisible={dataSourceType === DataSourceType.IndexPatterns}>
<CommonUseField
path="index"
config={{
Expand All @@ -535,17 +566,18 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
},
}}
/>
)}
</StyledVisibleContainer>
</EuiFlexItem>
</EuiFlexGroup>
</RuleTypeEuiFormRow>
);
}, [
dataSourceRadioIdSelected,
dataSourceType,
dataViewIndexPatternToggleButtonOptions,
DataViewSelectorMemo,
indexModified,
handleResetIndices,
onChangeDataSource,
]);

const QueryBarMemo = useMemo(
Expand Down Expand Up @@ -620,19 +652,39 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
[indexPattern]
);

let dataForDescription: Partial<DefineStepRule> = { ...initialState };

if (dataSourceType === DataSourceType.IndexPatterns) {
dataForDescription = omit(initialState, ['dataSourceType', 'dataViewId']);
} else if (dataSourceType === DataSourceType.DataView) {
dataForDescription = omit(initialState, ['dataSourceType', 'index']);
dataForDescription.dataViewTitle = dataViewTitle;
}

return isReadOnlyView ? (
<StepContentWrapper data-test-subj="definitionRule" addPadding={addPadding}>
<StepRuleDescription
columns={descriptionColumns}
indexPatterns={indexPattern}
schema={filterRuleFieldsForType(schema, ruleType)}
data={filterRuleFieldsForType(initialState, ruleType)}
data={filterRuleFieldsForType(dataForDescription, ruleType)}
/>
</StepContentWrapper>
) : (
<>
<StepContentWrapper addPadding={!isUpdateView}>
<Form form={form} data-test-subj="stepDefineRule">
<StyledVisibleContainer isVisible={false}>
<UseField
path="dataSourceType"
componentProps={{
euiFieldProps: {
fullWidth: true,
placeholder: '',
},
}}
/>
</StyledVisibleContainer>
<UseField
path="ruleType"
component={SelectRuleType}
Expand Down
Loading