Skip to content

Commit

Permalink
Feature] create detector | make data source multi select field (opens…
Browse files Browse the repository at this point in the history
…earch-project#424)

* [FEATURE] Detector must have at least one alert set opensearch-project#288

Signed-off-by: Jovan Cvetkovic <[email protected]>

* [BUG] Create detector | Interval field can be empty opensearch-project#378

Signed-off-by: Jovan Cvetkovic <[email protected]>

* Adjust styling for Finding details flyout opensearch-project#369

Signed-off-by: Jovan Cvetkovic <[email protected]>

* unit tests

Signed-off-by: Jovan Cvetkovic <[email protected]>

* detector unit tests

Signed-off-by: Jovan Cvetkovic <[email protected]>

* detector unit tests

Signed-off-by: Jovan Cvetkovic <[email protected]>

* detector unit tests

Signed-off-by: Jovan Cvetkovic <[email protected]>

* detector unit tests

Signed-off-by: Jovan Cvetkovic <[email protected]>

* detector unit tests

Signed-off-by: Jovan Cvetkovic <[email protected]>

* detector unit tests

Signed-off-by: Jovan Cvetkovic <[email protected]>

* detector unit tests

Signed-off-by: Jovan Cvetkovic <[email protected]>

* detector unit tests

Signed-off-by: Jovan Cvetkovic <[email protected]>

* detector unit tests

Signed-off-by: Jovan Cvetkovic <[email protected]>

* unit tests review

Signed-off-by: Jovan Cvetkovic <[email protected]>

* unit tests review

Signed-off-by: Jovan Cvetkovic <[email protected]>

* unit tests review

Signed-off-by: Jovan Cvetkovic <[email protected]>

* unit tests review

Signed-off-by: Jovan Cvetkovic <[email protected]>

* unit tests review

Signed-off-by: Jovan Cvetkovic <[email protected]>

* unit tests review

Signed-off-by: Jovan Cvetkovic <[email protected]>

* unit tests review

Signed-off-by: Jovan Cvetkovic <[email protected]>

* unit tests review

Signed-off-by: Jovan Cvetkovic <[email protected]>

* Feature/update vertical domain #372

Signed-off-by: Jovan Cvetkovic <[email protected]>

* Unit tests for public components opensearch-project#383

Signed-off-by: Jovan Cvetkovic <[email protected]>

* Unit tests for public components opensearch-project#383

Signed-off-by: Jovan Cvetkovic <[email protected]>

* Unit tests for public components opensearch-project#383

Signed-off-by: Jovan Cvetkovic <[email protected]>

* Unit tests for public components opensearch-project#383

Signed-off-by: Jovan Cvetkovic <[email protected]>

* Unit tests for public components opensearch-project#383
[BUG] Detector Edit | Custom rule are not selected on update rules opensearch-project#406

Signed-off-by: Jovan Cvetkovic <[email protected]>

* Unit tests for public components opensearch-project#383
[BUG] Detector Edit | Custom rule are not selected on update rules opensearch-project#406

Signed-off-by: Jovan Cvetkovic <[email protected]>

* PR code review

Signed-off-by: Jovan Cvetkovic <[email protected]>

* PR code review

Signed-off-by: Jovan Cvetkovic <[email protected]>

* PR code review

Signed-off-by: Jovan Cvetkovic <[email protected]>

* [FEATURE] Create detector | Make data source multi-select field opensearch-project#419

Signed-off-by: Jovan Cvetkovic <[email protected]>

* [FEATURE] Create detector | Make data source multi-select field opensearch-project#419

Signed-off-by: Jovan Cvetkovic <[email protected]>

* [FEATURE] Create detector | Make data source multi-select field opensearch-project#419

Signed-off-by: Jovan Cvetkovic <[email protected]>

* [FEATURE] Create detector | Make data source multi-select field opensearch-project#419

Signed-off-by: Jovan Cvetkovic <[email protected]>

* [FEATURE] Create detector | Make data source multi-select field opensearch-project#419

Signed-off-by: Jovan Cvetkovic <[email protected]>

* [FEATURE] Create detector | Make data source multi-select field opensearch-project#419

Signed-off-by: Jovan Cvetkovic <[email protected]>

* [FEATURE] Create detector | Make data source multi-select field opensearch-project#419

Signed-off-by: Jovan Cvetkovic <[email protected]>

* [FEATURE] Create detector | Make data source multi-select field opensearch-project#419

Signed-off-by: Jovan Cvetkovic <[email protected]>

* [FEATURE] Create detector | Make data source multi-select field opensearch-project#419

Signed-off-by: Jovan Cvetkovic <[email protected]>

* unit tests fix

Signed-off-by: Jovan Cvetkovic <[email protected]>

---------

Signed-off-by: Jovan Cvetkovic <[email protected]>
  • Loading branch information
jovancvetkovic3006 authored Feb 21, 2023
1 parent c4f97b2 commit 74fb409
Show file tree
Hide file tree
Showing 13 changed files with 1,664 additions and 4,074 deletions.
39 changes: 36 additions & 3 deletions cypress/integration/1_detectors.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { OPENSEARCH_DASHBOARDS_URL } from '../support/constants';
import sample_index_settings from '../fixtures/sample_index_settings.json';
import dns_rule_data from '../fixtures/integration_tests/rule/create_dns_rule.json';
import sample_dns_settings from '../fixtures/integration_tests/index/create_dns_settings.json';

const testMappings = {
properties: {
Expand All @@ -16,14 +17,15 @@ const testMappings = {
},
};

const cypressDNSRule = 'Cypress DNS Rule';
const cypressDNSRule = dns_rule_data.title;

describe('Detectors', () => {
const indexName = 'cypress-test-dns';
const detectorName = 'test detector';

before(() => {
cy.cleanUpTests();

// Create test index
cy.createIndex(indexName, sample_index_settings).then(() =>
cy
Expand All @@ -41,8 +43,6 @@ describe('Detectors', () => {
);

cy.createRule(dns_rule_data);

cy.contains(detectorName).should('not.exist');
});

beforeEach(() => {
Expand All @@ -57,6 +57,39 @@ describe('Detectors', () => {
});
});

it('...should show mappings warning', () => {
const indexName = 'cypress-index-windows';
const dnsName = 'cypress-index-dns';
cy.createIndex(indexName, sample_index_settings);
cy.createIndex(dnsName, sample_dns_settings);

// Locate Create detector button click to start
cy.get('.euiButton').filter(':contains("Create detector")').click({ force: true });

// Check to ensure process started
cy.waitForPageLoad('create-detector', {
contains: 'Define detector',
});

// Select our pre-seeded data source (check indexName)
cy.get(`[data-test-subj="define-detector-select-data-source"]`)
.find('input')
.focus()
.realType(indexName);

// Select threat detector type (Windows logs)
cy.get(`input[id="dns"]`).click({ force: true });

// Select our pre-seeded data source (check indexName)
cy.get(`[data-test-subj="define-detector-select-data-source"]`)
.find('input')
.focus()
.realType(dnsName)
.realPress('Enter');

cy.get('.euiCallOut').should('be.visible').contains('Detector configuration warning');
});

it('...can be created', () => {
// Locate Create detector button click to start
cy.get('.euiButton').filter(':contains("Create detector")').click({ force: true });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
EuiIcon,
EuiInMemoryTable,
EuiText,
EuiToolTip,
} from '@elastic/eui';
import { DEFAULT_EMPTY_DATA } from '../../../../../../utils/constants';
import { STATUS_ICON_PROPS } from '../../utils/constants';
Expand Down Expand Up @@ -140,14 +141,22 @@ export default class FieldMappingsTable<T extends MappingViewType> extends Compo
const { existingMappings: createdMappings, invalidMappingFieldNames } = this.props
.mappingProps as MappingProps[MappingViewType.Edit];
let iconProps = STATUS_ICON_PROPS['unmapped'];
let iconTooltip = 'This field needs to be mapped with a field from your log source.';
if (
createdMappings[entry.ruleFieldName] &&
!invalidMappingFieldNames.includes(entry.ruleFieldName)
) {
iconProps = STATUS_ICON_PROPS['mapped'];
iconTooltip = 'This field has been mapped.';
}

return <EuiIcon {...iconProps} /> || DEFAULT_EMPTY_DATA;
return (
(
<EuiToolTip position="top" content={iconTooltip}>
<EuiIcon {...iconProps} />
</EuiToolTip>
) || DEFAULT_EMPTY_DATA
);
},
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ export default class DetectorDataSource extends Component<
error={isInvalid && (errorMessage || 'Select an input source.')}
>
<EuiComboBox
singleSelection={{ asPlainText: true }}
placeholder={'Select an input source for the detector.'}
isLoading={loading}
options={indexOptions}
Expand All @@ -110,6 +109,7 @@ export default class DetectorDataSource extends Component<
onChange={this.onSelectionChange}
onCreateOption={this.onCreateOption}
isInvalid={!!errorMessage}
isClearable={true}
data-test-subj={'define-detector-select-data-source'}
/>
</EuiFormRow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

import React, { Component } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { EuiSpacer, EuiTitle, EuiText } from '@elastic/eui';
import { EuiSpacer, EuiTitle, EuiText, EuiCallOut, EuiTextColor } from '@elastic/eui';
import { Detector, PeriodSchedule } from '../../../../../../models/interfaces';
import DetectorBasicDetailsForm from '../components/DetectorDetails';
import DetectorDataSource from '../components/DetectorDataSource';
import DetectorType from '../components/DetectorType';
import { EuiComboBoxOptionOption } from '@opensearch-project/oui';
import { IndexService } from '../../../../../services';
import { FieldMappingService, IndexService } from '../../../../../services';
import { MIN_NUM_DATA_SOURCES } from '../../../../Detectors/utils/constants';
import { DetectorCreationStep } from '../../../models/types';
import { DetectorSchedule } from '../components/DetectorSchedule/DetectorSchedule';
Expand All @@ -21,11 +21,13 @@ import {
DetectionRules,
} from '../components/DetectionRules/DetectionRules';
import { NotificationsStart } from 'opensearch-dashboards/public';
import _ from 'lodash';

interface DefineDetectorProps extends RouteComponentProps {
detector: Detector;
isEdit: boolean;
indexService: IndexService;
filedMappingService: FieldMappingService;
rulesState: CreateDetectorRulesState;
notifications: NotificationsStart;
loadingRules?: boolean;
Expand All @@ -36,16 +38,66 @@ interface DefineDetectorProps extends RouteComponentProps {
onAllRulesToggle: (enabled: boolean) => void;
}

interface DefineDetectorState {}
interface DefineDetectorState {
message: string[];
}

export default class DefineDetector extends Component<DefineDetectorProps, DefineDetectorState> {
updateDetectorCreationState(detector: Detector) {
const isDataValid =
state = {
message: [],
};

private indicesMappings: any = {};

async updateDetectorCreationState(detector: Detector) {
let isDataValid =
!!detector.name &&
!!detector.detector_type &&
detector.inputs[0].detector_input.indices.length >= MIN_NUM_DATA_SOURCES &&
!!detector.schedule.period.interval;
this.props.changeDetector(detector);

const allIndices = detector.inputs[0].detector_input.indices;
for (let indexName in this.indicesMappings) {
if (allIndices.indexOf(indexName) === -1) {
// cleanup removed indexes
delete this.indicesMappings[indexName];
}
}

for (const indexName of allIndices) {
if (!this.indicesMappings[indexName]) {
const detectorType = this.props.detector.detector_type.toLowerCase();
const result = await this.props.filedMappingService.getMappingsView(
indexName,
detectorType
);
result.ok && (this.indicesMappings[indexName] = result.response.unmapped_field_aliases);
}
}

if (!_.isEmpty(this.indicesMappings)) {
let firstMapping: string[] = [];
let firstMatchMappingIndex: string = '';
let message: string[] = [];
for (let indexName in this.indicesMappings) {
if (this.indicesMappings.hasOwnProperty(indexName)) {
if (!firstMapping.length) firstMapping = this.indicesMappings[indexName];
!firstMatchMappingIndex.length && (firstMatchMappingIndex = indexName);
if (!_.isEqual(firstMapping, this.indicesMappings[indexName])) {
message = [
`The below log sources don't have the same fields, please consider creating separate detectors for them.`,
firstMatchMappingIndex,
indexName,
];
break;
}
}
}

this.setState({ message });
}

this.props.updateDataValidState(DetectorCreationStep.DEFINE_DETECTOR, isDataValid);
}

Expand Down Expand Up @@ -162,6 +214,7 @@ export default class DefineDetector extends Component<DefineDetectorProps, Defin
onPageChange,
onAllRulesToggle,
} = this.props;
const { message } = this.state;
const { name, inputs, detector_type } = this.props.detector;
const { description, indices } = inputs[0].detector_input;

Expand All @@ -177,6 +230,22 @@ export default class DefineDetector extends Component<DefineDetectorProps, Defin
</EuiText>

<EuiSpacer size={'m'} />
{message.length ? (
<>
<EuiCallOut title="Detector configuration warning" color="warning" iconType="alert">
{message.map((messageItem: string, index: number) => (
<EuiTextColor
color={index === 0 ? 'default' : 'warning'}
key={`callout-message-part-${index}`}
>
{messageItem}
<br />
</EuiTextColor>
))}
</EuiCallOut>
<EuiSpacer size={'m'} />
</>
) : null}

<DetectorBasicDetailsForm
{...this.props}
Expand Down
9 changes: 4 additions & 5 deletions public/pages/CreateDetector/containers/CreateDetector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { createDetectorSteps } from '../utils/constants';
import {
BREADCRUMBS,
EMPTY_DEFAULT_DETECTOR,
OS_NOTIFICATION_PLUGIN,
PLUGIN_NAME,
ROUTES,
OS_NOTIFICATION_PLUGIN,
} from '../../../utils/constants';
import ConfigureFieldMapping from '../components/ConfigureFieldMapping';
import ConfigureAlerts from '../components/ConfigureAlerts';
Expand All @@ -32,8 +32,8 @@ import {
import { NotificationsStart } from 'opensearch-dashboards/public';
import {
errorNotificationToast,
successNotificationToast,
getPlugins,
successNotificationToast,
} from '../../../utils/helpers';
import { RulesViewModelActor } from '../../Rules/models/RulesViewModelActor';

Expand Down Expand Up @@ -176,14 +176,12 @@ export default class CreateDetector extends Component<CreateDetectorProps, Creat

getRulesOptions(): CreateDetectorRulesOptions {
const enabledRules = this.state.rulesState.allRules.filter((rule) => rule.enabled);
const options: CreateDetectorRulesOptions = enabledRules.map((rule) => ({
return enabledRules.map((rule) => ({
id: rule._id,
name: rule._source.title,
severity: rule._source.level,
tags: rule._source.tags.map((tag: { value: string }) => tag.value),
}));

return options;
}

async setupRulesState() {
Expand Down Expand Up @@ -307,6 +305,7 @@ export default class CreateDetector extends Component<CreateDetectorProps, Creat
{...this.props}
detector={this.state.detector}
indexService={services.indexService}
filedMappingService={services.fieldMappingService}
rulesState={this.state.rulesState}
loadingRules={this.state.loadingRules}
onRuleToggle={this.onRuleToggle}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,28 @@
*/

import React from 'react';
import { render } from '@testing-library/react';
import props from '../../../../../test/mocks/Detectors/components/UpdateAlertConditions/UpdateAlertConditions.mock';
import { expect } from '@jest/globals';
import UpdateAlertConditions from './UpdateAlertConditions';
import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
import { contextServicesMock } from '../../../../../test/mocks/useContext.mock';

jest.mock(
'../../../CreateDetector/components/ConfigureAlerts/containers/ConfigureAlerts.tsx',
() => () => {
return <mock-component mock="ConfigureAlerts" />;
}
);

describe('<UpdateAlertConditions /> spec', () => {
it('renders the component', () => {
const view = render(<UpdateAlertConditions {...props} />);
expect(view).toMatchSnapshot();
it('renders the component', async () => {
let wrapper;
await act(async () => {
UpdateAlertConditions.contextType = React.createContext(contextServicesMock);
wrapper = await mount(<UpdateAlertConditions {...props} />);
});
wrapper.update();
expect(wrapper).toMatchSnapshot();
});
});
Loading

0 comments on commit 74fb409

Please sign in to comment.