diff --git a/cypress/integration/1_detectors.spec.js b/cypress/integration/1_detectors.spec.js
index 47e8e35f0..5d4439ecf 100644
--- a/cypress/integration/1_detectors.spec.js
+++ b/cypress/integration/1_detectors.spec.js
@@ -129,7 +129,7 @@ const createDetector = (detectorName, dataSource, expectFailure) => {
.click({ force: true, timeout: 5000 })
.then(() => cy.contains('.euiTable .euiTableRow', 'Dns'));
- cy.getElementByText('.euiAccordion .euiTitle', 'Configure field mapping - optional');
+ cy.getElementByText('.euiAccordion .euiTitle', 'Field mapping - optional');
cy.get('[aria-controls="mappedTitleFieldsAccordion"]').then(($btn) => {
// first check if the accordion is expanded, if not than expand the accordion
if ($btn && $btn[0] && $btn[0].getAttribute('aria-expanded') === 'false') {
@@ -141,8 +141,9 @@ const createDetector = (detectorName, dataSource, expectFailure) => {
getNextButton().click({ force: true });
// TEST ALERTS PAGE
+ // Open the trigger details accordion
+ cy.get('[data-test-subj="trigger-details-btn"]').click({ force: true });
cy.getElementByText('.euiTitle.euiTitle--medium', 'Set up alert triggers');
- cy.getInputByPlaceholder('Enter a name to describe the alert condition').type('test_trigger');
cy.getElementByTestSubject('alert-tags-combo-box')
.type(`attack.defense_evasion{enter}`)
.find('input')
@@ -151,27 +152,8 @@ const createDetector = (detectorName, dataSource, expectFailure) => {
cy.getFieldByLabel('Specify alert severity').selectComboboxItem('1 (Highest)');
- // go to review page
- getNextButton().click({ force: true });
-
- // TEST REVIEW AND CREATE PAGE
- cy.getElementByText('.euiTitle', 'Review and create');
- cy.getElementByText('.euiTitle', 'Detector details');
- cy.getElementByText('.euiTitle', 'Field mapping');
- cy.getElementByText('.euiTitle', 'Alert triggers');
-
- cy.validateDetailsItem('Detector name', detectorName);
- cy.validateDetailsItem('Description', '-');
- cy.validateDetailsItem('Detector schedule', 'Every 1 minute');
- cy.validateDetailsItem('Detection rules', '14');
- cy.validateDetailsItem('Created at', '-');
- cy.validateDetailsItem('Last updated time', '-');
- cy.validateDetailsItem('Detector dashboard', 'Not available for this log type');
-
- validateAlertPanel('test_trigger');
-
- cy.intercept('POST', '/mappings').as('createMappingsRequest');
- cy.intercept('POST', '/detectors').as('createDetectorRequest');
+ cy.intercept('POST', '/_plugins/_security_analytics/mappings').as('createMappingsRequest');
+ cy.intercept('POST', '/_plugins/_security_analytics/detectors').as('createDetectorRequest');
// create the detector
cy.getElementByText('button', 'Create').click({ force: true });
@@ -199,7 +181,7 @@ const createDetector = (detectorName, dataSource, expectFailure) => {
cy.wait(5000); // waiting for the page to be reloaded after pushing detector id into route
cy.getElementByText('button.euiTab', 'Alert triggers').should('be.visible').click();
- validateAlertPanel('test_trigger');
+ validateAlertPanel('Trigger 1');
});
});
});
@@ -239,7 +221,7 @@ describe('Detectors', () => {
describe('...should validate form fields', () => {
beforeEach(() => {
- cy.intercept('/detectors/_search').as('detectorsSearch');
+ cy.intercept('/_plugins/_security_analytics/detectors/_search').as('detectorsSearch');
// Visit Detectors page before any test
cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`);
@@ -342,28 +324,21 @@ describe('Detectors', () => {
it('...should validate alerts page', () => {
fillDetailsForm(detectorName, cypressIndexDns);
getNextButton().click({ force: true });
- getTriggerNameField().should('be.empty');
-
- getTriggerNameField().focus().blur();
- getTriggerNameField()
- .parents('.euiFormRow__fieldWrapper')
- .find('.euiFormErrorText')
- .contains('Enter a name.');
-
- getTriggerNameField().type('Trigger name').focus().blur();
-
+ // Open the trigger details accordion
+ cy.get('[data-test-subj="trigger-details-btn"]').click({ force: true });
+ getTriggerNameField().should('have.value', 'Trigger 1');
getTriggerNameField()
.parents('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.should('not.exist');
- getNextButton().should('be.enabled');
+ getCreateDetectorButton().should('be.enabled');
getTriggerNameField().type('{selectall}').type('{backspace}').focus().blur();
- getNextButton().should('be.disabled');
+ getCreateDetectorButton().should('be.disabled');
cy.getButtonByText('Remove').click({ force: true });
- getNextButton().should('be.enabled');
+ getCreateDetectorButton().should('be.enabled');
});
it('...should show mappings warning', () => {
@@ -382,7 +357,7 @@ describe('Detectors', () => {
describe('...validate create detector flow', () => {
beforeEach(() => {
- cy.intercept('/detectors/_search').as('detectorsSearch');
+ cy.intercept('/_plugins/_security_analytics/detectors/_search').as('detectorsSearch');
// Visit Detectors page before any test
cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`);
@@ -400,7 +375,7 @@ describe('Detectors', () => {
});
it('...basic details can be edited', () => {
- cy.intercept('GET', '/indices').as('getIndices');
+ cy.intercept('GET', '/_plugins/_security_analytics/indices').as('getIndices');
openDetectorDetails(detectorName);
editDetectorDetails(detectorName, 'Detector details');
diff --git a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx
index 79939763b..9025bd3e9 100644
--- a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx
+++ b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx
@@ -18,7 +18,6 @@ import {
EuiText,
EuiTextArea,
} from '@elastic/eui';
-import { Detector } from '../../../../../../../models/interfaces';
import { AlertCondition } from '../../../../../../../models/interfaces';
import {
createSelectedOptions,
@@ -31,6 +30,7 @@ import { NotificationChannelOption, NotificationChannelTypeOptions } from '../..
import { NOTIFICATIONS_HREF } from '../../../../../../utils/constants';
import { getNameErrorMessage, validateName } from '../../../../../../utils/validation';
import { NotificationsCallOut } from '../../../../../../components/NotificationsCallOut';
+import { Detector } from '../../../../../../../types';
interface AlertConditionPanelProps extends RouteComponentProps {
alertCondition: AlertCondition;
@@ -299,172 +299,213 @@ export default class AlertConditionPanel extends Component<
});
}
+ const triggerDetailsSubheading = `
+ ${
+ selectedNames.length === 1
+ ? '1 rule'
+ : `${!selectedNames.length ? 'All' : selectedNames.length} rules`
+ },
+ ${
+ ruleSeverityLevels.length === 1
+ ? '1 severity'
+ : `${!ruleSeverityLevels.length ? 'All' : ruleSeverityLevels.length} severities`
+ },
+ ${tags.length === 1 ? '1 tag' : `${!tags.length ? 'All' : tags.length} tags`}
+ `;
+
return (
-
- Trigger name
-
+
+ Trigger details and condition
+
+ {triggerDetailsSubheading}
+
+
}
- isInvalid={nameFieldTouched && nameIsInvalid}
- error={getNameErrorMessage(name, nameIsInvalid, nameFieldTouched)}
>
-
-
-
-
-
- If a detection rule matches
-
-
+
+ Trigger name
+
+ }
+ isInvalid={nameFieldTouched && nameIsInvalid}
+ error={getNameErrorMessage(name, nameIsInvalid, nameFieldTouched)}
+ >
+
+
+
+
+
+ If a detection rule matches
+
+
-
- Rule names
-
- }
- >
-
-
-
+
+ Rule names
+
+ }
+ >
+
+
+
-
- Rule Severities
-
- }
- >
-
-
-
+
+ Rule Severities
+
+ }
+ >
+
+
+
-
- Tags
-
- }
- >
-
-
-
-
+
+ Tags
+
+ }
+ >
+
+
+
+
+
- Alert and notify
+ Notification
-
- Specify alert severity
-
+
+ Notification
+
+ {`Configure notification to receive alerts when the trigger condition is met.`}
+
+
}
>
-
+ Specify alert severity
+
}
- onChange={this.onAlertSeverityChange}
- singleSelection={{ asPlainText: true }}
- isClearable={false}
- data-test-subj={'security-levels-combo-box'}
- />
-
+ >
+
+
-
+
-
-
-
- Select channel to notify
-
- }
- >
- []}
- selectedOptions={
- selectedNotificationChannelOption as EuiComboBoxOptionOption[]
+
+
+
+ Select channel to notify
+
}
- onChange={this.onNotificationChannelsChange}
- singleSelection={{ asPlainText: true }}
- onBlur={refreshNotificationChannels}
+ >
+ []}
+ selectedOptions={
+ selectedNotificationChannelOption as EuiComboBoxOptionOption[]
+ }
+ onChange={this.onNotificationChannelsChange}
+ singleSelection={{ asPlainText: true }}
+ onBlur={refreshNotificationChannels}
+ isDisabled={!hasNotificationPlugin}
+ />
+
+
+
+
-
-
-
-
- Manage channels
-
-
-
-
- {!hasNotificationPlugin && (
- <>
-
-
- >
- )}
-
-
+ >
+ Manage channels
+
+
+
+
+ {!hasNotificationPlugin && (
+ <>
+
+
+ >
+ )}
+
+
+
- Show notify message
+ Notification message
}
- paddingSize={'none'}
+ paddingSize={'l'}
initialIsOpen={false}
>
-
-
-
-
- If a detection rule matches
-
-
-
-
-
-
-
-
-
-
-
@@ -361,216 +422,273 @@ Object {
- Alert and notify
+ Notification
-
-
-
-
- Show notify message
+ Notification message
@@ -612,11 +730,8 @@ Object {
>
-
-
-
-
- If a detection rule matches
-
-
-
-
-
-
-
-
-
-
-
-
- Alert and notify
+ Notification
-
-
-
-
- Show notify message
+ Notification message
@@ -1351,11 +1584,8 @@ Object {
>
-
void;
notificationsService: NotificationsService;
hasNotificationPlugin: boolean;
- skipAndConfigureHandler: () => void;
+ getTriggerName: () => string;
}
interface ConfigureAlertsState {
@@ -109,8 +105,9 @@ export default class ConfigureAlerts extends Component
{
@@ -147,29 +143,14 @@ export default class ConfigureAlerts extends Component
-
-
- Set up alert triggers
-
-
- Get notified when specific rule conditions are found by the detector.
-
-
- {triggers?.length && (
-
- {
- const { changeDetector, detector } = this.props;
- changeDetector({ ...detector, triggers: [] });
- skipAndConfigureHandler();
- }}
- >
- Skip and configure later
-
-
- )}
-
+ <>
+
+ Set up alert triggers
+
+
+ Get notified when specific rule conditions are found by the detector.
+
+ >
);
};
@@ -188,7 +169,7 @@ export default class ConfigureAlerts extends Component
- {isEdit ? alertCondition.name : 'Alert trigger'}
+ {alertCondition.name}
}
paddingSize={'none'}
@@ -199,7 +180,6 @@ export default class ConfigureAlerts extends Component
}
>
-
extends Compo
const columns: EuiBasicTableColumn[] = [
{
field: 'ruleFieldName',
- name: 'Detector field name',
+ name: 'Detection rule field',
dataType: 'string',
width: '25%',
render: (ruleFieldName: string) => ruleFieldName || DEFAULT_EMPTY_DATA,
},
- {
- field: '',
- name: 'Maps to',
- align: 'center',
- width: '15%',
- render: () => ,
- },
{
field: 'logFieldName',
- name: 'Log source field name',
+ name: 'Data source field',
dataType: 'string',
width: '45%',
render: (logFieldName: string, entry: FieldMappingsTableItem) => {
diff --git a/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx b/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx
index f704cb4e8..9b2d6c4cc 100644
--- a/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx
+++ b/public/pages/CreateDetector/components/ConfigureFieldMapping/containers/ConfigureFieldMapping.tsx
@@ -6,13 +6,13 @@
import React, { Component } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import {
- EuiSpacer,
EuiTitle,
EuiText,
- EuiCallOut,
EuiAccordion,
- EuiHorizontalRule,
- EuiPanel,
+ EuiTabs,
+ EuiTab,
+ EuiEmptyPrompt,
+ EuiCallOut,
} from '@elastic/eui';
import FieldMappingsTable from '../components/RequiredFieldMapping';
import { FieldMapping } from '../../../../../../models/interfaces';
@@ -27,7 +27,7 @@ export interface ruleFieldToIndexFieldMap {
[fieldName: string]: string;
}
-interface ConfigureFieldMappingProps extends RouteComponentProps {
+export interface ConfigureFieldMappingProps extends RouteComponentProps {
isEdit: boolean;
detector: Detector;
fieldMappingService: FieldMappingService;
@@ -44,6 +44,15 @@ interface ConfigureFieldMappingState {
createdMappings: ruleFieldToIndexFieldMap;
invalidMappingFieldNames: string[];
fieldMappingIsOpen: boolean;
+ showMappingEmptyPrompt: boolean;
+ selectedTabId: FieldMappingTabId;
+ selectedTabContent: React.ReactNode | null;
+ tabs: any[];
+}
+
+enum FieldMappingTabId {
+ AutomaticMappings = 'automatic-mappings-tab',
+ PendingMappings = 'pending-mappings-tab',
}
export default class ConfigureFieldMapping extends Component<
@@ -63,11 +72,16 @@ export default class ConfigureFieldMapping extends Component<
invalidMappingFieldNames: [],
detector: props.detector,
fieldMappingIsOpen: false,
+ tabs: [],
+ selectedTabId: FieldMappingTabId.PendingMappings,
+ selectedTabContent: null,
+ showMappingEmptyPrompt: false,
};
}
componentDidMount = async () => {
- this.getAllMappings();
+ await this.getAllMappings();
+ this.setupTabs();
};
componentDidUpdate(
@@ -80,13 +94,127 @@ export default class ConfigureFieldMapping extends Component<
{
detector: this.props.detector,
},
- () => {
- this.getAllMappings();
+ async () => {
+ await this.getAllMappings();
+ this.setupTabs();
}
);
+ } else if (prevState.createdMappings !== this.state.createdMappings) {
+ this.setupTabs();
}
}
+ setupTabs() {
+ const {
+ loading,
+ mappingsData,
+ createdMappings,
+ invalidMappingFieldNames,
+ selectedTabId,
+ detector: { detector_type, inputs },
+ } = this.state;
+ const existingMappings: ruleFieldToIndexFieldMap = {
+ ...createdMappings,
+ };
+
+ const mappedRuleFields: string[] = [];
+ const logFields: Set = new Set(mappingsData.unmapped_index_fields || []);
+ let pendingCount = mappingsData.unmapped_field_aliases?.length || 0;
+ const unmappedRuleFields = [...(mappingsData.unmapped_field_aliases || [])];
+
+ Object.keys(mappingsData.properties).forEach((ruleFieldName) => {
+ mappedRuleFields.unshift(ruleFieldName);
+
+ // Need this check to avoid adding undefined value
+ // When user removes existing mapping for default mapped values, the mapping will be undefined
+ if (existingMappings[ruleFieldName]) {
+ logFields.add(existingMappings[ruleFieldName]);
+ }
+ });
+
+ Object.keys(existingMappings).forEach((mappedRuleField) => {
+ if (unmappedRuleFields.includes(mappedRuleField)) {
+ pendingCount--;
+ }
+ });
+
+ const indexFieldOptions = Array.from(logFields);
+
+ const tabs = [
+ {
+ id: FieldMappingTabId.AutomaticMappings,
+ name: `Mapped fields (${mappedRuleFields.length})`,
+ content: (
+ <>
+
+
+ To generate accurate findings, we recommend to review the following field mappings
+ between the detection rules fields and the data source fields.
+
+
+
+ {...this.props}
+ loading={loading}
+ ruleFields={mappedRuleFields}
+ indexFields={indexFieldOptions}
+ mappingProps={{
+ type: MappingViewType.Edit,
+ existingMappings,
+ invalidMappingFieldNames,
+ onMappingCreation: this.onMappingCreation,
+ }}
+ />
+ >
+ ),
+ },
+ {
+ id: FieldMappingTabId.PendingMappings,
+ name: `Available fields (${pendingCount})`,
+ content: (
+ <>
+
+
+ To generate accurate findings, we recommend mapping all the fields of interest in
+ your data source to the detection rules fields.
+
+
+ {pendingCount > 0 && (
+
+ )}
+
+ {...this.props}
+ loading={loading}
+ ruleFields={unmappedRuleFields}
+ indexFields={indexFieldOptions}
+ mappingProps={{
+ type: MappingViewType.Edit,
+ existingMappings,
+ invalidMappingFieldNames,
+ onMappingCreation: this.onMappingCreation,
+ }}
+ />
+ >
+ ),
+ },
+ ];
+
+ const showMappingEmptyPrompt =
+ !detector_type ||
+ (!mappedRuleFields.length && !unmappedRuleFields.length) ||
+ !inputs[0]?.detector_input.indices[0];
+
+ this.setState({
+ showMappingEmptyPrompt,
+ tabs,
+ selectedTabContent:
+ tabs[selectedTabId === FieldMappingTabId.AutomaticMappings ? 0 : 1].content,
+ });
+ }
+
private getRuleFieldsForEnabledRules(): Set {
const ruleFieldsForEnabledRules = new Set();
this.props.enabledRules.forEach((rule) => {
@@ -187,158 +315,66 @@ export default class ConfigureFieldMapping extends Component<
);
};
- render() {
- const {
- loading,
- mappingsData,
- createdMappings,
- invalidMappingFieldNames,
- fieldMappingIsOpen,
- } = this.state;
- const existingMappings: ruleFieldToIndexFieldMap = {
- ...createdMappings,
- };
-
- const mappedRuleFields: string[] = [];
- const logFields: Set = new Set(mappingsData.unmapped_index_fields || []);
- let pendingCount = mappingsData.unmapped_field_aliases?.length || 0;
- const unmappedRuleFields = [...(mappingsData.unmapped_field_aliases || [])];
-
- Object.keys(mappingsData.properties).forEach((ruleFieldName) => {
- mappedRuleFields.unshift(ruleFieldName);
-
- // Need this check to avoid adding undefined value
- // When user removes existing mapping for default mapped values, the mapping will be undefined
- if (existingMappings[ruleFieldName]) {
- logFields.add(existingMappings[ruleFieldName]);
- }
- });
-
- Object.keys(existingMappings).forEach((mappedRuleField) => {
- if (unmappedRuleFields.includes(mappedRuleField)) {
- pendingCount--;
- }
- });
+ renderTabs() {
+ return this.state.tabs.map((tab, index) => (
+ this.setState({ selectedTabId: tab.id, selectedTabContent: tab.content })}
+ isSelected={this.state.selectedTabId === tab.id}
+ >
+ {tab.name}
+
+ ));
+ }
- const indexFieldOptions = Array.from(logFields);
+ render() {
+ const { selectedTabContent, fieldMappingIsOpen, showMappingEmptyPrompt } = this.state;
return (
- <>
-
-
-
- Configure field mapping - optional
-
-
-
- To perform threat detection, known field names from your log data source are
- automatically mapped to rule field names. Additional fields that may require
- manual mapping will be shown below.
-
- >
+
+
+
+ Field mapping - optional
+
+
+
+ To perform threat detection the field names from your data source have to be mapped to
+ detection rules field names.
+
+ >
+ }
+ buttonProps={{ style: { padding: '5px' } }}
+ id={'mappedTitleFieldsAccordion'}
+ initialIsOpen={false}
+ forceState={fieldMappingIsOpen ? 'open' : 'closed'}
+ onToggle={(isOpen) => {
+ this.setState({ fieldMappingIsOpen: isOpen });
+ }}
+ >
+ {showMappingEmptyPrompt ? (
+
+ No field mappings to display
+
}
- buttonProps={{ style: { padding: '5px' } }}
- id={'mappedTitleFieldsAccordion'}
- initialIsOpen={!!unmappedRuleFields.length}
- forceState={fieldMappingIsOpen ? 'open' : 'closed'}
- onToggle={(isOpen) => {
- this.setState({ fieldMappingIsOpen: isOpen });
- }}
- >
-
-
-
-
-
- {`Automatically mapped fields (${mappedRuleFields.length})`}
-
-
- }
- buttonProps={{
- style: { paddingLeft: '5px', paddingRight: '5px', paddingTop: '5px' },
- }}
- id={'mappedFieldsAccordion'}
- initialIsOpen={false}
- >
-
-
-
- {...this.props}
- loading={loading}
- ruleFields={mappedRuleFields}
- indexFields={indexFieldOptions}
- mappingProps={{
- type: MappingViewType.Edit,
- existingMappings,
- invalidMappingFieldNames,
- onMappingCreation: this.onMappingCreation,
- }}
- />
-
-
-
-
-
-
-
- {unmappedRuleFields.length > 0 ? (
- <>
- {pendingCount > 0 ? (
-
-
- To generate accurate findings, we recommend mapping the following security
- rules fields with the log fields in your data source.
-
-
- ) : (
-
- Your data source(s) have been mapped with all security rule fields.
-
- )}
-
-
-
-
- Pending field mappings
-
-
-
- {...this.props}
- loading={loading}
- ruleFields={unmappedRuleFields}
- indexFields={indexFieldOptions}
- mappingProps={{
- type: MappingViewType.Edit,
- existingMappings,
- invalidMappingFieldNames,
- onMappingCreation: this.onMappingCreation,
- }}
- />
-
- >
- ) : (
- <>
-
-
- Your data source(s) have been mapped with all security rule fields. No action
- is needed.
-
-
- >
- )}
-
-
-
-
-
- >
+ body={
+
+ Automatically mapped fields and additional fields that may
+
require manual mapping will be shown here. Select log type
+
for your data source.
+
+ }
+ />
+ ) : (
+
+ {this.renderTabs()}
+ {selectedTabContent}
+
+ )}
+
);
}
}
diff --git a/public/pages/CreateDetector/components/DefineDetector/components/DetectionRules/DetectionRules.tsx b/public/pages/CreateDetector/components/DefineDetector/components/DetectionRules/DetectionRules.tsx
index 993ff6a05..8b715d12f 100644
--- a/public/pages/CreateDetector/components/DefineDetector/components/DetectionRules/DetectionRules.tsx
+++ b/public/pages/CreateDetector/components/DefineDetector/components/DetectionRules/DetectionRules.tsx
@@ -6,7 +6,6 @@
import {
EuiAccordion,
EuiTitle,
- EuiHorizontalRule,
CriteriaWithPagination,
EuiText,
EuiEmptyPrompt,
@@ -111,8 +110,6 @@ export const DetectionRules: React.FC
= ({
initialIsOpen={false}
isLoading={loading}
>
-
-
{ruleItems.length ? (
void;
rulesState: CreateDetectorRulesState;
+ configureFieldMappingProps: ConfigureFieldMappingProps;
loadingRules?: boolean;
+ onDetectorTypeChange: (detectorType: string) => void;
onPageChange: (page: { index: number; size: number }) => void;
onRuleToggle: (changedItem: RuleItem, isActive: boolean) => void;
onAllRulesToggle: (enabled: boolean) => void;
@@ -63,11 +66,7 @@ export default class DetectorType extends Component
+
@@ -102,6 +101,10 @@ export default class DetectorType extends Component
+
+
+
+
);
}
diff --git a/public/pages/CreateDetector/components/DefineDetector/containers/DefineDetector.tsx b/public/pages/CreateDetector/components/DefineDetector/containers/DefineDetector.tsx
index 7c06c8d3f..7db946786 100644
--- a/public/pages/CreateDetector/components/DefineDetector/containers/DefineDetector.tsx
+++ b/public/pages/CreateDetector/components/DefineDetector/containers/DefineDetector.tsx
@@ -13,14 +13,13 @@ import DetectorType from '../components/DetectorType';
import { EuiComboBoxOptionOption } from '@opensearch-project/oui';
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';
import { RuleItem } from '../components/DetectionRules/types/interfaces';
import { CreateDetectorRulesState } from '../components/DetectionRules/DetectionRules';
import { NotificationsStart } from 'opensearch-dashboards/public';
import { logTypesWithDashboards } from '../../../../../utils/constants';
-import ConfigureFieldMapping from '../../ConfigureFieldMapping';
-import { Detector, FieldMapping } from '../../../../../../types';
+import { Detector, DetectorCreationStep, FieldMapping } from '../../../../../../types';
+import { ConfigureFieldMappingProps } from '../../ConfigureFieldMapping/containers/ConfigureFieldMapping';
interface DefineDetectorProps extends RouteComponentProps {
detector: Detector;
@@ -179,10 +178,26 @@ export default class DefineDetector extends Component rule.enabled),
+ replaceFieldMappings: replaceFieldMappings,
+ };
return (
@@ -219,9 +234,10 @@ export default class DefineDetector extends Component
) : null}
-
rule.enabled)}
- replaceFieldMappings={this.props.replaceFieldMappings}
- />
-
void;
-}
-
-export interface ReviewAndCreateState {}
-
-export class ReviewAndCreate extends React.Component {
- setDefineDetectorStep = () => {
- this.props.setDetectorCreationStep(DetectorCreationStep.DEFINE_DETECTOR);
- };
-
- setConfigureFieldMappingStep = () => {
- this.props.setDetectorCreationStep(DetectorCreationStep.DEFINE_DETECTOR);
- };
-
- setConfigureAlertsStep = () => {
- this.props.setDetectorCreationStep(DetectorCreationStep.CONFIGURE_ALERTS);
- };
-
- render() {
- return (
-
-
- Review and create
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
diff --git a/public/pages/CreateDetector/containers/CreateDetector.tsx b/public/pages/CreateDetector/containers/CreateDetector.tsx
index b3f833b71..0e1c42d99 100644
--- a/public/pages/CreateDetector/containers/CreateDetector.tsx
+++ b/public/pages/CreateDetector/containers/CreateDetector.tsx
@@ -19,9 +19,7 @@ import ConfigureAlerts from '../components/ConfigureAlerts';
import { FieldMapping } from '../../../../models/interfaces';
import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import { CoreServicesContext } from '../../../components/core_services';
-import { DetectorCreationStep } from '../models/types';
import { BrowserServices } from '../../../models/interfaces';
-import { ReviewAndCreate } from '../components/ReviewAndCreate/containers/ReviewAndCreate';
import { CreateDetectorRulesOptions } from '../../../models/types';
import { CreateDetectorRulesState } from '../components/DefineDetector/components/DetectionRules/DetectionRules';
import {
@@ -30,7 +28,7 @@ import {
} from '../components/DefineDetector/components/DetectionRules/types/interfaces';
import { NotificationsStart } from 'opensearch-dashboards/public';
import { getPlugins } from '../../../utils/helpers';
-import { Detector } from '../../../../types';
+import { Detector, DetectorCreationStep } from '../../../../types';
import { DataStore } from '../../../store/DataStore';
interface CreateDetectorProps extends RouteComponentProps {
@@ -53,6 +51,7 @@ export interface CreateDetectorState {
export default class CreateDetector extends Component {
static contextType = CoreServicesContext;
+ private triggerCounter = 1;
constructor(props: CreateDetectorProps) {
super(props);
@@ -71,7 +70,6 @@ export default class CreateDetector extends Component {
+ return `Trigger ${this.triggerCounter++}`;
+ };
+
getDetectorWithUpdatedRules(newRules: RuleItemInfo[]) {
return {
...this.state.detector,
@@ -315,16 +317,7 @@ export default class CreateDetector extends Component
- );
- case DetectorCreationStep.REVIEW_CREATE:
- return (
-
);
}
@@ -333,7 +326,12 @@ export default class CreateDetector extends Component ({
title: stepData.title,
- status: currentStep < stepData.step ? 'disabled' : undefined,
+ status:
+ currentStep > stepData.step
+ ? 'complete'
+ : currentStep < stepData.step
+ ? 'disabled'
+ : undefined,
children: <>>,
}));
}
@@ -364,7 +362,7 @@ export default class CreateDetector extends Component
)}
- {currentStep < DetectorCreationStep.REVIEW_CREATE && (
+ {currentStep < DetectorCreationStep.CONFIGURE_ALERTS && (
)}
- {currentStep === DetectorCreationStep.REVIEW_CREATE && (
+ {currentStep === DetectorCreationStep.CONFIGURE_ALERTS && (
= {
diff --git a/public/pages/CreateDetector/utils/constants.ts b/public/pages/CreateDetector/utils/constants.ts
index d4a27e787..91dc25f9d 100644
--- a/public/pages/CreateDetector/utils/constants.ts
+++ b/public/pages/CreateDetector/utils/constants.ts
@@ -3,8 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import { DetectorCreationStep } from '../../../../types';
import { DetectorCreationStepInfo } from '../models/interfaces';
-import { DetectorCreationStep } from '../models/types';
export const createDetectorSteps: Record = {
[DetectorCreationStep.DEFINE_DETECTOR]: {
@@ -15,10 +15,6 @@ export const createDetectorSteps: Record {
@@ -210,6 +209,7 @@ export default class UpdateAlertConditions extends Component<
changeDetector={this.changeDetector}
updateDataValidState={this.updateDataValidState}
hasNotificationPlugin={this.state.plugins.includes(OS_NOTIFICATION_PLUGIN)}
+ getTriggerName={() => ''}
/>
diff --git a/public/pages/Detectors/components/UpdateAlertConditions/__snapshots__/UpdateAlertConditions.test.tsx.snap b/public/pages/Detectors/components/UpdateAlertConditions/__snapshots__/UpdateAlertConditions.test.tsx.snap
index 78bd34098..2079db8fd 100644
--- a/public/pages/Detectors/components/UpdateAlertConditions/__snapshots__/UpdateAlertConditions.test.tsx.snap
+++ b/public/pages/Detectors/components/UpdateAlertConditions/__snapshots__/UpdateAlertConditions.test.tsx.snap
@@ -809,6 +809,7 @@ exports[` spec renders the component 1`] = `
"updateDetector": [Function],
}
}
+ getTriggerName={[Function]}
hasNotificationPlugin={false}
isEdit={true}
location={
diff --git a/public/pages/Detectors/containers/Detectors/Detectors.tsx b/public/pages/Detectors/containers/Detectors/Detectors.tsx
index e31526703..56890d3ab 100644
--- a/public/pages/Detectors/containers/Detectors/Detectors.tsx
+++ b/public/pages/Detectors/containers/Detectors/Detectors.tsx
@@ -35,6 +35,7 @@ import { FieldValueSelectionFilterConfigType } from '@elastic/eui/src/components
import { DetectorsService } from '../../../../services';
import { DetectorHit } from '../../../../../server/models/interfaces';
import { NotificationsStart } from 'opensearch-dashboards/public';
+import { Direction } from '@opensearch-project/oui/src/services/sort/sort_direction';
export interface DetectorsProps extends RouteComponentProps {
detectorService: DetectorsService;
@@ -91,7 +92,7 @@ export default class Detectors extends Component
} else if (!res.error.includes('no such index')) {
errorNotificationToast(notifications, 'retrieve', 'detectors', res.error);
}
- } catch (e) {
+ } catch (e: any) {
errorNotificationToast(notifications, 'retrieve', 'detectors', e);
}
this.setState({ loadingDetectors: false });
@@ -117,7 +118,7 @@ export default class Detectors extends Component
if (!updateRes.ok) {
errorNotificationToast(notifications, 'update', 'detector', updateRes.error);
}
- } catch (e) {
+ } catch (e: any) {
errorNotificationToast(notifications, 'update', 'detector', e);
}
this.getDetectors();
@@ -143,7 +144,7 @@ export default class Detectors extends Component
if (!deleteRes.ok) {
errorNotificationToast(notifications, 'delete', 'detector', deleteRes.error);
}
- } catch (e) {
+ } catch (e: any) {
errorNotificationToast(notifications, 'delete', 'detector', e);
}
};
@@ -281,14 +282,14 @@ export default class Detectors extends Component
sortable: true,
dataType: 'number',
align: 'left',
- render: (count) => count || DEFAULT_EMPTY_DATA,
+ render: (count: number) => count || DEFAULT_EMPTY_DATA,
},
{
field: 'lastUpdatedTime',
name: 'Last updated time',
sortable: true,
dataType: 'date',
- render: (last_update_time) => renderTime(last_update_time) || DEFAULT_EMPTY_DATA,
+ render: (last_update_time: number) => renderTime(last_update_time) || DEFAULT_EMPTY_DATA,
},
];
@@ -327,7 +328,7 @@ export default class Detectors extends Component
],
};
- const sorting = {
+ const sorting: { sort: { field: string; direction: Direction } } = {
sort: {
field: 'name',
direction: 'asc',
diff --git a/public/pages/Detectors/containers/FieldMappings/__snapshots__/EditFieldMappings.test.tsx.snap b/public/pages/Detectors/containers/FieldMappings/__snapshots__/EditFieldMappings.test.tsx.snap
index e934dc1c0..29d318aa8 100644
--- a/public/pages/Detectors/containers/FieldMappings/__snapshots__/EditFieldMappings.test.tsx.snap
+++ b/public/pages/Detectors/containers/FieldMappings/__snapshots__/EditFieldMappings.test.tsx.snap
@@ -501,21 +501,14 @@ exports[` spec renders the component 1`] = `
Object {
"dataType": "string",
"field": "ruleFieldName",
- "name": "Detector field name",
+ "name": "Detection rule field",
"render": [Function],
"width": "25%",
},
- Object {
- "align": "center",
- "field": "",
- "name": "Maps to",
- "render": [Function],
- "width": "15%",
- },
Object {
"dataType": "string",
"field": "logFieldName",
- "name": "Log source field name",
+ "name": "Data source field",
"render": [Function],
"width": "45%",
},
@@ -572,21 +565,14 @@ exports[` spec renders the component 1`] = `
Object {
"dataType": "string",
"field": "ruleFieldName",
- "name": "Detector field name",
+ "name": "Detection rule field",
"render": [Function],
"width": "25%",
},
- Object {
- "align": "center",
- "field": "",
- "name": "Maps to",
- "render": [Function],
- "width": "15%",
- },
Object {
"dataType": "string",
"field": "logFieldName",
- "name": "Log source field name",
+ "name": "Data source field",
"render": [Function],
"width": "45%",
},
@@ -640,7 +626,7 @@ exports[` spec renders the component 1`] = `
"allowNeutralSort": true,
"sort": Object {
"direction": "asc",
- "field": "Detector field name",
+ "field": "Detection rule field",
},
}
}
@@ -681,7 +667,7 @@ exports[` spec renders the component 1`] = `
Object {
"isSortAscending": undefined,
"isSorted": false,
- "key": "_data_s_status_3",
+ "key": "_data_s_status_2",
"name": "Status",
"onSort": [Function],
},
@@ -833,62 +819,15 @@ exports[` spec renders the component 1`] = `
values={
Object {
"description": undefined,
- "innerText": "Detector field name",
- }
- }
- >
-
- Detector field name
-
-
-
-
-
-
-
-
-
-
-
-
-
- Maps to
+ Detection rule field
@@ -898,13 +837,13 @@ exports[` spec renders the component 1`] = `
| spec renders the component 1`] = `
values={
Object {
"description": undefined,
- "innerText": "Log source field name",
+ "innerText": "Data source field",
}
}
>
- Log source field name
+ Data source field
@@ -945,9 +884,9 @@ exports[` spec renders the component 1`] = `
@@ -955,7 +894,7 @@ exports[` spec renders the component 1`] = `
aria-live="polite"
aria-sort="none"
className="euiTableHeaderCell"
- data-test-subj="tableHeaderCell_status_3"
+ data-test-subj="tableHeaderCell_status_2"
role="columnheader"
scope="col"
style={
@@ -1013,12 +952,12 @@ exports[` spec renders the component 1`] = `
>
= ({
'After detectors are created, you can view insights and analyze security findings.'
}
buttons={[
- {
- text: 'Overview',
- onClick: () => dismissPopup(),
- opts: {
- fill: true,
- },
- },
{
text: 'View findings',
onClick: () => {
@@ -82,9 +75,6 @@ export const GettingStartedPopup: React.FC = ({
dismissPopup();
history.push(ROUTES.ALERTS);
},
- opts: {
- fill: true,
- },
},
]}
/>
@@ -96,16 +86,6 @@ export const GettingStartedPopup: React.FC = ({
{
- dismissPopup();
- history.push(ROUTES.RULES_CREATE);
- },
- opts: {
- fill: true,
- },
- },
{
text: 'Manage rules',
onClick: () => {
diff --git a/public/pages/Overview/components/Widgets/Summary.tsx b/public/pages/Overview/components/Widgets/Summary.tsx
index 56ae1e54d..c73b5b135 100644
--- a/public/pages/Overview/components/Widgets/Summary.tsx
+++ b/public/pages/Overview/components/Widgets/Summary.tsx
@@ -4,6 +4,7 @@
*/
import {
+ EuiButton,
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
@@ -23,7 +24,7 @@ import {
} from '../../utils/helpers';
import { AlertItem, FindingItem } from '../../models/interfaces';
import { createSelectComponent, renderVisualization } from '../../../../utils/helpers';
-import { ROUTES } from '../../../../utils/constants';
+import { PLUGIN_NAME, ROUTES } from '../../../../utils/constants';
import { ChartContainer } from '../../../../components/Charts/ChartContainer';
export interface SummaryProps {
@@ -146,29 +147,39 @@ export const Summary: React.FC = ({
-
- {createStatComponent(
- 'Total active alerts',
- { url: ROUTES.ALERTS, color: 'danger' },
- activeAlerts
- )}
- {createStatComponent(
- 'Total findings',
- { url: ROUTES.FINDINGS, color: 'primary' },
- totalFindings
- )}
-
+ {activeAlerts === 0 && totalFindings === 0 ? null : (
+
+ {createStatComponent(
+ 'Total active alerts',
+ { url: ROUTES.ALERTS, color: 'danger' },
+ activeAlerts
+ )}
+ {createStatComponent(
+ 'Total findings',
+ { url: ROUTES.FINDINGS, color: 'primary' },
+ totalFindings
+ )}
+
+ )}
{activeAlerts === 0 && totalFindings === 0 ? (
No alerts and findings found}
body={
-
- Adjust the time range to see more results or{' '}
- create a detector to
- generate findings.{' '}
-
+ <>
+
+ Adjust the time range to see more results or create a
+ detector to generate findings.
+
+
+ Create a detector
+
+ >
}
/>
) : (
diff --git a/public/pages/Overview/containers/Overview/Overview.tsx b/public/pages/Overview/containers/Overview/Overview.tsx
index 64c21ea4d..18c2e13b8 100644
--- a/public/pages/Overview/containers/Overview/Overview.tsx
+++ b/public/pages/Overview/containers/Overview/Overview.tsx
@@ -12,12 +12,15 @@ import {
EuiSuperDatePicker,
EuiTitle,
EuiSpacer,
+ EuiButton,
} from '@elastic/eui';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import {
BREADCRUMBS,
DEFAULT_DATE_RANGE,
MAX_RECENTLY_USED_TIME_RANGES,
+ PLUGIN_NAME,
+ ROUTES,
} from '../../../../utils/constants';
import { OverviewProps, OverviewState } from '../../models/interfaces';
import { CoreServicesContext } from '../../../../../public/components/core_services';
@@ -68,7 +71,7 @@ export const Overview: React.FC = (props) => {
};
const overviewViewModelActor = useMemo(
- () => new OverviewViewModelActor(services, context.notifications),
+ () => new OverviewViewModelActor(services, context?.notifications!),
[services, context]
);
@@ -139,7 +142,7 @@ export const Overview: React.FC = (props) => {
return (
-
+
Overview
@@ -166,6 +169,15 @@ export const Overview: React.FC = (props) => {
updateButtonProps={{ fill: false }}
/>
+
+
+ Create detector
+
+
diff --git a/types/Detector.ts b/types/Detector.ts
index 5b44c6164..7d3565b76 100644
--- a/types/Detector.ts
+++ b/types/Detector.ts
@@ -56,9 +56,7 @@ export interface SourceIndexOption {
export enum DetectorCreationStep {
DEFINE_DETECTOR = 1,
- CONFIGURE_FIELD_MAPPING = 2,
- CONFIGURE_ALERTS = 3,
- REVIEW_CREATE = 4,
+ CONFIGURE_ALERTS = 2,
}
export interface DetectorHit {
| |