Skip to content

Commit

Permalink
Adds error notification UI container and updates snapshots (#47)
Browse files Browse the repository at this point in the history
Signed-off-by: Drew Baugher <[email protected]>
  • Loading branch information
dbbaughe authored Aug 10, 2021
1 parent 72e4639 commit 4b6724b
Show file tree
Hide file tree
Showing 11 changed files with 512 additions and 12 deletions.
30 changes: 28 additions & 2 deletions models/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,41 @@ export interface DocumentTransform {
metadata: any;
}

// TODO: Fill out when needed
// TODO: separate a frontend Policy from backendPolicy
export interface Policy {
description: string;
default_state: string;
states: State[];
ism_template: any;
}

export interface ErrorNotification {
destination?: Destination;
channel?: Channel;
message_template: MessageTemplate;
}

export interface Channel {
id: string;
}

export interface Destination {
chime?: {
url: string;
};
slack?: {
url: string;
};
custom_webhook?: {
url: string;
[other: string]: any; // custom webhook also allows users to create by part including customizing headers/query params, port/host, etc.
};
}

export interface MessageTemplate {
source: string;
lang?: string;
}

export interface State {
name: string;
actions: object[];
Expand Down
29 changes: 24 additions & 5 deletions public/components/ContentPanel/ContentPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,35 @@
*/

import React from "react";
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel, EuiTitle } from "@elastic/eui";
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel, EuiTitle, EuiText } from "@elastic/eui";

interface ContentPanelProps {
title?: string;
title?: string | JSX.Element;
titleSize?: "xxxs" | "xxs" | "xs" | "s" | "m" | "l";
subTitleText?: string | JSX.Element;
bodyStyles?: object;
panelStyles?: object;
horizontalRuleClassName?: string;
actions?: React.ReactNode | React.ReactNode[];
children: React.ReactNode | React.ReactNode[];
}

const renderSubTitleText = (subTitleText: string | JSX.Element): JSX.Element | null => {
if (typeof subTitleText === "string") {
if (!subTitleText) return null;
return (
<EuiText size="s">
<span style={{ color: "grey", fontWeight: 200, fontSize: "15px" }}>{subTitleText}</span>
</EuiText>
);
}
return subTitleText;
};

const ContentPanel: React.SFC<ContentPanelProps> = ({
title = "",
titleSize = "l",
subTitleText = "",
bodyStyles = {},
panelStyles = {},
horizontalRuleClassName = "",
Expand All @@ -49,9 +63,14 @@ const ContentPanel: React.SFC<ContentPanelProps> = ({
<EuiPanel style={{ paddingLeft: "0px", paddingRight: "0px", ...panelStyles }}>
<EuiFlexGroup style={{ padding: "0px 10px" }} justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem>
<EuiTitle size={titleSize}>
<h3>{title}</h3>
</EuiTitle>
{typeof title === "string" ? (
<EuiTitle size={titleSize}>
<h3>{title}</h3>
</EuiTitle>
) : (
title
)}
{renderSubTitleText(subTitleText)}
</EuiFlexItem>
{actions ? (
<EuiFlexItem grow={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ exports[`<NewPolicy /> spec renders the component 1`] = `
<a
class="euiLink euiLink--primary"
href="https://docs-beta.opensearch.org/docs/im/ism/"
href="https://opensearch.org/docs/im-plugin/ism/index/"
rel="noopener noreferrer"
target="_blank"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ exports[`<ChangePolicy /> spec renders the component 1`] = `
<a
class="euiLink euiLink--primary"
href="https://docs-beta.opensearch.org/docs/im/ism/"
href="https://opensearch.org/docs/im-plugin/ism/index/"
rel="noopener noreferrer"
target="_blank"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ exports[`<DefinePolicy /> spec renders the component 1`] = `
<a
class="euiLink euiLink--primary"
href="https://docs-beta.opensearch.org/docs/im/ism/"
href="https://opensearch.org/docs/im-plugin/ism/index/"
rel="noopener noreferrer"
target="_blank"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ exports[`<CreatePolicy /> spec renders the edit component 1`] = `
<a
class="euiLink euiLink--primary"
href="https://docs-beta.opensearch.org/docs/im/ism/"
href="https://opensearch.org/docs/im-plugin/ism/index/"
rel="noopener noreferrer"
target="_blank"
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

import React from "react";
import "@testing-library/jest-dom/extend-expect";
import { render } from "@testing-library/react";
import ErrorNotification from "./ErrorNotification";
import { ServicesConsumer, ServicesContext } from "../../../../services";
import { browserServicesMock } from "../../../../../test/mocks";
import { BrowserServices } from "../../../../models/interfaces";
import { ErrorNotification as IErrorNotification } from "../../../../../models/interfaces";

function renderErrorNotification(errorNotification: IErrorNotification) {
return {
...render(
<ServicesContext.Provider value={browserServicesMock}>
<ServicesConsumer>
{(services: BrowserServices | null) =>
services && (
<ErrorNotification
errorNotification={errorNotification}
errorNotificationJsonString={""}
onChangeChannelId={() => {}}
onChangeMessage={() => {}}
onChangeErrorNotificationJsonString={() => {}}
onSwitchToChannels={() => {}}
notificationService={services?.notificationService}
/>
)
}
</ServicesConsumer>
</ServicesContext.Provider>
),
};
}

describe("<ErrorNotification /> spec", () => {
it("renders the component", () => {
const { container } = render(
<ErrorNotification
errorNotification={{ channel: { id: "some_id" }, message_template: { source: "some source message" } }}
errorNotificationJsonString={""}
onChangeChannelId={() => {}}
onChangeMessage={() => {}}
onChangeErrorNotificationJsonString={() => {}}
onSwitchToChannels={() => {}}
notificationService={browserServicesMock.notificationService}
/>
);
expect(container.firstChild).toMatchSnapshot();
});

it("renders the channel ui editor for channels", () => {
const errorNotification = { channel: { id: "some_id" }, message_template: { source: "some source message" } };
const { queryByTestId } = renderErrorNotification(errorNotification);

expect(queryByTestId("channel-notification-refresh")).not.toBeNull();
expect(queryByTestId("create-policy-legacy-notification")).toBeNull();
});

it("renders the json legacy editor for destinations", () => {
const errorNotification = { destination: { slack: { url: "https://slack.com" } }, message_template: { source: "some source message" } };
const { queryByTestId } = renderErrorNotification(errorNotification);

expect(queryByTestId("channel-notification-refresh")).toBeNull();
expect(queryByTestId("create-policy-legacy-notification")).not.toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

import React, { ChangeEvent, Component } from "react";
import { EuiLink, EuiIcon, EuiFlexGroup, EuiFlexItem, EuiText } from "@elastic/eui";
import { ContentPanel } from "../../../../components/ContentPanel";
import "brace/theme/github";
import "brace/mode/json";
import { FeatureChannelList } from "../../../../../server/models/interfaces";
import { NotificationService } from "../../../../services";
import { ErrorNotification as IErrorNotification } from "../../../../../models/interfaces";
import { getErrorMessage } from "../../../../utils/helpers";
import { CoreServicesContext } from "../../../../components/core_services";
import ChannelNotification from "../../components/ChannelNotification";
import LegacyNotification from "../../components/LegacyNotification";
import { DOCUMENTATION_URL } from "../../../../utils/constants";

interface ErrorNotificationProps {
errorNotification: IErrorNotification | undefined;
errorNotificationJsonString: string;
onChangeChannelId: (value: ChangeEvent<HTMLSelectElement>) => void;
onChangeMessage: (value: ChangeEvent<HTMLTextAreaElement>) => void;
onChangeErrorNotificationJsonString: (str: string) => void;
onSwitchToChannels: () => void;
notificationService: NotificationService;
}

interface ErrorNotificationState {
channels: FeatureChannelList[];
loadingChannels: boolean;
}

export default class ErrorNotification extends Component<ErrorNotificationProps, ErrorNotificationState> {
static contextType = CoreServicesContext;
constructor(props: ErrorNotificationProps) {
super(props);

this.state = {
channels: [],
loadingChannels: true,
};
}

componentDidMount = async (): Promise<void> => {
await this.getChannels();
};

getChannels = async (): Promise<void> => {
this.setState({ loadingChannels: true });
try {
const { notificationService } = this.props;
const response = await notificationService.getChannels();
if (response.ok) {
this.setState({ channels: response.response.feature_channel_list });
} else {
this.context.notifications.toasts.addDanger(`Could not load notification channels: ${response.error}`);
}
} catch (err) {
this.context.notifications.toasts.addDanger(getErrorMessage(err, "Could not load the notification channels"));
}
this.setState({ loadingChannels: false });
};

render() {
const {
errorNotification,
errorNotificationJsonString,
onChangeChannelId,
onChangeMessage,
onChangeErrorNotificationJsonString,
onSwitchToChannels,
} = this.props;
const { channels, loadingChannels } = this.state;
const hasDestination = !!errorNotification?.destination;

let content = (
<ChannelNotification
channelId={errorNotification?.channel?.id || ""}
channels={channels}
loadingChannels={loadingChannels}
message={errorNotification?.message_template?.source || ""}
onChangeChannelId={onChangeChannelId}
onChangeMessage={onChangeMessage}
getChannels={this.getChannels}
/>
);

// If we have a destination in the error notification then it's either an older policy or they created through the API
if (hasDestination) {
content = (
<LegacyNotification
notificationJsonString={errorNotificationJsonString}
onChangeNotificationJsonString={onChangeErrorNotificationJsonString}
onSwitchToChannels={onSwitchToChannels}
/>
);
}

return (
<ContentPanel
bodyStyles={{ padding: "initial" }}
title={
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<EuiText>
<h3>Error notification</h3>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiText color="subdued">
<i> - optional</i>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
}
titleSize="s"
subTitleText={
<EuiText size="s" style={{ padding: "5px 0px" }}>
<p style={{ color: "grey", fontWeight: 200 }}>
You can set up an error notification for when a policy execution fails to notify you.{" "}
<EuiLink href={DOCUMENTATION_URL} target="_blank">
Learn more <EuiIcon type="popout" size="s" />
</EuiLink>
</p>
</EuiText>
}
>
<div style={{ padding: "10px 0px 0px 10px" }}>{content}</div>
</ContentPanel>
);
}
}
Loading

0 comments on commit 4b6724b

Please sign in to comment.