From 3037d946db69ce5310d88df25e1e769628865b0b Mon Sep 17 00:00:00 2001 From: shivani170 Date: Thu, 2 Jan 2025 22:42:54 +0530 Subject: [PATCH 01/31] chore: code refactoring --- .eslintignore | 1 - .../ClusterEnvironmentDrawer.tsx | 4 +-- .../notifications/AddNotification.tsx | 2 +- .../notifications/ConfigurationTab.tsx | 21 ++++++++---- .../ConfigurationTabsSwitcher.tsx | 2 +- .../notifications/Notifications.tsx | 2 +- src/components/notifications/constants.ts | 30 +++++++++++++--- .../notifications/notifications.scss | 9 +++++ .../notifications/notifications.util.tsx | 34 ++++++++++++++++--- src/components/notifications/types.tsx | 8 +++++ 10 files changed, 92 insertions(+), 21 deletions(-) diff --git a/.eslintignore b/.eslintignore index 024d2614ef..7dfaf4054a 100755 --- a/.eslintignore +++ b/.eslintignore @@ -319,7 +319,6 @@ src/components/notifications/CreateHeaderDetails.tsx src/components/notifications/ModifyRecipientsModal.tsx src/components/notifications/NotificationTab.tsx src/components/notifications/Notifications.tsx -src/components/notifications/SESConfigModal.tsx src/components/notifications/SMTPConfigModal.tsx src/components/notifications/SlackConfigModal.tsx src/components/notifications/WebhookConfigModal.tsx diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx index 549f1ea7ef..2088b7f5ac 100644 --- a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx @@ -238,10 +238,10 @@ export const ClusterEnvironmentDrawer = ({
-
+
{ ) } const payload = deleteConfigPayload() + + const renderContent = () => ( +
+ {renderSESConfigModal()} + {renderSMTPConfigModal()} + {renderSlackConfigModal()} + {renderWebhookConfigModal()} +
+ ) return (
- + - {renderSESConfigModal()} - {renderSMTPConfigModal()} - {renderSlackConfigModal()} - {renderWebhookConfigModal()} + {renderContent()} {state.confirmation && ( handleTabClick(tab.link)} >
- + {tab.icon} {tab.label}
diff --git a/src/components/notifications/Notifications.tsx b/src/components/notifications/Notifications.tsx index 922ef7bfb4..3a6ad14471 100644 --- a/src/components/notifications/Notifications.tsx +++ b/src/components/notifications/Notifications.tsx @@ -30,7 +30,7 @@ interface NotificationsProps extends RouteComponentProps<{}> { export default class Notifications extends Component { renderNotificationHeader() { return ( -
+
{ return } +export const getConfigTabIcons = (tab: ConfigurationsTabTypes, size: number = 20 ) => { + switch (tab) { + case ConfigurationsTabTypes.SES: + return + case ConfigurationsTabTypes.SMTP: + return + case ConfigurationsTabTypes.SLACK: + return + case ConfigurationsTabTypes.WEBHOOK: + return + default: + return SES + } +} + export const getConfigurationTabTextWithIcon = () => [ { label: ConfigurationTabText.SES, - icon: SES, + icon: getConfigTabIcons(ConfigurationsTabTypes.SES), link: ConfigurationsTabTypes.SES, }, { label: ConfigurationTabText.SMTP, - icon: SMTP, + icon: getConfigTabIcons(ConfigurationsTabTypes.SMTP), link: ConfigurationsTabTypes.SMTP, }, { label: ConfigurationTabText.SLACK, - icon: Slack, + icon: getConfigTabIcons(ConfigurationsTabTypes.SLACK), link: ConfigurationsTabTypes.SLACK, }, { label: ConfigurationTabText.WEBHOOK, - icon: Webhook, + icon: getConfigTabIcons(ConfigurationsTabTypes.WEBHOOK), link: ConfigurationsTabTypes.WEBHOOK, }, ] + +export const getSESDefaultConfiguration = (shouldBeDefault: boolean) => ({ + configName: '', + accessKey: '', + secretKey: '', + region: { label: '', value: '' }, + fromEmail: '', + default: shouldBeDefault, + isLoading: false, + isError: true, +}) \ No newline at end of file diff --git a/src/components/notifications/types.tsx b/src/components/notifications/types.tsx index fa62cc55b9..b5f15b58d4 100644 --- a/src/components/notifications/types.tsx +++ b/src/components/notifications/types.tsx @@ -162,3 +162,11 @@ export interface ConfigurationTabSwitcherProps { state: ConfigurationTabState setState: React.Dispatch> } + +export interface SESConfigModalProps { + sesConfigId: number + shouldBeDefault: boolean + selectSESFromChild?: (sesConfigId: number) => void + onSaveSuccess: () => void + closeSESConfigModal: (event) => void +} From 6457569fa4b367218d1cb5a9f252099e786d71dc Mon Sep 17 00:00:00 2001 From: shivani170 Date: Fri, 3 Jan 2025 09:25:08 +0530 Subject: [PATCH 02/31] chore: ses code refactoring --- .eslintignore | 3 - src/assets/img/ses-empty.png | Bin 0 -> 1793 bytes src/assets/img/slack-empty.png | Bin 0 -> 4096 bytes src/assets/img/smtp-empty.png | Bin 0 -> 3073 bytes .../notifications/AddNotification.tsx | 4 +- .../ConfigTableRowActionButton.tsx | 32 + .../notifications/ConfigurationTab.tsx | 293 ++++----- .../ConfigurationTabsSwitcher.tsx | 62 +- .../notifications/EmptyConfigurationView.tsx | 4 +- .../notifications/SESConfigModal.tsx | 577 ++++++++---------- .../notifications/SESConfigurationTable.tsx | 145 ++--- .../notifications/SMTPConfigModal.tsx | 441 ++++++------- .../notifications/SMTPConfigurationTable.tsx | 132 ++-- .../notifications/SlackConfigModal.tsx | 533 ++++++++-------- .../notifications/SlackConfigurationTable.tsx | 125 ++-- .../WebhookConfigurationTable.tsx | 120 ++-- .../notifications/notifications.scss | 52 +- .../notifications/notifications.util.tsx | 126 ++-- src/components/notifications/types.tsx | 45 +- src/css/base.scss | 29 +- 20 files changed, 1216 insertions(+), 1507 deletions(-) create mode 100644 src/assets/img/ses-empty.png create mode 100644 src/assets/img/slack-empty.png create mode 100644 src/assets/img/smtp-empty.png create mode 100644 src/components/notifications/ConfigTableRowActionButton.tsx diff --git a/.eslintignore b/.eslintignore index 7dfaf4054a..cc645d3ba9 100755 --- a/.eslintignore +++ b/.eslintignore @@ -314,13 +314,10 @@ src/components/material/MaterialList.tsx src/components/material/MaterialView.tsx src/components/material/UpdateMaterial.tsx src/components/notifications/AddNotification.tsx -src/components/notifications/ConfigurationTab.component.tsx src/components/notifications/CreateHeaderDetails.tsx src/components/notifications/ModifyRecipientsModal.tsx src/components/notifications/NotificationTab.tsx src/components/notifications/Notifications.tsx -src/components/notifications/SMTPConfigModal.tsx -src/components/notifications/SlackConfigModal.tsx src/components/notifications/WebhookConfigModal.tsx src/components/notifications/notifications.service.ts src/components/notifications/notifications.util.tsx diff --git a/src/assets/img/ses-empty.png b/src/assets/img/ses-empty.png new file mode 100644 index 0000000000000000000000000000000000000000..15178ac1f7ec82575655c44121f95dffaa82ce22 GIT binary patch literal 1793 zcmV+c2mbhpP)001Zm1^@s6c`Wgm00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yPlb;A*OLrPz6XOpa^1GQLA4EiW&z&3lbH{p%V0z+(J|-p@&NJ66a8yLnZ7> zTd9ZS2CXW^k*Y#VCAOjBQmUAWLZPYRAOZaVYT7iA#JkgZYwyNMZSQWp{#a{%lI7W* z$?in+_kZTSSsQ@Eu?b44pS?~|W3Y`MI2^mkif@vd2iy7ya5y%AuEyR5v|~AufQ69} zP^PAU&d!1>7zFo$10WtenEPi@+XWbT``J))qi}dnaD6OrR7wJ;?qT9SfG<#te+J_czodH4bbQnM`cikxDacHcRs&E!Df!U=!ciK ztWnG9vuEM)@Nm)gYhp3g5fK4wAkxs>CGA*l-Mm&mf9v;0F!xwi%Up={&1S!-mvQZ91g;A; zN)q63-_A*p(K>s;XQ zMFpr%y+j#=Rf5xSlP{B`%(!7@Er{-i(J{K;mZ#PLr#?Utj$ip3-aq>Xd_OY>w^M1o z4kP7>UsQ3T@CHzPcu;f-W{vtO1{@`@Yuh@w_~QfJ1yOL7aKAnK4uvR?AOUshAbt1E z7ZNxKC-)!4cINWKymjr{eNygmK>?!s(VV9nR?7x|COLgCwV=8nI$t^6FVxxTUz|;= zDI!AX6?|s{IkJ1-p@hdL_DJz~3Tl{Y_W$+3I(ThoX{LZ{!LP3WW4e7|2PEVI*hJuI zA6!gjToBVT>bWwx-@>ds!qyevGRFw#oE~6~63z~62h=dt?0>1^)2?AffO*$|k-j!u z_qBjcpbO@@UAf#3r!7JtxMJ{Bv~$=7YLsgC-->YTx<-Bnq@){c1|9Ft{dv^|%eeLn zu{G0pp-MvGib9|t2T>tP{L=X^)t&R*`#XaIO)twuR->LRdD@NoPl>wyDM1;)=@Va- zB!3i4squb_MbPyyDlTW_%hpPU>m!zl%;qya1}!UJZ?6BOuD(l=*8xv_{Ie7}7LdT^ zeOgRLH#NGUTrl@YPzr|&)l-2SChWttGNQb6K8{XQ@HwJN)vJ*&g+ZhKLmg2#56J#e zV&W5N1x!wFV%hR?p$UscjQ<-%JjVtvQOoHJr}uv~6pVtI3not<+c88yI0X*Eoff;& zqCE1-{x_xtlG)x<6dJ@$K+z|5L5)+*{#!}ISWUMSqG%%ey^n50*;I^ES_mA1B&m6_ zuIzM%$=nnhym6;EM>YFzB@JUW-BJj{hY?(9;kYa+li)D2115n(sYd@%Q0RhY+uDJl z0N?%Or$nST5MwL+e&EzKwUdTf!MwL2%-)i~;n<8R0fwWA6x3PxVeKa!8R!A6f5 zXH#*C?Gwi-rN_Yz@Oo4+{w^QKE|O3sNS<7%sVAZ}Za%!<^VX3c^xPnekXN6gVZu`= zjd>d&y0uHH=_gd`2@1+MORZI9J|dq%r9CRd)fYf5&_cT~a&(fMT8LI>V6e|?EWdfP&TkU{oo z3cb2(Jv2Y*$vB>uJDw957`xj-WQt#pe9lHkOk%Ku>rI^Ui#4?E_W@Ce&+qVuwzqh> j@HiX}hr{7;YMcK661s<5bMw9900000NkvXXu0mjffwEO> literal 0 HcmV?d00001 diff --git a/src/assets/img/slack-empty.png b/src/assets/img/slack-empty.png new file mode 100644 index 0000000000000000000000000000000000000000..cb0da428740262f0740147290c2a0c14830ce782 GIT binary patch literal 4096 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 P00000NkvXXu0mjfX8r_Y literal 0 HcmV?d00001 diff --git a/src/components/notifications/AddNotification.tsx b/src/components/notifications/AddNotification.tsx index 6958062499..0fc1ee4ea9 100644 --- a/src/components/notifications/AddNotification.tsx +++ b/src/components/notifications/AddNotification.tsx @@ -985,7 +985,7 @@ export class AddNotification extends Component { + closeSESConfigModal={() => { this.setState({ showSESConfigModal: false }) }} /> @@ -1011,7 +1011,7 @@ export class AddNotification extends Component { + closeSMTPConfigModal={() => { this.setState({ showSMTPConfigModal: false }) }} /> diff --git a/src/components/notifications/ConfigTableRowActionButton.tsx b/src/components/notifications/ConfigTableRowActionButton.tsx new file mode 100644 index 0000000000..89c828f832 --- /dev/null +++ b/src/components/notifications/ConfigTableRowActionButton.tsx @@ -0,0 +1,32 @@ +import { Trash } from '@Components/common' +import { ReactComponent as Edit } from '@Icons/ic-edit.svg' +import { Button, ButtonStyleType, ButtonVariantType, ComponentSizeType } from '@devtron-labs/devtron-fe-common-lib' +import { ConfigTableRowActionButtonProps } from './types' + +export const ConfigTableRowActionButton = ({ + rootClassName, + onClickEditRow, + onClickDeleteRow, + modal, +}: ConfigTableRowActionButtonProps) => ( +
+
+) diff --git a/src/components/notifications/ConfigurationTab.tsx b/src/components/notifications/ConfigurationTab.tsx index 3383d9eece..9769efb03b 100644 --- a/src/components/notifications/ConfigurationTab.tsx +++ b/src/components/notifications/ConfigurationTab.tsx @@ -15,8 +15,14 @@ */ import { useEffect, useState } from 'react' -import { showError, Progressing, ErrorScreenNotAuthorized, DeleteComponent } from '@devtron-labs/devtron-fe-common-lib' -import { Route, Switch, useHistory, useRouteMatch } from 'react-router-dom' +import { + showError, + Progressing, + ErrorScreenNotAuthorized, + DeleteComponent, + useSearchString, +} from '@devtron-labs/devtron-fe-common-lib' +import { Route, Switch, useHistory, useLocation, useRouteMatch } from 'react-router-dom' import { SlackConfigModal } from './SlackConfigModal' import SESConfigModal from './SESConfigModal' import { @@ -32,9 +38,9 @@ import { DC_CONFIGURATION_CONFIRMATION_MESSAGE, DeleteComponentsName } from '../ import { SMTPConfigModal } from './SMTPConfigModal' import { WebhookConfigModal } from './WebhookConfigModal' import { ConfigurationTabState } from './types' -import { SlackConfigurationTable } from './SlackConfigurationTable' +import SlackConfigurationTable from './SlackConfigurationTable' import { WebhookConfigurationTable } from './WebhookConfigurationTable' -import { SESConfigurationTable } from './SESConfigurationTable' +import SESConfigurationTable from './SESConfigurationTable' import { SMTPConfigurationTable } from './SMTPConfigurationTable' import { ConfigurationTabSwitcher } from './ConfigurationTabsSwitcher' import { ConfigurationsTabTypes } from './constants' @@ -42,12 +48,13 @@ import { ConfigurationsTabTypes } from './constants' export const ConfigurationTab = () => { const { path } = useRouteMatch() const history = useHistory() + const location = useLocation() + const { searchParams } = useSearchString() + const queryString = new URLSearchParams(location.search) + const modal = queryString.get('modal') + const [state, setState] = useState({ view: ViewType.LOADING, - showSlackConfigModal: false, - showSESConfigModal: false, - showSMTPConfigModal: false, - showWebhookConfigModal: false, slackConfigId: 0, sesConfigId: 0, smtpConfigId: 0, @@ -63,80 +70,84 @@ export const ConfigurationTab = () => { smtpConfig: {}, slackConfig: {}, webhookConfig: {}, - showDeleteConfigModalType: '', + showDeleteConfigModalType: ConfigurationsTabTypes.SES, activeTab: ConfigurationsTabTypes.SES, }) - const getAllChannelConfigs = (): void => { - getConfigs() - .then((response) => { - setState({ - ...state, - slackConfigurationList: response.result.slackConfigurationList, - sesConfigurationList: response.result.sesConfigurationList, - smtpConfigurationList: response.result.smtpConfigurationList, - webhookConfigurationList: response.result.webhookConfigurationList, - view: ViewType.FORM, - }) - }) - .catch((error) => { - showError(error, true, true) - setState({ ...state, view: ViewType.ERROR }) + const closeModal = () => { + history.push(path) + } + + const getAllChannelConfigs = async () => { + try { + const { result } = await getConfigs() + setState({ + ...state, + slackConfigurationList: result.slackConfigurationList, + sesConfigurationList: result.sesConfigurationList, + smtpConfigurationList: result.smtpConfigurationList, + webhookConfigurationList: result.webhookConfigurationList, + view: ViewType.FORM, }) + } catch (error) { + showError(error, true, true) + setState({ ...state, view: ViewType.ERROR }) + } } + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises getAllChannelConfigs() - history.push(`${path}/ses`) - }, []) - const onSaveWebhook = () => { - setState({ ...state, showWebhookConfigModal: false, webhookConfigId: 0 }) - getAllChannelConfigs() - } + const newParams = { + ...searchParams, + modal: modal ?? ConfigurationsTabTypes.SES, + } - const onCloseWebhookModal = () => { - setState({ ...state, showWebhookConfigModal: false, webhookConfigId: 0 }) - } + history.push({ + search: new URLSearchParams(newParams).toString(), + }) + }, []) - const deleteClickHandler = async (configId, type) => { + const deleteClickHandler = async (configId, type: ConfigurationsTabTypes) => { try { - if (type === DeleteComponentsName.SlackConfigurationTab) { + if (type === ConfigurationsTabTypes.SLACK) { const { result } = await getSlackConfiguration(configId, true) setState({ ...state, slackConfigId: configId, slackConfig: { ...result, - channel: DeleteComponentsName.SlackConfigurationTab, + channel: ConfigurationsTabTypes.SLACK, }, confirmation: true, - showDeleteConfigModalType: DeleteComponentsName.SlackConfigurationTab, + showDeleteConfigModalType: ConfigurationsTabTypes.SLACK, }) - } else if (type === DeleteComponentsName.SesConfigurationTab) { + } else if (type === ConfigurationsTabTypes.SES) { const { result } = await getSESConfiguration(configId) setState({ ...state, sesConfigId: configId, sesConfig: { ...result, - channel: DeleteComponentsName.SesConfigurationTab, + channel: ConfigurationsTabTypes.SES, }, confirmation: true, - showDeleteConfigModalType: DeleteComponentsName.SesConfigurationTab, + showDeleteConfigModalType: ConfigurationsTabTypes.SES, }) - } else if (type === DeleteComponentsName.SMTPConfigurationTab) { + } else if (type === ConfigurationsTabTypes.SMTP) { const { result } = await getSMTPConfiguration(configId) setState({ ...state, smtpConfigId: configId, smtpConfig: { ...result, - channel: DeleteComponentsName.SMTPConfigurationTab, + channel: ConfigurationsTabTypes.SMTP, }, confirmation: true, - showDeleteConfigModalType: DeleteComponentsName.SMTPConfigurationTab, + showDeleteConfigModalType: ConfigurationsTabTypes.SMTP, }) - } else if (type === DeleteComponentsName.WebhookConfigurationTab) { + } else if (type === ConfigurationsTabTypes.WEBHOOK) { const { result } = await getWebhookConfiguration(configId) setState({ ...state, @@ -146,7 +157,7 @@ export const ConfigurationTab = () => { channel: DeleteComponentsName.WebhookConfigurationTab, }, confirmation: true, - showDeleteConfigModalType: DeleteComponentsName.WebhookConfigurationTab, + showDeleteConfigModalType: ConfigurationsTabTypes.WEBHOOK, }) } } catch (e) { @@ -154,111 +165,23 @@ export const ConfigurationTab = () => { } } - const renderSlackConfigurationTable = () => ( - - ) - - const renderWebhookConfigurationTable = () => ( - - ) - - const renderSESConfigurationTable = () => ( - - ) - - const renderSMTPConfigurationTable = () => ( - - ) - - const setDeleting = () => { - setState({ - ...state, - deleting: true, - }) - } - const toggleConfirmation = (confirmation) => { setState({ ...state, confirmation, - ...(!confirmation && { showDeleteConfigModalType: '' }), + ...(!confirmation && { showDeleteConfigModalType: ConfigurationsTabTypes.SES }), }) } - const renderSESConfigModal = () => { - const { showSESConfigModal, sesConfigId, sesConfigurationList } = state - if (!showSESConfigModal) return null - return ( - { - setState({ ...state, showSESConfigModal: false, sesConfigId: 0 }) - getAllChannelConfigs() - }} - closeSESConfigModal={() => { - setState({ ...state, showSESConfigModal: false }) - }} - /> - ) - } - - const renderSMTPConfigModal = () => { - const { showSMTPConfigModal, smtpConfigId, smtpConfigurationList } = state - if (!showSMTPConfigModal) return null - return ( - { - setState({ ...state, showSMTPConfigModal: false, smtpConfigId: 0 }) - getAllChannelConfigs() - }} - closeSMTPConfigModal={() => { - setState({ ...state, showSMTPConfigModal: false }) - }} - /> - ) - } - - const renderSlackConfigModal = () => { - const { showSlackConfigModal, slackConfigId } = state - if (!showSlackConfigModal) return null - return ( - { - setState({ ...state, showSlackConfigModal: false, slackConfigId: 0 }) - getAllChannelConfigs() - }} - closeSlackConfigModal={() => { - setState({ ...state, showSlackConfigModal: false, slackConfigId: 0 }) - }} - /> - ) - } - - const renderWebhookConfigModal = () => { - const { showWebhookConfigModal, webhookConfigId } = state - if (!showWebhookConfigModal) return null - return ( - - ) - } - const deleteConfigPayload = (): any => { const { showDeleteConfigModalType, slackConfig, sesConfig, webhookConfig, smtpConfig } = state - if (showDeleteConfigModalType === DeleteComponentsName.SlackConfigurationTab) { + if (showDeleteConfigModalType === ConfigurationsTabTypes.SLACK) { return slackConfig } - if (showDeleteConfigModalType === DeleteComponentsName.SesConfigurationTab) { + if (showDeleteConfigModalType === ConfigurationsTabTypes.SES) { return sesConfig } - if (showDeleteConfigModalType === DeleteComponentsName.WebhookConfigurationTab) { + if (showDeleteConfigModalType === ConfigurationsTabTypes.WEBHOOK) { return webhookConfig } return smtpConfig @@ -266,21 +189,21 @@ export const ConfigurationTab = () => { const deleteConfigComponent = (): string => { const { showDeleteConfigModalType } = state - if (showDeleteConfigModalType === DeleteComponentsName.SlackConfigurationTab) { - return DeleteComponentsName.SlackConfigurationTab + if (showDeleteConfigModalType === ConfigurationsTabTypes.SLACK) { + return ConfigurationsTabTypes.SLACK } - if (showDeleteConfigModalType === DeleteComponentsName.SesConfigurationTab) { - return DeleteComponentsName.SesConfigurationTab + if (showDeleteConfigModalType === ConfigurationsTabTypes.SES) { + return ConfigurationsTabTypes.SES } - if (showDeleteConfigModalType === DeleteComponentsName.WebhookConfigurationTab) { - return DeleteComponentsName.WebhookConfigurationTab + if (showDeleteConfigModalType === ConfigurationsTabTypes.WEBHOOK) { + return ConfigurationsTabTypes.WEBHOOK } return DeleteComponentsName.SMTPConfigurationTab } if (state.view === ViewType.LOADING) { return ( -
+
) @@ -294,38 +217,80 @@ export const ConfigurationTab = () => { } const payload = deleteConfigPayload() - const renderContent = () => ( -
- {renderSESConfigModal()} - {renderSMTPConfigModal()} - {renderSlackConfigModal()} - {renderWebhookConfigModal()} -
- ) + const renderModal = () => { + if (queryString.get('configId') === null) return null + const configId = parseInt(queryString.get('configId') || '0', 10) + + switch (modal) { + case ConfigurationsTabTypes.SES: + return ( + + ) + case ConfigurationsTabTypes.SMTP: + return ( + + ) + case ConfigurationsTabTypes.SLACK: + return + case ConfigurationsTabTypes.WEBHOOK: + return ( + + ) + default: + return null + } + } + + const reloadDeleteConfig = () => { + setState({ ...state, deleting: false }) + // eslint-disable-next-line @typescript-eslint/no-floating-promises + getAllChannelConfigs() + } + + const renderTableComponent = () => { + switch (modal) { + case ConfigurationsTabTypes.SES: + return + case ConfigurationsTabTypes.SMTP: + return + case ConfigurationsTabTypes.SLACK: + return + case ConfigurationsTabTypes.WEBHOOK: + return + default: + return null + } + } + return ( -
- +
+ - - - - + renderTableComponent()} /> - {renderContent()} + {renderModal()} {state.confirmation && ( )} diff --git a/src/components/notifications/ConfigurationTabsSwitcher.tsx b/src/components/notifications/ConfigurationTabsSwitcher.tsx index 37cc067db5..8ed59484dd 100644 --- a/src/components/notifications/ConfigurationTabsSwitcher.tsx +++ b/src/components/notifications/ConfigurationTabsSwitcher.tsx @@ -1,55 +1,55 @@ -import { NavLink, useRouteMatch } from 'react-router-dom' -import { Button, ButtonVariantType, ComponentSizeType } from '@devtron-labs/devtron-fe-common-lib' +import { useHistory } from 'react-router-dom' +import { Button, ButtonVariantType, ComponentSizeType, useSearchString } from '@devtron-labs/devtron-fe-common-lib' import { ReactComponent as Add } from '@Icons/ic-add.svg' import { getConfigurationTabTextWithIcon } from './notifications.util' -import { ConfigurationTabSwitcherProps } from './types' import { ConfigurationsTabTypes } from './constants' -export const ConfigurationTabSwitcher = ({ activeTab, setState, state }: ConfigurationTabSwitcherProps) => { - const match = useRouteMatch() +export const ConfigurationTabSwitcher = () => { + const history = useHistory() + const { searchParams } = useSearchString() + const queryParams = new URLSearchParams(history.location.search) + const activeTab = queryParams.get('modal') - const handleTabClick = (tab: ConfigurationsTabTypes) => { - setState({ ...state, activeTab: tab }) + const handleTabClick = (tab: ConfigurationsTabTypes) => () => { + const newParams = { + ...searchParams, + modal: tab, + } + history.push({ + search: new URLSearchParams(newParams).toString(), + }) } const handleAddClick = () => { - switch (activeTab) { - case ConfigurationsTabTypes.SES: - setState({ ...state, showSESConfigModal: true, sesConfigId: 0 }) - break - case ConfigurationsTabTypes.SMTP: - setState({ ...state, showSMTPConfigModal: true, smtpConfigId: 0 }) - break - case ConfigurationsTabTypes.SLACK: - setState({ ...state, showSlackConfigModal: true, slackConfigId: 0 }) - break - case ConfigurationsTabTypes.WEBHOOK: - setState({ ...state, showWebhookConfigModal: true, webhookConfigId: 0 }) - break - default: - break + const newParams = { + ...searchParams, + modal: activeTab, + configId: '0', } + history.push({ + search: new URLSearchParams(newParams).toString(), + }) } return (
-
- {getConfigurationTabTextWithIcon().map((tab) => ( - + {getConfigurationTabTextWithIcon().map((tab, index) => ( + ))}
-
- {body} + const renderSESFooter = () => ( +
+
+ ) + + const renderSESContent = () => ( +
+
+ handleInputChange(SESFieldKeys.CONFIG_NAME, e.target.value)} + onBlur={(event) => handleBlur(event, SESFieldKeys.CONFIG_NAME)} + placeholder="Configuration name" + autoFocus + isRequiredField + error={!isValid.configName && REQUIRED_FIELD_MSG} + /> + handleInputChange(SESFieldKeys.ACCESS_KEY, e.target.value)} + onBlur={(event) => handleBlur(event, SESFieldKeys.ACCESS_KEY)} + placeholder="Access Key ID" + isRequiredField + error={!isValid.accessKey && REQUIRED_FIELD_MSG} + /> + handleInputChange(SESFieldKeys.SECRET_KEY, e.target.value)} + onBlur={(event) => handleBlur(event, SESFieldKeys.SECRET_KEY)} + placeholder="Secret Access Key" + isRequiredField + error={!isValid.secretKey && REQUIRED_FIELD_MSG} + /> +
+ handleBlur(event, SESFieldKeys.REGION)} + onChange={(selected) => handleAWSRegionChange(selected)} + options={awsRegionListParsed} + size={ComponentSizeType.large} + /> + + {!isValid.region ? ( + <> + + {REQUIRED_FIELD_MSG}
+ + ) : null} +
- - ) - } + handleBlur(event, SESFieldKeys.FROM_EMAIL)} + placeholder="Email" + onChange={(e) => handleInputChange(SESFieldKeys.FROM_EMAIL, e.target.value)} + isRequiredField + error={!isValid.fromEmail && REQUIRED_FIELD_MSG} + helperText="This email must be verified with SES." + /> + + Set as default configuration to send emails + +
- onSaveClickHandler(event) { - event.preventDefault() - this.saveSESConfig() - } + {renderSESFooter()} +
+ ) - render() { - let body - if (this.state.view === ViewType.LOADING) { - body = ( -
- + const renderLoadingState = () => ( +
+ +
+ ) + return ( + +
+
+

Configure SES

+
- ) - } else { - body = ( - <> -
- - - -
- this.handleBlur(event, 'region')} - onChange={(selected) => this.handleAWSRegionChange(selected)} - options={this.awsRegionListParsed} - size={ComponentSizeType.large} - /> - - {!this.state.isValid.region ? ( - <> - - This is a required field
- - ) : null} -
-
- -
-
- - Set as default configuration to send emails - -
- - -
-
- - ) - } - return this.renderWithBackdrop(body) - } + {form.isLoading ? renderLoadingState() : renderSESContent()} +
+
+ ) } + +export default SESConfigModal diff --git a/src/components/notifications/SESConfigurationTable.tsx b/src/components/notifications/SESConfigurationTable.tsx index 681c033030..b0c872f5ab 100644 --- a/src/components/notifications/SESConfigurationTable.tsx +++ b/src/components/notifications/SESConfigurationTable.tsx @@ -1,96 +1,69 @@ -import { Trash } from '@Components/common' import { DeleteComponentsName } from '@Config/constantMessaging' -import { ViewType } from '@Config/constants' -import { Progressing } from '@devtron-labs/devtron-fe-common-lib' -import Tippy from '@tippyjs/react' -import { ReactComponent as Edit } from '@Icons/ic-edit.svg' +import { useSearchString } from '@devtron-labs/devtron-fe-common-lib' +import { useHistory } from 'react-router-dom' import { ConfigurationTableProps } from './types' import { EmptyConfigurationView } from './EmptyConfigurationView' import { ConfigurationsTabTypes } from './constants' +import { getConfigTabIcons, renderDefaultTag, renderText } from './notifications.util' +import './notifications.scss' +import emptySES from '../../assets/img/ses-empty.png' +import { ConfigTableRowActionButton } from './ConfigTableRowActionButton' + +const SESConfigurationTable = ({ state, deleteClickHandler }: ConfigurationTableProps) => { + const { searchParams } = useSearchString() + const history = useHistory() + + const { sesConfigurationList } = state -export const SESConfigurationTable = ({ setState, state, deleteClickHandler }: ConfigurationTableProps) => { - const { view, sesConfigurationList } = state - if (view === ViewType.LOADING) { - return ( -
- -
- ) - } if (sesConfigurationList.length === 0) { - return + return + } + + const onClickSESConfigEdit = (id: number) => () => { + const newParams = { + ...searchParams, + configId: id.toString(), + modal: ConfigurationsTabTypes.SES, + } + history.push({ + search: new URLSearchParams(newParams).toString(), + }) } + return ( - - - - - - - - - - - {sesConfigurationList.map((sesConfig) => ( - - ))} - - -
NameAccess key IdSender' Email -
-
- {sesConfig.name} - {sesConfig.isDefault ? ( - Default - ) : null} -
-
- {sesConfig.accessKeyId} -
-
{sesConfig.email}
-
- - - - - - -
-
+
+
+

+

Name

+

Access Key Id

+

Sender's Email

+

+

+ + {sesConfigurationList.map((sesConfig) => ( +
+ {getConfigTabIcons(ConfigurationsTabTypes.SES)} +
+ {renderText(sesConfig.name, true, onClickSESConfigEdit(sesConfig.id))} + {renderDefaultTag(sesConfig.isDefault)} +
+ {renderText(sesConfig.accessKeyId)} + {renderText(sesConfig.email)} + { + deleteClickHandler(sesConfig.id, DeleteComponentsName.SesConfigurationTab) + }} + rootClassName="ses-config-table__action" + modal={ConfigurationsTabTypes.SES} + /> +
+ ))} +
) } + +export default SESConfigurationTable diff --git a/src/components/notifications/SMTPConfigModal.tsx b/src/components/notifications/SMTPConfigModal.tsx index 5f89c396a5..1e4cdbd230 100644 --- a/src/components/notifications/SMTPConfigModal.tsx +++ b/src/components/notifications/SMTPConfigModal.tsx @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import React, { Component } from 'react' +import { useState, useEffect, useCallback } from 'react' import { showError, Progressing, @@ -24,298 +23,236 @@ import { CHECKBOX_VALUE, ToastManager, ToastVariantType, + Button, + ButtonStyleType, + ButtonVariantType, + ComponentSizeType, } from '@devtron-labs/devtron-fe-common-lib' +import { useHistory } from 'react-router-dom' import { validateEmail } from '../common' import { getSMTPConfiguration, saveEmailConfiguration } from './notifications.service' import { ReactComponent as Close } from '../../assets/icons/ic-close.svg' import { ViewType } from '../../config/constants' import { ProtectedInput } from '../globalConfigurations/GlobalConfiguration' -import { SMTPConfigModalProps, SMTPConfigModalState } from './types' import { REQUIRED_FIELD_MSG } from '../../config/constantMessaging' +import { ConfigurationsTabTypes } from './constants' +import { SMTPConfigModalProps } from './types' -export class SMTPConfigModal extends Component { - constructor(props) { - super(props) - this.state = { - view: ViewType.LOADING, - form: { - configName: '', - port: null, - host: '', - authUser: '', - authPassword: '', - fromEmail: '', - default: this.props.shouldBeDefault, - isLoading: false, - isError: true, - }, - isValid: { - configName: true, - port: true, - host: true, - authUser: true, - authPassword: true, - fromEmail: true, - }, - } - this.handleCheckbox = this.handleCheckbox.bind(this) - this.handleBlur = this.handleBlur.bind(this) - this.handleInputChange = this.handleInputChange.bind(this) - this.onSaveClickHandler = this.onSaveClickHandler.bind(this) - } +export const SMTPConfigModal = ({ + smtpConfigId, + shouldBeDefault, + closeSMTPConfigModal, + onSaveSuccess, + selectSMTPFromChild, +}: SMTPConfigModalProps) => { + const history = useHistory() - componentDidMount() { - if (this.props.smtpConfigId) { - getSMTPConfiguration(this.props.smtpConfigId) + const [view, setView] = useState(ViewType.LOADING) + const [form, setForm] = useState({ + configName: '', + port: null, + host: '', + authUser: '', + authPassword: '', + fromEmail: '', + default: shouldBeDefault, + isLoading: false, + isError: true, + }) + const [isValid, setIsValid] = useState({ + configName: true, + port: true, + host: true, + authUser: true, + authPassword: true, + fromEmail: true, + }) + + const resetValidation = () => { + setIsValid({ + configName: true, + port: true, + host: true, + authUser: true, + authPassword: true, + fromEmail: true, + }) + } + useEffect(() => { + if (smtpConfigId) { + getSMTPConfiguration(smtpConfigId) .then((response) => { - this.setState((prevState) => ({ - ...prevState, - form: { ...response.result, isLoading: false, isError: true }, - view: ViewType.FORM, - isValid: { - configName: true, - port: true, - host: true, - authUser: true, - authPassword: true, - fromEmail: true, - }, - })) - }) - .catch((error) => { - showError(error) + setForm({ ...response.result, isLoading: false, isError: true }) + setView(ViewType.FORM) + resetValidation() }) + .catch(showError) } else { - this.setState((prevState) => ({ - ...prevState, - form: { ...prevState.form, default: this.props.shouldBeDefault }, - view: ViewType.FORM, - })) + setForm((prevForm) => ({ ...prevForm, default: shouldBeDefault })) + setView(ViewType.FORM) } + }, [smtpConfigId, shouldBeDefault]) + + const handleBlur = (e) => { + const { name, value } = e.target + setIsValid((prevValid) => ({ ...prevValid, [name]: !!value.length })) } - handleBlur(event): void { - const { name, value } = event.target - this.setState((prevState) => ({ - ...prevState, - isValid: { ...prevState.isValid, [name]: !!value.length }, - })) + const handleInputChange = (e) => { + const { name, value } = e.target + setForm((prevForm) => ({ ...prevForm, [name]: value })) + setIsValid((prevValid) => ({ ...prevValid, [name]: !!value.length })) } - handleInputChange(event: React.ChangeEvent): void { - const { name, value } = event.target - this.setState((prevState) => ({ - ...prevState, - form: { ...prevState.form, [name]: value }, - })) + const handleCheckbox = () => { + setForm((prevForm) => ({ ...prevForm, default: !prevForm.default })) } - handleCheckbox(): void { - this.setState((prevState) => ({ - ...prevState, - form: { ...prevState.form, default: !prevState.form.default }, - })) + const validateForm = useCallback(() => { + const allValid = Object.keys(isValid).every((key) => isValid[key]) + return allValid && validateEmail(form.fromEmail) + }, [isValid, form.fromEmail]) + + const closeSMTPConfig = () => { + if (typeof closeSMTPConfigModal === 'function') { + closeSMTPConfigModal() + } else { + const newParams = { + modal: ConfigurationsTabTypes.SMTP, + } + history.push({ + search: new URLSearchParams(newParams).toString(), + }) + } } - saveSMTPConfig(): void { - const keys = Object.keys(this.state.isValid) - let isFormValid = keys.reduce((isFormValid, key) => { - isFormValid = isFormValid && this.state.isValid[key] - return isFormValid - }, true) - isFormValid = isFormValid && validateEmail(this.state.form.fromEmail) - if (!isFormValid) { - this.setState((prevState) => ({ - ...prevState, - form: { ...prevState.form, isLoading: false, isError: true }, - })) + const saveSMTPConfig = () => { + if (!validateForm()) { ToastManager.showToast({ variant: ToastVariantType.error, description: 'Some required fields are missing or Invalid', }) + setForm((prevForm) => ({ ...prevForm, isLoading: false, isError: true })) return } - this.setState((prevState) => ({ - ...prevState, - form: { ...prevState.form, isLoading: true, isError: false }, - })) - saveEmailConfiguration(this.state.form, 'smtp') + setForm((prevForm) => ({ ...prevForm, isLoading: true, isError: false })) + + saveEmailConfiguration(form, ConfigurationsTabTypes.SMTP) .then((response) => { - this.setState((prevState) => ({ - ...prevState, - form: { ...prevState.form, isLoading: false }, - })) + setForm((prevForm) => ({ ...prevForm, isLoading: false })) ToastManager.showToast({ variant: ToastVariantType.success, description: 'Saved Successfully', }) - this.props.onSaveSuccess() - if (this.props.selectSMTPFromChild) { - this.props.selectSMTPFromChild(response?.result[0]) + onSaveSuccess() + closeSMTPConfig() + if (selectSMTPFromChild) { + selectSMTPFromChild(response?.result[0]) } }) .catch((error) => { showError(error) - this.setState((prevState) => ({ - ...prevState, - form: { ...prevState.form, isLoading: false }, - })) + setForm((prevForm) => ({ ...prevForm, isLoading: false })) }) } - renderWithBackdrop(body) { - return ( - -
-
-

Configure SMTP

- -
- {body} + const renderWithBackdrop = (body) => ( + +
+
+

Configure SMTP

+
- - ) - } + {body} +
+
+ ) - onSaveClickHandler(event) { - event.preventDefault() - this.saveSMTPConfig() - } + const renderSMTPFooter = () => ( +
+
+ ) - render() { - let body - if (this.state.view === ViewType.LOADING) { - body = ( -
- -
- ) - } else { - body = ( - <> -
- - - -
- -
-
- -
- -
-
- - Set as default configuration to send emails - -
- - -
+ const renderForm = () => ( +
+
+ {['configName', 'host', 'port', 'authUser'].map((field, index) => ( +
+
- - ) - } - return this.renderWithBackdrop(body) - } + ))} +
+ +
+ + + + Set as default configuration to send emails + +
+ {renderSMTPFooter()} +
+ ) + + return renderWithBackdrop(view === ViewType.LOADING ? : renderForm()) } diff --git a/src/components/notifications/SMTPConfigurationTable.tsx b/src/components/notifications/SMTPConfigurationTable.tsx index 3b6ceaf0c1..d16a05394b 100644 --- a/src/components/notifications/SMTPConfigurationTable.tsx +++ b/src/components/notifications/SMTPConfigurationTable.tsx @@ -1,98 +1,74 @@ -import { Trash } from '@Components/common' import { DeleteComponentsName } from '@Config/constantMessaging' -import { ViewType } from '@Config/constants' -import { Progressing } from '@devtron-labs/devtron-fe-common-lib' -import Tippy from '@tippyjs/react' -import { ReactComponent as Edit } from '@Icons/ic-edit.svg' +import { noop, useSearchString } from '@devtron-labs/devtron-fe-common-lib' +import { useHistory } from 'react-router-dom' import { ConfigurationTableProps } from './types' import { ConfigurationsTabTypes } from './constants' import { EmptyConfigurationView } from './EmptyConfigurationView' +import emptySmtp from '../../assets/img/smtp-empty.png' +import { getConfigTabIcons, renderDefaultTag, renderText } from './notifications.util' +import { ConfigTableRowActionButton } from './ConfigTableRowActionButton' -export const SMTPConfigurationTable = ({ setState, state, deleteClickHandler }: ConfigurationTableProps) => { - const { smtpConfigurationList, view } = state - if (view === ViewType.LOADING) { - return ( -
- -
- ) +export const SMTPConfigurationTable = ({ state, deleteClickHandler }: ConfigurationTableProps) => { + const { smtpConfigurationList } = state + const { searchParams } = useSearchString() + const history = useHistory() + + const onClickEditRow = (configId) => () => { + const newParams = { + ...searchParams, + configId: configId.toString(), + modal: ConfigurationsTabTypes.SMTP, + } + history.push({ + search: new URLSearchParams(newParams).toString(), + }) } + if (smtpConfigurationList.length === 0) { - return + return } + return ( - - - - - - - - - - - +
+
+

+

Name

+

Host

+

Port

+

Sender' Email

+

+

+
+
{smtpConfigurationList.map((smtpConfig) => ( -
+ {renderText(smtpConfig.host, false, noop, `smtp-config-host-${smtpConfig.host}`)} + {renderText(smtpConfig.port)} + {renderText(smtpConfig.email)} + { + deleteClickHandler(smtpConfig.id, DeleteComponentsName.SMTPConfigurationTab) + }} + rootClassName="ses-config-table__action" + modal={ConfigurationsTabTypes.SMTP} + /> + ))} - - -
NameHostPortSender' Email -
+ {getConfigTabIcons(ConfigurationsTabTypes.SMTP)}
- {smtpConfig.name} - {smtpConfig.isDefault ? ( - Default - ) : null} + {renderText(smtpConfig.name, true, onClickEditRow(smtpConfig.id))} + {renderDefaultTag(smtpConfig.isDefault)}
-
- {smtpConfig.host} -
-
{smtpConfig.port}
-
{smtpConfig.email}
-
- - - - - - -
-
+
+
+
) } diff --git a/src/components/notifications/SlackConfigModal.tsx b/src/components/notifications/SlackConfigModal.tsx index e229ddde11..88df45daee 100644 --- a/src/components/notifications/SlackConfigModal.tsx +++ b/src/components/notifications/SlackConfigModal.tsx @@ -1,20 +1,4 @@ -/* - * Copyright (c) 2024. Devtron Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { Component } from 'react' +import React, { useState, useEffect } from 'react' import { showError, Progressing, @@ -23,79 +7,62 @@ import { CustomInput, ToastManager, ToastVariantType, + SelectPicker, + Button, + ButtonStyleType, + ButtonVariantType, + ComponentSizeType, } from '@devtron-labs/devtron-fe-common-lib' import Tippy from '@tippyjs/react' +import { useHistory } from 'react-router-dom' import { ReactComponent as Close } from '../../assets/icons/ic-close.svg' -import { Select } from '../common' import { ViewType } from '../../config/constants' import { saveSlackConfiguration, updateSlackConfiguration, getSlackConfiguration } from './notifications.service' import { ReactComponent as ICHelpOutline } from '../../assets/icons/ic-help-outline.svg' import { ReactComponent as Error } from '../../assets/icons/ic-warning.svg' import { REQUIRED_FIELD_MSG } from '../../config/constantMessaging' +import { SlackConfigModalProps } from './types' +import { ConfigurationsTabTypes } from './constants' -export interface SlackConfigModalProps { - slackConfigId: number - onSaveSuccess: () => void - closeSlackConfigModal: (event) => void -} - -export interface SlackConfigModalState { - view: string - projectList: Array<{ id: number; name: string; active: boolean }> - form: { - projectId: number - configName: string - webhookUrl: string - isLoading: boolean - isError: boolean - } - isValid: { - projectId: boolean - configName: boolean - webhookUrl: boolean - } -} +export const SlackConfigModal: React.FC = ({ + slackConfigId, + onSaveSuccess, + closeSlackConfigModal, +}: SlackConfigModalProps) => { + const history = useHistory() -export class SlackConfigModal extends Component { - constructor(props) { - super(props) - this.state = { - view: ViewType.LOADING, - projectList: [], - form: { - projectId: 0, - configName: '', - webhookUrl: '', - isLoading: false, - isError: false, - }, - isValid: { - projectId: true, - configName: true, - webhookUrl: true, - }, - } - this.handleSlackChannelChange = this.handleSlackChannelChange.bind(this) - this.handleWebhookUrlChange = this.handleWebhookUrlChange.bind(this) - this.handleProjectChange = this.handleProjectChange.bind(this) - this.isValid = this.isValid.bind(this) - this.onSaveClickHandler = this.onSaveClickHandler.bind(this) - } + const [view, setView] = useState(ViewType.LOADING) + const [projectList, setProjectList] = useState>([]) + const [selectedProject, setSelectedProject] = useState<{ label: string; value: number }>({ label: '', value: 0 }) + const [form, setForm] = useState({ + projectId: 0, + configName: '', + webhookUrl: '', + isLoading: false, + isError: false, + }) + const [isValid, setIsValid] = useState({ + projectId: true, + configName: true, + webhookUrl: true, + }) - componentDidMount() { - if (this.props.slackConfigId) { - Promise.all([getSlackConfiguration(this.props.slackConfigId), getProjectListMin()]) + useEffect(() => { + if (slackConfigId) { + Promise.all([getSlackConfiguration(slackConfigId), getProjectListMin()]) .then(([slackConfigRes, projectListRes]) => { - const state = { ...this.state } - state.view = ViewType.FORM - state.projectList = projectListRes.result || [] - state.form = { ...slackConfigRes.result } - state.isValid = { + setView(ViewType.FORM) + setProjectList(projectListRes.result || []) + setForm({ ...slackConfigRes.result }) + setIsValid({ projectId: true, configName: true, webhookUrl: true, - } - this.setState(state) + }) + setSelectedProject({ + label: projectListRes.result.find((p) => p.id === slackConfigRes.result.projectId).name, + value: slackConfigRes.result.projectId, + }) }) .catch((error) => { showError(error) @@ -103,251 +70,251 @@ export class SlackConfigModal extends Component { - this.setState({ - projectList: response.result || [], - view: ViewType.FORM, - }) + setProjectList(response.result || []) + setView(ViewType.FORM) }) .catch((error) => { showError(error) }) } - } + }, [slackConfigId]) - handleSlackChannelChange(event: React.ChangeEvent): void { - const { form } = { ...this.state } - form.configName = event.target.value - this.setState({ form }) + const checkIsValid = (event, key: 'configName' | 'webhookUrl' | 'projectId') => { + setIsValid((prevValid) => ({ + ...prevValid, + [key]: !!event.target.value, + })) } - isValid(event, key: 'configName' | 'webhookUrl' | 'projectId'): void { - const { form, isValid } = { ...this.state } - if (key === 'projectId') { - isValid[key] = event.target.value - } else { - isValid[key] = event.target.value.length !== 0 - } - this.setState({ form, isValid }) + const handleSlackChannelChange = (event: React.ChangeEvent) => { + setForm((prevForm) => ({ + ...prevForm, + configName: event.target.value, + })) + checkIsValid(event, 'configName') } - handleWebhookUrlChange(event: React.ChangeEvent): void { - const { form } = { ...this.state } - form.webhookUrl = event.target.value - this.setState({ form }) + const handleWebhookUrlChange = (event: React.ChangeEvent) => { + setForm((prevForm) => ({ + ...prevForm, + webhookUrl: event.target.value, + })) + checkIsValid(event, 'webhookUrl') } - handleProjectChange(event: React.ChangeEvent): void { - const { form, isValid } = { ...this.state } - form.projectId = Number(event.target.value) - isValid.projectId = !!event.target.value - this.setState({ form, isValid }) + const handleProjectChange = (_selectedProject) => { + const projectId = Number(selectedProject.value) + setSelectedProject(_selectedProject) + setForm((prevForm) => ({ + ...prevForm, + projectId, + })) + setIsValid((prevValid) => ({ + ...prevValid, + projectId: !!_selectedProject.value, + })) } - saveSlackConfig(): void { - const state = { ...this.state } - state.form.isLoading = true - state.isValid.projectId = !!state.form.projectId - this.setState(state) - const keys = Object.keys(this.state.isValid) - const isFormValid = keys.reduce((isFormValid, key) => { - isFormValid = isFormValid && this.state.isValid[key] - return isFormValid - }, true) + const isFormValid = () => Object.keys(isValid).every((key) => isValid[key]) - if (!isFormValid) { - state.form.isLoading = false - state.form.isError = true - this.setState(state) + const closeSlackConfig = () => { + if (typeof closeSlackConfigModal === 'function') { + closeSlackConfigModal() + } else { + const newParams = { + modal: ConfigurationsTabTypes.SLACK, + } + history.push({ + search: new URLSearchParams(newParams).toString(), + }) + } + } + + const saveSlackConfig = () => { + if (!isFormValid()) { + setForm((prevForm) => ({ ...prevForm, isLoading: false, isError: true })) return } - const requestBody = this.state.form - if (this.props.slackConfigId) { - requestBody['id'] = this.props.slackConfigId + + let requestBody + if (slackConfigId) { + requestBody = { + ...form, + id: slackConfigId, + } } - const promise = this.props.slackConfigId - ? updateSlackConfiguration(requestBody) - : saveSlackConfiguration(requestBody) + + setForm((prevForm) => ({ ...prevForm, isLoading: true })) + + const promise = slackConfigId ? updateSlackConfiguration(requestBody) : saveSlackConfiguration(requestBody) + promise - .then((response) => { - const state = { ...this.state } - state.form.isLoading = false - state.form.isError = false - this.setState(state) + .then(() => { + setForm((prevForm) => ({ ...prevForm, isLoading: false, isError: false })) ToastManager.showToast({ variant: ToastVariantType.success, description: 'Saved Successfully', }) - this.props.onSaveSuccess() + onSaveSuccess() + closeSlackConfig() }) .catch((error) => { showError(error) }) } - renderWithBackdrop(body) { - return ( - -
- + ) - onSaveClickHandler(event) { - event.preventDefault() - this.saveSlackConfig() + const onSaveSlack = () => { + onSaveSuccess() + closeSlackConfig() } - renderWebhookUrlLabel = () => { - return ( -
-
Webhook URL
-
- - Learn how to setup slack webhooks - - } - > -
- -
-
+ const renderWithBackdrop = (body: JSX.Element) => ( + +
+
+

Configure Slack

+
+ {body}
- ) - } +
+ ) - render() { - const project = this.state.projectList.find((p) => p.id === this.state.form.projectId) - let body - if (this.state.view === ViewType.LOADING) { - body = ( -
- + const renderSlackFooter = () => ( +
+
+ ) + + const renderProjectLabel = () => ( +
+ Project + +
+
- ) - } else { - body = ( - <> -
- - -
- - - - {!this.state.isValid.projectId ? ( - <> - - This is as required field.
- - ) : null} -
-
-
-
-
- - -
-
- - ) - } +
+
+ ) - return this.renderWithBackdrop(body) + let body + if (view === ViewType.LOADING) { + body = ( +
+ +
+ ) + } else { + body = ( +
+
+ checkIsValid(event, 'configName')} + placeholder="channel name" + autoFocus + isRequiredField + error={!isValid.configName && REQUIRED_FIELD_MSG} + /> + checkIsValid(event, 'webhookUrl')} + isRequiredField + error={!isValid.webhookUrl && REQUIRED_FIELD_MSG} + /> +
+ ({ label: p.name, value: p.id }))} + /> + + {!isValid.projectId && ( + <> + + {REQUIRED_FIELD_MSG} + + )} + +
+
+ {renderSlackFooter()} +
+ ) } + + return renderWithBackdrop(body) } diff --git a/src/components/notifications/SlackConfigurationTable.tsx b/src/components/notifications/SlackConfigurationTable.tsx index 67cc74a8c3..ac3869aa1b 100644 --- a/src/components/notifications/SlackConfigurationTable.tsx +++ b/src/components/notifications/SlackConfigurationTable.tsx @@ -1,84 +1,61 @@ -import { Trash } from '@Components/common' import { DeleteComponentsName } from '@Config/constantMessaging' -import { ViewType } from '@Config/constants' -import { Progressing } from '@devtron-labs/devtron-fe-common-lib' -import Tippy from '@tippyjs/react' -import { ReactComponent as Edit } from '@Icons/ic-edit.svg' +import { useSearchString } from '@devtron-labs/devtron-fe-common-lib' +import { useHistory } from 'react-router-dom' import { ConfigurationTableProps } from './types' import { EmptyConfigurationView } from './EmptyConfigurationView' import { ConfigurationsTabTypes } from './constants' +import { getConfigTabIcons, renderText } from './notifications.util' +import './notifications.scss' +import emptySlack from '../../assets/img/slack-empty.png' +import { ConfigTableRowActionButton } from './ConfigTableRowActionButton' + +const SlackConfigurationTable = ({ state, deleteClickHandler }: ConfigurationTableProps) => { + const { searchParams } = useSearchString() + const history = useHistory() + const { slackConfigurationList } = state -export const SlackConfigurationTable = ({ setState, state, deleteClickHandler }: ConfigurationTableProps) => { - const { slackConfigurationList, view } = state - if (view === ViewType.LOADING) { - return ( -
- -
- ) - } if (slackConfigurationList.length === 0) { - return + return + } + + const onClickSlackConfigEdit = (id: number) => () => { + const newParams = { + ...searchParams, + configId: id.toString(), + modal: ConfigurationsTabTypes.SLACK, + } + history.push({ + search: new URLSearchParams(newParams).toString(), + }) } + return ( - - - - - - - - - - {slackConfigurationList.map((slackConfig) => ( - - ))} - - -
NameWebhook URL -
-
- {slackConfig.slackChannel} -
-
- {slackConfig.webhookUrl} -
-
- - - - - - -
-
+
+
+
+

Name

+

Webhook URL

+

+

+
+ {slackConfigurationList.map((slackConfig) => ( +
+ {getConfigTabIcons(ConfigurationsTabTypes.SLACK)} + {renderText(slackConfig.slackChannel)} + {renderText(slackConfig.webhookUrl)} + { + deleteClickHandler(slackConfig.id, DeleteComponentsName.SlackConfigurationTab) + }} + rootClassName="slack-config-table__action" + modal={ConfigurationsTabTypes.SLACK} + /> +
+ ))} +
+
) } + +export default SlackConfigurationTable diff --git a/src/components/notifications/WebhookConfigurationTable.tsx b/src/components/notifications/WebhookConfigurationTable.tsx index 7a7ad7a595..1881e622eb 100644 --- a/src/components/notifications/WebhookConfigurationTable.tsx +++ b/src/components/notifications/WebhookConfigurationTable.tsx @@ -1,15 +1,18 @@ -import { Trash } from '@Components/common' import { DeleteComponentsName } from '@Config/constantMessaging' import { ViewType } from '@Config/constants' -import { Progressing } from '@devtron-labs/devtron-fe-common-lib' -import Tippy from '@tippyjs/react' -import { ReactComponent as Edit } from '@Icons/ic-edit.svg' +import { noop, Progressing, useSearchString } from '@devtron-labs/devtron-fe-common-lib' +import { useHistory } from 'react-router-dom' import { ConfigurationTableProps } from './types' import { EmptyConfigurationView } from './EmptyConfigurationView' import { ConfigurationsTabTypes } from './constants' +import { ConfigTableRowActionButton } from './ConfigTableRowActionButton' +import { getConfigTabIcons, renderText } from './notifications.util' -export const WebhookConfigurationTable = ({ setState, state, deleteClickHandler }: ConfigurationTableProps) => { +export const WebhookConfigurationTable = ({ state, deleteClickHandler }: ConfigurationTableProps) => { const { view, webhookConfigurationList } = state + const { searchParams } = useSearchString() + const history = useHistory() + if (view === ViewType.LOADING) { return (
@@ -21,74 +24,49 @@ export const WebhookConfigurationTable = ({ setState, state, deleteClickHandler return } - const editWebhookHandler = (e) => { - setState({ ...state, showWebhookConfigModal: true, webhookConfigId: e.currentTarget.dataset.webhookid }) + const onClickWebhookConfigEdit = (id: number) => () => { + const newParams = { + ...searchParams, + configId: id.toString(), + modal: ConfigurationsTabTypes.WEBHOOK, + } + history.push({ + search: new URLSearchParams(newParams).toString(), + }) } return ( - - - - - - - - - - {webhookConfigurationList.map((webhookConfig) => ( - - ))} - - -
NameWebhook URL -
-
- {webhookConfig.name} -
-
- {webhookConfig.webhookUrl} -
-
- - - - - - -
-
+
+
+

+

Name

+

Webhook URL

+

+

+ {webhookConfigurationList.map((webhookConfig) => ( +
+ {getConfigTabIcons(ConfigurationsTabTypes.WEBHOOK)} + {renderText( + webhookConfig.webhookUrl, + true, + onClickWebhookConfigEdit(webhookConfig.id), + `webhook-config-name-${webhookConfig.name}`, + )} + {renderText(webhookConfig.name, false, noop, `webhook-url-${webhookConfig.webhookUrl}`)} + { + deleteClickHandler(webhookConfig.id, DeleteComponentsName.WebhookConfigurationTab) + }} + rootClassName="slack-config-table__action" + modal={ConfigurationsTabTypes.WEBHOOK} + /> +
+ ))} +
) } diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 7179ebc110..fee9b954f1 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -181,45 +181,29 @@ box-shadow: 0 1px 0 0 var(--N200); } -// ses config table - -.ses-config-container { - .ses-config-grid { - display: grid; - grid-template-columns: 24px 250px 1fr 1fr 52px; +.configuration-tab__container { + .ses-config-container { + .ses-config-grid { + display: grid; + grid-template-columns: 24px 250px 1fr 1fr 52px; + } } -} - -.ses-config-table__name { - flex-basis: 25%; - color: var(--N900); - font-weight: inherit; -} - -.ses-config-table__access-key { - flex-basis: 20%; - font-weight: inherit; -} -.ses-config-table__email { - flex-basis: 40%; - font-weight: inherit; -} - -.smtp-config-table__host { - flex-basis: 33%; - font-weight: inherit; -} + .slack-config-container { + .slack-config-grid { + display: grid; + grid-template-columns: 24px 250px 1fr 52px; + } + } -.smtp-config-table__port { - flex-basis: 5%; - font-weight: inherit; + .smtp-config-container { + .smtp-config-grid { + display: grid; + grid-template-columns: 24px 250px 1fr 60px 1fr 52px; + } + } } -.smtp-config-table__email { - flex-basis: 30%; - font-weight: inherit; -} .slack-config-table__name { flex-basis: 25%; diff --git a/src/components/notifications/notifications.util.tsx b/src/components/notifications/notifications.util.tsx index 45e7774f3a..1e62b7d3c5 100644 --- a/src/components/notifications/notifications.util.tsx +++ b/src/components/notifications/notifications.util.tsx @@ -15,7 +15,6 @@ */ import { components } from 'react-select' -import { validateEmail } from '../common' import { ReactComponent as ArrowDown } from '@Icons/ic-chevron-down.svg' import { ReactComponent as Email } from '@Icons/ic-mail.svg' import { ReactComponent as RedWarning } from '@Icons/ic-error-medium.svg' @@ -26,7 +25,9 @@ import { ReactComponent as Slack } from '@Icons/slack-logo.svg' import { ReactComponent as SES } from '@Icons/ic-aws-ses.svg' import { ReactComponent as Webhook } from '@Icons/ic-CIWebhook.svg' import { ReactComponent as SMTP } from '@Icons/ic-smtp.svg' +import { Tooltip } from '@devtron-labs/devtron-fe-common-lib' import { ConfigurationsTabTypes, ConfigurationTabText } from './constants' +import { validateEmail } from '../common' export const multiSelectStyles = { control: (base, state) => ({ @@ -39,60 +40,48 @@ export const multiSelectStyles = { ...base, top: `38px`, }), - option: (base, state) => { - return { - ...base, - color: 'var(--N900)', - display: `flex`, - alignItems: `center`, - fontSize: '12px', - padding: '8px 24px', - } - }, - multiValue: (base, state) => { - return { - ...base, - border: - state.data.data.dest !== 'slack' && - state.data.data.dest !== 'webhook' && - !validateEmail(state.data.label) - ? `1px solid var(--R500)` - : `1px solid var(--N200)`, - borderRadius: `4px`, - background: - state.data.data.dest !== 'slack' && - state.data.data.dest !== 'webhook' && - !validateEmail(state.data.label) - ? 'var(--R100)' - : 'var(--N000)', - padding: `2px`, - textTransform: `lowercase`, - fontSize: `12px`, - lineHeight: `1.5`, - letterSpacing: `normal`, - color: `var(--N900)`, - userSelect: `none`, - display: `inline-flex`, - } - }, - multiValueLabel: (base, state) => { - return { - ...base, - display: `flex`, - alignItems: `center`, - fontSize: '12px', - padding: '0px', - } - }, + option: (base, state) => ({ + ...base, + color: 'var(--N900)', + display: `flex`, + alignItems: `center`, + fontSize: '12px', + padding: '8px 24px', + }), + multiValue: (base, state) => ({ + ...base, + border: + state.data.data.dest !== 'slack' && state.data.data.dest !== 'webhook' && !validateEmail(state.data.label) + ? `1px solid var(--R500)` + : `1px solid var(--N200)`, + borderRadius: `4px`, + background: + state.data.data.dest !== 'slack' && state.data.data.dest !== 'webhook' && !validateEmail(state.data.label) + ? 'var(--R100)' + : 'var(--N000)', + padding: `2px`, + textTransform: `lowercase`, + fontSize: `12px`, + lineHeight: `1.5`, + letterSpacing: `normal`, + color: `var(--N900)`, + userSelect: `none`, + display: `inline-flex`, + }), + multiValueLabel: (base, state) => ({ + ...base, + display: `flex`, + alignItems: `center`, + fontSize: '12px', + padding: '0px', + }), } -export const DropdownIndicator = (props) => { - return ( - - - - ) -} +export const DropdownIndicator = (props) => ( + + + +) export const MultiValueLabel = (props) => { const item = props.data @@ -164,16 +153,16 @@ export const renderPipelineTypeIcon = (row) => { return } -export const getConfigTabIcons = (tab: ConfigurationsTabTypes, size: number = 20 ) => { +export const getConfigTabIcons = (tab: ConfigurationsTabTypes, size: number = 20) => { switch (tab) { case ConfigurationsTabTypes.SES: - return + return case ConfigurationsTabTypes.SMTP: - return + return case ConfigurationsTabTypes.SLACK: - return + return case ConfigurationsTabTypes.WEBHOOK: - return + return default: return SES } @@ -211,4 +200,23 @@ export const getSESDefaultConfiguration = (shouldBeDefault: boolean) => ({ default: shouldBeDefault, isLoading: false, isError: true, -}) \ No newline at end of file +}) + +export const renderText = (text: string, isLink: boolean = false, linkTo?: () => void, dataTestId?: string,) => ( + + {isLink ? ( + + {text || '-'} + + ) : ( +

{text || '-'}

+ )} +
+) + +export const renderDefaultTag = (isDefault: boolean) => { + if (isDefault) { + return Default + } + return null +} diff --git a/src/components/notifications/types.tsx b/src/components/notifications/types.tsx index b5f15b58d4..5e8f008137 100644 --- a/src/components/notifications/types.tsx +++ b/src/components/notifications/types.tsx @@ -43,14 +43,6 @@ export enum EMAIL_AGENT { SMTP = 'SMTP', } -export interface SMTPConfigModalProps { - smtpConfigId: number - shouldBeDefault: boolean - selectSMTPFromChild?: (smtpConfigId: number) => void - onSaveSuccess: () => void - closeSMTPConfigModal: (event) => void -} - export interface SMTPConfigModalState { view: string form: { @@ -76,11 +68,8 @@ export interface SMTPConfigModalState { export interface ConfigurationTabState { view: string - showSlackConfigModal: boolean - showSESConfigModal: boolean - showSMTPConfigModal: boolean - slackConfigId: number sesConfigId: number + slackConfigId: number smtpConfigId: number webhookConfigId: number sesConfigurationList: Array<{ id: number; name: string; accessKeyId: string; email: string; isDefault: boolean }> @@ -101,13 +90,11 @@ export interface ConfigurationTabState { smtpConfig: any slackConfig: any webhookConfig: any - showDeleteConfigModalType: string - showWebhookConfigModal: boolean + showDeleteConfigModalType: ConfigurationsTabTypes activeTab?: ConfigurationsTabTypes } export interface ConfigurationTableProps { - setState: React.Dispatch> state: ConfigurationTabState deleteClickHandler: (id: number, name: string) => void } @@ -155,18 +142,38 @@ export interface WebhookAttributesResponseType extends ResponseType { export interface EmptyConfigurationViewProps { configTabType: ConfigurationsTabTypes + image?: any } export interface ConfigurationTabSwitcherProps { activeTab: ConfigurationsTabTypes - state: ConfigurationTabState - setState: React.Dispatch> } export interface SESConfigModalProps { - sesConfigId: number shouldBeDefault: boolean selectSESFromChild?: (sesConfigId: number) => void onSaveSuccess: () => void - closeSESConfigModal: (event) => void + closeSESConfigModal?: () => void + sesConfigId: number +} + +export interface SlackConfigModalProps { + slackConfigId: number + onSaveSuccess: () => void + closeSlackConfigModal?: () => void +} + +export interface SMTPConfigModalProps { + smtpConfigId: number + shouldBeDefault: boolean + selectSMTPFromChild?: (smtpConfigId: number) => void + onSaveSuccess: () => void + closeSMTPConfigModal?: () => void +} + +export interface ConfigTableRowActionButtonProps { + onClickEditRow: () => void + onClickDeleteRow: () => void + rootClassName: string + modal: ConfigurationsTabTypes } diff --git a/src/css/base.scss b/src/css/base.scss index 1f6c0e6327..585ba12bf3 100644 --- a/src/css/base.scss +++ b/src/css/base.scss @@ -957,21 +957,6 @@ button.anchor { background-color: var(--N000); } -.dc__ses_config-table__tag { - border-radius: 2px; - padding: 2px 8px; - font-size: 11px; - font-weight: 600; - font-stretch: normal; - line-height: 1.82; - text-transform: uppercase; - margin: 0 8px; - color: var(--B500); - border: solid 1px #b5d3f2; - background-blend-mode: multiply; - background-image: linear-gradient(to bottom, var(--B50), var(--B50)); -} - .dc__saved-filter__clear-btn--dark { height: 36px; background-color: var(--white); @@ -3458,6 +3443,10 @@ textarea, // width fix content .dc__w-fit-content { width: fit-content; + + &--imp { + width: fit-content !important; + } } .dc__width-inherit { @@ -3467,8 +3456,16 @@ textarea, // Height in pixels .dc__height-inherit { height: inherit; -} + } + +.dc__height-auto { + height: auto; + &--imp { + height: auto !important; + } + +} .dc__min-width-fit-content { min-width: fit-content !important; } From 8c78b5e5ccf31c592edd102b479eb33ff0c4665b Mon Sep 17 00:00:00 2001 From: shivani170 Date: Fri, 3 Jan 2025 09:39:35 +0530 Subject: [PATCH 03/31] chore: configuration code refactoring --- .../notifications/SMTPConfigurationTable.tsx | 8 +-- .../WebhookConfigurationTable.tsx | 20 ++---- .../notifications/notifications.scss | 65 +++++++++---------- 3 files changed, 39 insertions(+), 54 deletions(-) diff --git a/src/components/notifications/SMTPConfigurationTable.tsx b/src/components/notifications/SMTPConfigurationTable.tsx index d16a05394b..6da34b3f05 100644 --- a/src/components/notifications/SMTPConfigurationTable.tsx +++ b/src/components/notifications/SMTPConfigurationTable.tsx @@ -32,11 +32,11 @@ export const SMTPConfigurationTable = ({ state, deleteClickHandler }: Configurat

-

Name

+

Name

Host

Port

Sender' Email

-

+

@@ -49,7 +49,7 @@ export const SMTPConfigurationTable = ({ state, deleteClickHandler }: Configurat {getConfigTabIcons(ConfigurationsTabTypes.SMTP)}
{renderText(smtpConfig.name, true, onClickEditRow(smtpConfig.id))} {renderDefaultTag(smtpConfig.isDefault)} @@ -62,7 +62,7 @@ export const SMTPConfigurationTable = ({ state, deleteClickHandler }: Configurat onClickDeleteRow={() => { deleteClickHandler(smtpConfig.id, DeleteComponentsName.SMTPConfigurationTab) }} - rootClassName="ses-config-table__action" + rootClassName="smtp-config-table__action" modal={ConfigurationsTabTypes.SMTP} />
diff --git a/src/components/notifications/WebhookConfigurationTable.tsx b/src/components/notifications/WebhookConfigurationTable.tsx index 1881e622eb..621d5bff93 100644 --- a/src/components/notifications/WebhookConfigurationTable.tsx +++ b/src/components/notifications/WebhookConfigurationTable.tsx @@ -1,6 +1,5 @@ import { DeleteComponentsName } from '@Config/constantMessaging' -import { ViewType } from '@Config/constants' -import { noop, Progressing, useSearchString } from '@devtron-labs/devtron-fe-common-lib' +import { noop, useSearchString } from '@devtron-labs/devtron-fe-common-lib' import { useHistory } from 'react-router-dom' import { ConfigurationTableProps } from './types' import { EmptyConfigurationView } from './EmptyConfigurationView' @@ -9,17 +8,10 @@ import { ConfigTableRowActionButton } from './ConfigTableRowActionButton' import { getConfigTabIcons, renderText } from './notifications.util' export const WebhookConfigurationTable = ({ state, deleteClickHandler }: ConfigurationTableProps) => { - const { view, webhookConfigurationList } = state + const { webhookConfigurationList } = state const { searchParams } = useSearchString() const history = useHistory() - if (view === ViewType.LOADING) { - return ( -
- -
- ) - } if (webhookConfigurationList.length === 0) { return } @@ -39,9 +31,9 @@ export const WebhookConfigurationTable = ({ state, deleteClickHandler }: Configu

-

Name

-

Webhook URL

-

+

Name

+

Webhook URL

+

{webhookConfigurationList.map((webhookConfig) => (
{ deleteClickHandler(webhookConfig.id, DeleteComponentsName.WebhookConfigurationTab) }} - rootClassName="slack-config-table__action" + rootClassName="webhook-config-table__action" modal={ConfigurationsTabTypes.WEBHOOK} />
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index fee9b954f1..2c97c4a437 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -183,40 +183,39 @@ .configuration-tab__container { .ses-config-container { - .ses-config-grid { - display: grid; - grid-template-columns: 24px 250px 1fr 1fr 52px; + .ses-config-grid { + display: grid; + grid-template-columns: 24px 250px 1fr 1fr 52px; + } } - } - - .slack-config-container { - .slack-config-grid { - display: grid; - grid-template-columns: 24px 250px 1fr 52px; + + .slack-config-container { + .slack-config-grid { + display: grid; + grid-template-columns: 24px 250px 1fr 52px; + } } - } - - .smtp-config-container { - .smtp-config-grid { - display: grid; - grid-template-columns: 24px 250px 1fr 60px 1fr 52px; + + .smtp-config-container { + .smtp-config-grid { + display: grid; + grid-template-columns: 24px 250px 1fr 60px 1fr 52px; + } + } + + .webhook-config-container { + .webhook-config-grid { + display: grid; + grid-template-columns: 24px 250px 1fr 52px; + } } } -} -.slack-config-table__name { - flex-basis: 25%; - color: var(--N900); - font-weight: inherit; -} - -.slack-config-table__webhook { - flex-basis: 60%; -} - .slack-config-table__action, -.ses-config-table__action { +.ses-config-table__action, +.webhook-config-table__action, +.smtp-config-table__action{ display: none; margin: auto; margin-right: 0; @@ -228,7 +227,9 @@ } .configuration-tab__table-row:hover .slack-config-table__action, -.configuration-tab__table-row:hover .ses-config-table__action { +.configuration-tab__table-row:hover .ses-config-table__action, +.configuration-tab__table-row:hover .webhook-config-table__action, +.configuration-tab__table-row:hover .smtp-config-table__action { display: flex; background-color: var(--window-bg); } @@ -398,11 +399,3 @@ } } } - -.empty-state-height { - height: 'calc(100% - 70px)'; -} - -.progressing-loader-height { - height: 'calc(100% - 68px)'; -} From b4c61ca9c3b25acf768076349223e57f9ae79bbd Mon Sep 17 00:00:00 2001 From: shivani170 Date: Fri, 3 Jan 2025 13:51:47 +0530 Subject: [PATCH 04/31] chore: webhook code refactoring --- .eslintignore | 1 - .../ConfigurationDrawerModal.tsx | 83 +++ .../notifications/ConfigurationTab.tsx | 14 +- .../notifications/WebhookConfigModal.tsx | 565 +++++++----------- .../notifications/notifications.scss | 2 +- src/components/notifications/types.tsx | 24 +- 6 files changed, 305 insertions(+), 384 deletions(-) create mode 100644 src/components/notifications/ConfigurationDrawerModal.tsx diff --git a/.eslintignore b/.eslintignore index cc645d3ba9..1b8324e1e8 100755 --- a/.eslintignore +++ b/.eslintignore @@ -318,7 +318,6 @@ src/components/notifications/CreateHeaderDetails.tsx src/components/notifications/ModifyRecipientsModal.tsx src/components/notifications/NotificationTab.tsx src/components/notifications/Notifications.tsx -src/components/notifications/WebhookConfigModal.tsx src/components/notifications/notifications.service.ts src/components/notifications/notifications.util.tsx src/components/onboardingGuide/GuideCommonHeader.tsx diff --git a/src/components/notifications/ConfigurationDrawerModal.tsx b/src/components/notifications/ConfigurationDrawerModal.tsx new file mode 100644 index 0000000000..739af7ee45 --- /dev/null +++ b/src/components/notifications/ConfigurationDrawerModal.tsx @@ -0,0 +1,83 @@ +import { + Button, + ButtonStyleType, + ButtonVariantType, + ComponentSizeType, + Drawer, + Progressing, +} from '@devtron-labs/devtron-fe-common-lib' +import { ReactComponent as Close } from '@Icons/ic-close.svg' +import { ConfigurationsTabTypes } from './constants' + +export interface ConfigurationTabDrawerModalProps { + renderContent: () => JSX.Element + closeModal: () => void + modal: ConfigurationsTabTypes + isLoading: boolean + saveConfigModal: () => void +} + +export const ConfigurationTabDrawerModal = ({ + renderContent, + closeModal, + modal, + isLoading, + saveConfigModal, +}: ConfigurationTabDrawerModalProps) => { + const renderLoadingState = () => ( +
+ +
+ ) + + const renderFooter = () => ( +
+
+ ) + + const renderModalContent = () => ( +
+ {renderContent()} + {renderFooter()} +
+ ) + return ( + +
+
+

Configure {modal}

+
+ {isLoading ? renderLoadingState() : renderModalContent()} +
+
+ ) +} diff --git a/src/components/notifications/ConfigurationTab.tsx b/src/components/notifications/ConfigurationTab.tsx index 9769efb03b..bdb76195e8 100644 --- a/src/components/notifications/ConfigurationTab.tsx +++ b/src/components/notifications/ConfigurationTab.tsx @@ -74,10 +74,6 @@ export const ConfigurationTab = () => { activeTab: ConfigurationsTabTypes.SES, }) - const closeModal = () => { - history.push(path) - } - const getAllChannelConfigs = async () => { try { const { result } = await getConfigs() @@ -225,7 +221,7 @@ export const ConfigurationTab = () => { case ConfigurationsTabTypes.SES: return ( @@ -241,13 +237,7 @@ export const ConfigurationTab = () => { case ConfigurationsTabTypes.SLACK: return case ConfigurationsTabTypes.WEBHOOK: - return ( - - ) + return default: return null } diff --git a/src/components/notifications/WebhookConfigModal.tsx b/src/components/notifications/WebhookConfigModal.tsx index e315c06b14..c07f77e4d7 100644 --- a/src/components/notifications/WebhookConfigModal.tsx +++ b/src/components/notifications/WebhookConfigModal.tsx @@ -13,419 +13,258 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import React, { Component } from 'react' +import { useState, useEffect } from 'react' import { showError, - Progressing, getTeamListMin as getProjectListMin, - Drawer, CustomInput, ClipboardButton, CodeEditor, ToastVariantType, ToastManager, } from '@devtron-labs/devtron-fe-common-lib' -import { ReactComponent as Close } from '../../assets/icons/ic-close.svg' -import { ViewType } from '../../config/constants' -import { getWebhookAttributes, getWebhookConfiguration, saveUpdateWebhookConfiguration } from './notifications.service' -import { ReactComponent as Error } from '../../assets/icons/ic-warning.svg' +import { useHistory } from 'react-router-dom' import { ReactComponent as Add } from '../../assets/icons/ic-add.svg' import { ReactComponent as Help } from '../../assets/icons/ic-help.svg' -import { WebhhookConfigModalState, WebhookConfigModalProps } from './types' import CreateHeaderDetails from './CreateHeaderDetails' import { REQUIRED_FIELD_MSG } from '../../config/constantMessaging' +import { getWebhookAttributes, getWebhookConfiguration, saveUpdateWebhookConfiguration } from './notifications.service' +import { ConfigurationsTabTypes } from './constants' +import { ConfigurationTabDrawerModal } from './ConfigurationDrawerModal' +import { WebhookConfigModalProps } from './types' -export class WebhookConfigModal extends Component { - constructor(props) { - super(props) - this.state = { - view: ViewType.LOADING, - form: { - configName: '', - webhookUrl: '', - isLoading: false, - isError: false, - payload: '', - header: [{ key: '', value: '' }], - }, - isValid: { - configName: true, - webhookUrl: true, - payload: true, - }, - webhookAttribute: {}, - copyAttribute: false, - } - this.handleWebhookConfigNameChange = this.handleWebhookConfigNameChange.bind(this) - this.handleWebhookUrlChange = this.handleWebhookUrlChange.bind(this) - this.handleWebhookPaylodChange = this.handleWebhookPaylodChange.bind(this) - this.addNewHeader = this.addNewHeader.bind(this) - this.renderHeadersList = this.renderHeadersList.bind(this) - this.setHeaderData = this.setHeaderData.bind(this) - this.removeHeader = this.removeHeader.bind(this) - this.renderHeadersList = this.renderHeadersList.bind(this) - this.setCopied = this.setCopied.bind(this) - this.isValid = this.isValid.bind(this) - this.onSaveClickHandler = this.onSaveClickHandler.bind(this) - this.onClickSave = this.onClickSave.bind(this) - this.onBlur = this.onBlur.bind(this) - } - - componentDidMount() { - if (this.props.webhookConfigId) { - getWebhookConfiguration(this.props.webhookConfigId) - .then((response) => { - const state = { ...this.state } - const _headers = [...this.state.form.header] - state.view = ViewType.FORM - const _responseKeys = response.result?.header ? Object.keys(response.result.header) : [] - _responseKeys.forEach((_key) => { - _headers.push({ key: _key, value: response.result.header[_key] }) - }) - const _responsePayload = response.result?.payload ?? '' - state.form = { - ...response.result, - header: _headers, - payload: _responsePayload, - } - state.isValid = { - configName: true, - webhookUrl: true, - payload: true, - } - this.setState(state) - }) - .catch((error) => { - showError(error) - }) - } else { - getProjectListMin() - .then((response) => { - this.setState({ - view: ViewType.FORM, - }) - }) - .catch((error) => { - showError(error) - }) - } - getWebhookAttributes() - .then((response) => { - const state = { ...this.state } - state.webhookAttribute = { ...response.result } - this.setState(state) - }) - .catch((error) => { - showError(error) - }) - } +export const WebhookConfigModal = ({ + webhookConfigId, + closeWebhookConfigModal, + onSaveSuccess, +}: WebhookConfigModalProps) => { + const [form, setForm] = useState({ + configName: '', + webhookUrl: '', + isLoading: false, + isError: false, + payload: '', + header: [{ key: '', value: '' }], + }) + const [isValid, setIsValid] = useState({ + configName: true, + webhookUrl: true, + payload: true, + }) - handleWebhookConfigNameChange(event: React.ChangeEvent): void { - const { form } = { ...this.state } - form.configName = event.target.value - this.setState({ form }) - } + const history = useHistory() + const [webhookAttribute, setWebhookAttribute] = useState({}) - isValid(event, key: 'configName' | 'webhookUrl' | 'payload'): void { - const { form, isValid } = { ...this.state } - if (key != 'payload') { - isValid[key] = event.target.value.length !== 0 - } else if (this.state.form.payload != '') { + useEffect(() => { + const fetchWebhookData = async () => { + setForm((prev) => ({ ...prev, isLoading: true })) try { - isValid[key] = event.target.value.length !== 0 - if (isValid[key]) { - isValid[key] = true + if (webhookConfigId) { + const response = await getWebhookConfiguration(webhookConfigId) + const { header = {}, payload = '' } = response.result || {} + const headers = Object.keys(header).map((key) => ({ key, value: header[key] })) + setForm((prev) => ({ ...prev, ...response.result, header: headers, payload, isLoading: false })) + } else { + await getProjectListMin() + setForm((prev) => ({ ...prev, isLoading: false })) } - } catch (err) { - isValid[key] = false + + const attributes = await getWebhookAttributes() + setWebhookAttribute(attributes.result || {}) + } catch (error) { + showError(error) + setForm((prev) => ({ ...prev, isLoading: false })) } } - this.setState({ form, isValid }) - } - handleWebhookUrlChange(event: React.ChangeEvent): void { - const { form } = { ...this.state } - form.webhookUrl = event.target.value - this.setState({ form }) - } + // eslint-disable-next-line @typescript-eslint/no-floating-promises + fetchWebhookData() + }, [webhookConfigId]) - handleWebhookPaylodChange(value): void { - const { form } = { ...this.state } - form.payload = value - this.setState({ form }) + const closeWebhookConfig = () => { + if (typeof closeWebhookConfigModal === 'function') { + closeWebhookConfigModal() + } else { + const newParams = { + modal: ConfigurationsTabTypes.WEBHOOK, + } + history.push({ + search: new URLSearchParams(newParams).toString(), + }) + } } - saveWebhookConfig(): void { - const state = { ...this.state } - state.form.isLoading = true - this.setState(state) - const keys = Object.keys(this.state.isValid) - const isFormValid = keys.reduce((isFormValid, key) => { - isFormValid = isFormValid && this.state.isValid[key] - return isFormValid - }, true) - if (!isFormValid) { - state.form.isLoading = false - state.form.isError = true - this.setState(state) - return - } - const requestBody = this.state.form - if (this.props.webhookConfigId) { - requestBody['id'] = this.props.webhookConfigId + const validateField = (field, value) => { + let isValidField = true + if (field !== 'payload') { + isValidField = value.trim().length > 0 } else { - requestBody['id'] = 0 + try { + JSON.parse(value) + } catch { + isValidField = false + } } - saveUpdateWebhookConfiguration(requestBody) - .then((response) => { - const state = { ...this.state } - state.form.isLoading = false - state.form.isError = false - this.setState(state) - ToastManager.showToast({ - variant: ToastVariantType.success, - description: 'Saved Successfully', - }) - this.props.onSaveSuccess() - }) - .catch((error) => { - showError(error) - }) + setIsValid((prev) => ({ ...prev, [field]: isValidField })) } - setHeaderData(index, _headerData) { - const _headers = [...this.state.form.header] - _headers[index] = _headerData - const { form } = { ...this.state } - form.header = _headers - this.setState({ form }) + const handleInputChange = (field) => (event) => { + const { value } = event.target + setForm((prev) => ({ ...prev, [field]: value })) + if (field !== 'payload') { + validateField(field, value) + } } - addNewHeader() { - const _headers = [...this.state.form.header] - _headers.splice(0, 0, { - key: '', - value: '', - }) - const { form } = { ...this.state } - form.header = _headers - this.setState({ form }) + const handlePayloadChange = (value) => { + setForm((prev) => ({ ...prev, payload: value })) + validateField('payload', value) } - removeHeader(index) { - const _headers = [...this.state.form.header] - _headers.splice(index, 1) - const { form } = { ...this.state } - form.header = _headers - this.setState({ form }) + const addHeader = () => { + setForm((prev) => ({ ...prev, header: [{ key: '', value: '' }, ...prev.header] })) } - setCopied(value: boolean) { - this.setState({ copyAttribute: value }) + const updateHeader = (index, updatedHeader) => { + setForm((prev) => { + const headers = [...prev.header] + headers[index] = updatedHeader + return { ...prev, header: headers } + }) } - renderDataList(attribute, index) { - return ( -
- {this.state.webhookAttribute[attribute]} -
- -
-
- ) + const removeHeader = (index) => { + setForm((prev) => { + const headers = [...prev.header] + headers.splice(index, 1) + return { ...prev, header: headers } + }) } - renderConfigureLinkInfoColumn() { - const keys = Object.keys(this.state.webhookAttribute) - return ( -
-
- - Available data + const renderDataList = () => ( +
+ {Object.keys(webhookAttribute).map((attribute, index) => ( +
+

{webhookAttribute[attribute]}

+
+ +
- - Following data are available to be shared through Webhook. Use Payload to configure. - - {keys.map((attribute, index) => this.renderDataList(attribute, index))} -
- ) - } + ))} +
+ ) - renderHeadersList() { - return ( -
- {this.state.form.header?.map((headerData, index) => ( - - ))} + const renderConfigureLinkInfoColumn = () => ( +
+
+ + Available data
- ) - } +

+ Following data are available to be shared through Webhook. Use Payload to configure. +

+ {renderDataList()} +
+ ) - renderWithBackdrop(body) { - return ( - -
-
-

Configure Webhook

- -
- {body} -
-
- ) + const onSaveSES = () => { + onSaveSuccess() + closeWebhookConfig() } - onSaveClickHandler(event) { - event.preventDefault() - this.saveWebhookConfig() - } + const saveWebhookConfig = async () => { + setForm((prev) => ({ ...prev, isLoading: true })) - onClickSave(event) { - event.preventDefault() - this.saveWebhookConfig() - } + if (!Object.values(isValid).every((v) => v)) { + setForm((prev) => ({ ...prev, isLoading: false, isError: true })) + return + } - onBlur(event) { - this.isValid(event, event.currentTarget.dataset.field) + try { + const requestBody = { + ...form, + id: webhookConfigId || 0, + } + await saveUpdateWebhookConfiguration(requestBody) + ToastManager.showToast({ variant: ToastVariantType.success, description: 'Saved Successfully' }) + onSaveSES() + } catch (error) { + showError(error) + } finally { + setForm((prev) => ({ ...prev, isLoading: false })) + } } - renderWebhookModal = () => { - if (this.state.view === ViewType.LOADING) { - return ( -
- -
- ) - } - return ( - <> -
-
- - -
-
- Headers - - Add - -
- {this.renderHeadersList()} -
- + const renderHeadersList = () => ( +
+ {form.header.map((header, index) => ( + + ))} +
+ ) + + const renderWebhookModal = () => ( +
+
+ + +
+
+ Headers + + Add +
- {this.renderConfigureLinkInfoColumn()} + {renderHeadersList()}
-
-
- - -
+
+ Data to be shared through Webhook + + {!isValid.payload && Invalid JSON}
- - ) - } +
+ {renderConfigureLinkInfoColumn()} +
+ ) - render() { - return this.renderWithBackdrop(this.renderWebhookModal()) - } + return ( + + ) } diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 2c97c4a437..a25be3e414 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -378,7 +378,7 @@ } } -.data-conatiner { +.data-container { .hover-only { display: none; } diff --git a/src/components/notifications/types.tsx b/src/components/notifications/types.tsx index 5e8f008137..00dfce0746 100644 --- a/src/components/notifications/types.tsx +++ b/src/components/notifications/types.tsx @@ -15,7 +15,7 @@ */ import { RouteComponentProps } from 'react-router-dom' -import { ServerError, ResponseType } from '@devtron-labs/devtron-fe-common-lib' +import { ServerError, ResponseType, DynamicDataTableRowType } from '@devtron-labs/devtron-fe-common-lib' import { ConfigurationsTabTypes } from './constants' export interface NotifierProps extends RouteComponentProps<{ id: string }> {} @@ -99,12 +99,6 @@ export interface ConfigurationTableProps { deleteClickHandler: (id: number, name: string) => void } -export interface WebhookConfigModalProps { - webhookConfigId: number - onSaveSuccess: () => void - closeWebhookConfigModal: (event) => void -} - export interface WebhhookConfigModalState { view: string form: { @@ -177,3 +171,19 @@ export interface ConfigTableRowActionButtonProps { rootClassName: string modal: ConfigurationsTabTypes } + +// ----------------------------Webhook Config Modal-------------------------------- +export interface WebhookConfigModalProps { + webhookConfigId: number + closeWebhookConfigModal?: () => void + onSaveSuccess: () => void +} + +export type WebhookHeaderKeyType = 'key' | 'value' + +export type WebhookDataRowType = DynamicDataTableRowType + +export interface WebhookConfigDynamicDataTableProps { + rows: WebhookDataRowType[] + setRows: React.Dispatch> +} From 7364e052d20d5318205728db648767d93edc5b1c Mon Sep 17 00:00:00 2001 From: shivani170 Date: Fri, 3 Jan 2025 14:04:12 +0530 Subject: [PATCH 05/31] chore: webhook code refactoring --- .../notifications/CreateHeaderDetails.tsx | 3 +-- .../notifications/WebhookConfigModal.tsx | 20 +++++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/components/notifications/CreateHeaderDetails.tsx b/src/components/notifications/CreateHeaderDetails.tsx index 3eda97179b..75b910bc19 100644 --- a/src/components/notifications/CreateHeaderDetails.tsx +++ b/src/components/notifications/CreateHeaderDetails.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import React from 'react' import { CustomInput } from '@devtron-labs/devtron-fe-common-lib' import { ReactComponent as DeleteCross } from '../../assets/icons/ic-cross.svg' import { CreateHeaderDetailsType } from './types' @@ -37,7 +36,7 @@ export default function CreateHeaderDetails({ } return ( -
+
( -
+
{form.header.map((header, index) => ( ( -
+
-
- Headers +
+ Headers Add @@ -249,8 +249,16 @@ export const WebhookConfigModal = ({ {renderHeadersList()}
- Data to be shared through Webhook - + Data to be shared through webhook +
+ +
{!isValid.payload && Invalid JSON}
From eeac79323fe2b30d9dddafba66b97cf260a5931b Mon Sep 17 00:00:00 2001 From: shivani170 Date: Fri, 3 Jan 2025 15:17:54 +0530 Subject: [PATCH 06/31] chore: code refactoring --- .../ConfigurationDrawerModal.tsx | 9 +- .../notifications/ConfigurationTab.tsx | 20 +- .../notifications/SESConfigModal.tsx | 212 +++++++----------- .../notifications/SMTPConfigModal.tsx | 151 +++++-------- .../notifications/SlackConfigModal.tsx | 197 +++++----------- src/components/notifications/constants.ts | 19 ++ .../notifications/notifications.util.tsx | 60 ++++- src/components/notifications/types.tsx | 7 + 8 files changed, 283 insertions(+), 392 deletions(-) diff --git a/src/components/notifications/ConfigurationDrawerModal.tsx b/src/components/notifications/ConfigurationDrawerModal.tsx index 739af7ee45..d7941f93d8 100644 --- a/src/components/notifications/ConfigurationDrawerModal.tsx +++ b/src/components/notifications/ConfigurationDrawerModal.tsx @@ -8,14 +8,7 @@ import { } from '@devtron-labs/devtron-fe-common-lib' import { ReactComponent as Close } from '@Icons/ic-close.svg' import { ConfigurationsTabTypes } from './constants' - -export interface ConfigurationTabDrawerModalProps { - renderContent: () => JSX.Element - closeModal: () => void - modal: ConfigurationsTabTypes - isLoading: boolean - saveConfigModal: () => void -} +import { ConfigurationTabDrawerModalProps } from './types' export const ConfigurationTabDrawerModal = ({ renderContent, diff --git a/src/components/notifications/ConfigurationTab.tsx b/src/components/notifications/ConfigurationTab.tsx index bdb76195e8..6ee330baf3 100644 --- a/src/components/notifications/ConfigurationTab.tsx +++ b/src/components/notifications/ConfigurationTab.tsx @@ -44,6 +44,7 @@ import SESConfigurationTable from './SESConfigurationTable' import { SMTPConfigurationTable } from './SMTPConfigurationTable' import { ConfigurationTabSwitcher } from './ConfigurationTabsSwitcher' import { ConfigurationsTabTypes } from './constants' +import { getDeleteConfigComponent } from './notifications.util' export const ConfigurationTab = () => { const { path } = useRouteMatch() @@ -183,19 +184,7 @@ export const ConfigurationTab = () => { return smtpConfig } - const deleteConfigComponent = (): string => { - const { showDeleteConfigModalType } = state - if (showDeleteConfigModalType === ConfigurationsTabTypes.SLACK) { - return ConfigurationsTabTypes.SLACK - } - if (showDeleteConfigModalType === ConfigurationsTabTypes.SES) { - return ConfigurationsTabTypes.SES - } - if (showDeleteConfigModalType === ConfigurationsTabTypes.WEBHOOK) { - return ConfigurationsTabTypes.WEBHOOK - } - return DeleteComponentsName.SMTPConfigurationTab - } + const payload = deleteConfigPayload() if (state.view === ViewType.LOADING) { return ( @@ -211,7 +200,6 @@ export const ConfigurationTab = () => {
) } - const payload = deleteConfigPayload() const renderModal = () => { if (queryString.get('configId') === null) return null @@ -268,7 +256,7 @@ export const ConfigurationTab = () => {
- renderTableComponent()} /> + {renderModal()} @@ -278,7 +266,7 @@ export const ConfigurationTab = () => { payload={payload} title={payload.configName} toggleConfirmation={toggleConfirmation} - component={deleteConfigComponent()} + component={getDeleteConfigComponent(state.showDeleteConfigModalType)} confirmationDialogDescription={DC_CONFIGURATION_CONFIRMATION_MESSAGE} reload={reloadDeleteConfig} configuration="configuration" diff --git a/src/components/notifications/SESConfigModal.tsx b/src/components/notifications/SESConfigModal.tsx index f7c54ab1b2..d914da53ae 100644 --- a/src/components/notifications/SESConfigModal.tsx +++ b/src/components/notifications/SESConfigModal.tsx @@ -17,20 +17,14 @@ import React, { useEffect, useState } from 'react' import { showError, - Progressing, Checkbox, - Drawer, CustomInput, CHECKBOX_VALUE, ToastManager, ToastVariantType, SelectPicker, ComponentSizeType, - Button, - ButtonStyleType, - ButtonVariantType, } from '@devtron-labs/devtron-fe-common-lib' -import { ReactComponent as Close } from '@Icons/ic-close.svg' import { ReactComponent as Error } from '@Icons/ic-warning.svg' import { useHistory } from 'react-router-dom' import { saveEmailConfiguration, getSESConfiguration } from './notifications.service' @@ -40,6 +34,7 @@ import { REQUIRED_FIELD_MSG } from '../../config/constantMessaging' import { SESConfigModalProps } from './types' import { getSESDefaultConfiguration } from './notifications.util' import { ConfigurationsTabTypes, DEFAULT_MASKED_SECRET_KEY, DefaultSESValidationKeys, SESFieldKeys } from './constants' +import { ConfigurationTabDrawerModal } from './ConfigurationDrawerModal' const SESConfigModal = ({ sesConfigId, @@ -178,137 +173,96 @@ const SESConfigModal = ({ } } - const renderSESFooter = () => ( -
-
- ) - - const renderSESContent = () => ( -
-
- handleInputChange(SESFieldKeys.CONFIG_NAME, e.target.value)} - onBlur={(event) => handleBlur(event, SESFieldKeys.CONFIG_NAME)} - placeholder="Configuration name" - autoFocus - isRequiredField - error={!isValid.configName && REQUIRED_FIELD_MSG} - /> - handleInputChange(SESFieldKeys.ACCESS_KEY, e.target.value)} - onBlur={(event) => handleBlur(event, SESFieldKeys.ACCESS_KEY)} - placeholder="Access Key ID" - isRequiredField - error={!isValid.accessKey && REQUIRED_FIELD_MSG} - /> - handleInputChange(SESFieldKeys.SECRET_KEY, e.target.value)} - onBlur={(event) => handleBlur(event, SESFieldKeys.SECRET_KEY)} - placeholder="Secret Access Key" - isRequiredField - error={!isValid.secretKey && REQUIRED_FIELD_MSG} - /> -
- handleBlur(event, SESFieldKeys.REGION)} - onChange={(selected) => handleAWSRegionChange(selected)} - options={awsRegionListParsed} - size={ComponentSizeType.large} - /> - - {!isValid.region ? ( - <> - - {REQUIRED_FIELD_MSG}
- - ) : null} -
-
- handleBlur(event, SESFieldKeys.FROM_EMAIL)} - placeholder="Email" - onChange={(e) => handleInputChange(SESFieldKeys.FROM_EMAIL, e.target.value)} - isRequiredField - error={!isValid.fromEmail && REQUIRED_FIELD_MSG} - helperText="This email must be verified with SES." + handleInputChange(SESFieldKeys.SECRET_KEY, e.target.value)} + onBlur={(event) => handleBlur(event, SESFieldKeys.SECRET_KEY)} + placeholder="Secret Access Key" + isRequiredField + error={!isValid.secretKey && REQUIRED_FIELD_MSG} + /> +
+ handleBlur(event, SESFieldKeys.REGION)} + onChange={(selected) => handleAWSRegionChange(selected)} + options={awsRegionListParsed} + size={ComponentSizeType.large} /> - - Set as default configuration to send emails - + + {!isValid.region ? ( + <> + + {REQUIRED_FIELD_MSG}
+ + ) : null} +
- - {renderSESFooter()} + handleBlur(event, SESFieldKeys.FROM_EMAIL)} + placeholder="Email" + onChange={(e) => handleInputChange(SESFieldKeys.FROM_EMAIL, e.target.value)} + isRequiredField + error={!isValid.fromEmail && REQUIRED_FIELD_MSG} + helperText="This email must be verified with SES." + /> + + Set as default configuration to send emails +
) - const renderLoadingState = () => ( -
- -
- ) return ( - -
-
-

Configure SES

-
- {form.isLoading ? renderLoadingState() : renderSESContent()} -
-
+ ) } diff --git a/src/components/notifications/SMTPConfigModal.tsx b/src/components/notifications/SMTPConfigModal.tsx index 1e4cdbd230..eae0f235d8 100644 --- a/src/components/notifications/SMTPConfigModal.tsx +++ b/src/components/notifications/SMTPConfigModal.tsx @@ -16,27 +16,20 @@ import { useState, useEffect, useCallback } from 'react' import { showError, - Progressing, Checkbox, - Drawer, CustomInput, CHECKBOX_VALUE, ToastManager, ToastVariantType, - Button, - ButtonStyleType, - ButtonVariantType, - ComponentSizeType, } from '@devtron-labs/devtron-fe-common-lib' import { useHistory } from 'react-router-dom' import { validateEmail } from '../common' import { getSMTPConfiguration, saveEmailConfiguration } from './notifications.service' -import { ReactComponent as Close } from '../../assets/icons/ic-close.svg' -import { ViewType } from '../../config/constants' import { ProtectedInput } from '../globalConfigurations/GlobalConfiguration' import { REQUIRED_FIELD_MSG } from '../../config/constantMessaging' import { ConfigurationsTabTypes } from './constants' import { SMTPConfigModalProps } from './types' +import { ConfigurationTabDrawerModal } from './ConfigurationDrawerModal' export const SMTPConfigModal = ({ smtpConfigId, @@ -47,7 +40,6 @@ export const SMTPConfigModal = ({ }: SMTPConfigModalProps) => { const history = useHistory() - const [view, setView] = useState(ViewType.LOADING) const [form, setForm] = useState({ configName: '', port: null, @@ -83,13 +75,11 @@ export const SMTPConfigModal = ({ getSMTPConfiguration(smtpConfigId) .then((response) => { setForm({ ...response.result, isLoading: false, isError: true }) - setView(ViewType.FORM) resetValidation() }) .catch(showError) } else { setForm((prevForm) => ({ ...prevForm, default: shouldBeDefault })) - setView(ViewType.FORM) } }, [smtpConfigId, shouldBeDefault]) @@ -157,102 +147,65 @@ export const SMTPConfigModal = ({ }) } - const renderWithBackdrop = (body) => ( - -
-
-

Configure SMTP

-
- {body} -
-
- ) - - const renderSMTPFooter = () => ( -
-
- ) - const renderForm = () => ( -
-
- {['configName', 'host', 'port', 'authUser'].map((field, index) => ( -
- -
- ))} -
- + {['configName', 'host', 'port', 'authUser'].map((field, index) => ( +
+
- - + - - Set as default configuration to send emails -
- {renderSMTPFooter()} + + + + Set as default configuration to send emails +
) - return renderWithBackdrop(view === ViewType.LOADING ? : renderForm()) + return ( + + ) } diff --git a/src/components/notifications/SlackConfigModal.tsx b/src/components/notifications/SlackConfigModal.tsx index 88df45daee..af04dcb44f 100644 --- a/src/components/notifications/SlackConfigModal.tsx +++ b/src/components/notifications/SlackConfigModal.tsx @@ -1,28 +1,21 @@ import React, { useState, useEffect } from 'react' import { showError, - Progressing, getTeamListMin as getProjectListMin, - Drawer, CustomInput, ToastManager, ToastVariantType, SelectPicker, - Button, - ButtonStyleType, - ButtonVariantType, - ComponentSizeType, } from '@devtron-labs/devtron-fe-common-lib' import Tippy from '@tippyjs/react' import { useHistory } from 'react-router-dom' -import { ReactComponent as Close } from '../../assets/icons/ic-close.svg' -import { ViewType } from '../../config/constants' import { saveSlackConfiguration, updateSlackConfiguration, getSlackConfiguration } from './notifications.service' import { ReactComponent as ICHelpOutline } from '../../assets/icons/ic-help-outline.svg' import { ReactComponent as Error } from '../../assets/icons/ic-warning.svg' import { REQUIRED_FIELD_MSG } from '../../config/constantMessaging' import { SlackConfigModalProps } from './types' -import { ConfigurationsTabTypes } from './constants' +import { ConfigurationsTabTypes, DefaultSlackKeys, DefaultSlackValidationKeys, SlackRegion } from './constants' +import { ConfigurationTabDrawerModal } from './ConfigurationDrawerModal' export const SlackConfigModal: React.FC = ({ slackConfigId, @@ -31,34 +24,19 @@ export const SlackConfigModal: React.FC = ({ }: SlackConfigModalProps) => { const history = useHistory() - const [view, setView] = useState(ViewType.LOADING) const [projectList, setProjectList] = useState>([]) const [selectedProject, setSelectedProject] = useState<{ label: string; value: number }>({ label: '', value: 0 }) - const [form, setForm] = useState({ - projectId: 0, - configName: '', - webhookUrl: '', - isLoading: false, - isError: false, - }) - const [isValid, setIsValid] = useState({ - projectId: true, - configName: true, - webhookUrl: true, - }) + const [form, setForm] = useState(DefaultSlackKeys) + const [isValid, setIsValid] = useState(DefaultSlackValidationKeys) useEffect(() => { if (slackConfigId) { + setForm((prevForm) => ({ ...prevForm, isLoading: true })) Promise.all([getSlackConfiguration(slackConfigId), getProjectListMin()]) .then(([slackConfigRes, projectListRes]) => { - setView(ViewType.FORM) setProjectList(projectListRes.result || []) - setForm({ ...slackConfigRes.result }) - setIsValid({ - projectId: true, - configName: true, - webhookUrl: true, - }) + setForm({ ...slackConfigRes.result, isLoading: false, isError: false }) + setIsValid(DefaultSlackValidationKeys) setSelectedProject({ label: projectListRes.result.find((p) => p.id === slackConfigRes.result.projectId).name, value: slackConfigRes.result.projectId, @@ -71,10 +49,11 @@ export const SlackConfigModal: React.FC = ({ getProjectListMin() .then((response) => { setProjectList(response.result || []) - setView(ViewType.FORM) + setForm((prevForm) => ({ ...prevForm, isLoading: false })) }) .catch((error) => { showError(error) + setForm((prevForm) => ({ ...prevForm, isLoading: false })) }) } }, [slackConfigId]) @@ -192,53 +171,6 @@ export const SlackConfigModal: React.FC = ({
) - const onSaveSlack = () => { - onSaveSuccess() - closeSlackConfig() - } - - const renderWithBackdrop = (body: JSX.Element) => ( - -
-
-

Configure Slack

-
- {body} -
-
- ) - - const renderSlackFooter = () => ( -
-
- ) - const renderProjectLabel = () => (
Project @@ -257,64 +189,59 @@ export const SlackConfigModal: React.FC = ({
) - let body - if (view === ViewType.LOADING) { - body = ( -
- -
- ) - } else { - body = ( -
-
- checkIsValid(event, 'configName')} - placeholder="channel name" - autoFocus - isRequiredField - error={!isValid.configName && REQUIRED_FIELD_MSG} - /> - checkIsValid(event, 'webhookUrl')} - isRequiredField - error={!isValid.webhookUrl && REQUIRED_FIELD_MSG} - /> -
- ({ label: p.name, value: p.id }))} - /> - - {!isValid.projectId && ( - <> - - {REQUIRED_FIELD_MSG} - - )} - -
-
- {renderSlackFooter()} + const renderContent = () => ( +
+ checkIsValid(event, 'configName')} + placeholder="channel name" + autoFocus + isRequiredField + error={!isValid.configName && REQUIRED_FIELD_MSG} + /> + checkIsValid(event, 'webhookUrl')} + isRequiredField + error={!isValid.webhookUrl && REQUIRED_FIELD_MSG} + /> +
+ ({ label: p.name, value: p.id }))} + /> + + {!isValid[SlackRegion.PROJECT_ID] && ( + <> + + {REQUIRED_FIELD_MSG} + + )} +
- ) - } +
+ ) - return renderWithBackdrop(body) + return ( + + ) } diff --git a/src/components/notifications/constants.ts b/src/components/notifications/constants.ts index 3768c48556..098ffa7408 100644 --- a/src/components/notifications/constants.ts +++ b/src/components/notifications/constants.ts @@ -40,3 +40,22 @@ export const DefaultSESValidationKeys = { [SESFieldKeys.REGION]: true, [SESFieldKeys.FROM_EMAIL]: true, } + +export enum SlackRegion { + PROJECT_ID = 'project_id', + CONFIG_NAME = 'configName', + WEBHOOK_URL = 'webhookUrl', +} + +export const DefaultSlackKeys = { + [SlackRegion.PROJECT_ID]: 0, + [SlackRegion.CONFIG_NAME]: '', + [SlackRegion.WEBHOOK_URL]: '', + isLoading: false, + isError: false, +} +export const DefaultSlackValidationKeys = { + [SlackRegion.PROJECT_ID]: true, + [SlackRegion.CONFIG_NAME]: true, + [SlackRegion.WEBHOOK_URL]: true, +} diff --git a/src/components/notifications/notifications.util.tsx b/src/components/notifications/notifications.util.tsx index 1e62b7d3c5..66b3553912 100644 --- a/src/components/notifications/notifications.util.tsx +++ b/src/components/notifications/notifications.util.tsx @@ -25,9 +25,10 @@ import { ReactComponent as Slack } from '@Icons/slack-logo.svg' import { ReactComponent as SES } from '@Icons/ic-aws-ses.svg' import { ReactComponent as Webhook } from '@Icons/ic-CIWebhook.svg' import { ReactComponent as SMTP } from '@Icons/ic-smtp.svg' -import { Tooltip } from '@devtron-labs/devtron-fe-common-lib' +import { DynamicDataTableRowDataType, Tooltip } from '@devtron-labs/devtron-fe-common-lib' import { ConfigurationsTabTypes, ConfigurationTabText } from './constants' import { validateEmail } from '../common' +import { WebhookDataRowType } from './types' export const multiSelectStyles = { control: (base, state) => ({ @@ -202,21 +203,70 @@ export const getSESDefaultConfiguration = (shouldBeDefault: boolean) => ({ isError: true, }) -export const renderText = (text: string, isLink: boolean = false, linkTo?: () => void, dataTestId?: string,) => ( +export const renderText = (text: string, isLink: boolean = false, linkTo?: () => void, dataTestId?: string) => ( {isLink ? ( - + {text || '-'} ) : ( -

{text || '-'}

+

+ {text || '-'} +

)}
) export const renderDefaultTag = (isDefault: boolean) => { if (isDefault) { - return Default + return Default } return null } + +export const getDeleteConfigComponent = (showDeleteConfigModalType: ConfigurationsTabTypes): string => { + if (showDeleteConfigModalType === ConfigurationsTabTypes.SLACK) { + return ConfigurationsTabTypes.SLACK + } + if (showDeleteConfigModalType === ConfigurationsTabTypes.SES) { + return ConfigurationsTabTypes.SES + } + if (showDeleteConfigModalType === ConfigurationsTabTypes.WEBHOOK) { + return ConfigurationsTabTypes.WEBHOOK + } + return ConfigurationsTabTypes.SMTP +} + +export const getTableHeaders = () => [ + { label: 'Header key', key: 'key', width: '1fr' }, + { label: 'Value', key: 'value', width: '1fr' }, +] + +export const generate16DigitRandomNumber = () => Math.floor(Math.random() * 9e15) + 1e15 + +export const getEmptyVariableDataRow = (): WebhookDataRowType => { + const id = generate16DigitRandomNumber() + return { + data: { + key: { + value: null, + type: DynamicDataTableRowDataType.TEXT, + props: { + placeholder: 'Eg. owner-name', + }, + }, + value: { + value: '', + type: DynamicDataTableRowDataType.TEXT, + props: { + placeholder: 'Enter value', + }, + }, + }, + id, + } +} diff --git a/src/components/notifications/types.tsx b/src/components/notifications/types.tsx index 00dfce0746..657264ca3e 100644 --- a/src/components/notifications/types.tsx +++ b/src/components/notifications/types.tsx @@ -178,6 +178,13 @@ export interface WebhookConfigModalProps { closeWebhookConfigModal?: () => void onSaveSuccess: () => void } +export interface ConfigurationTabDrawerModalProps { + renderContent: () => JSX.Element + closeModal: () => void + modal: ConfigurationsTabTypes + isLoading: boolean + saveConfigModal: () => void +} export type WebhookHeaderKeyType = 'key' | 'value' From ab50856357ac5bd255aed55e10db8be66e259574 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Fri, 3 Jan 2025 15:20:39 +0530 Subject: [PATCH 07/31] chore: code feedback fixes --- src/components/notifications/ConfigurationTabsSwitcher.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/notifications/ConfigurationTabsSwitcher.tsx b/src/components/notifications/ConfigurationTabsSwitcher.tsx index 8ed59484dd..73de67126f 100644 --- a/src/components/notifications/ConfigurationTabsSwitcher.tsx +++ b/src/components/notifications/ConfigurationTabsSwitcher.tsx @@ -53,7 +53,7 @@ export const ConfigurationTabSwitcher = () => { variant={ButtonVariantType.primary} size={ComponentSizeType.small} dataTestId="add-configuration" - startIcon={} + startIcon={} text={`Add ${activeTab}`} />
From 7c469f9e5edc3df74c19bfa24e804a669a572829 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Fri, 3 Jan 2025 15:30:35 +0530 Subject: [PATCH 08/31] chore: image added in empty state --- src/assets/img/ses-empty.png | Bin 1793 -> 3526 bytes src/assets/img/webhook-empty.png | Bin 0 -> 4608 bytes .../notifications/EmptyConfigurationView.tsx | 2 +- .../WebhookConfigurationTable.tsx | 3 ++- src/css/base.scss | 4 ++++ 5 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 src/assets/img/webhook-empty.png diff --git a/src/assets/img/ses-empty.png b/src/assets/img/ses-empty.png index 15178ac1f7ec82575655c44121f95dffaa82ce22..7fcef34a8ea4137f820b208157ffc32abac66591 100644 GIT binary patch literal 3526 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 At^fc4 delta 1784 zcmV0000e2nGNE0C_C&m;e9(32;bR za{vGf6951U69E94oEVWdAAbgxNkl7fK6vyAJ*N!2kaZykONF|^MVp>tF zUk8dB2SE!G70ICz^pxB}R4Ji{O7s%vP@F?0>`PmzhvWvWD#ekiLQ5sKq2f}in2JK7 zsp233{Qzp(G?2u*(|K#}#z}4OZoK|jYkrdD*`3MmMDzE5=Dk@PfPcfW2}-G-y-rbM zu#F!$9J|PhZ<3k^+xiJ`I5vQ;#@+_BV>yw4g^>|Zrlx?-&Vnl#1owdhARauJ`)5(x z1sHkz*-&$%aClE}eJpTPN&=_uVd86HZ`0Z_3U6U_G;do^cQ<%?dh}!41bFgTpp#v! zpA|!ReBk8`(C8IMWq(kHxDacHcRs&E!Df!U=!ciKtWnG9vuEM)@Nm)gYhp3g5fK4w zAkxs> zCGA*l-Mm&mf9v;0F!xwi%Up={&1S!-mvQZ91g;A;N)q63-+#_YkmU=Iopv`gd7#UJ)xI#U2s_kxDe}#2Xjkf zEVQ;7w%68wYJ0H2h0Cc)DyJuJobH&uac1vWzBYcJ$fn{({dns3T*2$15FzWH9OrWN z_G7)A8{rnhoqv;xF)hI1zCgdMsNpu!!tAfJP}{&3AFzN#Ezd9)Tuc`Tya*&%gggoy zM|1V=k^s@&iir^GM3xWKFeSi9ZyOUF34=-8BGChq#k9g)@ZY@cCK7**NBVY#KMlXo z2^`1r>z@lSF|{M;sa zW9GLf;*aq*r_;h9Lv|&W}&sY0XAhLVsyD&r^^F|%V9$YjPyR!&BWIY z7D2J$RE*?v!Rxm&k&xDg<7^%+D)8aN{bf#Agcy5u5JFN#1*lHFL>Yutg41x5FO#Iq zxM5~3h=1;f(J{K;mZ#PLr#?Utj$ip3-aq>Xd_OY>w^M1o4kP7>UsQ3T@CHzPcu;f- zW{vtO1{@`@Yuh@w_~QfJ1yOL7aKAnK4uvR?AOUshAbt1E7ZNxKC-)!4cINWKymjr{ zeNygmK>?!s(VV9nR?7x|COLgCwV=8nI$t^6FMrh8>0g{pt0^Kv=oNft139vL-=T!Z zC-zA3cnWHmYWDy2z&dzsXKAK@Yr(Is|6{s+VFx7S0@y_0X&+onWn2)`GU~Z9x!=O9 zJi^u$-!jJt=bRp3juOreYzNdZ)$D(%;?u5SMSywNfRVm7T=%trO`r?rx?Q>452r0c zAb+@G@Km&O*am8pYWLrYaO}E9eg~wa8*Bz0@6P>s)dkDA_6xB!(|DmuLg9)+pdSZO zAxiwx`7hO-^WFP9g91%2%SBeBo-KLWjrvcCy8S6Z8NlfiUzH?(6ilh{eu_oV^)M+IOMj8q0Z)AVvlKZNkih1BT1-YaHM*f(F!xAM z3Wp2TQ-K^N?8CJ(qP%oIj!snYIigC{tC25-L8Jad9Z@(B$o^4c;uC2FOipiN+46Fs z35!LH{~JU+#|AG^%jpcK_kT4MjDnd9CQlyQF+@N(1rEZU7Q51-Jo3u^H>L%W*?-YFzB@JUW-BJj{hY?(9;kYa+li)D2115n(sYd@%Q0RhY+uDJl0N?%Or$nST5MwL+ ze&EzKwUdTf!MwL2%-)i~;n<8R0e^<0i4@dX_|K^gOh1y3xxq${7-v&)itQ7}DW%83 z4)A(ZG5#(e$1ajkB}krJsHrESHEuq<-}BayAN1THi;!2JqG7^QD2;g=AiA|ns_7?G z>In+UI7_WnWj)Wf5la(bIGQY7C|r0|#YJ6lh0v-9P!WWomFH~vX%17M@PB{~c^c@} zz)>m8#aA6Zcypt~TrdU|hOm1^yEZ0Qp10(HtGqLJyc&bL7Q=$v&`tq@Pyuj{US{{q zS67k)l46Nc?szpzv}ex@$c&Kk3Oh zo|ikG6Brn~+d^cDUyppwMma}JVz7hjO`P(JHMH&b0a1w0@9>AVw|KemI2;a#!{Kmh aoBse3x`$hH^S$E$0000@wd_l==5mS@VqONrB@ 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 ) diff --git a/src/components/notifications/WebhookConfigurationTable.tsx b/src/components/notifications/WebhookConfigurationTable.tsx index 621d5bff93..a9043d0ac8 100644 --- a/src/components/notifications/WebhookConfigurationTable.tsx +++ b/src/components/notifications/WebhookConfigurationTable.tsx @@ -6,6 +6,7 @@ import { EmptyConfigurationView } from './EmptyConfigurationView' import { ConfigurationsTabTypes } from './constants' import { ConfigTableRowActionButton } from './ConfigTableRowActionButton' import { getConfigTabIcons, renderText } from './notifications.util' +import webhookEmpty from '../../assets/img/webhook-empty.png' export const WebhookConfigurationTable = ({ state, deleteClickHandler }: ConfigurationTableProps) => { const { webhookConfigurationList } = state @@ -13,7 +14,7 @@ export const WebhookConfigurationTable = ({ state, deleteClickHandler }: Configu const history = useHistory() if (webhookConfigurationList.length === 0) { - return + return } const onClickWebhookConfigEdit = (id: number) => () => { diff --git a/src/css/base.scss b/src/css/base.scss index 585ba12bf3..398abcb809 100644 --- a/src/css/base.scss +++ b/src/css/base.scss @@ -3207,6 +3207,10 @@ textarea, .w-160 { width: 160px; + + &--imp { + width: 160px !important; + } } .w-180 { From b385d5e51cc01c44f0d434184252299d62bb7ea4 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Mon, 6 Jan 2025 10:02:56 +0530 Subject: [PATCH 09/31] chore: code feedback fixes --- .../ConfigurationDrawerModal.tsx | 9 +-- .../notifications/ConfigurationTab.tsx | 10 +-- .../notifications/SESConfigModal.tsx | 32 +++++---- .../notifications/SESConfigurationTable.tsx | 4 +- .../notifications/SMTPConfigurationTable.tsx | 7 +- .../notifications/SlackConfigurationTable.tsx | 7 +- .../notifications/WebhookConfigModal.tsx | 65 ++++++++----------- .../WebhookConfigurationTable.tsx | 7 +- src/components/notifications/constants.ts | 18 ++--- .../notifications/notifications.util.tsx | 61 +++++++++++++++-- src/components/notifications/types.tsx | 26 +++++++- 11 files changed, 159 insertions(+), 87 deletions(-) diff --git a/src/components/notifications/ConfigurationDrawerModal.tsx b/src/components/notifications/ConfigurationDrawerModal.tsx index d7941f93d8..c5a15e55a8 100644 --- a/src/components/notifications/ConfigurationDrawerModal.tsx +++ b/src/components/notifications/ConfigurationDrawerModal.tsx @@ -16,6 +16,7 @@ export const ConfigurationTabDrawerModal = ({ modal, isLoading, saveConfigModal, + disableSave, }: ConfigurationTabDrawerModalProps) => { const renderLoadingState = () => (

@@ -24,7 +25,7 @@ export const ConfigurationTabDrawerModal = ({ ) const renderFooter = () => ( -
+
) const renderModalContent = () => ( -
+
{renderContent()} {renderFooter()}
@@ -56,7 +57,7 @@ export const ConfigurationTabDrawerModal = ({
-
+

Configure {modal}

) diff --git a/src/components/notifications/SESConfigModal.tsx b/src/components/notifications/SESConfigModal.tsx index 047d12e2b6..0203572b83 100644 --- a/src/components/notifications/SESConfigModal.tsx +++ b/src/components/notifications/SESConfigModal.tsx @@ -24,6 +24,7 @@ import { ToastVariantType, SelectPicker, ComponentSizeType, + DEFAULT_SECRET_PLACEHOLDER, } from '@devtron-labs/devtron-fe-common-lib' import { ReactComponent as Error } from '@Icons/ic-warning.svg' import { useHistory } from 'react-router-dom' @@ -31,12 +32,7 @@ import { saveEmailConfiguration, getSESConfiguration } from './notifications.ser import awsRegionList from '../common/awsRegionList.json' import { SESConfigModalProps } from './types' import { getFormValidated, getSESDefaultConfiguration, validateKeyValueConfig } from './notifications.util' -import { - ConfigurationFieldKeys, - ConfigurationsTabTypes, - DEFAULT_MASKED_SECRET_KEY, - DefaultSESValidations, -} from './constants' +import { ConfigurationFieldKeys, ConfigurationsTabTypes, DefaultSESValidations } from './constants' import { ConfigurationTabDrawerModal } from './ConfigurationDrawerModal' const SESConfigModal = ({ @@ -64,7 +60,7 @@ const SESConfigModal = ({ isLoading: false, isError: true, region: awsRegion, - secretKey: DEFAULT_MASKED_SECRET_KEY, // Masked secretKey for security + secretKey: DEFAULT_SECRET_PLACEHOLDER, // Masked secretKey for security }) setFormValid(DefaultSESValidations) } catch (error) { diff --git a/src/components/notifications/SESConfigurationTable.tsx b/src/components/notifications/SESConfigurationTable.tsx index ee4a2a6aa5..85a65374ba 100644 --- a/src/components/notifications/SESConfigurationTable.tsx +++ b/src/components/notifications/SESConfigurationTable.tsx @@ -42,7 +42,7 @@ const SESConfigurationTable = ({ state, deleteClickHandler }: ConfigurationTable {sesConfigurationList.map((sesConfig) => (
{getConfigTabIcons(ConfigurationsTabTypes.SES)} diff --git a/src/components/notifications/SMTPConfigModal.tsx b/src/components/notifications/SMTPConfigModal.tsx index 4a0adecfba..4475f54d5d 100644 --- a/src/components/notifications/SMTPConfigModal.tsx +++ b/src/components/notifications/SMTPConfigModal.tsx @@ -148,9 +148,10 @@ export const SMTPConfigModal = ({ value={form.authPassword} onChange={handleInputChange} error={isFormValid.authPassword.message} - label="SMTP Password *" + label="SMTP Password" labelClassName="form__label--fs-13 mb-8 fw-5 fs-13" placeholder="Enter SMTP password" + isRequiredField />
diff --git a/src/components/notifications/SMTPConfigurationTable.tsx b/src/components/notifications/SMTPConfigurationTable.tsx index 525c583814..2daf2f1ff2 100644 --- a/src/components/notifications/SMTPConfigurationTable.tsx +++ b/src/components/notifications/SMTPConfigurationTable.tsx @@ -44,7 +44,7 @@ export const SMTPConfigurationTable = ({ state, deleteClickHandler }: Configurat
{getConfigTabIcons(ConfigurationsTabTypes.SMTP)}
{slackConfigurationList.map((slackConfig) => ( -
+
{getConfigTabIcons(ConfigurationsTabTypes.SLACK)} {renderText(slackConfig.slackChannel)} {renderText(slackConfig.webhookUrl)} diff --git a/src/components/notifications/WebhookConfigDynamicDataTable.tsx b/src/components/notifications/WebhookConfigDynamicDataTable.tsx index 656a6e33d6..caf1b3edae 100644 --- a/src/components/notifications/WebhookConfigDynamicDataTable.tsx +++ b/src/components/notifications/WebhookConfigDynamicDataTable.tsx @@ -15,7 +15,6 @@ export const WebhookConfigDynamicDataTable = ({ rows, setRows, headers }: Webhoo }, []) const handleRowUpdateAction = ({ actionType, actionValue, rowId, headerKey }: HandleRowUpdateActionProps) => { - if (!rowId || !headerKey) return let updatedRows: WebhookDataRowType[] = [...rows] switch (actionType) { case VariableDataTableActionType.UPDATE_ROW: diff --git a/src/components/notifications/WebhookConfigurationTable.tsx b/src/components/notifications/WebhookConfigurationTable.tsx index 06c7b2f1f5..1f187eb904 100644 --- a/src/components/notifications/WebhookConfigurationTable.tsx +++ b/src/components/notifications/WebhookConfigurationTable.tsx @@ -39,7 +39,7 @@ export const WebhookConfigurationTable = ({ state, deleteClickHandler }: Configu {webhookConfigurationList.map((webhookConfig) => (
{getConfigTabIcons(ConfigurationsTabTypes.WEBHOOK)} diff --git a/src/components/notifications/constants.ts b/src/components/notifications/constants.ts index 9b0e4c484c..9e12bfa372 100644 --- a/src/components/notifications/constants.ts +++ b/src/components/notifications/constants.ts @@ -41,7 +41,6 @@ export enum ConfigurationFieldKeys { } // ------------ SES Configuration Constants ------------ -export const DEFAULT_MASKED_SECRET_KEY = '*******' export const ConfigValidationKeys = { isValid: true, message: '' } diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index a25be3e414..e01d1b6ccb 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -122,17 +122,6 @@ box-shadow: 0 -1px 0 0 var(--N200); } -.configuration-tab__header { - display: flex; - justify-content: space-between; - padding: 18px 24px; -} - -.configuration-tab__title { - font-size: 14px; - font-weight: 600; - margin: 0px; -} .white-card--configuration-tab { overflow-y: auto; @@ -141,84 +130,67 @@ height: 100%; } -.configuration-tab { - display: grid; - grid-template-rows: 1fr 1fr 1fr 1fr; - padding: 16px 24px; - grid-gap: 16px; - height: calc(100vh - 220px); -} - -.configuration-tab__table-row { - display: flex; - font-size: 14px; - font-weight: normal; - line-height: 1.43; - letter-spacing: normal; - color: var(--N600); - align-items: center; - padding: 10px 24px; - height: 48px; - cursor: default; - box-shadow: inset 0 -1px 0 0 var(--N100); - - &:hover { - background-color: var(--window-bg); +// --------------Configuration Tab---------------- +.configuration-tab__container { + .configuration-tab__table-row { + display: flex; + font-size: 14px; + font-weight: normal; + line-height: 1.43; + letter-spacing: normal; + color: var(--N600); + align-items: center; + padding: 10px 24px; + height: 48px; + cursor: default; + box-shadow: inset 0 -1px 0 0 var(--N100); } -} - -.configuration-tab__table-header { - display: flex; - font-size: 12px; - font-weight: 600; - line-height: 1.5; - letter-spacing: normal; - color: var(--N500); - text-transform: uppercase; - align-items: center; - padding: 9px 24px; - cursor: default; - box-shadow: 0 1px 0 0 var(--N200); -} -.configuration-tab__container { .ses-config-container { - .ses-config-grid { - display: grid; - grid-template-columns: 24px 250px 1fr 1fr 52px; - } + .ses-config-grid { + display: grid; + grid-template-columns: 24px 250px 1fr 1fr 52px; } - - .slack-config-container { - .slack-config-grid { - display: grid; - grid-template-columns: 24px 250px 1fr 52px; - } + } + + .slack-config-container { + .slack-config-grid { + display: grid; + grid-template-columns: 24px 250px 1fr 52px; } - - .smtp-config-container { - .smtp-config-grid { - display: grid; - grid-template-columns: 24px 250px 1fr 60px 1fr 52px; - } + } + + .smtp-config-container { + .smtp-config-grid { + display: grid; + grid-template-columns: 24px 250px 1fr 60px 1fr 52px; } - - .webhook-config-container { - .webhook-config-grid { - display: grid; - grid-template-columns: 24px 250px 1fr 52px; - } + } + + .webhook-config-container { + .webhook-config-grid { + display: grid; + grid-template-columns: 24px 250px 1fr 52px; } } + .configuration-tab__table-row:hover .slack-config-table__action, + .configuration-tab__table-row:hover .ses-config-table__action, + .configuration-tab__table-row:hover .webhook-config-table__action, + .configuration-tab__table-row:hover .smtp-config-table__action { + display: flex; + background-color: var(--window-bg); + } + + .slack-config-table__action, + .ses-config-table__action, + .webhook-config-table__action, + .smtp-config-table__action { + display: none; + margin: auto; + margin-right: 0; + } -.slack-config-table__action, -.ses-config-table__action, -.webhook-config-table__action, -.smtp-config-table__action{ - display: none; - margin: auto; - margin-right: 0; } .form__row--ses-account { @@ -226,14 +198,6 @@ min-width: 300px; } -.configuration-tab__table-row:hover .slack-config-table__action, -.configuration-tab__table-row:hover .ses-config-table__action, -.configuration-tab__table-row:hover .webhook-config-table__action, -.configuration-tab__table-row:hover .smtp-config-table__action { - display: flex; - background-color: var(--window-bg); -} - .kebab-menu__list--notification-tab { width: 216px; } @@ -398,4 +362,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/components/notifications/notifications.util.tsx b/src/components/notifications/notifications.util.tsx index 00fcd3786d..380a973f10 100644 --- a/src/components/notifications/notifications.util.tsx +++ b/src/components/notifications/notifications.util.tsx @@ -34,7 +34,7 @@ import { } from '@devtron-labs/devtron-fe-common-lib' import { ConfigurationFieldKeys, ConfigurationsTabTypes, ConfigurationTabText } from './constants' import { validateEmail } from '../common' -import { WebhookDataRowType, WebhookHeaderKeyType, WebhookRowCellType } from './types' +import { FormValidation, WebhookDataRowType, WebhookHeaderKeyType, WebhookRowCellType } from './types' import { REQUIRED_FIELD_MSG } from '@Config/constantMessaging' export const multiSelectStyles = { @@ -265,9 +265,8 @@ export const getTableHeaders = (): DynamicDataTableHeaderType[] => +export const getInitialWebhookKeyRow = (rows: WebhookRowCellType[]): DynamicDataTableRowType[] => rows.map((row) => { - const id = row.id || getUniqueId() return { data: { key: { @@ -285,7 +284,7 @@ export const getInitialWebhookKeyRow = (rows): DynamicDataTableRowType { } } -export const validateKeyValueConfig = (key: ConfigurationFieldKeys, value: string): { isValid: boolean; message: string } => { +export const validateKeyValueConfig = ( + key: ConfigurationFieldKeys, + value: string, +): { isValid: boolean; message: string } => { if (!value) { return { isValid: false, message: REQUIRED_FIELD_MSG } } @@ -322,10 +324,6 @@ export const validateKeyValueConfig = (key: ConfigurationFieldKeys, value: strin return { isValid: true, message: '' } } -type FormValidation = { - [key: string]: { isValid: boolean; message: string } -} - export const getFormValidated = (isFormValid: FormValidation, fromEmail: string): boolean => { return Object.values(isFormValid).every((field) => field.isValid && !field.message) && validateEmail(fromEmail) } @@ -334,3 +332,18 @@ export enum ConfigTableRowActionType { UPDATE_ROW = 'UPDATE_ROW', DELETE_ROW = 'DELETE_ROW', } + +export const getTabText = (tab: ConfigurationsTabTypes) => { + switch (tab) { + case ConfigurationsTabTypes.SES: + return 'SES' + case ConfigurationsTabTypes.SLACK: + return 'Slack' + case ConfigurationsTabTypes.WEBHOOK: + return 'Webhook' + case ConfigurationsTabTypes.SMTP: + return 'SMTP' + default: + return '' + } +} diff --git a/src/components/notifications/types.tsx b/src/components/notifications/types.tsx index 97b88fc44a..7f75b84d0d 100644 --- a/src/components/notifications/types.tsx +++ b/src/components/notifications/types.tsx @@ -218,3 +218,7 @@ export type HandleRowUpdateActionProps = VariableDataTableAction & { headerKey: WebhookHeaderKeyType rowId: string | number } + +export type FormValidation = { + [key: string]: { isValid: boolean; message: string } +} From 563e55ebea72c1ddfe91551eb572448731f2cd68 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Tue, 7 Jan 2025 16:51:41 +0530 Subject: [PATCH 16/31] chore: css fixes --- .../GlobalConfiguration.tsx | 16 ++- .../globalConfiguration.type.ts | 1 + .../ConfigurationDrawerModal.tsx | 18 +++ .../notifications/ConfigurationTab.tsx | 2 +- .../ConfigurationTabsSwitcher.tsx | 2 +- .../notifications/SESConfigModal.tsx | 135 +++++++++--------- .../notifications/SMTPConfigModal.tsx | 73 +++++++--- .../notifications/SMTPConfigurationTable.tsx | 58 ++++---- .../notifications/SlackConfigModal.tsx | 105 +++++++------- src/components/notifications/constants.ts | 8 +- .../notifications/notifications.util.tsx | 10 +- 11 files changed, 245 insertions(+), 183 deletions(-) diff --git a/src/components/globalConfigurations/GlobalConfiguration.tsx b/src/components/globalConfigurations/GlobalConfiguration.tsx index 9fd291ed85..faab5ad6c0 100644 --- a/src/components/globalConfigurations/GlobalConfiguration.tsx +++ b/src/components/globalConfigurations/GlobalConfiguration.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { lazy, useState, useEffect, Suspense, isValidElement } from 'react' +import { lazy, useState, useEffect, Suspense, isValidElement, useRef } from 'react' import { Route, NavLink, Router, Switch, Redirect, useHistory, useLocation } from 'react-router-dom' import { showError, @@ -852,7 +852,19 @@ export const ProtectedInput = ({ dataTestid = '', onBlur = (e) => { }, isRequiredField = false, + autoFocus = false, }: ProtectedInputType) => { + const inputRef = useRef() + + useEffect(() => { + setTimeout(() => { + // Added timeout to ensure the autofocus code is executed post the re-renders + if (inputRef.current && autoFocus) { + inputRef.current.focus() + } + }, 100) + }, [autoFocus]) + const [shown, toggleShown] = useState(false) useEffect(() => { toggleShown(!hidden) @@ -881,6 +893,8 @@ export const ProtectedInput = ({ value={value} disabled={disabled} onBlur={onBlur} + autoFocus={autoFocus} + ref={inputRef} /> ) => void isRequiredField?: boolean + autoFocus?: boolean } diff --git a/src/components/notifications/ConfigurationDrawerModal.tsx b/src/components/notifications/ConfigurationDrawerModal.tsx index 013a789daa..5f02af1ba6 100644 --- a/src/components/notifications/ConfigurationDrawerModal.tsx +++ b/src/components/notifications/ConfigurationDrawerModal.tsx @@ -7,6 +7,7 @@ import { Progressing, } from '@devtron-labs/devtron-fe-common-lib' import { ReactComponent as Close } from '@Icons/ic-close.svg' +import { useEffect, useRef } from 'react' import { ConfigurationsTabTypes } from './constants' import { ConfigurationTabDrawerModalProps } from './types' import { getTabText } from './notifications.util' @@ -19,6 +20,22 @@ export const ConfigurationTabDrawerModal = ({ saveConfigModal, disableSave, }: ConfigurationTabDrawerModalProps) => { + const configRef = useRef(null) + + const escKeyPressHandler = (evt): void => { + if (evt && evt.key === 'Escape') { + evt.preventDefault() + closeModal() + } + } + + useEffect(() => { + document.addEventListener('keydown', escKeyPressHandler) + return (): void => { + document.removeEventListener('keydown', escKeyPressHandler) + } + }, [escKeyPressHandler]) + const renderLoadingState = () => (
@@ -57,6 +74,7 @@ export const ConfigurationTabDrawerModal = ({

Configure {getTabText(modal)}

diff --git a/src/components/notifications/ConfigurationTab.tsx b/src/components/notifications/ConfigurationTab.tsx index 2e9e6d4785..84444d71ae 100644 --- a/src/components/notifications/ConfigurationTab.tsx +++ b/src/components/notifications/ConfigurationTab.tsx @@ -253,7 +253,7 @@ export const ConfigurationTab = () => { } return ( -
+
diff --git a/src/components/notifications/ConfigurationTabsSwitcher.tsx b/src/components/notifications/ConfigurationTabsSwitcher.tsx index c0c5d0aadf..51ca694b3e 100644 --- a/src/components/notifications/ConfigurationTabsSwitcher.tsx +++ b/src/components/notifications/ConfigurationTabsSwitcher.tsx @@ -31,7 +31,7 @@ export const ConfigurationTabSwitcher = () => { }) } return ( -
+
{getConfigurationTabTextWithIcon().map((tab, index) => ( + + + ) + return (
@@ -260,18 +285,27 @@ export const ConfigurationTab = () => { {renderModal()} - {state.confirmation && ( - - )} + + + {state.showCannotDeleteDialogModal && renderCannotDeleteDialogModal()}
) } diff --git a/src/components/notifications/ConfigurationTabsSwitcher.tsx b/src/components/notifications/ConfigurationTabsSwitcher.tsx index 51ca694b3e..85a947d5fa 100644 --- a/src/components/notifications/ConfigurationTabsSwitcher.tsx +++ b/src/components/notifications/ConfigurationTabsSwitcher.tsx @@ -37,14 +37,13 @@ export const ConfigurationTabSwitcher = () => { ))}
diff --git a/src/components/notifications/Notifications.tsx b/src/components/notifications/Notifications.tsx index 3a6ad14471..d4da9c3310 100644 --- a/src/components/notifications/Notifications.tsx +++ b/src/components/notifications/Notifications.tsx @@ -30,7 +30,7 @@ interface NotificationsProps extends RouteComponentProps<{}> { export default class Notifications extends Component { renderNotificationHeader() { return ( -
+
@@ -208,7 +208,7 @@ const SESConfigModal = ({ value={form.secretKey} onChange={handleInputChange} onBlur={handleBlur} - placeholder="Secret Access Key" + placeholder="Enter Secret access Key" isRequiredField error={isFormValid.secretKey.message} /> @@ -218,7 +218,7 @@ const SESConfigModal = ({ classNamePrefix="add-ses-aws-region" required value={form.region} - placeholder="Select AWS Region" + placeholder="Select region" onBlur={handleAWSBlur} onChange={handleAWSRegionChange} options={awsRegionListParsed} @@ -235,7 +235,7 @@ const SESConfigModal = ({ name={ConfigurationFieldKeys.FROM_EMAIL} value={form.fromEmail} onBlur={handleBlur} - placeholder="Email" + placeholder="Enter sender's email" onChange={handleInputChange} isRequiredField error={isFormValid.fromEmail.message} diff --git a/src/components/notifications/SESConfigurationTable.tsx b/src/components/notifications/SESConfigurationTable.tsx index 85a65374ba..f4e835a7a2 100644 --- a/src/components/notifications/SESConfigurationTable.tsx +++ b/src/components/notifications/SESConfigurationTable.tsx @@ -31,35 +31,39 @@ const SESConfigurationTable = ({ state, deleteClickHandler }: ConfigurationTable } return ( -
-
+
+

Name

Access Key Id

Sender's Email

- - {sesConfigurationList.map((sesConfig) => ( -
- {getConfigTabIcons(ConfigurationsTabTypes.SES)} -
- {renderText(sesConfig.name, true, onClickSESConfigEdit(sesConfig.id))} - {renderDefaultTag(sesConfig.isDefault)} +
+ {sesConfigurationList.map((sesConfig) => ( +
+ {getConfigTabIcons(ConfigurationsTabTypes.SES)} +
+ {renderText(sesConfig.name, true, onClickSESConfigEdit(sesConfig.id))} + {renderDefaultTag(sesConfig.isDefault)} +
+ {renderText(sesConfig.accessKeyId)} + {renderText(sesConfig.email)} +
- {renderText(sesConfig.accessKeyId)} - {renderText(sesConfig.email)} - -
- ))} + ))} +
) } diff --git a/src/components/notifications/SMTPConfigurationTable.tsx b/src/components/notifications/SMTPConfigurationTable.tsx index 7ef94780e4..40c15de0ac 100644 --- a/src/components/notifications/SMTPConfigurationTable.tsx +++ b/src/components/notifications/SMTPConfigurationTable.tsx @@ -38,7 +38,7 @@ export const SMTPConfigurationTable = ({ state, deleteClickHandler }: Configurat

Sender' Email

-
+
{smtpConfigurationList.map((smtpConfig) => (
= ({ }) } + const renderInfoText = () => ( + + Learn how to setup slack webhooks + + ) + const renderWebhookUrlLabel = () => ( -
+
Webhook URL
-
- - Learn how to setup slack webhooks - - } - > -
- -
-
-
+ +
+ +
+
) diff --git a/src/components/notifications/SlackConfigurationTable.tsx b/src/components/notifications/SlackConfigurationTable.tsx index f58832010e..07c29ed0da 100644 --- a/src/components/notifications/SlackConfigurationTable.tsx +++ b/src/components/notifications/SlackConfigurationTable.tsx @@ -30,14 +30,14 @@ const SlackConfigurationTable = ({ state, deleteClickHandler }: ConfigurationTab } return ( -
-
+
+

Name

Webhook URL

-
+
{slackConfigurationList.map((slackConfig) => (
Webhook URL

- {webhookConfigurationList.map((webhookConfig) => ( -
- {getConfigTabIcons(ConfigurationsTabTypes.WEBHOOK)} - {renderText( - webhookConfig.webhookUrl, - true, - onClickWebhookConfigEdit(webhookConfig.id), - `webhook-config-name-${webhookConfig.name}`, - )} - {renderText(webhookConfig.name, false, noop, `webhook-url-${webhookConfig.webhookUrl}`)} - + {webhookConfigurationList.map((webhookConfig) => ( +
+ {getConfigTabIcons(ConfigurationsTabTypes.WEBHOOK)} + {renderText( + webhookConfig.webhookUrl, + true, + onClickWebhookConfigEdit(webhookConfig.id), + `webhook-config-name-${webhookConfig.name}`, )} - rootClassName="webhook-config-table__action" - modal={ConfigurationsTabTypes.WEBHOOK} - /> -
- ))} + {renderText(webhookConfig.name, false, noop, `webhook-url-${webhookConfig.webhookUrl}`)} + +
+ ))} +
) } diff --git a/src/components/notifications/notifications.util.tsx b/src/components/notifications/notifications.util.tsx index ebccc31a87..2516af7625 100644 --- a/src/components/notifications/notifications.util.tsx +++ b/src/components/notifications/notifications.util.tsx @@ -247,19 +247,6 @@ export const renderDefaultTag = (isDefault: boolean) => { return null } -export const getDeleteConfigComponent = (showDeleteConfigModalType: ConfigurationsTabTypes): string => { - if (showDeleteConfigModalType === ConfigurationsTabTypes.SLACK) { - return ConfigurationsTabTypes.SLACK - } - if (showDeleteConfigModalType === ConfigurationsTabTypes.SES) { - return ConfigurationsTabTypes.SES - } - if (showDeleteConfigModalType === ConfigurationsTabTypes.WEBHOOK) { - return ConfigurationsTabTypes.WEBHOOK - } - return ConfigurationsTabTypes.SMTP -} - export const getTableHeaders = (): DynamicDataTableHeaderType[] => [ { label: 'Header key', key: 'key', width: '1fr' }, { label: 'Value', key: 'value', width: '1fr' }, diff --git a/src/components/notifications/types.tsx b/src/components/notifications/types.tsx index 7f75b84d0d..a343143788 100644 --- a/src/components/notifications/types.tsx +++ b/src/components/notifications/types.tsx @@ -68,7 +68,7 @@ export interface SMTPConfigModalState { } export interface ConfigurationTabState { - view: string + isLoading: boolean sesConfigId: number slackConfigId: number smtpConfigId: number @@ -85,14 +85,13 @@ export interface ConfigurationTabState { slackConfigurationList: Array<{ id: number; slackChannel: string; projectId: number; webhookUrl: string }> webhookConfigurationList: Array<{ id: number; name: string; webhookUrl: string }> abortAPI: boolean - deleting: boolean confirmation: boolean sesConfig: any smtpConfig: any slackConfig: any webhookConfig: any - showDeleteConfigModalType: ConfigurationsTabTypes activeTab?: ConfigurationsTabTypes + showCannotDeleteDialogModal: boolean } export interface ConfigurationTableProps { diff --git a/src/css/base.scss b/src/css/base.scss index 21b717f540..67b3a64605 100644 --- a/src/css/base.scss +++ b/src/css/base.scss @@ -3460,7 +3460,7 @@ textarea, // Height in pixels .dc__height-inherit { height: inherit; - } +} .dc__height-auto { height: auto; @@ -3470,6 +3470,7 @@ textarea, } } + .dc__min-width-fit-content { min-width: fit-content !important; } @@ -4384,6 +4385,10 @@ textarea::placeholder { background-color: var(--N50); } +.dc__hover-text-n90:hover { + color: var(--N900) !important; +} + .dc__bg-g5 { background-color: var(--G500) !important; } @@ -5350,9 +5355,14 @@ details[open] { background: inherit; border: none; padding: inherit; - white-space: pre-wrap; /* Since CSS 2.1 */ - white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ - white-space: -pre-wrap; /* Opera 4-6 */ - white-space: -o-pre-wrap; /* Opera 7 */ - word-wrap: break-word; /* Internet Explorer 5.5+ */ -} + white-space: pre-wrap; + /* Since CSS 2.1 */ + white-space: -moz-pre-wrap; + /* Mozilla, since 1999 */ + white-space: -pre-wrap; + /* Opera 4-6 */ + white-space: -o-pre-wrap; + /* Opera 7 */ + word-wrap: break-word; + /* Internet Explorer 5.5+ */ +} \ No newline at end of file From 40a6ec220c8b2e0379600c11fde6e4bc613d205d Mon Sep 17 00:00:00 2001 From: shivani170 Date: Wed, 8 Jan 2025 09:20:29 +0530 Subject: [PATCH 19/31] default css --- src/components/notifications/notifications.util.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/notifications/notifications.util.tsx b/src/components/notifications/notifications.util.tsx index 2516af7625..89cadb7564 100644 --- a/src/components/notifications/notifications.util.tsx +++ b/src/components/notifications/notifications.util.tsx @@ -242,7 +242,7 @@ export const renderText = (text: string, isLink: boolean = false, linkTo?: () => export const renderDefaultTag = (isDefault: boolean) => { if (isDefault) { - return Default + return Default } return null } From 5a2cbcabab91bb8b697f2bf2bd0912cf2307ac42 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Wed, 8 Jan 2025 09:31:57 +0530 Subject: [PATCH 20/31] chore: unnecessary code removed --- .../notifications/ConfigurationDrawerModal.tsx | 2 +- src/components/notifications/ConfigurationTab.tsx | 13 +++++-------- src/components/notifications/types.tsx | 4 ---- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/components/notifications/ConfigurationDrawerModal.tsx b/src/components/notifications/ConfigurationDrawerModal.tsx index 5f02af1ba6..2f048e0771 100644 --- a/src/components/notifications/ConfigurationDrawerModal.tsx +++ b/src/components/notifications/ConfigurationDrawerModal.tsx @@ -43,7 +43,7 @@ export const ConfigurationTabDrawerModal = ({ ) const renderFooter = () => ( -
+
) diff --git a/src/components/notifications/SlackConfigurationTable.tsx b/src/components/notifications/SlackConfigurationTable.tsx index 07c29ed0da..b8c01890f8 100644 --- a/src/components/notifications/SlackConfigurationTable.tsx +++ b/src/components/notifications/SlackConfigurationTable.tsx @@ -44,7 +44,9 @@ const SlackConfigurationTable = ({ state, deleteClickHandler }: ConfigurationTab className="slack-config-grid configuration-tab__table-row dc__gap-16 dc__hover-n50" > {getConfigTabIcons(ConfigurationsTabTypes.SLACK)} - {renderText(slackConfig.slackChannel)} +
+ {renderText(slackConfig.slackChannel, true, onClickSlackConfigEdit(slackConfig.id))} +
{renderText(slackConfig.webhookUrl)} void + isDefault: boolean +} From 1ab1175e1f0171179edd770b12e6cd1756f898e2 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Wed, 8 Jan 2025 12:29:13 +0530 Subject: [PATCH 22/31] chore: css fixes for confirmation modal --- .../notifications/ConfigurationTab.tsx | 30 +++++++++---------- .../notifications/SlackConfigModal.tsx | 20 ++----------- .../notifications/notifications.scss | 1 - .../notifications/notifications.util.tsx | 12 ++++---- 4 files changed, 24 insertions(+), 39 deletions(-) diff --git a/src/components/notifications/ConfigurationTab.tsx b/src/components/notifications/ConfigurationTab.tsx index fdf34cb535..5219ce2716 100644 --- a/src/components/notifications/ConfigurationTab.tsx +++ b/src/components/notifications/ConfigurationTab.tsx @@ -22,10 +22,8 @@ import { ConfirmationModal, ConfirmationModalVariantType, ServerErrors, - ConfirmationDialog, } from '@devtron-labs/devtron-fe-common-lib' import { Route, Switch, useHistory, useLocation, useRouteMatch } from 'react-router-dom' -import info from '@Icons/ic-info-filled.svg' import { SlackConfigModal } from './SlackConfigModal' import SESConfigModal from './SESConfigModal' import { @@ -45,8 +43,7 @@ import { WebhookConfigurationTable } from './WebhookConfigurationTable' import SESConfigurationTable from './SESConfigurationTable' import { SMTPConfigurationTable } from './SMTPConfigurationTable' import { ConfigurationTabSwitcher } from './ConfigurationTabsSwitcher' -import { ConfigurationFieldKeys, ConfigurationsTabTypes } from './constants' -import { getTabText } from './notifications.util' +import { ConfigurationFieldKeys, ConfigurationsTabTypes, ConfigurationTabText } from './constants' export const ConfigurationTab = () => { const { path } = useRouteMatch() @@ -258,16 +255,19 @@ export const ConfigurationTab = () => { } const renderCannotDeleteDialogModal = () => ( - - - -

{DC_CONFIGURATION_CONFIRMATION_MESSAGE}

- - - -
+ ) if (state.isLoading) { @@ -284,7 +284,7 @@ export const ConfigurationTab = () => { = ({ target="_blank" rel="noopener noreferrer" > - Learn how to setup slack webhooks + How to setup slack webhooks? ) const renderWebhookUrlLabel = () => ( -
+
Webhook URL
- -
- -
-
+ {renderInfoText()}
) diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 96b2094f31..fd27328956 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -177,7 +177,6 @@ .configuration-tab__table-row:hover .webhook-config-table__action, .configuration-tab__table-row:hover .smtp-config-table__action { display: flex; - background-color: var(--window-bg); } .slack-config-table__action, diff --git a/src/components/notifications/notifications.util.tsx b/src/components/notifications/notifications.util.tsx index 89cadb7564..4af2f06631 100644 --- a/src/components/notifications/notifications.util.tsx +++ b/src/components/notifications/notifications.util.tsx @@ -161,7 +161,7 @@ export const renderPipelineTypeIcon = (row) => { return } -export const getConfigTabIcons = (tab: ConfigurationsTabTypes, size: number = 20) => { +export const getConfigTabIcons = (tab: ConfigurationsTabTypes, size: number = 24) => { switch (tab) { case ConfigurationsTabTypes.SES: return @@ -179,22 +179,22 @@ export const getConfigTabIcons = (tab: ConfigurationsTabTypes, size: number = 20 export const getConfigurationTabTextWithIcon = () => [ { label: ConfigurationTabText.SES, - icon: getConfigTabIcons(ConfigurationsTabTypes.SES), + icon: getConfigTabIcons(ConfigurationsTabTypes.SES, 20), link: ConfigurationsTabTypes.SES, }, { label: ConfigurationTabText.SMTP, - icon: getConfigTabIcons(ConfigurationsTabTypes.SMTP), + icon: getConfigTabIcons(ConfigurationsTabTypes.SMTP, 20), link: ConfigurationsTabTypes.SMTP, }, { label: ConfigurationTabText.SLACK, - icon: getConfigTabIcons(ConfigurationsTabTypes.SLACK), + icon: getConfigTabIcons(ConfigurationsTabTypes.SLACK, 20), link: ConfigurationsTabTypes.SLACK, }, { label: ConfigurationTabText.WEBHOOK, - icon: getConfigTabIcons(ConfigurationsTabTypes.WEBHOOK), + icon: getConfigTabIcons(ConfigurationsTabTypes.WEBHOOK, 20), link: ConfigurationsTabTypes.WEBHOOK, }, ] @@ -242,7 +242,7 @@ export const renderText = (text: string, isLink: boolean = false, linkTo?: () => export const renderDefaultTag = (isDefault: boolean) => { if (isDefault) { - return Default + return Default } return null } From f01f8b4941bc90becbf9457a190add9cd4f8084a Mon Sep 17 00:00:00 2001 From: shivani170 Date: Wed, 8 Jan 2025 14:25:20 +0530 Subject: [PATCH 23/31] chore: validation added --- .../ConfigurationDrawerModal.tsx | 2 +- .../notifications/SESConfigModal.tsx | 23 +++++++++++++++++-- .../notifications/WebhookConfigModal.tsx | 4 ++-- .../notifications/notifications.scss | 7 ++++++ 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/components/notifications/ConfigurationDrawerModal.tsx b/src/components/notifications/ConfigurationDrawerModal.tsx index 2f048e0771..5aea91be4e 100644 --- a/src/components/notifications/ConfigurationDrawerModal.tsx +++ b/src/components/notifications/ConfigurationDrawerModal.tsx @@ -73,7 +73,7 @@ export const ConfigurationTabDrawerModal = ({ return (
diff --git a/src/components/notifications/SESConfigModal.tsx b/src/components/notifications/SESConfigModal.tsx index e609314e7e..b7aadde035 100644 --- a/src/components/notifications/SESConfigModal.tsx +++ b/src/components/notifications/SESConfigModal.tsx @@ -144,18 +144,38 @@ const SESConfigModal = ({ } } + const getAllFieldsValidated = () => { + const { configName, accessKey, secretKey, region, fromEmail } = form + return ( + !!configName && + !!accessKey && + !!secretKey && + !!region && + !!fromEmail && + getFormValidated(isFormValid, fromEmail) + ) + } const saveSESConfig = async () => { - if (!isFormValid) { + if (!getAllFieldsValidated()) { setForm((prevForm) => ({ ...prevForm, isLoading: false, isError: true, })) + setFormValid({ + ...isFormValid, + configName: validateKeyValueConfig(ConfigurationFieldKeys.CONFIG_NAME, form.configName), + accessKey: validateKeyValueConfig(ConfigurationFieldKeys.ACCESS_KEY, form.accessKey), + secretKey: validateKeyValueConfig(ConfigurationFieldKeys.SECRET_KEY, form.secretKey), + region: validateKeyValueConfig(ConfigurationFieldKeys.REGION, form.region?.value), + fromEmail: validateKeyValueConfig(ConfigurationFieldKeys.FROM_EMAIL, form.fromEmail), + }) // eslint-disable-next-line @typescript-eslint/no-floating-promises ToastManager.showToast({ variant: ToastVariantType.error, description: 'Some required fields are missing or Invalid', }) + return } @@ -255,7 +275,6 @@ const SESConfigModal = ({ modal={ConfigurationsTabTypes.SES} isLoading={form.isLoading} saveConfigModal={saveSESConfig} - disableSave={!getFormValidated(isFormValid, form.fromEmail)} /> ) } diff --git a/src/components/notifications/WebhookConfigModal.tsx b/src/components/notifications/WebhookConfigModal.tsx index 8a62a1dd6b..ce17af5437 100644 --- a/src/components/notifications/WebhookConfigModal.tsx +++ b/src/components/notifications/WebhookConfigModal.tsx @@ -199,8 +199,8 @@ export const WebhookConfigModal = ({ setFormValid((prev) => ({ ...prev, [name]: validateKeyValueConfig(name, value) })) } const renderWebhookModal = () => ( -
-
+
+
Date: Wed, 8 Jan 2025 14:31:22 +0530 Subject: [PATCH 24/31] chore: validation added --- .../notifications/SMTPConfigModal.tsx | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/components/notifications/SMTPConfigModal.tsx b/src/components/notifications/SMTPConfigModal.tsx index e82b6cf83e..fbb37e9578 100644 --- a/src/components/notifications/SMTPConfigModal.tsx +++ b/src/components/notifications/SMTPConfigModal.tsx @@ -82,12 +82,34 @@ export const SMTPConfigModal = ({ } } + const getAllFieldsValidated = () => { + const { configName, host, port, authUser, authPassword, fromEmail } = form + return ( + !!configName && + !!host && + !!port && + !!authUser && + !!authPassword && + !!fromEmail && + getFormValidated(isFormValid, fromEmail) + ) + } + const saveSMTPConfig = () => { - if (!getFormValidated(isFormValid, form.fromEmail)) { + if (!getAllFieldsValidated()) { ToastManager.showToast({ variant: ToastVariantType.error, description: 'Some required fields are missing or Invalid', }) + setFormValid((prevValid) => ({ + ...prevValid, + configName: validateKeyValueConfig(ConfigurationFieldKeys.CONFIG_NAME, form.configName), + host: validateKeyValueConfig(ConfigurationFieldKeys.HOST, form.host), + port: validateKeyValueConfig(ConfigurationFieldKeys.PORT, form.port), + authUser: validateKeyValueConfig(ConfigurationFieldKeys.AUTH_USER, form.authUser), + authPassword: validateKeyValueConfig(ConfigurationFieldKeys.AUTH_PASSWORD, form.authPassword), + fromEmail: validateKeyValueConfig(ConfigurationFieldKeys.FROM_EMAIL, form.fromEmail), + })) setForm((prevForm) => ({ ...prevForm, isLoading: false, isError: true })) return } @@ -202,7 +224,6 @@ export const SMTPConfigModal = ({ modal={ConfigurationsTabTypes.SMTP} isLoading={form.isLoading} saveConfigModal={saveSMTPConfig} - disableSave={!getFormValidated(isFormValid, form.fromEmail)} /> ) } From df64c924d1918f4f4fd4b2aaab852f39452adab0 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Wed, 8 Jan 2025 15:24:54 +0530 Subject: [PATCH 25/31] chore: calidation required for webhook & slack modal --- .../notifications/SlackConfigModal.tsx | 1 - .../notifications/WebhookConfigModal.tsx | 45 +++++++++++-------- .../WebhookConfigurationTable.tsx | 2 +- .../notifications/notifications.util.tsx | 17 ++++++- src/components/notifications/types.tsx | 7 ++- 5 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/components/notifications/SlackConfigModal.tsx b/src/components/notifications/SlackConfigModal.tsx index aa3fb93ffd..cf27741090 100644 --- a/src/components/notifications/SlackConfigModal.tsx +++ b/src/components/notifications/SlackConfigModal.tsx @@ -225,7 +225,6 @@ export const SlackConfigModal: React.FC = ({ modal={ConfigurationsTabTypes.SLACK} isLoading={form.isLoading} saveConfigModal={saveSlackConfig} - disableSave={!getFormValidated(isFormValid)} /> ) } diff --git a/src/components/notifications/WebhookConfigModal.tsx b/src/components/notifications/WebhookConfigModal.tsx index ce17af5437..633fdcc753 100644 --- a/src/components/notifications/WebhookConfigModal.tsx +++ b/src/components/notifications/WebhookConfigModal.tsx @@ -34,7 +34,7 @@ import { import { ConfigurationTabDrawerModal } from './ConfigurationDrawerModal' import { WebhookConfigModalProps, WebhookDataRowType } from './types' import { WebhookConfigDynamicDataTable } from './WebhookConfigDynamicDataTable' -import { getFormValidated, validateKeyValueConfig } from './notifications.util' +import { validateKeyValueConfig, validatePayloadField } from './notifications.util' export const WebhookConfigModal = ({ webhookConfigId, @@ -113,19 +113,6 @@ export const WebhookConfigModal = ({ } } - const validateField = (field, value: string) => { - let isValidField = true - let errorMessage = '' - // Validate if the value is a valid JSON string - try { - JSON.parse(value) - } catch { - isValidField = false - errorMessage = 'Invalid JSON format.' - } - setFormValid((prev) => ({ ...prev, [field]: { isValid: isValidField, message: errorMessage } })) - } - const handleInputChange = (event) => { const { name, value } = event.target setForm((prev) => ({ ...prev, [name]: value })) @@ -134,7 +121,7 @@ export const WebhookConfigModal = ({ const handlePayloadChange = (value) => { setForm((prev) => ({ ...prev, payload: value })) - validateField(ConfigurationFieldKeys.PAYLOAD, value) + setFormValid((prev) => ({ ...prev, payload: validatePayloadField(value) })) } const renderDataList = () => ( @@ -170,11 +157,34 @@ export const WebhookConfigModal = ({
) + const getAllFieldsValidated = (): boolean => { + const { configName, webhookUrl, payload } = form + return !!configName && !!webhookUrl && validatePayloadField(payload).isValid + } + const saveWebhookConfig = async () => { - setForm((prev) => ({ ...prev, isLoading: true })) + if (!getAllFieldsValidated()) { + setFormValid({ + ...isFormValid, + configName: validateKeyValueConfig(ConfigurationFieldKeys.CONFIG_NAME, form.configName), + webhookUrl: validateKeyValueConfig(ConfigurationFieldKeys.WEBHOOK_URL, form.webhookUrl), + payload: validatePayloadField(form.payload), + }) + // eslint-disable-next-line @typescript-eslint/no-floating-promises + ToastManager.showToast({ + variant: ToastVariantType.error, + description: 'Some required fields are missing or Invalid', + }) + } - if (!Object.values(isFormValid).every((field) => field)) { + if (!getAllFieldsValidated()) { setForm((prev) => ({ ...prev, isLoading: false })) + setFormValid({ + ...isFormValid, + configName: validateKeyValueConfig(ConfigurationFieldKeys.CONFIG_NAME, form.configName), + webhookUrl: validateKeyValueConfig(ConfigurationFieldKeys.WEBHOOK_URL, form.webhookUrl), + payload: validatePayloadField(form.payload), + }) return } @@ -251,7 +261,6 @@ export const WebhookConfigModal = ({ modal={ConfigurationsTabTypes.WEBHOOK} isLoading={form.isLoading} saveConfigModal={saveWebhookConfig} - disableSave={!getFormValidated(isFormValid)} /> ) } diff --git a/src/components/notifications/WebhookConfigurationTable.tsx b/src/components/notifications/WebhookConfigurationTable.tsx index 88be3583f6..26ce598ba9 100644 --- a/src/components/notifications/WebhookConfigurationTable.tsx +++ b/src/components/notifications/WebhookConfigurationTable.tsx @@ -45,7 +45,7 @@ export const WebhookConfigurationTable = ({ state, deleteClickHandler }: Configu > {getConfigTabIcons(ConfigurationsTabTypes.WEBHOOK)} {renderText( - webhookConfig.webhookUrl, + webhookConfig.name, true, onClickWebhookConfigEdit(webhookConfig.id), `webhook-config-name-${webhookConfig.name}`, diff --git a/src/components/notifications/notifications.util.tsx b/src/components/notifications/notifications.util.tsx index 4af2f06631..d09a61a024 100644 --- a/src/components/notifications/notifications.util.tsx +++ b/src/components/notifications/notifications.util.tsx @@ -34,7 +34,7 @@ import { } from '@devtron-labs/devtron-fe-common-lib' import { ConfigurationFieldKeys, ConfigurationsTabTypes, ConfigurationTabText } from './constants' import { validateEmail } from '../common' -import { FormValidation, WebhookDataRowType, WebhookHeaderKeyType, WebhookRowCellType } from './types' +import { FormError, FormValidation, WebhookDataRowType, WebhookHeaderKeyType, WebhookRowCellType } from './types' import { REQUIRED_FIELD_MSG } from '@Config/constantMessaging' export const multiSelectStyles = { @@ -301,7 +301,7 @@ export const getEmptyVariableDataRow = (): WebhookDataRowType => { export const validateKeyValueConfig = ( key: ConfigurationFieldKeys, value: string, -): { isValid: boolean; message: string } => { +): FormError => { if (!value) { return { isValid: false, message: REQUIRED_FIELD_MSG } } @@ -338,3 +338,16 @@ export const getTabText = (tab: ConfigurationsTabTypes) => { return '' } } + +export const validatePayloadField = (value: string): FormError => { + let isValid = true + let errorMessage = '' + // Validate if the value is a valid JSON string + try { + JSON.parse(value) + } catch { + isValid = false + errorMessage = 'Invalid JSON format.' + } + return { isValid, message: errorMessage } +} diff --git a/src/components/notifications/types.tsx b/src/components/notifications/types.tsx index e6db43493b..049f423df5 100644 --- a/src/components/notifications/types.tsx +++ b/src/components/notifications/types.tsx @@ -214,8 +214,13 @@ export type HandleRowUpdateActionProps = VariableDataTableAction & { rowId: string | number } +export interface FormError { + isValid: boolean + message: string +} + export type FormValidation = { - [key: string]: { isValid: boolean; message: string } + [key: string]: FormError } export interface DefaultCheckboxProps { From b2ad1574113f1039f27bfe96f7afc545f6e2d4f2 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Wed, 8 Jan 2025 15:55:09 +0530 Subject: [PATCH 26/31] fix: character limit validation added --- .../ConfigurationDrawerModal.tsx | 9 +-------- .../notifications/SlackConfigModal.tsx | 19 +++++++++++++++---- .../notifications/notifications.util.tsx | 13 ++++++++++++- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/components/notifications/ConfigurationDrawerModal.tsx b/src/components/notifications/ConfigurationDrawerModal.tsx index 5aea91be4e..367625024a 100644 --- a/src/components/notifications/ConfigurationDrawerModal.tsx +++ b/src/components/notifications/ConfigurationDrawerModal.tsx @@ -4,7 +4,6 @@ import { ButtonVariantType, ComponentSizeType, Drawer, - Progressing, } from '@devtron-labs/devtron-fe-common-lib' import { ReactComponent as Close } from '@Icons/ic-close.svg' import { useEffect, useRef } from 'react' @@ -36,12 +35,6 @@ export const ConfigurationTabDrawerModal = ({ } }, [escKeyPressHandler]) - const renderLoadingState = () => ( -
- -
- ) - const renderFooter = () => (
- {isLoading ? renderLoadingState() : renderModalContent()} + {renderModalContent()}
) diff --git a/src/components/notifications/SlackConfigModal.tsx b/src/components/notifications/SlackConfigModal.tsx index cf27741090..ebf8a928a7 100644 --- a/src/components/notifications/SlackConfigModal.tsx +++ b/src/components/notifications/SlackConfigModal.tsx @@ -15,7 +15,7 @@ import { ReactComponent as ICHelpOutline } from '../../assets/icons/ic-help-outl import { SlackConfigModalProps } from './types' import { ConfigurationFieldKeys, ConfigurationsTabTypes, DefaultSlackKeys, DefaultSlackValidations } from './constants' import { ConfigurationTabDrawerModal } from './ConfigurationDrawerModal' -import { getFormValidated, validateKeyValueConfig } from './notifications.util' +import { renderErrorToast, validateKeyValueConfig } from './notifications.util' export const SlackConfigModal: React.FC = ({ slackConfigId, @@ -72,9 +72,21 @@ export const SlackConfigModal: React.FC = ({ } } + const getAllFieldsValidated = (): boolean => { + const { configName, webhookUrl } = form + return !!configName && !!webhookUrl && !!selectedProject.value + } + const saveSlackConfig = () => { - if (!getFormValidated(isFormValid)) { + if (!getAllFieldsValidated()) { setForm((prevForm) => ({ ...prevForm, isLoading: false, isError: true })) + setFormValid((prevValid) => ({ + ...prevValid, + configName: validateKeyValueConfig(ConfigurationFieldKeys.CONFIG_NAME, form.configName), + webhookUrl: validateKeyValueConfig(ConfigurationFieldKeys.WEBHOOK_URL, form.webhookUrl), + projectId: validateKeyValueConfig(ConfigurationFieldKeys.PROJECT_ID, selectedProject?.value), + })) + renderErrorToast() return } @@ -85,11 +97,9 @@ export const SlackConfigModal: React.FC = ({ id: slackConfigId, } } - setForm((prevForm) => ({ ...prevForm, isLoading: true })) const promise = slackConfigId ? updateSlackConfiguration(requestBody) : saveSlackConfiguration(requestBody) - promise .then(() => { setForm((prevForm) => ({ ...prevForm, isLoading: false, isError: false })) @@ -102,6 +112,7 @@ export const SlackConfigModal: React.FC = ({ }) .catch((error) => { showError(error) + setForm((prevForm) => ({ ...prevForm, isLoading: false })) }) } diff --git a/src/components/notifications/notifications.util.tsx b/src/components/notifications/notifications.util.tsx index d09a61a024..b8cea41636 100644 --- a/src/components/notifications/notifications.util.tsx +++ b/src/components/notifications/notifications.util.tsx @@ -30,6 +30,8 @@ import { DynamicDataTableRowDataType, DynamicDataTableRowType, getUniqueId, + ToastManager, + ToastVariantType, Tooltip, } from '@devtron-labs/devtron-fe-common-lib' import { ConfigurationFieldKeys, ConfigurationsTabTypes, ConfigurationTabText } from './constants' @@ -305,6 +307,9 @@ export const validateKeyValueConfig = ( if (!value) { return { isValid: false, message: REQUIRED_FIELD_MSG } } + if (value.length > 250) { + return { isValid: false, message: 'Value should be less than 250 characters' } + } if (key === ConfigurationFieldKeys.FROM_EMAIL) { return { isValid: validateEmail(value), message: validateEmail(value) ? '' : 'Invalid email' } } @@ -339,7 +344,7 @@ export const getTabText = (tab: ConfigurationsTabTypes) => { } } -export const validatePayloadField = (value: string): FormError => { +export const validatePayloadField = (value: string): FormError => { let isValid = true let errorMessage = '' // Validate if the value is a valid JSON string @@ -351,3 +356,9 @@ export const validatePayloadField = (value: string): FormError => { } return { isValid, message: errorMessage } } + +export const renderErrorToast = () => + ToastManager.showToast({ + variant: ToastVariantType.error, + description: 'Some required fields are missing or Invalid', + }) \ No newline at end of file From 4bb9d21f3ff77a22769e391b104def5503296b6c Mon Sep 17 00:00:00 2001 From: shivani170 Date: Wed, 8 Jan 2025 16:46:35 +0530 Subject: [PATCH 27/31] fix: feedback fixes --- .../ConfigurationDrawerModal.tsx | 20 +------------------ .../notifications/SlackConfigModal.tsx | 10 +++++----- .../notifications/notifications.util.tsx | 17 ++++++---------- 3 files changed, 12 insertions(+), 35 deletions(-) diff --git a/src/components/notifications/ConfigurationDrawerModal.tsx b/src/components/notifications/ConfigurationDrawerModal.tsx index 367625024a..eb78e7356c 100644 --- a/src/components/notifications/ConfigurationDrawerModal.tsx +++ b/src/components/notifications/ConfigurationDrawerModal.tsx @@ -6,7 +6,6 @@ import { Drawer, } from '@devtron-labs/devtron-fe-common-lib' import { ReactComponent as Close } from '@Icons/ic-close.svg' -import { useEffect, useRef } from 'react' import { ConfigurationsTabTypes } from './constants' import { ConfigurationTabDrawerModalProps } from './types' import { getTabText } from './notifications.util' @@ -19,22 +18,6 @@ export const ConfigurationTabDrawerModal = ({ saveConfigModal, disableSave, }: ConfigurationTabDrawerModalProps) => { - const configRef = useRef(null) - - const escKeyPressHandler = (evt): void => { - if (evt && evt.key === 'Escape') { - evt.preventDefault() - closeModal() - } - } - - useEffect(() => { - document.addEventListener('keydown', escKeyPressHandler) - return (): void => { - document.removeEventListener('keydown', escKeyPressHandler) - } - }, [escKeyPressHandler]) - const renderFooter = () => (
) : (

{text || '-'} @@ -300,16 +301,10 @@ export const getEmptyVariableDataRow = (): WebhookDataRowType => { } } -export const validateKeyValueConfig = ( - key: ConfigurationFieldKeys, - value: string, -): FormError => { +export const validateKeyValueConfig = (key: ConfigurationFieldKeys, value: string): FormError => { if (!value) { return { isValid: false, message: REQUIRED_FIELD_MSG } } - if (value.length > 250) { - return { isValid: false, message: 'Value should be less than 250 characters' } - } if (key === ConfigurationFieldKeys.FROM_EMAIL) { return { isValid: validateEmail(value), message: validateEmail(value) ? '' : 'Invalid email' } } @@ -361,4 +356,4 @@ export const renderErrorToast = () => ToastManager.showToast({ variant: ToastVariantType.error, description: 'Some required fields are missing or Invalid', - }) \ No newline at end of file + }) From d5030140c8c0f9d2845f712d2060416c15092014 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Wed, 8 Jan 2025 16:48:31 +0530 Subject: [PATCH 28/31] fix: css fixes --- src/components/notifications/notifications.util.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/notifications/notifications.util.tsx b/src/components/notifications/notifications.util.tsx index b38b2b0f5e..08171d401d 100644 --- a/src/components/notifications/notifications.util.tsx +++ b/src/components/notifications/notifications.util.tsx @@ -230,7 +230,7 @@ export const renderText = (text: string, isLink: boolean = false, linkTo?: () =>

) - const renderModalContent = () => ( -
- {renderContent()} - {renderFooter()} -
- ) + const renderModalContent = () => { + if (isLoading) { + return + } + return ( +
+ {renderContent()} + {renderFooter()} +
+ ) + } + return (
{ - e.stopPropagation() - removeHeader(index) - } - - const handleInputChange = (e): void => { - const _headerData = { ...headerData } - _headerData[e.target.name] = e.target.value - setHeaderData(index, _headerData) - } - - return ( -
- - - -
- -
-
- ) -} diff --git a/src/components/notifications/SESConfigModal.tsx b/src/components/notifications/SESConfigModal.tsx index b7aadde035..a4dd24c21f 100644 --- a/src/components/notifications/SESConfigModal.tsx +++ b/src/components/notifications/SESConfigModal.tsx @@ -30,8 +30,13 @@ import { import { useHistory } from 'react-router-dom' import { saveEmailConfiguration, getSESConfiguration } from './notifications.service' import awsRegionList from '../common/awsRegionList.json' -import { SESConfigModalProps } from './types' -import { getFormValidated, getSESDefaultConfiguration, validateKeyValueConfig } from './notifications.util' +import { SESConfigModalProps, SESFormType } from './types' +import { + getFormValidated, + getSESDefaultConfiguration, + renderErrorToast, + validateKeyValueConfig, +} from './notifications.util' import { ConfigurationFieldKeys, ConfigurationsTabTypes, DefaultSESValidations } from './constants' import { ConfigurationTabDrawerModal } from './ConfigurationDrawerModal' import { DefaultCheckbox } from './DefaultCheckbox' @@ -46,7 +51,7 @@ const SESConfigModal = ({ const history = useHistory() const selectRef = useRef(null) - const [form, setForm] = useState(getSESDefaultConfiguration(shouldBeDefault)) + const [form, setForm] = useState(getSESDefaultConfiguration(shouldBeDefault)) const [isFormValid, setFormValid] = useState(DefaultSESValidations) const awsRegionListParsed = awsRegionList @@ -63,7 +68,6 @@ const SESConfigModal = ({ setForm({ ...response.result, isLoading: false, - isError: true, region: awsRegion, secretKey: DEFAULT_SECRET_PLACEHOLDER, // Masked secretKey for security }) @@ -167,15 +171,10 @@ const SESConfigModal = ({ configName: validateKeyValueConfig(ConfigurationFieldKeys.CONFIG_NAME, form.configName), accessKey: validateKeyValueConfig(ConfigurationFieldKeys.ACCESS_KEY, form.accessKey), secretKey: validateKeyValueConfig(ConfigurationFieldKeys.SECRET_KEY, form.secretKey), - region: validateKeyValueConfig(ConfigurationFieldKeys.REGION, form.region?.value), + region: validateKeyValueConfig(ConfigurationFieldKeys.REGION, form.region?.value?.toString()), fromEmail: validateKeyValueConfig(ConfigurationFieldKeys.FROM_EMAIL, form.fromEmail), }) - // eslint-disable-next-line @typescript-eslint/no-floating-promises - ToastManager.showToast({ - variant: ToastVariantType.error, - description: 'Some required fields are missing or Invalid', - }) - + renderErrorToast() return } @@ -207,7 +206,7 @@ const SESConfigModal = ({ placeholder="Configuration name" autoFocus isRequiredField - error={isFormValid.configName.message} + error={isFormValid[ConfigurationFieldKeys.CONFIG_NAME].message} /> @@ -257,7 +256,7 @@ const SESConfigModal = ({ placeholder="Enter sender's email" onChange={handleInputChange} isRequiredField - error={isFormValid.fromEmail.message} + error={isFormValid[ConfigurationFieldKeys.FROM_EMAIL].message} helperText="This email must be verified with SES." /> { const history = useHistory() - const [form, setForm] = useState(getSMTPDefaultConfiguration(shouldBeDefault)) + const [form, setForm] = useState(getSMTPDefaultConfiguration(shouldBeDefault)) const [isFormValid, setFormValid] = useState(DefaultSMTPValidation) + const fetchSMTPConfig = async () => { + setForm((prevForm) => ({ ...prevForm, isLoading: true })) + try { + const response = await getSMTPConfiguration(smtpConfigId) + setForm({ + ...response.result, + isLoading: false, + port: response.result.port.toString(), + }) + setFormValid(DefaultSMTPValidation) + } catch (error) { + showError(error) + setForm((prevForm) => ({ ...prevForm, isLoading: false })) + } + } + useEffect(() => { if (smtpConfigId) { - getSMTPConfiguration(smtpConfigId) - .then((response) => { - setForm({ - ...response.result, - isLoading: false, - isError: true, - port: response.result.port.toString(), - }) - setFormValid(DefaultSMTPValidation) - }) - .catch(showError) + // eslint-disable-next-line @typescript-eslint/no-floating-promises + fetchSMTPConfig() } else { setForm((prevForm) => ({ ...prevForm, default: shouldBeDefault })) } @@ -110,11 +117,11 @@ export const SMTPConfigModal = ({ authPassword: validateKeyValueConfig(ConfigurationFieldKeys.AUTH_PASSWORD, form.authPassword), fromEmail: validateKeyValueConfig(ConfigurationFieldKeys.FROM_EMAIL, form.fromEmail), })) - setForm((prevForm) => ({ ...prevForm, isLoading: false, isError: true })) + setForm((prevForm) => ({ ...prevForm, isLoading: false })) return } - setForm((prevForm) => ({ ...prevForm, isLoading: true, isError: false })) + setForm((prevForm) => ({ ...prevForm, isLoading: true })) saveEmailConfiguration(form, ConfigurationsTabTypes.SMTP) .then((response) => { @@ -146,7 +153,7 @@ export const SMTPConfigModal = ({ onBlur={handleBlur} placeholder="Enter a name" isRequiredField - error={isFormValid.configName.message} + error={isFormValid[ConfigurationFieldKeys.CONFIG_NAME].message} autoFocus />
= ({ const history = useHistory() const projectRef = useRef(null) - const [projectList, setProjectList] = useState>([]) - const [selectedProject, setSelectedProject] = useState<{ label: string; value: string }>() - const [form, setForm] = useState(DefaultSlackKeys) + const [projectList, setProjectList] = useState([]) + const [selectedProject, setSelectedProject] = useState() + const [form, setForm] = useState(DefaultSlackKeys) const [isFormValid, setFormValid] = useState(DefaultSlackValidations) + const fetchSlackConfig = async () => { + setForm((prevForm) => ({ ...prevForm, isLoading: true })) + Promise.all([getSlackConfiguration(slackConfigId), getProjectListMin()]) + .then(([slackConfigRes, projectListRes]) => { + setProjectList(projectListRes.result || []) + setForm({ ...slackConfigRes.result, isLoading: false }) + setFormValid(DefaultSlackValidations) + setSelectedProject({ + label: projectListRes.result.find((p) => p.id === slackConfigRes.result.projectId).name, + value: slackConfigRes.result.projectId, + }) + }) + .catch((error) => { + showError(error) + setForm((prevForm) => ({ ...prevForm, isLoading: false })) + }) + } + useEffect(() => { if (slackConfigId) { - setForm((prevForm) => ({ ...prevForm, isLoading: true })) - Promise.all([getSlackConfiguration(slackConfigId), getProjectListMin()]) - .then(([slackConfigRes, projectListRes]) => { - setProjectList(projectListRes.result || []) - setForm({ ...slackConfigRes.result, isLoading: false, isError: false }) - setFormValid(DefaultSlackValidations) - setSelectedProject({ - label: projectListRes.result.find((p) => p.id === slackConfigRes.result.projectId).name, - value: slackConfigRes.result.projectId, - }) - }) - .catch((error) => { - showError(error) - }) + // eslint-disable-next-line @typescript-eslint/no-floating-promises + fetchSlackConfig() } else { getProjectListMin() .then((response) => { @@ -74,17 +81,17 @@ export const SlackConfigModal: React.FC = ({ const getAllFieldsValidated = (): boolean => { const { configName, webhookUrl } = form - return !!configName && !!webhookUrl && !!selectedProject.value + return !!configName && !!webhookUrl && !!selectedProject?.value } const saveSlackConfig = () => { if (!getAllFieldsValidated()) { - setForm((prevForm) => ({ ...prevForm, isLoading: false, isError: true })) + setForm((prevForm) => ({ ...prevForm, isLoading: false })) setFormValid((prevValid) => ({ ...prevValid, configName: validateKeyValueConfig(ConfigurationFieldKeys.CONFIG_NAME, form.configName), webhookUrl: validateKeyValueConfig(ConfigurationFieldKeys.WEBHOOK_URL, form.webhookUrl), - project_id: validateKeyValueConfig(ConfigurationFieldKeys.PROJECT_ID, form.id || ''), + projectId: validateKeyValueConfig(ConfigurationFieldKeys.PROJECT_ID, selectedProject?.value ?? ''), })) renderErrorToast() return @@ -102,7 +109,7 @@ export const SlackConfigModal: React.FC = ({ const promise = slackConfigId ? updateSlackConfiguration(requestBody) : saveSlackConfiguration(requestBody) promise .then(() => { - setForm((prevForm) => ({ ...prevForm, isLoading: false, isError: false })) + setForm((prevForm) => ({ ...prevForm, isLoading: false })) ToastManager.showToast({ variant: ToastVariantType.success, description: 'Saved Successfully', @@ -173,7 +180,7 @@ export const SlackConfigModal: React.FC = ({ })) setFormValid((prevValid) => ({ ...prevValid, - project_id: validateKeyValueConfig(ConfigurationFieldKeys.PROJECT_ID, _selectedProject?.value ?? ''), + projectId: validateKeyValueConfig(ConfigurationFieldKeys.PROJECT_ID, _selectedProject?.value ?? ''), })) } diff --git a/src/components/notifications/WebhookConfigModal.tsx b/src/components/notifications/WebhookConfigModal.tsx index 633fdcc753..9f64f5afdd 100644 --- a/src/components/notifications/WebhookConfigModal.tsx +++ b/src/components/notifications/WebhookConfigModal.tsx @@ -23,6 +23,7 @@ import { ToastManager, } from '@devtron-labs/devtron-fe-common-lib' import { useHistory } from 'react-router-dom' +import { ReactComponent as ErrorIcon } from '@Icons/ic-warning.svg' import { ReactComponent as Help } from '../../assets/icons/ic-help.svg' import { getWebhookAttributes, getWebhookConfiguration, saveUpdateWebhookConfiguration } from './notifications.service' import { @@ -32,19 +33,18 @@ import { DefaultWebhookValidations, } from './constants' import { ConfigurationTabDrawerModal } from './ConfigurationDrawerModal' -import { WebhookConfigModalProps, WebhookDataRowType } from './types' +import { WebhookConfigModalProps, WebhookDataRowType, WebhookFormTypes, WebhookValidations } from './types' import { WebhookConfigDynamicDataTable } from './WebhookConfigDynamicDataTable' -import { validateKeyValueConfig, validatePayloadField } from './notifications.util' +import { renderErrorToast, validateKeyValueConfig, validatePayloadField } from './notifications.util' export const WebhookConfigModal = ({ webhookConfigId, closeWebhookConfigModal, onSaveSuccess, }: WebhookConfigModalProps) => { - const [form, setForm] = useState(DefaultWebhookConfig) - const [isFormValid, setFormValid] = useState(DefaultWebhookValidations) - + const [form, setForm] = useState(DefaultWebhookConfig) const [rows, setRows] = useState() + const [isFormValid, setFormValid] = useState(DefaultWebhookValidations) const history = useHistory() const [webhookAttribute, setWebhookAttribute] = useState({}) @@ -170,11 +170,7 @@ export const WebhookConfigModal = ({ webhookUrl: validateKeyValueConfig(ConfigurationFieldKeys.WEBHOOK_URL, form.webhookUrl), payload: validatePayloadField(form.payload), }) - // eslint-disable-next-line @typescript-eslint/no-floating-promises - ToastManager.showToast({ - variant: ToastVariantType.error, - description: 'Some required fields are missing or Invalid', - }) + renderErrorToast() } if (!getAllFieldsValidated()) { @@ -216,7 +212,7 @@ export const WebhookConfigModal = ({ value={form.configName} onChange={handleInputChange} placeholder="Enter name" - error={isFormValid.configName.message} + error={isFormValid[ConfigurationFieldKeys.CONFIG_NAME].message} name={ConfigurationFieldKeys.CONFIG_NAME} dataTestid="webhook-modal__name" isRequiredField @@ -228,7 +224,7 @@ export const WebhookConfigModal = ({ value={form.webhookUrl} onChange={handleInputChange} placeholder="Enter Incoming Webhook URL" - error={isFormValid.webhookUrl.message} + error={isFormValid[ConfigurationFieldKeys.WEBHOOK_URL].message} name={ConfigurationFieldKeys.WEBHOOK_URL} dataTestid="webhook-modal__url" isRequiredField @@ -247,7 +243,12 @@ export const WebhookConfigModal = ({ height={150} />
- {isFormValid.payload.message} + {isFormValid[ConfigurationFieldKeys.PAYLOAD].message && ( +
+ + {isFormValid[ConfigurationFieldKeys.PAYLOAD].message} +
+ )}
{renderConfigureLinkInfoColumn()} diff --git a/src/components/notifications/constants.ts b/src/components/notifications/constants.ts index 7112f09941..a50127998b 100644 --- a/src/components/notifications/constants.ts +++ b/src/components/notifications/constants.ts @@ -1,5 +1,7 @@ // ------------ Configuration Constants ------------ +import { SlackFormType } from './types' + export enum ConfigurationsTabTypes { SES = 'ses', SMTP = 'smtp', @@ -29,7 +31,7 @@ export enum ConfigurationFieldKeys { SECRET_KEY = 'secretKey', REGION = 'region', FROM_EMAIL = 'fromEmail', - PROJECT_ID = 'project_id', + PROJECT_ID = 'projectId', WEBHOOK_URL = 'webhookUrl', CONFIG_ID = 'configId', HOST = 'host', @@ -52,6 +54,18 @@ export const DefaultSESValidations = { fromEmail: ConfigValidationKeys, } +export enum SESSortableHeaderKeys { + CONFIG_NAME = ConfigurationFieldKeys.CONFIG_NAME, + ACCESS_KEY = ConfigurationFieldKeys.ACCESS_KEY, + EMAIL = ConfigurationFieldKeys.FROM_EMAIL, +} + +export const SESSortableHeaderTitle = { + CONFIG_NAME: 'Name', + ACCESS_KEY: 'Access Key Id', + EMAIL: "Sender's Email", +} + // ------------ SMTP Configuration Constants ------------ export const DefaultSMTPValidation = { @@ -65,17 +79,16 @@ export const DefaultSMTPValidation = { // ------------ Slack Configuration Constants ------------ -export const DefaultSlackKeys = { +export const DefaultSlackKeys: SlackFormType = { [ConfigurationFieldKeys.PROJECT_ID]: 0, [ConfigurationFieldKeys.CONFIG_NAME]: '', [ConfigurationFieldKeys.WEBHOOK_URL]: '', isLoading: false, - isError: false, - id: 0, + id: null, } export const DefaultSlackValidations = { - project_id: ConfigValidationKeys, + projectId: ConfigValidationKeys, configName: ConfigValidationKeys, webhookUrl: ConfigValidationKeys, } @@ -88,7 +101,6 @@ export const DefaultWebhookConfig = { configName: '', webhookUrl: '', isLoading: false, - isError: false, payload: '', header: [DefaultHeaders], } diff --git a/src/components/notifications/notifications.util.tsx b/src/components/notifications/notifications.util.tsx index d5019cde63..e59f6b3ef0 100644 --- a/src/components/notifications/notifications.util.tsx +++ b/src/components/notifications/notifications.util.tsx @@ -36,7 +36,7 @@ import { } from '@devtron-labs/devtron-fe-common-lib' import { ConfigurationFieldKeys, ConfigurationsTabTypes, ConfigurationTabText } from './constants' import { validateEmail } from '../common' -import { FormError, FormValidation, WebhookDataRowType, WebhookHeaderKeyType, WebhookRowCellType } from './types' +import { FormError, FormValidation, SESFormType, SMTPFormType, WebhookDataRowType, WebhookHeaderKeyType, WebhookRowCellType } from './types' import { REQUIRED_FIELD_MSG } from '@Config/constantMessaging' export const multiSelectStyles = { @@ -201,7 +201,7 @@ export const getConfigurationTabTextWithIcon = () => [ }, ] -export const getSESDefaultConfiguration = (shouldBeDefault: boolean) => ({ +export const getSESDefaultConfiguration = (shouldBeDefault: boolean): SESFormType => ({ configName: '', accessKey: '', secretKey: '', @@ -209,10 +209,9 @@ export const getSESDefaultConfiguration = (shouldBeDefault: boolean) => ({ fromEmail: '', default: shouldBeDefault, isLoading: false, - isError: true, }) -export const getSMTPDefaultConfiguration = (shouldBeDefault: boolean) => ({ +export const getSMTPDefaultConfiguration = (shouldBeDefault: boolean): SMTPFormType => ({ configName: '', host: '', port: '', @@ -221,7 +220,6 @@ export const getSMTPDefaultConfiguration = (shouldBeDefault: boolean) => ({ fromEmail: '', default: shouldBeDefault, isLoading: false, - isError: true, }) export const renderText = (text: string, isLink: boolean = false, linkTo?: () => void, dataTestId?: string) => ( @@ -343,6 +341,9 @@ export const validatePayloadField = (value: string): FormError => { let isValid = true let errorMessage = '' // Validate if the value is a valid JSON string + if (!value) { + return { isValid: false, message: REQUIRED_FIELD_MSG } + } try { JSON.parse(value) } catch { diff --git a/src/components/notifications/types.tsx b/src/components/notifications/types.tsx index 049f423df5..2e9eb0837f 100644 --- a/src/components/notifications/types.tsx +++ b/src/components/notifications/types.tsx @@ -15,9 +15,14 @@ */ import { RouteComponentProps } from 'react-router-dom' -import { ServerError, ResponseType, DynamicDataTableRowType } from '@devtron-labs/devtron-fe-common-lib' +import { + ServerError, + ResponseType, + DynamicDataTableRowType, + SelectPickerOptionType, +} from '@devtron-labs/devtron-fe-common-lib' import { VariableDataTableActionType } from '@Components/CIPipelineN/VariableDataTable/types' -import { ConfigurationsTabTypes } from './constants' +import { ConfigurationFieldKeys, ConfigurationsTabTypes } from './constants' export interface NotifierProps extends RouteComponentProps<{ id: string }> {} @@ -27,6 +32,7 @@ export interface NotifierState { successMessage: string | null channel: string } + export interface SMTPConfigResponseType extends ResponseType { result?: { configName: string @@ -44,29 +50,16 @@ export enum EMAIL_AGENT { SMTP = 'SMTP', } -export interface SMTPConfigModalState { - view: string - form: { - configName: string - port: number - host: string - authUser: string - authPassword: string - fromEmail: string - default: boolean - isLoading: boolean - isError: boolean - } - isValid: { - configName: boolean - port: boolean - host: boolean - authUser: boolean - authPassword: boolean - fromEmail: boolean - } +export interface SMTPConfigModalProps { + smtpConfigId: number + shouldBeDefault: boolean + selectSMTPFromChild?: (smtpConfigId: number) => void + onSaveSuccess: () => void + closeSMTPConfigModal?: () => void } +// ----------------------------Configuration Tab Types---------------------------- + export interface ConfigurationTabState { isLoading: boolean sesConfigurationList: Array<{ id: number; name: string; accessKeyId: string; email: string; isDefault: boolean }> @@ -95,41 +88,32 @@ export interface ConfigurationTableProps { deleteClickHandler: (id: number, name: string) => void } -export interface WebhhookConfigModalState { - view: string - form: { - configName: string - webhookUrl: string - isLoading: boolean - isError: boolean - payload: string - header: HeaderType[] - } - isValid: { - configName: boolean - webhookUrl: boolean - payload: boolean - } - webhookAttribute: Record - copyAttribute: boolean +export interface FormError { + isValid: boolean + message: string } -export interface HeaderType { - key: string - value: string +export interface ConfigurationTabDrawerModalProps { + renderContent: () => JSX.Element + closeModal: () => void + modal: ConfigurationsTabTypes + isLoading: boolean + saveConfigModal: () => void + disableSave?: boolean } -export interface CreateHeaderDetailsType { - index: number - headerData: HeaderType - setHeaderData: (index: number, headerData: HeaderType) => void - removeHeader?: (index: number) => void +export type FormValidation = { + [key: string]: FormError } -export interface WebhookAttributesResponseType extends ResponseType { - result?: Record +export interface DefaultCheckboxProps { + shouldBeDefault: boolean + handleCheckbox: () => void + isDefault: boolean } +// ----------------------------Configuration Tab---------------------------- + export interface EmptyConfigurationViewProps { configTabType: ConfigurationsTabTypes image?: any @@ -139,6 +123,15 @@ export interface ConfigurationTabSwitcherProps { activeTab: ConfigurationsTabTypes } +export interface ConfigTableRowActionButtonProps { + onClickEditRow: () => void + onClickDeleteRow: any + rootClassName: string + modal: ConfigurationsTabTypes +} + +// ----------------------------SES Config Modal---------------------------- + export interface SESConfigModalProps { shouldBeDefault: boolean selectSESFromChild?: (sesConfigId: number) => void @@ -147,41 +140,61 @@ export interface SESConfigModalProps { sesConfigId: number } +export interface SESFormType { + configName: string + accessKey: string + secretKey: string + region: SelectPickerOptionType + default: boolean + isLoading: boolean + fromEmail: string +} + +// ----------------------------Slack Config Modal---------------------------- + +export interface ProjectListTypes { + id: number + name: string + active: boolean +} + export interface SlackConfigModalProps { slackConfigId: number onSaveSuccess: () => void closeSlackConfigModal?: () => void } -export interface SMTPConfigModalProps { - smtpConfigId: number - shouldBeDefault: boolean - selectSMTPFromChild?: (smtpConfigId: number) => void - onSaveSuccess: () => void - closeSMTPConfigModal?: () => void +export interface SlackFormType { + configName: string + projectId: number + webhookUrl: string + isLoading: boolean + id: number | null +} + +// ----------------------------SMTP Config Modal---------------------------- +export interface SMTPFormType { + configName: string + port: string + host: string + authUser: string + authPassword: string + fromEmail: string + default: boolean + isLoading: boolean } -export interface ConfigTableRowActionButtonProps { - onClickEditRow: () => void - onClickDeleteRow: any - rootClassName: string - modal: ConfigurationsTabTypes +// ----------------------------Webhook Config Modal-------------------------------- + +export interface WebhookAttributesResponseType extends ResponseType { + result?: Record } -// ----------------------------Webhook Config Modal-------------------------------- export interface WebhookConfigModalProps { webhookConfigId: number closeWebhookConfigModal?: () => void onSaveSuccess: () => void } -export interface ConfigurationTabDrawerModalProps { - renderContent: () => JSX.Element - closeModal: () => void - modal: ConfigurationsTabTypes - isLoading: boolean - saveConfigModal: () => void - disableSave?: boolean -} export type WebhookHeaderKeyType = 'key' | 'value' @@ -214,17 +227,21 @@ export type HandleRowUpdateActionProps = VariableDataTableAction & { rowId: string | number } -export interface FormError { - isValid: boolean - message: string +export type WebhookValidations = { + [ConfigurationFieldKeys.CONFIG_NAME]: FormError + [ConfigurationFieldKeys.WEBHOOK_URL]: FormError + [ConfigurationFieldKeys.PAYLOAD]: FormError } -export type FormValidation = { - [key: string]: FormError +export interface HeaderType { + key: string + value: string } -export interface DefaultCheckboxProps { - shouldBeDefault: boolean - handleCheckbox: () => void - isDefault: boolean +export interface WebhookFormTypes { + configName: string + webhookUrl: string + isLoading: boolean + payload: string + header: HeaderType[] } From f8acb4ffe5a713adc2885519752cf7a6ce008670 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Wed, 8 Jan 2025 18:45:31 +0530 Subject: [PATCH 31/31] chore: type fix added for all modal --- src/components/notifications/constants.ts | 48 +++++++++++------------ 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/components/notifications/constants.ts b/src/components/notifications/constants.ts index a50127998b..f5b1481daf 100644 --- a/src/components/notifications/constants.ts +++ b/src/components/notifications/constants.ts @@ -47,17 +47,11 @@ export enum ConfigurationFieldKeys { export const ConfigValidationKeys = { isValid: true, message: '' } export const DefaultSESValidations = { - configName: ConfigValidationKeys, - accessKey: ConfigValidationKeys, - secretKey: ConfigValidationKeys, - region: ConfigValidationKeys, - fromEmail: ConfigValidationKeys, -} - -export enum SESSortableHeaderKeys { - CONFIG_NAME = ConfigurationFieldKeys.CONFIG_NAME, - ACCESS_KEY = ConfigurationFieldKeys.ACCESS_KEY, - EMAIL = ConfigurationFieldKeys.FROM_EMAIL, + [ConfigurationFieldKeys.CONFIG_NAME]: ConfigValidationKeys, + [ConfigurationFieldKeys.ACCESS_KEY]: ConfigValidationKeys, + [ConfigurationFieldKeys.SECRET_KEY]: ConfigValidationKeys, + [ConfigurationFieldKeys.REGION]: ConfigValidationKeys, + [ConfigurationFieldKeys.FROM_EMAIL]: ConfigValidationKeys, } export const SESSortableHeaderTitle = { @@ -69,12 +63,12 @@ export const SESSortableHeaderTitle = { // ------------ SMTP Configuration Constants ------------ export const DefaultSMTPValidation = { - configName: ConfigValidationKeys, - host: ConfigValidationKeys, - port: ConfigValidationKeys, - authUser: ConfigValidationKeys, - authPassword: ConfigValidationKeys, - fromEmail: ConfigValidationKeys, + [ConfigurationFieldKeys.CONFIG_NAME]: ConfigValidationKeys, + [ConfigurationFieldKeys.HOST]: ConfigValidationKeys, + [ConfigurationFieldKeys.PORT]: ConfigValidationKeys, + [ConfigurationFieldKeys.AUTH_USER]: ConfigValidationKeys, + [ConfigurationFieldKeys.AUTH_PASSWORD]: ConfigValidationKeys, + [ConfigurationFieldKeys.FROM_EMAIL]: ConfigValidationKeys, } // ------------ Slack Configuration Constants ------------ @@ -88,9 +82,9 @@ export const DefaultSlackKeys: SlackFormType = { } export const DefaultSlackValidations = { - projectId: ConfigValidationKeys, - configName: ConfigValidationKeys, - webhookUrl: ConfigValidationKeys, + [ConfigurationFieldKeys.PROJECT_ID]: ConfigValidationKeys, + [ConfigurationFieldKeys.CONFIG_NAME]: ConfigValidationKeys, + [ConfigurationFieldKeys.WEBHOOK_URL]: ConfigValidationKeys, } // ------------ Webhook Configuration Constants ------------ @@ -98,15 +92,17 @@ export const DefaultSlackValidations = { export const DefaultHeaders = { key: '', value: '' } export const DefaultWebhookConfig = { - configName: '', - webhookUrl: '', + [ConfigurationFieldKeys.CONFIG_NAME]: '', + [ConfigurationFieldKeys.WEBHOOK_URL]: '', + [ConfigurationFieldKeys.PAYLOAD]: '', isLoading: false, - payload: '', header: [DefaultHeaders], } export const DefaultWebhookValidations = { - configName: ConfigValidationKeys, - webhookUrl: ConfigValidationKeys, - payload: ConfigValidationKeys, + [ConfigurationFieldKeys.CONFIG_NAME]: ConfigValidationKeys, + [ConfigurationFieldKeys.WEBHOOK_URL]: ConfigValidationKeys, + [ConfigurationFieldKeys.PAYLOAD]: ConfigValidationKeys, } + +export const SlackIncomingWebhookUrl = 'https://slack.com/marketplace/A0F7XDUAZ-incoming-webhooks'