Skip to content

Commit

Permalink
Make notifications optional (opensearch-project#796)
Browse files Browse the repository at this point in the history
* Make notifications optional 2.11 (opensearch-project#770)

* Threat intel feed support for detector creation (opensearch-project#762)

* added threat intel feed support for detector creation

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* updated cypress workflow file

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* updated alerts; findings UX

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* refactored alert condition panel; update detector for intel feeds

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* updated snapshots, mocks

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* updated workflow

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* updated tests

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* updated snapshot

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* updated UI; tests

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

---------

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* fixed tests; make notification optional

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

---------

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* revert notification change

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

---------

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
  • Loading branch information
amsiglan committed Feb 14, 2024
1 parent ffa49b2 commit 8c0ba83
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 177 deletions.
291 changes: 133 additions & 158 deletions cypress/integration/1_detectors.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const cypressIndexDns = 'cypress-index-dns';
const cypressIndexWindows = 'cypress-index-windows';
const detectorName = 'test detector';
const cypressLogTypeDns = 'dns';
const sampleNotificationChannel = 'sample_chime_channel';
const creationFailedMessage = 'Create detector failed.';

const cypressDNSRule = dns_name_rule_data.title;
Expand All @@ -43,10 +42,6 @@ const logTypeLabel = 'Log type';

const getLogTypeField = () => cy.getFieldByLabel(logTypeLabel);

const notificationLabel = 'Notification channel';

const getNotificationField = () => cy.getFieldByLabel(notificationLabel);

const openDetectorDetails = (detectorName) => {
cy.getInputByPlaceholder('Search threat detectors').type(`${detectorName}`).pressEnterKey();
cy.getElementByText('.euiTableCellContent button', detectorName).click();
Expand Down Expand Up @@ -162,8 +157,6 @@ const createDetector = (detectorName, dataSource, expectFailure) => {
.focus()
.blur();

getNotificationField().selectComboboxItem(`[Channel] ${sampleNotificationChannel}`);

cy.intercept('POST', '/_plugins/_security_analytics/mappings').as('createMappingsRequest');
cy.intercept('POST', '/_plugins/_security_analytics/detectors').as('createDetectorRequest');

Expand Down Expand Up @@ -227,155 +220,141 @@ describe('Detectors', () => {

cy.createRule(dns_name_rule_data);
cy.createRule(dns_type_rule_data);

cy.request('POST', 'http://localhost:9200/_plugins/_notifications/configs/', {
config_id: 'sa_notification-channel_id',
name: sampleNotificationChannel,
config: {
name: sampleNotificationChannel,
description: 'This is a sample chime channel',
config_type: 'chime',
is_enabled: true,
chime: {
url: 'https://sample-chime-webhook',
},
},
}).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 Expand Up @@ -521,9 +500,5 @@ describe('Detectors', () => {

after(() => {
cy.cleanUpTests();
cy.request(
'DELETE',
'http://localhost:9200/_plugins/_notifications/configs/sa_notification-channel_id'
);
});
});
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 @@ -214,7 +212,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 @@ -294,7 +291,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 @@ -540,16 +536,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 @@ -562,13 +556,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
Loading

0 comments on commit 8c0ba83

Please sign in to comment.