Skip to content
Snippets Groups Projects
Unverified Commit c9bfb047 authored by Nick Fitzpatrick's avatar Nick Fitzpatrick Committed by GitHub
Browse files

Alert isValid check only looks at configured channels (#48485)

* converting UpdateAlertModalContent and CreateAlertModalContent to functional

* lint stuff

* e2e adjustments:

* type and e2e adjustments
parent 94081aea
No related branches found
No related tags found
No related merge requests found
Showing
with 290 additions and 302 deletions
...@@ -31,6 +31,7 @@ import { ...@@ -31,6 +31,7 @@ import {
sidesheet, sidesheet,
summarize, summarize,
tableHeaderClick, tableHeaderClick,
toggleAlertChannel,
visitQuestion, visitQuestion,
} from "e2e/support/helpers"; } from "e2e/support/helpers";
...@@ -497,10 +498,8 @@ describe( ...@@ -497,10 +498,8 @@ describe(
popover().findByText("Create alert").click(); popover().findByText("Create alert").click();
modal().button("Set up an alert").click(); modal().button("Set up an alert").click();
modal().within(() => { modal().within(() => {
getAlertChannel(secondWebhookName).scrollIntoView(); toggleAlertChannel("Email");
getAlertChannel(secondWebhookName) toggleAlertChannel(secondWebhookName);
.findByRole("checkbox")
.click({ force: true });
cy.button("Done").click(); cy.button("Done").click();
}); });
cy.findByTestId("sharing-menu-button").click(); cy.findByTestId("sharing-menu-button").click();
......
...@@ -17,7 +17,18 @@ import { ...@@ -17,7 +17,18 @@ import {
visitQuestion, visitQuestion,
} from "e2e/support/helpers"; } from "e2e/support/helpers";
const channels = { slack: mockSlackConfigured, email: setupSMTP }; const channels = {
slack: {
setup: mockSlackConfigured,
createAlert: () => {
toggleAlertChannel("Email");
toggleAlertChannel("Slack");
cy.findByPlaceholderText(/Pick a user or channel/).click();
popover().findByText("#work").click();
},
},
email: { setup: setupSMTP, createAlert: () => {} },
};
describe("scenarios > alert", () => { describe("scenarios > alert", () => {
beforeEach(() => { beforeEach(() => {
...@@ -66,9 +77,9 @@ describe("scenarios > alert", () => { ...@@ -66,9 +77,9 @@ describe("scenarios > alert", () => {
}); });
}); });
Object.entries(channels).forEach(([channel, setup]) => { Object.entries(channels).forEach(([channel, config]) => {
describe(`with ${channel} set up`, { tags: "@external" }, () => { describe(`with ${channel} set up`, { tags: "@external" }, () => {
beforeEach(setup); beforeEach(config.setup);
it("educational screen should show for the first alert, but not for the second", () => { it("educational screen should show for the first alert, but not for the second", () => {
cy.intercept("POST", "/api/alert").as("savedAlert"); cy.intercept("POST", "/api/alert").as("savedAlert");
...@@ -94,6 +105,8 @@ describe("scenarios > alert", () => { ...@@ -94,6 +105,8 @@ describe("scenarios > alert", () => {
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Set up an alert").click(); cy.findByText("Set up an alert").click();
config.createAlert();
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
cy.findByText("Done").click(); cy.findByText("Done").click();
......
...@@ -58,6 +58,7 @@ describe("scenarios > alert > email_alert", { tags: "@external" }, () => { ...@@ -58,6 +58,7 @@ describe("scenarios > alert > email_alert", { tags: "@external" }, () => {
it("should respect email alerts toggled off (metabase#12349)", () => { it("should respect email alerts toggled off (metabase#12349)", () => {
updateSetting("report-timezone", "America/New_York"); updateSetting("report-timezone", "America/New_York");
mockSlackConfigured();
//For this test, we need to pretend that slack is set up //For this test, we need to pretend that slack is set up
mockSlackConfigured(); mockSlackConfigured();
......
...@@ -17,6 +17,7 @@ import { ...@@ -17,6 +17,7 @@ import {
getDashboardCard, getDashboardCard,
getFullName, getFullName,
getIframeBody, getIframeBody,
mockSlackConfigured,
modal, modal,
openAndAddEmailsToSubscriptions, openAndAddEmailsToSubscriptions,
openNewPublicLinkDropdown, openNewPublicLinkDropdown,
...@@ -960,6 +961,7 @@ describe("issue 17547", () => { ...@@ -960,6 +961,7 @@ describe("issue 17547", () => {
beforeEach(() => { beforeEach(() => {
restore(); restore();
cy.signInAsAdmin(); cy.signInAsAdmin();
mockSlackConfigured();
cy.createQuestion(questionDetails).then(({ body: { id: questionId } }) => { cy.createQuestion(questionDetails).then(({ body: { id: questionId } }) => {
setUpAlert(questionId); setUpAlert(questionId);
......
...@@ -5,11 +5,11 @@ import type { User } from "metabase-types/api/user"; ...@@ -5,11 +5,11 @@ import type { User } from "metabase-types/api/user";
import { ALERT_TYPE_ROWS } from "./constants"; import { ALERT_TYPE_ROWS } from "./constants";
export const getDefaultAlert = ( export const getDefaultAlert = (
question: Question, question: Question | undefined,
user: User, user: User | null,
visualizationSettings: VisualizationSettings, visualizationSettings: VisualizationSettings,
) => { ) => {
const alertType = question.alertType(visualizationSettings); const alertType = question?.alertType(visualizationSettings);
const typeDependentAlertFields = const typeDependentAlertFields =
alertType === ALERT_TYPE_ROWS alertType === ALERT_TYPE_ROWS
? { ? {
...@@ -24,7 +24,7 @@ export const getDefaultAlert = ( ...@@ -24,7 +24,7 @@ export const getDefaultAlert = (
return { return {
card: { card: {
id: question.id(), id: question?.id(),
include_csv: false, include_csv: false,
include_xls: false, include_xls: false,
}, },
......
...@@ -11,7 +11,7 @@ import type { CommonModalProps } from "./types"; ...@@ -11,7 +11,7 @@ import type { CommonModalProps } from "./types";
export interface ModalContentProps extends CommonModalProps { export interface ModalContentProps extends CommonModalProps {
"data-testid"?: string; "data-testid"?: string;
id?: string; id?: string;
title: string; title?: string;
footer?: ReactNode; footer?: ReactNode;
children: ReactNode; children: ReactNode;
......
...@@ -22,7 +22,15 @@ export function channelIsEnabled(channel) { ...@@ -22,7 +22,15 @@ export function channelIsEnabled(channel) {
return channel.enabled; return channel.enabled;
} }
export function alertIsValid(alert) { export function alertIsValid(alert, channelSpec) {
const enabledChannels = alert.channels.filter(channelIsEnabled); const enabledChannels = alert.channels.filter(channelIsEnabled);
return enabledChannels.length > 0 && enabledChannels.every(channelIsValid);
return (
channelSpec.channels &&
enabledChannels.length > 0 &&
enabledChannels.every(channel => channelIsValid(channel)) &&
enabledChannels
.filter(c => c.enabled)
.every(c => channelSpec.channels[c.channel_type]?.configured)
);
} }
...@@ -8,6 +8,7 @@ import { ...@@ -8,6 +8,7 @@ import {
} from "metabase-lib/v1/parameters/utils/parameter-values"; } from "metabase-lib/v1/parameters/utils/parameter-values";
import type { import type {
Channel, Channel,
ChannelApiResponse,
ChannelSpec, ChannelSpec,
Pulse, Pulse,
PulseParameter, PulseParameter,
...@@ -214,3 +215,20 @@ export function getActivePulseParameters( ...@@ -214,3 +215,20 @@ export function getActivePulseParameters(
(parameter: any) => parameter.value != null, (parameter: any) => parameter.value != null,
); );
} }
export const getHasConfiguredAnyChannel = (
formInput: Partial<ChannelApiResponse>,
) =>
(formInput.channels &&
_.some(Object.values(formInput.channels), c => c.configured)) ||
false;
export const getHasConfiguredEmailChannel = (
formInput: Partial<ChannelApiResponse>,
) =>
(formInput.channels &&
_.some(
Object.values(formInput.channels),
c => c.type === "email" && c.configured,
)) ||
false;
import { createSelector } from "@reduxjs/toolkit";
import _ from "underscore"; import _ from "underscore";
export const getEditingPulse = state => state.pulse.editingPulse; export const getEditingPulse = state => state.pulse.editingPulse;
export const getPulseFormInput = state => state.pulse?.formInput; export const getPulseFormInput = state => state.pulse?.formInput;
export const hasLoadedChannelInfoSelector = createSelector(
[getPulseFormInput],
formInput => !!formInput.channels,
);
export const hasConfiguredAnyChannelSelector = createSelector(
[getPulseFormInput],
formInput =>
(formInput.channels &&
_.some(Object.values(formInput.channels), c => c.configured)) ||
false,
);
export const hasConfiguredEmailChannelSelector = createSelector(
[getPulseFormInput],
formInput =>
(formInput.channels &&
_.some(
Object.values(formInput.channels),
c => c.type === "email" && c.configured,
)) ||
false,
);
export const getPulseCardPreviews = state => state.pulse.cardPreviews; export const getPulseCardPreviews = state => state.pulse.cardPreviews;
export const getPulseId = (state, props) => export const getPulseId = (state, props) =>
......
...@@ -85,11 +85,13 @@ export const AlertPopover = forwardRef(function _AlertPopover( ...@@ -85,11 +85,13 @@ export const AlertPopover = forwardRef(function _AlertPopover(
isOpen={showingElement === "update-modal"} isOpen={showingElement === "update-modal"}
onClose={onClose} onClose={onClose}
> >
<UpdateAlertModalContent {editingAlert && (
alert={editingAlert} <UpdateAlertModalContent
onCancel={onClose} alert={editingAlert}
onAlertUpdated={onClose} onCancel={onClose}
/> onAlertUpdated={onClose}
/>
)}
</Modal> </Modal>
</> </>
); );
......
/* eslint-disable react/prop-types */
import cx from "classnames";
import { Component } from "react";
import { connect } from "react-redux";
import { t } from "ttag";
import { createAlert } from "metabase/alert/alert";
import ButtonWithStatus from "metabase/components/ButtonWithStatus";
import ChannelSetupModal from "metabase/components/ChannelSetupModal";
import ModalContent from "metabase/components/ModalContent";
import Button from "metabase/core/components/Button";
import CS from "metabase/css/core/index.css";
import { alertIsValid } from "metabase/lib/alert";
import MetabaseCookies from "metabase/lib/cookies";
import { fetchPulseFormInput } from "metabase/pulse/actions";
import {
hasConfiguredAnyChannelSelector,
hasConfiguredEmailChannelSelector,
hasLoadedChannelInfoSelector,
} from "metabase/pulse/selectors";
import { apiUpdateQuestion, updateUrl } from "metabase/query_builder/actions";
import {
getQuestion,
getVisualizationSettings,
} from "metabase/query_builder/selectors";
import { getUser, getUserIsAdmin } from "metabase/selectors/user";
import { getDefaultAlert } from "metabase-lib/v1/Alert";
import { AlertEditForm } from "./AlertEditForm";
import { AlertEducationalScreen } from "./AlertEducationalScreen";
import { AlertModalTitle } from "./AlertModalTitle";
import { AlertModalFooter } from "./AlertModals.styled";
class CreateAlertModalContentInner extends Component {
constructor(props) {
super();
const { question, user, visualizationSettings } = props;
this.state = {
hasSeenEducationalScreen: MetabaseCookies.getHasSeenAlertSplash(),
alert: getDefaultAlert(question, user, visualizationSettings),
};
}
UNSAFE_componentWillReceiveProps(newProps) {
// NOTE Atte Keinänen 11/6/17: Don't fill in the card information yet
// Because `onCreate` and `onSave` of QueryHeader mix Redux action dispatches and `setState` calls,
// we don't have up-to-date card information in the constructor yet
// TODO: Refactor QueryHeader so that `onCreate` and `onSave` only call Redux actions and don't modify the local state
if (this.props.question !== newProps.question) {
this.setState({
alert: {
...this.state.alert,
card: { ...this.state.alert.card, id: newProps.question.id() },
},
});
}
}
UNSAFE_componentWillMount() {
// loads the channel information
this.props.fetchPulseFormInput();
}
onAlertChange = alert => this.setState({ alert });
onCreateAlert = async () => {
const { question, createAlert, updateUrl, onAlertCreated } = this.props;
const { alert } = this.state;
await createAlert(alert);
await updateUrl(question, { dirty: false });
onAlertCreated();
};
proceedFromEducationalScreen = () => {
MetabaseCookies.setHasSeenAlertSplash(true);
this.setState({ hasSeenEducationalScreen: true });
};
render() {
const {
question,
visualizationSettings,
onCancel,
hasConfiguredAnyChannel,
hasConfiguredEmailChannel,
isAdmin,
user,
hasLoadedChannelInfo,
} = this.props;
const { alert, hasSeenEducationalScreen } = this.state;
const channelRequirementsMet = isAdmin
? hasConfiguredAnyChannel
: hasConfiguredEmailChannel;
const isValid = alertIsValid(alert);
if (hasLoadedChannelInfo && !channelRequirementsMet) {
return (
<ChannelSetupModal
user={user}
onClose={onCancel}
entityNamePlural={t`alerts`}
channels={isAdmin ? ["email", "Slack", "Webhook"] : ["email"]}
/>
);
}
if (!hasSeenEducationalScreen) {
return (
<ModalContent onClose={onCancel} data-testid="alert-education-screen">
<AlertEducationalScreen
onProceed={this.proceedFromEducationalScreen}
/>
</ModalContent>
);
}
// TODO: Remove PulseEdit css hack
return (
<ModalContent data-testid="alert-create" onClose={onCancel}>
<div
className={cx(CS.mlAuto, CS.mrAuto, CS.mb4)}
style={{ maxWidth: "550px" }}
>
<AlertModalTitle text={t`Let's set up your alert`} />
<AlertEditForm
alertType={question.alertType(visualizationSettings)}
alert={alert}
onAlertChange={this.onAlertChange}
/>
<AlertModalFooter>
<Button onClick={onCancel} className={CS.mr2}>{t`Cancel`}</Button>
<ButtonWithStatus
titleForState={{ default: t`Done` }}
disabled={!isValid}
onClickOperation={this.onCreateAlert}
/>
</AlertModalFooter>
</div>
</ModalContent>
);
}
}
export const CreateAlertModalContent = connect(
state => ({
question: getQuestion(state),
visualizationSettings: getVisualizationSettings(state),
isAdmin: getUserIsAdmin(state),
user: getUser(state),
hasLoadedChannelInfo: hasLoadedChannelInfoSelector(state),
hasConfiguredAnyChannel: hasConfiguredAnyChannelSelector(state),
hasConfiguredEmailChannel: hasConfiguredEmailChannelSelector(state),
}),
{ createAlert, fetchPulseFormInput, apiUpdateQuestion, updateUrl },
)(CreateAlertModalContentInner);
import cx from "classnames";
import { useEffect, useState } from "react";
import { t } from "ttag";
import { createAlert } from "metabase/alert/alert";
import { useGetChannelInfoQuery } from "metabase/api";
import ButtonWithStatus from "metabase/components/ButtonWithStatus";
import ChannelSetupModal from "metabase/components/ChannelSetupModal";
import ModalContent from "metabase/components/ModalContent";
import Button from "metabase/core/components/Button";
import CS from "metabase/css/core/index.css";
import { alertIsValid } from "metabase/lib/alert";
import MetabaseCookies from "metabase/lib/cookies";
import {
getHasConfiguredAnyChannel,
getHasConfiguredEmailChannel,
} from "metabase/lib/pulse";
import { useDispatch, useSelector } from "metabase/lib/redux";
import { updateUrl } from "metabase/query_builder/actions";
import {
getQuestion,
getVisualizationSettings,
} from "metabase/query_builder/selectors";
import { getUser, getUserIsAdmin } from "metabase/selectors/user";
import { getDefaultAlert } from "metabase-lib/v1/Alert";
import type { Alert } from "metabase-types/api";
import { AlertEditForm } from "./AlertEditForm";
import { AlertEducationalScreen } from "./AlertEducationalScreen";
import { AlertModalTitle } from "./AlertModalTitle";
import { AlertModalFooter } from "./AlertModals.styled";
interface CreateAlertModalContentProps {
onAlertCreated: () => void;
onCancel: () => void;
}
export const CreateAlertModalContent = ({
onAlertCreated,
onCancel,
}: CreateAlertModalContentProps) => {
const dispatch = useDispatch();
const question = useSelector(getQuestion);
const visualizationSettings = useSelector(getVisualizationSettings);
const isAdmin = useSelector(getUserIsAdmin);
const user = useSelector(getUser);
const { data: channelSpec = {}, isLoading: isLoadingChannelInfo } =
useGetChannelInfoQuery();
const hasConfiguredAnyChannel = getHasConfiguredAnyChannel(channelSpec);
const hasConfiguredEmailChannel = getHasConfiguredEmailChannel(channelSpec);
const [alert, setAlert] = useState<any>(
getDefaultAlert(question, user, visualizationSettings),
);
const [hasSeenEducationalScreen, setHasSeenEducationalScreen] = useState(
MetabaseCookies.getHasSeenAlertSplash(),
);
useEffect(() => {
// NOTE Atte Keinänen 11/6/17: Don't fill in the card information yet
// Because `onCreate` and `onSave` of QueryHeader mix Redux action dispatches and `setState` calls,
// we don't have up-to-date card information in the constructor yet
// TODO: Refactor QueryHeader so that `onCreate` and `onSave` only call Redux actions and don't modify the local state
setAlert((currentAlert: any) => ({
...currentAlert,
card: { ...currentAlert.card, id: question?.id() },
}));
}, [question]);
const onAlertChange = (newAlert: Alert) => setAlert(newAlert);
const onCreateAlert = async () => {
await dispatch(createAlert(alert));
await dispatch(updateUrl(question, { dirty: false }));
onAlertCreated();
};
const proceedFromEducationalScreen = () => {
MetabaseCookies.setHasSeenAlertSplash(true);
setHasSeenEducationalScreen(true);
};
const channelRequirementsMet = isAdmin
? hasConfiguredAnyChannel
: hasConfiguredEmailChannel;
const isValid = alertIsValid(alert, channelSpec);
if (!isLoadingChannelInfo && !channelRequirementsMet) {
return (
<ChannelSetupModal
user={user}
onClose={onCancel}
entityNamePlural={t`alerts`}
channels={isAdmin ? ["email", "Slack", "Webhook"] : ["email"]}
/>
);
}
if (!hasSeenEducationalScreen) {
return (
<ModalContent onClose={onCancel} data-testid="alert-education-screen">
<AlertEducationalScreen onProceed={proceedFromEducationalScreen} />
</ModalContent>
);
}
// TODO: Remove PulseEdit css hack
return (
<ModalContent data-testid="alert-create" onClose={onCancel}>
<div
className={cx(CS.mlAuto, CS.mrAuto, CS.mb4)}
style={{ maxWidth: "550px" }}
>
<AlertModalTitle text={t`Let's set up your alert`} />
<AlertEditForm
alertType={question?.alertType(visualizationSettings)}
alert={alert}
onAlertChange={onAlertChange}
/>
<AlertModalFooter>
<Button onClick={onCancel} className={CS.mr2}>{t`Cancel`}</Button>
<ButtonWithStatus
titleForState={{ default: t`Done` }}
disabled={!isValid}
onClickOperation={onCreateAlert}
/>
</AlertModalFooter>
</div>
</ModalContent>
);
};
/* eslint-disable react/prop-types */
import cx from "classnames";
import { Component } from "react";
import { connect } from "react-redux";
import { t } from "ttag";
import { deleteAlert, updateAlert } from "metabase/alert/alert";
import ButtonWithStatus from "metabase/components/ButtonWithStatus";
import ModalContent from "metabase/components/ModalContent";
import Button from "metabase/core/components/Button";
import CS from "metabase/css/core/index.css";
import { alertIsValid } from "metabase/lib/alert";
import { updateUrl } from "metabase/query_builder/actions";
import {
getQuestion,
getVisualizationSettings,
} from "metabase/query_builder/selectors";
import { getUser, getUserIsAdmin } from "metabase/selectors/user";
import { AlertEditForm } from "./AlertEditForm";
import { AlertModalTitle } from "./AlertModalTitle";
import { AlertModalFooter } from "./AlertModals.styled";
import { DeleteAlertSection } from "./DeleteAlertSection";
class UpdateAlertModalContentInner extends Component {
constructor(props) {
super();
this.state = {
modifiedAlert: props.alert,
};
}
onAlertChange = modifiedAlert => this.setState({ modifiedAlert });
onUpdateAlert = async () => {
const { question, updateAlert, updateUrl, onAlertUpdated } = this.props;
const { modifiedAlert } = this.state;
await updateAlert(modifiedAlert);
await updateUrl(question, { dirty: false });
onAlertUpdated();
};
onDeleteAlert = async () => {
const { alert, deleteAlert, onAlertUpdated } = this.props;
await deleteAlert(alert.id);
onAlertUpdated();
};
render() {
const { onCancel, question, visualizationSettings, alert, user, isAdmin } =
this.props;
const { modifiedAlert } = this.state;
const isCurrentUser = alert.creator.id === user.id;
const title = isCurrentUser ? t`Edit your alert` : t`Edit alert`;
const isValid = alertIsValid(alert);
// TODO: Remove PulseEdit css hack
return (
<ModalContent onClose={onCancel} data-testid="alert-edit">
<div
className={cx(CS.mlAuto, CS.mrAuto, CS.mb4)}
style={{ maxWidth: "550px" }}
>
<AlertModalTitle text={title} />
<AlertEditForm
alertType={question.alertType(visualizationSettings)}
alert={modifiedAlert}
onAlertChange={this.onAlertChange}
/>
{isAdmin && (
<DeleteAlertSection
alert={alert}
onDeleteAlert={this.onDeleteAlert}
/>
)}
<AlertModalFooter>
<Button onClick={onCancel} className={CS.mr2}>{t`Cancel`}</Button>
<ButtonWithStatus
titleForState={{ default: t`Save changes` }}
disabled={!isValid}
onClickOperation={this.onUpdateAlert}
/>
</AlertModalFooter>
</div>
</ModalContent>
);
}
}
export const UpdateAlertModalContent = connect(
state => ({
user: getUser(state),
isAdmin: getUserIsAdmin(state),
question: getQuestion(state),
visualizationSettings: getVisualizationSettings(state),
}),
{ updateAlert, deleteAlert, updateUrl },
)(UpdateAlertModalContentInner);
import cx from "classnames";
import { useState } from "react";
import { t } from "ttag";
import { deleteAlert, updateAlert } from "metabase/alert/alert";
import { useGetChannelInfoQuery } from "metabase/api";
import ButtonWithStatus from "metabase/components/ButtonWithStatus";
import ModalContent from "metabase/components/ModalContent";
import Button from "metabase/core/components/Button";
import CS from "metabase/css/core/index.css";
import { alertIsValid } from "metabase/lib/alert";
import { useDispatch, useSelector } from "metabase/lib/redux";
import { updateUrl } from "metabase/query_builder/actions";
import {
getQuestion,
getVisualizationSettings,
} from "metabase/query_builder/selectors";
import { getUser, getUserIsAdmin } from "metabase/selectors/user";
import type { Alert } from "metabase-types/api";
import { AlertEditForm } from "./AlertEditForm";
import { AlertModalTitle } from "./AlertModalTitle";
import { AlertModalFooter } from "./AlertModals.styled";
import { DeleteAlertSection } from "./DeleteAlertSection";
interface UpdateAlertModalContentProps {
alert: Alert;
onAlertUpdated: () => void;
onCancel: () => void;
}
export const UpdateAlertModalContent = ({
alert,
onAlertUpdated,
onCancel,
}: UpdateAlertModalContentProps) => {
const dispatch = useDispatch();
const user = useSelector(getUser);
const isAdmin = useSelector(getUserIsAdmin);
const question = useSelector(getQuestion);
const visualizationSettings = useSelector(getVisualizationSettings);
const [modifiedAlert, setModifiedAlert] = useState(alert);
const onAlertChange = (newModifiedAlert: Alert) =>
setModifiedAlert(newModifiedAlert);
const { data: channelSpec = {} } = useGetChannelInfoQuery();
const onUpdateAlert = async () => {
await dispatch(updateAlert(modifiedAlert));
await dispatch(updateUrl(question, { dirty: false }));
onAlertUpdated();
};
const onDeleteAlert = async () => {
await dispatch(deleteAlert(alert?.id));
onAlertUpdated();
};
const isCurrentUser = alert?.creator.id === user?.id;
const title = isCurrentUser ? t`Edit your alert` : t`Edit alert`;
const isValid = alertIsValid(modifiedAlert, channelSpec);
// TODO: Remove PulseEdit css hack
return (
<ModalContent onClose={onCancel} data-testid="alert-edit">
<div
className={cx(CS.mlAuto, CS.mrAuto, CS.mb4)}
style={{ maxWidth: "550px" }}
>
<AlertModalTitle text={title} />
<AlertEditForm
alertType={question?.alertType(visualizationSettings)}
alert={modifiedAlert}
onAlertChange={onAlertChange}
/>
{isAdmin && (
<DeleteAlertSection alert={alert} onDeleteAlert={onDeleteAlert} />
)}
<AlertModalFooter>
<Button onClick={onCancel} className={CS.mr2}>{t`Cancel`}</Button>
<ButtonWithStatus
titleForState={{ default: t`Save changes` }}
disabled={!isValid}
onClickOperation={onUpdateAlert}
/>
</AlertModalFooter>
</div>
</ModalContent>
);
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment