Skip to content

Commit

Permalink
Disable notification option for alerts if notification plugin is not …
Browse files Browse the repository at this point in the history
…present (opensearch-project#136)

* disable notification option if no notif plugin

Signed-off-by: Aleksandar Djindjic <djindjic@gmail.com>

* use fragment and &nbsp;

Signed-off-by: Aleksandar Djindjic <djindjic@gmail.com>

* reference github issue in TODO

Signed-off-by: Aleksandar Djindjic <djindjic@gmail.com>

* change console.error to teaster notifications

Signed-off-by: Aleksandar Djindjic <djindjic@gmail.com>

* add learn more link Resolves opensearch-project#137

Signed-off-by: Aleksandar Djindjic <djindjic@gmail.com>

* fix console.error log

Signed-off-by: Aleksandar Djindjic <djindjic@gmail.com>

* Adjusted OSD version used by test workflows. (opensearch-project#149)

* Adjusted OSD version used by test workflows.

Signed-off-by: AWSHurneyt <hurneyt@amazon.com>

* Fixed env variable call.

Signed-off-by: AWSHurneyt <hurneyt@amazon.com>

* Revised comment to include github issue link.

Signed-off-by: AWSHurneyt <hurneyt@amazon.com>

Signed-off-by: AWSHurneyt <hurneyt@amazon.com>
Signed-off-by: Aleksandar Djindjic <djindjic@gmail.com>

* use console.warn(s)

Signed-off-by: Aleksandar Djindjic <djindjic@gmail.com>

* extract response logic to helper method

Signed-off-by: Aleksandar Djindjic <djindjic@gmail.com>

Signed-off-by: Aleksandar Djindjic <djindjic@gmail.com>
Signed-off-by: AWSHurneyt <hurneyt@amazon.com>
Co-authored-by: AWSHurneyt <hurneyt@amazon.com>
  • Loading branch information
djindjic and AWSHurneyt committed Nov 23, 2022
1 parent d73d43c commit 0c5d28a
Show file tree
Hide file tree
Showing 17 changed files with 178 additions and 7 deletions.
27 changes: 27 additions & 0 deletions public/components/NotificationsCallOut/NotificationsCallOut.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';

export const NotificationsCallOut = () => {
return (
<>
<EuiCallOut title="Notifications plugin is not installed" color="danger" iconType="alert">
<p>
Install the notifications plugin in order to create and select channels to send out
notifications.&nbsp;
<EuiLink href="https://opensearch.org/docs/latest/notifications-plugin/index/" external>
Learn more
</EuiLink>
.
</p>
</EuiCallOut>
<EuiSpacer size="m" />
</>
);
};

export default NotificationsCallOut;
8 changes: 8 additions & 0 deletions public/components/NotificationsCallOut/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { NotificationsCallOut } from './NotificationsCallOut';

export { NotificationsCallOut };
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { CreateDetectorRulesOptions } from '../../../../../../models/types';
import { NotificationChannelOption, NotificationChannelTypeOptions } from '../../models/interfaces';
import { NOTIFICATIONS_HREF } from '../../../../../../utils/constants';
import { getNameErrorMessage, validateName } from '../../../../../../utils/validation';
import { NotificationsCallOut } from '../../../../../../components/NotificationsCallOut';

interface AlertConditionPanelProps extends RouteComponentProps {
alertCondition: AlertCondition;
Expand All @@ -34,6 +35,7 @@ interface AlertConditionPanelProps extends RouteComponentProps {
detector: Detector;
indexNum: number;
isEdit: boolean;
hasNotificationPlugin: boolean;
loadingNotifications: boolean;
onAlertTriggerChanged: (newDetector: Detector) => void;
refreshNotificationChannels: () => void;
Expand Down Expand Up @@ -251,6 +253,7 @@ export default class AlertConditionPanel extends Component<
loadingNotifications,
refreshNotificationChannels,
rulesOptions,
hasNotificationPlugin,
} = this.props;
const { nameFieldTouched, nameIsInvalid, selectedNames } = this.state;
const { name, sev_levels: ruleSeverityLevels, tags, severity } = alertCondition;
Expand Down Expand Up @@ -422,16 +425,29 @@ export default class AlertConditionPanel extends Component<
onChange={this.onNotificationChannelsChange}
singleSelection={{ asPlainText: true }}
onBlur={refreshNotificationChannels}
isDisabled={!hasNotificationPlugin}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton href={NOTIFICATIONS_HREF} iconType={'popout'} target={'_blank'}>
<EuiButton
href={NOTIFICATIONS_HREF}
iconType={'popout'}
target={'_blank'}
isDisabled={!hasNotificationPlugin}
>
Manage channels
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>

{!hasNotificationPlugin && (
<>
<EuiSpacer size="m" />
<NotificationsCallOut />
</>
)}

<EuiSpacer size={'xxl'} />

<EuiAccordion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface ConfigureAlertsProps extends RouteComponentProps {
changeDetector: (detector: Detector) => void;
updateDataValidState: (step: DetectorCreationStep, isValid: boolean) => void;
notificationsService: NotificationsService;
hasNotificationPlugin: boolean;
}

interface ConfigureAlertsState {
Expand Down Expand Up @@ -133,6 +134,7 @@ export default class ConfigureAlerts extends Component<ConfigureAlertsProps, Con
loadingNotifications={loading}
onAlertTriggerChanged={this.onAlertTriggerChanged}
refreshNotificationChannels={this.getNotificationChannels}
hasNotificationPlugin={this.props.hasNotificationPlugin}
/>
</EuiAccordion>
</EuiPanel>
Expand Down
25 changes: 23 additions & 2 deletions public/pages/CreateDetector/containers/CreateDetector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import { RouteComponentProps } from 'react-router-dom';
import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSteps } from '@elastic/eui';
import DefineDetector from '../components/DefineDetector/containers/DefineDetector';
import { createDetectorSteps } from '../utils/constants';
import { BREADCRUMBS, EMPTY_DEFAULT_DETECTOR, PLUGIN_NAME, ROUTES } from '../../../utils/constants';
import {
BREADCRUMBS,
EMPTY_DEFAULT_DETECTOR,
PLUGIN_NAME,
ROUTES,
OS_NOTIFICATION_PLUGIN,
} from '../../../utils/constants';
import ConfigureFieldMapping from '../components/ConfigureFieldMapping';
import ConfigureAlerts from '../components/ConfigureAlerts';
import { Detector, FieldMapping } from '../../../../models/interfaces';
Expand All @@ -25,7 +31,11 @@ import {
} from '../components/DefineDetector/components/DetectionRules/types/interfaces';
import { RuleInfo } from '../../../../server/models/interfaces';
import { NotificationsStart } from 'opensearch-dashboards/public';
import { errorNotificationToast, successNotificationToast } from '../../../utils/helpers';
import {
errorNotificationToast,
successNotificationToast,
getPlugins,
} from '../../../utils/helpers';

interface CreateDetectorProps extends RouteComponentProps {
isEdit: boolean;
Expand All @@ -40,6 +50,7 @@ interface CreateDetectorState {
stepDataValid: { [step in DetectorCreationStep]: boolean };
creatingDetector: boolean;
rulesState: CreateDetectorRulesState;
plugins: string[];
}

export default class CreateDetector extends Component<CreateDetectorProps, CreateDetectorState> {
Expand All @@ -59,12 +70,14 @@ export default class CreateDetector extends Component<CreateDetectorProps, Creat
},
creatingDetector: false,
rulesState: { page: { index: 0 }, allRules: [] },
plugins: [],
};
}

componentDidMount(): void {
this.context.chrome.setBreadcrumbs([BREADCRUMBS.SECURITY_ANALYTICS, BREADCRUMBS.DETECTORS]);
this.setupRulesState();
this.getPlugins();
}

componentDidUpdate(
Expand Down Expand Up @@ -190,6 +203,13 @@ export default class CreateDetector extends Component<CreateDetectorProps, Creat
});
}

async getPlugins() {
const { services } = this.props;
const plugins = await getPlugins(services.opensearchService);

this.setState({ plugins });
}

async getRules(prePackaged: boolean): Promise<RuleItemInfo[]> {
try {
const { detector_type } = this.state.detector;
Expand Down Expand Up @@ -348,6 +368,7 @@ export default class CreateDetector extends Component<CreateDetectorProps, Creat
changeDetector={this.changeDetector}
updateDataValidState={this.updateDataValidState}
notificationsService={services.notificationsService}
hasNotificationPlugin={this.state.plugins.includes(OS_NOTIFICATION_PLUGIN)}
/>
);
case DetectorCreationStep.REVIEW_CREATE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { DetectorHit } from '../../../../../server/models/interfaces';
import { Detector } from '../../../../../models/interfaces';
import ConfigureAlerts from '../../../CreateDetector/components/ConfigureAlerts';
import { DetectorsService, NotificationsService, RuleService } from '../../../../services';
import {
DetectorsService,
NotificationsService,
RuleService,
OpenSearchService,
} from '../../../../services';
import { RulesSharedState } from '../../../../models/interfaces';
import { ROUTES } from '../../../../utils/constants';
import { ROUTES, OS_NOTIFICATION_PLUGIN } from '../../../../utils/constants';
import { NotificationsStart } from 'opensearch-dashboards/public';
import { errorNotificationToast } from '../../../../utils/helpers';
import { errorNotificationToast, getPlugins } from '../../../../utils/helpers';

export interface UpdateAlertConditionsProps
extends RouteComponentProps<any, any, { detectorHit: DetectorHit }> {
detectorService: DetectorsService;
opensearchService: OpenSearchService;
ruleService: RuleService;
notificationsService: NotificationsService;
notifications: NotificationsStart;
Expand All @@ -28,6 +34,7 @@ export interface UpdateAlertConditionsState {
rules: object;
rulesOptions: Pick<RulesSharedState, 'rulesOptions'>['rulesOptions'];
submitting: boolean;
plugins: string[];
}

export default class UpdateAlertConditions extends Component<
Expand All @@ -41,11 +48,13 @@ export default class UpdateAlertConditions extends Component<
rules: {},
rulesOptions: [],
submitting: false,
plugins: [],
};
}

componentDidMount() {
this.getRules();
this.getPlugins();
}

changeDetector = (detector: Detector) => {
Expand Down Expand Up @@ -123,6 +132,13 @@ export default class UpdateAlertConditions extends Component<
}
};

async getPlugins() {
const { opensearchService } = this.props;
const plugins = await getPlugins(opensearchService);

this.setState({ plugins });
}

onCancel = () => {
this.props.history.replace({
pathname: `${ROUTES.DETECTOR_DETAILS}/${this.props.location.state?.detectorHit._id}`,
Expand Down Expand Up @@ -178,6 +194,7 @@ export default class UpdateAlertConditions extends Component<
rulesOptions={rulesOptions}
changeDetector={this.changeDetector}
updateDataValidState={() => {}}
hasNotificationPlugin={this.state.plugins.includes(OS_NOTIFICATION_PLUGIN)}
/>

<EuiFlexGroup justifyContent={'flexEnd'}>
Expand Down
2 changes: 2 additions & 0 deletions public/pages/Findings/components/CreateAlertFlyout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface CreateAlertFlyoutProps extends RouteComponentProps {
refreshNotificationChannels: () => void;
allRules: object;
rulesOptions: Pick<RulesSharedState, 'rulesOptions'>['rulesOptions'];
hasNotificationPlugin: boolean;
}

interface CreateAlertFlyoutState {
Expand Down Expand Up @@ -146,6 +147,7 @@ export default class CreateAlertFlyout extends Component<
isEdit={false}
loadingNotifications={loading}
onAlertTriggerChanged={this.onAlertConditionChange}
hasNotificationPlugin={this.props.hasNotificationPlugin}
/>
<EuiSpacer size={'m'} />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ interface FindingsTableProps extends RouteComponentProps {
endTime: string;
onRefresh: () => void;
onFindingsFiltered: (findings: FindingItemType[]) => void;
hasNotificationsPlugin: boolean;
}

interface FindingsTableState {
Expand Down Expand Up @@ -129,6 +130,7 @@ export default class FindingsTable extends Component<FindingsTableProps, Finding
allRules={this.props.rules}
refreshNotificationChannels={this.props.refreshNotificationChannels}
rulesOptions={ruleOptions}
hasNotificationPlugin={this.props.hasNotificationsPlugin}
/>
),
flyoutOpen: true,
Expand Down
13 changes: 13 additions & 0 deletions public/pages/Findings/containers/Findings/Findings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
BREADCRUMBS,
DEFAULT_DATE_RANGE,
MAX_RECENTLY_USED_TIME_RANGES,
OS_NOTIFICATION_PLUGIN,
} from '../../../../utils/constants';
import { getFindingsVisualizationSpec } from '../../../Overview/utils/helpers';
import { CoreServicesContext } from '../../../../components/core_services';
Expand All @@ -41,6 +42,7 @@ import {
createSelectComponent,
errorNotificationToast,
renderVisualization,
getPlugins,
} from '../../../../utils/helpers';
import { DetectorHit, RuleSource } from '../../../../../server/models/interfaces';
import { NotificationsStart } from 'opensearch-dashboards/public';
Expand All @@ -65,6 +67,7 @@ interface FindingsState {
recentlyUsedRanges: DurationRange[];
groupBy: FindingsGroupByType;
filteredFindings: FindingItemType[];
plugins: string[];
}

interface FindingVisualizationData {
Expand Down Expand Up @@ -99,6 +102,7 @@ export default class Findings extends Component<FindingsProps, FindingsState> {
recentlyUsedRanges: [DEFAULT_DATE_RANGE],
groupBy: 'logType',
filteredFindings: [],
plugins: [],
};
}

Expand All @@ -119,6 +123,7 @@ export default class Findings extends Component<FindingsProps, FindingsState> {
onRefresh = async () => {
await this.getFindings();
await this.getNotificationChannels();
await this.getPlugins();
renderVisualization(this.generateVisualizationSpec(), 'findings-view');
};

Expand Down Expand Up @@ -213,6 +218,13 @@ export default class Findings extends Component<FindingsProps, FindingsState> {
this.setState({ notificationChannels: channels });
};

async getPlugins() {
const { opensearchService } = this.props;
const plugins = await getPlugins(opensearchService);

this.setState({ plugins });
}

onTimeChange = ({ start, end }: { start: string; end: string }) => {
let { recentlyUsedRanges } = this.state;
recentlyUsedRanges = recentlyUsedRanges.filter(
Expand Down Expand Up @@ -336,6 +348,7 @@ export default class Findings extends Component<FindingsProps, FindingsState> {
notificationChannels={parseNotificationChannelsToOptions(notificationChannels)}
refreshNotificationChannels={this.getNotificationChannels}
onFindingsFiltered={this.onFindingsFiltered}
hasNotificationsPlugin={this.state.plugins.includes(OS_NOTIFICATION_PLUGIN)}
/>
</ContentPanel>
</EuiFlexItem>
Expand Down
1 change: 1 addition & 0 deletions public/pages/Main/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ export default class Main extends Component<MainProps, MainState> {
ruleService={services.ruleService}
notificationsService={services.notificationsService}
notifications={core?.notifications}
opensearchService={services.opensearchService}
/>
)}
/>
Expand Down
7 changes: 6 additions & 1 deletion public/services/OpenSearchService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { HttpSetup } from 'opensearch-dashboards/public';
import { ServerResponse } from '../../server/models/types';
import { SearchResponse } from '../../server/models/interfaces';
import { SearchResponse, Plugin } from '../../server/models/interfaces';
import { API } from '../../server/utils/constants';

export default class OpenSearchService {
Expand Down Expand Up @@ -36,4 +36,9 @@ export default class OpenSearchService {
query: { index, timeField, startTime, endTime },
})) as ServerResponse<SearchResponse<any>>;
};

getPlugins = async (): Promise<ServerResponse<Plugin[]>> => {
let url = `..${API.PLUGINS}`;
return await this.httpClient.get(url);
};
}
1 change: 1 addition & 0 deletions public/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const MAX_RECENTLY_USED_TIME_RANGES = 5;
export const DEFAULT_DATE_RANGE = { start: 'now-15m', end: 'now' };

export const PLUGIN_NAME = 'opensearch_security_analytics_dashboards';
export const OS_NOTIFICATION_PLUGIN = 'opensearch-notifications';

// TODO: Replace with actual documentation link once it's available
export const DOCUMENTATION_URL = 'https://opensearch.org/docs/latest/';
Expand Down
Loading

0 comments on commit 0c5d28a

Please sign in to comment.