Skip to content

Commit

Permalink
Merge pull request #5473 from gooddata/SHA_master
Browse files Browse the repository at this point in the history
fix: validate recipients e-mail for smtps
  • Loading branch information
hackerstanislav authored Oct 15, 2024
2 parents ef30590 + c5d03b1 commit e40897d
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,11 @@
"comment": "Message for recipients input when the minimum number of recipients is reached",
"limit": 0
},
"dialogs.schedule.email.user.missing.email": {
"value": "This user is missing an email address.",
"comment": "Message for recipients input when a user is missing an email address.",
"limit": 0
},
"dialogs.schedule.email.subject.label": {
"value": "Subject",
"comment": "Subject of the schedule email.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ export function ScheduledMailDialogRenderer({
onChange={onRecipientsChange}
allowEmptySelection
maxRecipients={maxAutomationsRecipients}
notificationChannels={notificationChannels}
notificationChannelId={editedAutomation.notificationChannel}
/>
) : null}
<Input
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// (C) 2019-2024 GoodData Corporation
/* eslint-disable import/named,import/namespace */
import React, { useMemo, useState } from "react";
import { IAutomationRecipient, IWorkspaceUser } from "@gooddata/sdk-model";
import {
IAutomationRecipient,
INotificationChannelMetadataObject,
IWorkspaceUser,
} from "@gooddata/sdk-model";
import sortBy from "lodash/sortBy.js";

import { RecipientsSelectRenderer } from "./RecipientsSelectRenderer.js";
Expand Down Expand Up @@ -42,10 +46,30 @@ interface IRecipientsSelectProps {
* Additional class name
*/
className?: string;

/**
* Notification channels
*/
notificationChannels?: INotificationChannelMetadataObject[];

/**
* Notification channel id
*/
notificationChannelId?: string;
}

export const RecipientsSelect: React.FC<IRecipientsSelectProps> = (props) => {
const { users, value, originalValue, onChange, allowEmptySelection, maxRecipients, className } = props;
const {
users,
value,
originalValue,
onChange,
allowEmptySelection,
maxRecipients,
className,
notificationChannels,
notificationChannelId,
} = props;

const [search, setSearch] = useState<string>();

Expand All @@ -54,6 +78,10 @@ export const RecipientsSelect: React.FC<IRecipientsSelectProps> = (props) => {
return sortBy(filteredUsers?.map(convertUserToAutomationRecipient) ?? [], "user.email");
}, [users, search]);

const notificationChannel = useMemo(() => {
return notificationChannels?.find((channel) => channel.id === notificationChannelId);
}, [notificationChannelId, notificationChannels]);

return (
<RecipientsSelectRenderer
canListUsersInProject
Expand All @@ -68,6 +96,7 @@ export const RecipientsSelect: React.FC<IRecipientsSelectProps> = (props) => {
allowEmptySelection={allowEmptySelection}
maxRecipients={maxRecipients}
className={className}
notificationChannel={notificationChannel}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// (C) 2019-2024 GoodData Corporation
/* eslint-disable import/named,import/namespace */
import { IAutomationRecipient, isAutomationUserRecipient } from "@gooddata/sdk-model";
import {
IAutomationRecipient,
INotificationChannelMetadataObject,
isAutomationUserRecipient,
} from "@gooddata/sdk-model";
import React from "react";
import { FormattedMessage } from "react-intl";
import ReactSelect, {
Expand All @@ -17,7 +21,15 @@ import isEmpty from "lodash/isEmpty.js";
import isEqual from "lodash/isEqual.js";
import includes from "lodash/includes.js";
import { IWorkspaceUsersQueryOptions } from "@gooddata/sdk-backend-spi";
import { LoadingMask, Overlay, OverlayController, OverlayControllerProvider } from "@gooddata/sdk-ui-kit";
import {
Bubble,
BubbleHoverTrigger,
IAlignPoint,
LoadingMask,
Overlay,
OverlayController,
OverlayControllerProvider,
} from "@gooddata/sdk-ui-kit";
import cx from "classnames";

import { isEmail } from "../../utils/validate.js";
Expand All @@ -33,6 +45,17 @@ const SELECT_OPTION = "select-option";
const { Menu, Input } = ReactSelectComponents;
const overlayController = OverlayController.getInstance(DASHBOARD_DIALOG_OVERS_Z_INDEX);

const TOOLTIP_ALIGN_POINTS: IAlignPoint[] = [
{
align: "cr cl",
offset: { x: 0, y: -2 },
},
{
align: "cl cr",
offset: { x: 0, y: -2 },
},
];

export interface IRecipientsSelectRendererProps {
/**
* Currently selected recipients.
Expand Down Expand Up @@ -88,6 +111,11 @@ export interface IRecipientsSelectRendererProps {
* Additional class name
*/
className?: string;

/**
* Notification channel
*/
notificationChannel?: INotificationChannelMetadataObject;
}

interface IRecipientsSelectRendererState {
Expand Down Expand Up @@ -137,8 +165,14 @@ export class RecipientsSelectRenderer extends React.PureComponent<
Placeholder: this.renderEmptyContainer,
NoOptionsMessage: this.renderNoOptionsContainer,
};

const maxRecipientsError = maxRecipients && value.length > maxRecipients;
const showInputError = maxRecipientsError || this.state.minRecipientsError;
const minRecipientsError = this.state.minRecipientsError;
const someRecipientsMissingEmail = this.isEmailChannel()
? value.some((v) => (isAutomationUserRecipient(v) ? !isEmail(v.email ?? "") : false))
: false;

const showInputError = maxRecipientsError || minRecipientsError || someRecipientsMissingEmail;

return (
<div
Expand Down Expand Up @@ -179,9 +213,10 @@ export class RecipientsSelectRenderer extends React.PureComponent<
id="dialogs.schedule.email.max.recipients"
values={{ maxRecipients }}
/>
) : (
) : null}
{minRecipientsError ? (
<FormattedMessage id="dialogs.schedule.email.min.recipients" />
)}
) : null}
</div>
) : null}
</div>
Expand All @@ -205,6 +240,12 @@ export class RecipientsSelectRenderer extends React.PureComponent<
};
}

private isEmailChannel() {
const { notificationChannel } = this.props;

return notificationChannel?.type === "smtp";
}

private renderNoOptionsContainer = (): React.ReactElement | null => {
return this.renderEmptyContainer();
};
Expand Down Expand Up @@ -258,18 +299,39 @@ export class RecipientsSelectRenderer extends React.PureComponent<
private renderMultiValueItemContainer = (
label: string,
removeIcon: React.ReactElement | null,
validation: { hasEmail?: boolean } = {},
): React.ReactElement => {
const style = this.getStyle();
return (
<div className="gd-recipient-value-item s-gd-recipient-value-item multiple-value">
<div style={{ maxWidth: style.maxWidth }} className="gd-recipient-label">
{label}
</div>
<div aria-label="remove-icon" className="s-gd-recipient-remove">
{removeIcon}

const render = () => {
return (
<div
className={cx("gd-recipient-value-item s-gd-recipient-value-item multiple-value", {
"invalid-email": !validation.hasEmail,
})}
>
<div style={{ maxWidth: style.maxWidth }} className="gd-recipient-label">
{label}
</div>
<div aria-label="remove-icon" className="s-gd-recipient-remove">
{removeIcon}
</div>
</div>
</div>
);
);
};

if (validation.hasEmail === false) {
return (
<BubbleHoverTrigger>
{render()}
<Bubble className="bubble-negative" alignPoints={TOOLTIP_ALIGN_POINTS}>
<FormattedMessage id="dialogs.schedule.email.user.missing.email" />
</Bubble>
</BubbleHoverTrigger>
);
}

return render();
};

private renderMultiValueContainer = (
Expand All @@ -280,8 +342,10 @@ export class RecipientsSelectRenderer extends React.PureComponent<
// MultiValueRemove component from react-select
const removeIcon: React.ReactElement | null = (children as any)![1];
const name = data.name ?? data.id;
const hasEmail =
this.isEmailChannel() && isAutomationUserRecipient(data) ? isEmail(data.email ?? "") : true;

return this.renderMultiValueItemContainer(name, removeIcon);
return this.renderMultiValueItemContainer(name, removeIcon, { hasEmail });
};

private renderOptionLabel = (recipient: IAutomationRecipient): React.ReactElement | null => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
IUser,
IFilter,
INotificationChannelMetadataObject,
isAutomationUserRecipient,
} from "@gooddata/sdk-model";
import parseISO from "date-fns/parseISO/index.js";
import { getUserTimezone } from "../utils/timezone.js";
Expand Down Expand Up @@ -41,6 +42,7 @@ import {
import { invariant } from "ts-invariant";
import { useIntl } from "react-intl";
import { useScheduleValidation } from "./useScheduleValidation.js";
import { isEmail } from "../utils/validate.js";

export interface IUseEditScheduledEmailProps {
scheduledExportToEdit?: IAutomationMetadataObject;
Expand Down Expand Up @@ -341,9 +343,20 @@ export function useEditScheduledEmail(props: IUseEditScheduledEmailProps) {
const hasRecipients = (editedAutomation.recipients?.length ?? 0) > 0;
const hasDestination = !!editedAutomation.notificationChannel;
const respectsRecipientsLimit = (editedAutomation.recipients?.length ?? 0) <= maxAutomationsRecipients;
const hasFilledEmails =
selectedNotificationChannel?.type === "smtp"
? editedAutomation.recipients?.every((recipient) =>
isAutomationUserRecipient(recipient) ? isEmail(recipient.email ?? "") : true,
)
: true;

const isValid =
isCronValid && hasRecipients && respectsRecipientsLimit && hasAttachments && hasDestination;
isCronValid &&
hasRecipients &&
respectsRecipientsLimit &&
hasAttachments &&
hasDestination &&
hasFilledEmails;

const isSubmitDisabled =
!isValid || (scheduledExportToEdit && areAutomationsEqual(originalAutomation, editedAutomation));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ export const EditAlert: React.FC<IEditAlertProps> = ({
allowEmptySelection
maxRecipients={maxAutomationsRecipients}
className="gd-edit-alert__recipients"
notificationChannels={destinations}
notificationChannelId={updatedAlert.notificationChannel}
/>
) : null}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
IAutomationRecipient,
ICatalogMeasure,
INotificationChannelMetadataObject,
isAutomationUserRecipient,
} from "@gooddata/sdk-model";
import isEqual from "lodash/isEqual.js";
import {
Expand All @@ -24,6 +25,7 @@ import {
import { AlertMetric } from "../../../types.js";
import { selectCurrentUser, useDashboardSelector } from "../../../../../../model/index.js";
import { convertUserToAutomationRecipient } from "../../../../../../_staging/automation/index.js";
import { isEmail } from "../../../../../scheduledEmail/DefaultScheduledEmailDialog/utils/validate.js";

export interface IUseEditAlertProps {
metrics: AlertMetric[];
Expand Down Expand Up @@ -142,7 +144,14 @@ export const useEditAlert = ({
const isValueDefined = isAlertValueDefined(updatedAlert.alert);
const isRecipientsValid = isAlertRecipientsValid(updatedAlert);
const isAlertChanged = !isEqual(updatedAlert, alert);
const canSubmit = isValueDefined && isAlertChanged && isRecipientsValid;
const areEmailsValid =
selectedDestination?.type === "smtp"
? updatedAlert.recipients?.every((v) =>
isAutomationUserRecipient(v) ? isEmail(v.email ?? "") : true,
)
: true;

const canSubmit = isValueDefined && isAlertChanged && isRecipientsValid && areEmailsValid;

return {
viewMode,
Expand Down
21 changes: 18 additions & 3 deletions libs/sdk-ui-dashboard/styles/scss/scheduled_mail_recipients.scss
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ $input-height: 28px;
font-family: Avenir, sans-serif;
font-size: 12px;
margin-top: 5px;

&:empty {
display: none;
margin-top: 0;
}
}

// custom className from react-select rendered
Expand Down Expand Up @@ -159,9 +164,19 @@ $input-height: 28px;
padding: 0;
}

&.not-valid {
color: kit-variables.$gd-color-white;
background: rgba(229, 77, 66, 0.98);
&.not-valid,
&.invalid-email {
color: kit-variables.$gd-palette-error-base;
background: var(--gd-palette-error-dimmed, #fcedec);

.gd-recipient-label {
color: kit-variables.$gd-palette-error-base;
}

.gd-recipients__multi-value__remove,
.gd-recipients__multi-value__remove:hover {
color: kit-variables.$gd-palette-error-base;
}
}
}
}
Expand Down

0 comments on commit e40897d

Please sign in to comment.