Skip to content

Commit

Permalink
[Data View Rule Creation] - Update UI and data view check on rule run (
Browse files Browse the repository at this point in the history
…elastic#23)

* update UI and kibana advanced setting

* removed advanced settings logic

* removing weird change

* adding debug logging

* update ui to show data view title in select
  • Loading branch information
yctercero authored May 19, 2022
1 parent 58b2432 commit 3722a4a
Showing 25 changed files with 596 additions and 447 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { shallow } from 'enzyme';

import { DataViewSelector } from '.';
import { useFormFieldMock } from '../../../../common/mock';

describe('data_view_selector', () => {
it('renders correctly', () => {
const Component = () => {
const field = useFormFieldMock({ value: '' });

return <DataViewSelector kibanaDataViews={[]} setIndexPattern={jest.fn()} field={field} />;
};
const wrapper = shallow(<Component />);

expect(wrapper.dive().find('[data-test-subj="pick-rule-data-source"]')).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
@@ -5,100 +5,113 @@
* 2.0.
*/

import React, { useCallback, useEffect, useState } from 'react';
import React, { useMemo, useState } from 'react';

import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui';
import {
EuiCallOut,
EuiComboBox,
EuiComboBoxOptionOption,
EuiFormRow,
EuiSpacer,
} from '@elastic/eui';

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

interface DataViewSelectorProps {
kibanaDataViews: { [x: string]: DataViewListItem };
field: FieldHook;
dataViewId?: string;
field: FieldHook<DefineStepRule['dataViewId']>;
setIndexPattern: (indexPattern: DataViewBase) => void;
setIsIndexPatternLoading: (b: boolean) => void;
}

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

const [selectedOptions, setSelectedOptions] = useState<Array<EuiComboBoxOptionOption<string>>>(
dataViewId != null && dataViewId.length > 0 ? [{ label: dataViewId }] : []
const dataViewId = field.value;
const kibanaDataViewsDefined = useMemo(
() => kibanaDataViews != null && Object.keys(kibanaDataViews).length > 0,
[kibanaDataViews]
);
const [selectedDataView, setSelectedDataView] = useState<DataViewListItem>();
useEffect(() => {
const fetchSingleDataView = async () => {
if (selectedDataView != null) {
const dv = await data.dataViews.get(selectedDataView.id);
setIndexPattern(dv);
}
};

fetchSingleDataView();
}, [data.dataViews, selectedDataView, setIndexPattern]);
const onChangeIndexPatterns = useCallback(
(options: Array<EuiComboBoxOptionOption<string>>) => {
const dataView = options;

setSelectedOptions(options);
setSelectedDataView(kibanaDataViews[dataView[0].label]);
field.setValue(dataView[0].label);
},
[field, kibanaDataViews]
// Most likely case here is that a data view of an existing rule was deleted
// and can no longer be found
const selectedDataViewNotFound = useMemo(
() =>
dataViewId != null &&
dataViewId !== '' &&
kibanaDataViewsDefined &&
!Object.keys(kibanaDataViews).includes(dataViewId),
[kibanaDataViewsDefined, dataViewId, kibanaDataViews]
);
const [selectedOption, setSelectedOption] = useState<Array<EuiComboBoxOptionOption<string>>>(
!selectedDataViewNotFound && dataViewId != null && dataViewId !== ''
? [{ id: kibanaDataViews[dataViewId].id, label: kibanaDataViews[dataViewId].title }]
: []
);

// 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
const dataViewOptions = useMemo(() => {
return kibanaDataViewsDefined
? Object.values(kibanaDataViews).map((dv) => ({
label: dv.title,
id: dv.id,
}))
: [];
}, [kibanaDataViewsDefined, kibanaDataViews]);

// sometimes the form isn't loaded before this component renders
// so the state of dataViewId changes from the initial state
// in the parent component to the state pulled from the rule form
// this makes sure we update the dropdown to display the data view id
// stored in the rule params when editing the rule.
useEffect(() => {
if (dataViewId != null && dataViewId.length > 0) setSelectedOptions([{ label: dataViewId }]);
}, [dataViewId]);
const onChangeDataViews = async (options: Array<EuiComboBoxOptionOption<string>>) => {
const [selectedDataView] = options;

// const onChangeIndexPatterns = (options: Array<EuiComboBoxOptionOption<string>>) => {
// setSelectedOptions(options);
// };
if (selectedDataView != null && selectedDataView.id != null) {
const dv = await data.dataViews.get(selectedDataView.id);

/* kibanaDataViews.map((dataView) => ({ label: dataView.id }))} */
setSelectedOption(options);
setIndexPattern(dv);
field.setValue(selectedDataView.id);
}
};

return (
<EuiFormRow
label={field.label}
helpText={field.helpText}
error={errorMessage}
isInvalid={isInvalid}
>
<EuiComboBox
isClearable
singleSelection={{ asPlainText: true }}
onChange={onChangeIndexPatterns}
// 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
options={
kibanaDataViews != null && Object.keys(kibanaDataViews).length > 0
? Object.keys(kibanaDataViews).map((dvId) => ({
label: dvId,
id: dvId,
}))
: []
}
selectedOptions={selectedOptions}
aria-label={i18n.PICK_INDEX_PATTERNS}
placeholder={i18n.PICK_INDEX_PATTERNS}
data-test-subj="detectionsDataViewSelectorDropdown"
/>
</EuiFormRow>
<>
{selectedDataViewNotFound && dataViewId != null && (
<>
<EuiCallOut
title={i18n.DATA_VIEW_NOT_FOUND_WARNING_LABEL}
color="warning"
iconType="help"
>
<p>{i18n.DATA_VIEW_NOT_FOUND_WARNING_DESCRIPTION(dataViewId)}</p>
</EuiCallOut>
<EuiSpacer size="s" />
</>
)}
<EuiFormRow
label={field.label}
helpText={field.helpText}
error={errorMessage}
isInvalid={isInvalid}
data-test-subj="pick-rule-data-source"
>
<EuiComboBox
isClearable
singleSelection={{ asPlainText: true }}
onChange={onChangeDataViews}
options={dataViewOptions}
selectedOptions={selectedOption}
aria-label={i18n.PICK_INDEX_PATTERNS}
placeholder={i18n.PICK_INDEX_PATTERNS}
data-test-subj="detectionsDataViewSelectorDropdown"
/>
</EuiFormRow>
</>
);
};
Original file line number Diff line number Diff line change
@@ -7,16 +7,26 @@

import { i18n } from '@kbn/i18n';

export const IMPORT_TIMELINE_MODAL = i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.importTimelineModalTitle',
export const PICK_INDEX_PATTERNS = i18n.translate(
'xpack.securitySolution.detectionEngine.stepDefineRule.pickDataView',
{
defaultMessage: 'Import query from saved timeline',
defaultMessage: 'Select a Data View',
}
);

export const PICK_INDEX_PATTERNS = i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.pickDataView',
export const DATA_VIEW_NOT_FOUND_WARNING_LABEL = i18n.translate(
'xpack.securitySolution.detectionEngine.stepDefineRule.dataViewNotFoundLabel',
{
defaultMessage: 'Select a Data View',
defaultMessage: 'Selected data view not found',
}
);

export const DATA_VIEW_NOT_FOUND_WARNING_DESCRIPTION = (dataView: string) =>
i18n.translate(
'xpack.securitySolution.detectionEngine.stepDefineRule.dataViewNotFoundDescription',
{
values: { dataView },
defaultMessage:
'Your data view of "id": "{dataView}" was not found. It could be that it has since been deleted.',
}
);
Loading

0 comments on commit 3722a4a

Please sign in to comment.