Skip to content

Commit

Permalink
Make notifications optional 2.11 (opensearch-project#770)
Browse files Browse the repository at this point in the history
* Threat intel feed support for detector creation (opensearch-project#762)

* added threat intel feed support for detector creation

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated cypress workflow file

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated alerts; findings UX

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* refactored alert condition panel; update detector for intel feeds

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated snapshots, mocks

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated workflow

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated tests

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated snapshot

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated UI; tests

Signed-off-by: Amardeepsingh Siglani <[email protected]>

---------

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* fixed tests; make notification optional

Signed-off-by: Amardeepsingh Siglani <[email protected]>

---------

Signed-off-by: Amardeepsingh Siglani <[email protected]>
  • Loading branch information
amsiglan committed Oct 27, 2023
1 parent 5f92126 commit bb51a00
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 152 deletions.
266 changes: 133 additions & 133 deletions cypress/integration/1_detectors.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,139 +236,139 @@ describe('Detectors', () => {
}).should('have.property', 'status', 200);
});

// describe('...should validate form fields', () => {
// beforeEach(() => {
// cy.intercept('/_plugins/_security_analytics/detectors/_search').as('detectorsSearch');

// // Visit Detectors page before any test
// cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`);
// cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete');

// openCreateForm();
// });

// it('...should validate name field', () => {
// getNameField().should('be.empty');
// getNameField().focus().blur();
// getNameField().parentsUntil('.euiFormRow__fieldWrapper').siblings().contains('Enter a name.');

// getNameField().type('text').focus().blur();

// getNameField()
// .parents('.euiFormRow__fieldWrapper')
// .find('.euiFormErrorText')
// .contains(
// 'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.'
// );

// getNameField().type('{selectall}').type('{backspace}').type('tex&').focus().blur();

// getNameField()
// .parents('.euiFormRow__fieldWrapper')
// .find('.euiFormErrorText')
// .contains(
// 'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.'
// );

// getNameField()
// .type('{selectall}')
// .type('{backspace}')
// .type('Detector name')
// .focus()
// .blur()
// .parents('.euiFormRow__fieldWrapper')
// .find('.euiFormErrorText')
// .should('not.exist');
// });

// it('...should validate description field', () => {
// const longDescriptionText =
// 'This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.';

// getDescriptionField().should('be.empty');

// getDescriptionField().type(longDescriptionText).focus().blur();

// getDescriptionField()
// .parents('.euiFormRow__fieldWrapper')
// .find('.euiFormErrorText')
// .contains(
// 'Description should only consist of upper and lowercase letters, numbers 0-9, commas, hyphens, periods, spaces, and underscores. Max limit of 500 characters.'
// );

// getDescriptionField()
// .type('{selectall}')
// .type('{backspace}')
// .type('Detector description...')
// .focus()
// .blur();

// getDescriptionField()
// .type('{selectall}')
// .type('{backspace}')
// .type('Detector name')
// .focus()
// .blur()
// .parents('.euiFormRow__fieldWrapper')
// .find('.euiFormErrorText')
// .should('not.exist');
// });

// it('...should validate data source field', () => {
// getDataSourceField()
// .focus()
// .blur()
// .parentsUntil('.euiFormRow__fieldWrapper')
// .siblings()
// .contains('Select an input source.');

// getDataSourceField().selectComboboxItem(cypressIndexDns);
// getDataSourceField()
// .focus()
// .blur()
// .parentsUntil('.euiFormRow__fieldWrapper')
// .find('.euiFormErrorText')
// .should('not.exist');
// });

// it('...should validate next button', () => {
// getNextButton().should('be.disabled');

// fillDetailsForm(detectorName, cypressIndexDns);
// getNextButton().should('be.enabled');
// });

// it('...should validate alerts page', () => {
// fillDetailsForm(detectorName, cypressIndexDns);
// getNextButton().click({ force: true });
// // 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');

// getTriggerNameField().type('{selectall}').type('{backspace}').focus().blur();
// getCreateDetectorButton().should('be.disabled');

// cy.getButtonByText('Remove').click({ force: true });
// getCreateDetectorButton().should('be.enabled');
// });

// it('...should show mappings warning', () => {
// fillDetailsForm(detectorName, cypressIndexDns);

// getDataSourceField().selectComboboxItem(cypressIndexWindows);
// getDataSourceField().focus().blur();

// cy.get('[data-test-subj="define-detector-diff-log-types-warning"]')
// .should('be.visible')
// .contains(
// 'To avoid issues with field mappings, we recommend creating separate detectors for different log types.'
// );
// });
// });
describe('...should validate form fields', () => {
beforeEach(() => {
cy.intercept('/_plugins/_security_analytics/detectors/_search').as('detectorsSearch');

// Visit Detectors page before any test
cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`);
cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete');

openCreateForm();
});

it('...should validate name field', () => {
getNameField().should('be.empty');
getNameField().focus().blur();
getNameField().parentsUntil('.euiFormRow__fieldWrapper').siblings().contains('Enter a name.');

getNameField().type('text').focus().blur();

getNameField()
.parents('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.contains(
'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.'
);

getNameField().type('{selectall}').type('{backspace}').type('tex&').focus().blur();

getNameField()
.parents('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.contains(
'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.'
);

getNameField()
.type('{selectall}')
.type('{backspace}')
.type('Detector name')
.focus()
.blur()
.parents('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.should('not.exist');
});

it('...should validate description field', () => {
const longDescriptionText =
'This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.';

getDescriptionField().should('be.empty');

getDescriptionField().type(longDescriptionText).focus().blur();

getDescriptionField()
.parents('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.contains(
'Description should only consist of upper and lowercase letters, numbers 0-9, commas, hyphens, periods, spaces, and underscores. Max limit of 500 characters.'
);

getDescriptionField()
.type('{selectall}')
.type('{backspace}')
.type('Detector description...')
.focus()
.blur();

getDescriptionField()
.type('{selectall}')
.type('{backspace}')
.type('Detector name')
.focus()
.blur()
.parents('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.should('not.exist');
});

it('...should validate data source field', () => {
getDataSourceField()
.focus()
.blur()
.parentsUntil('.euiFormRow__fieldWrapper')
.siblings()
.contains('Select an input source.');

getDataSourceField().selectComboboxItem(cypressIndexDns);
getDataSourceField()
.focus()
.blur()
.parentsUntil('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.should('not.exist');
});

it('...should validate next button', () => {
getNextButton().should('be.disabled');

fillDetailsForm(detectorName, cypressIndexDns);
getNextButton().should('be.enabled');
});

it('...should validate alerts page', () => {
fillDetailsForm(detectorName, cypressIndexDns);
getNextButton().click({ force: true });
// 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');

getTriggerNameField().type('{selectall}').type('{backspace}').focus().blur();
getCreateDetectorButton().should('be.disabled');

cy.getButtonByText('Remove').click({ force: true });
getCreateDetectorButton().should('be.enabled');
});

it('...should show mappings warning', () => {
fillDetailsForm(detectorName, cypressIndexDns);

getDataSourceField().selectComboboxItem(cypressIndexWindows);
getDataSourceField().focus().blur();

cy.get('[data-test-subj="define-detector-diff-log-types-warning"]')
.should('be.visible')
.contains(
'To avoid issues with field mappings, we recommend creating separate detectors for different log types.'
);
});
});

describe('...validate create detector flow', () => {
beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ interface AlertConditionPanelState {
showNotificationDetails: boolean;
detectionRulesTriggerEnabled: boolean;
threatIntelTriggerEnabled: boolean;
notificationError: string;
}

export default class AlertConditionPanel extends Component<
Expand All @@ -73,7 +72,6 @@ export default class AlertConditionPanel extends Component<
showNotificationDetails: true,
detectionRulesTriggerEnabled: props.alertCondition.detection_types.includes('rules'),
threatIntelTriggerEnabled: props.alertCondition.detection_types.includes('threat_intel'),
notificationError: '',
};
}

Expand Down Expand Up @@ -211,7 +209,6 @@ export default class AlertConditionPanel extends Component<
const actions = alertCondition.actions;
if (selectedOptions.length > 0) {
actions[0].destination_id = selectedOptions[0].value!;
this.setState({ notificationError: '' });
} else {
actions[0].destination_id = '';
}
Expand Down Expand Up @@ -291,7 +288,6 @@ export default class AlertConditionPanel extends Component<
showNotificationDetails,
detectionRulesTriggerEnabled,
threatIntelTriggerEnabled,
notificationError,
} = this.state;
const { name, sev_levels: ruleSeverityLevels, tags, severity } = alertCondition;
const uniqueTagsOptions = new Set(
Expand Down Expand Up @@ -537,16 +533,14 @@ export default class AlertConditionPanel extends Component<

<EuiSpacer size={'l'} />

<EuiFlexGroup alignItems={notificationError ? 'center' : 'flexEnd'}>
<EuiFlexGroup alignItems={'flexEnd'}>
<EuiFlexItem style={{ maxWidth: 400 }}>
<EuiFormRow
label={
<EuiText size="m">
<p>Notification channel</p>
</EuiText>
}
isInvalid={!!notificationError}
error={notificationError}
>
<EuiComboBox
placeholder={'Select notification channel.'}
Expand All @@ -559,13 +553,6 @@ export default class AlertConditionPanel extends Component<
onChange={this.onNotificationChannelsChange}
singleSelection={{ asPlainText: true }}
onFocus={refreshNotificationChannels}
onBlur={(_e) => {
this.setState({
notificationError: selectedNotificationChannelOption.length
? ''
: 'Notification channel is required',
});
}}
isDisabled={!hasNotificationPlugin}
/>
</EuiFormRow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,6 @@ Object {
class="euiFormRow__labelWrapper"
>
<label
aria-invalid="false"
class="euiFormLabel euiFormRow__label"
for="some_html_id"
>
Expand Down Expand Up @@ -1402,7 +1401,6 @@ Object {
class="euiFormRow__labelWrapper"
>
<label
aria-invalid="false"
class="euiFormLabel euiFormRow__label"
for="some_html_id"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ const isTriggerValid = (triggers: AlertCondition[], hasNotificationPlugin: boole
!!trigger.name &&
validateName(trigger.name) &&
trigger.severity &&
trigger.detection_types.length &&
(!hasNotificationPlugin ||
(hasNotificationPlugin && trigger.actions.every((action) => !!action.destination_id)))
trigger.detection_types.length
);
})
);
Expand Down

0 comments on commit bb51a00

Please sign in to comment.