Skip to content

Commit 97e4eb5

Browse files
authored
Merge pull request #1904 from bluewave-labs/feat/handleTestNotification
Implemented the handleTestNotification function.
2 parents a4d1f21 + 51da6b1 commit 97e4eb5

File tree

7 files changed

+233
-28
lines changed

7 files changed

+233
-28
lines changed

src/Components/NotificationIntegrationModal/NotificationIntegrationModal.jsx src/Components/NotificationIntegrationModal/Components/NotificationIntegrationModal.jsx

+68-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState, useMemo } from "react";
22
import { useTranslation } from "react-i18next";
3+
34
import {
45
Dialog,
56
DialogContent,
@@ -8,11 +9,29 @@ import {
89
Typography,
910
Box,
1011
Tabs,
11-
Tab
12+
Tab,
13+
CircularProgress
1214
} from "@mui/material";
1315
import { useTheme } from "@emotion/react";
1416
import TabPanel from "./TabPanel";
1517
import TabComponent from "./TabComponent";
18+
import useNotifications from "../Hooks/useNotification";
19+
20+
// Define constants for notification types to avoid magic values
21+
const NOTIFICATION_TYPES = {
22+
SLACK: 'slack',
23+
DISCORD: 'discord',
24+
TELEGRAM: 'telegram',
25+
WEBHOOK: 'webhook'
26+
};
27+
28+
// Define constants for field IDs
29+
const FIELD_IDS = {
30+
WEBHOOK: 'webhook',
31+
TOKEN: 'token',
32+
CHAT_ID: 'chatId',
33+
URL: 'url'
34+
};
1635

1736
const NotificationIntegrationModal = ({
1837
open,
@@ -26,60 +45,75 @@ const NotificationIntegrationModal = ({
2645
const theme = useTheme();
2746
const [tabValue, setTabValue] = useState(0);
2847

48+
const [loading, _, sendTestNotification] = useNotifications();
49+
50+
// Helper to get the field state key with error handling
51+
const getFieldKey = (typeId, fieldId) => {
52+
if (typeof typeId !== 'string' || typeId === '') {
53+
throw new Error('Invalid typeId provided to getFieldKey');
54+
}
55+
56+
if (typeof fieldId !== 'string' || fieldId === '') {
57+
throw new Error('Invalid fieldId provided to getFieldKey');
58+
}
59+
60+
return `${typeId}${fieldId.charAt(0).toUpperCase() + fieldId.slice(1)}`;
61+
};
62+
2963
// Define notification types
3064
const DEFAULT_NOTIFICATION_TYPES = [
3165
{
32-
id: 'slack',
66+
id: NOTIFICATION_TYPES.SLACK,
3367
label: t('notifications.slack.label'),
3468
description: t('notifications.slack.description'),
3569
fields: [
3670
{
37-
id: 'webhook',
71+
id: FIELD_IDS.WEBHOOK,
3872
label: t('notifications.slack.webhookLabel'),
3973
placeholder: t('notifications.slack.webhookPlaceholder'),
4074
type: 'text'
4175
}
4276
]
4377
},
4478
{
45-
id: 'discord',
79+
id: NOTIFICATION_TYPES.DISCORD,
4680
label: t('notifications.discord.label'),
4781
description: t('notifications.discord.description'),
4882
fields: [
4983
{
50-
id: 'webhook',
84+
id: FIELD_IDS.WEBHOOK,
5185
label: t('notifications.discord.webhookLabel'),
5286
placeholder: t('notifications.discord.webhookPlaceholder'),
5387
type: 'text'
5488
}
5589
]
5690
},
5791
{
58-
id: 'telegram',
92+
id: NOTIFICATION_TYPES.TELEGRAM,
5993
label: t('notifications.telegram.label'),
6094
description: t('notifications.telegram.description'),
6195
fields: [
6296
{
63-
id: 'token',
97+
id: FIELD_IDS.TOKEN,
6498
label: t('notifications.telegram.tokenLabel'),
6599
placeholder: t('notifications.telegram.tokenPlaceholder'),
66100
type: 'text'
67101
},
68102
{
69-
id: 'chatId',
103+
id: FIELD_IDS.CHAT_ID,
70104
label: t('notifications.telegram.chatIdLabel'),
71105
placeholder: t('notifications.telegram.chatIdPlaceholder'),
72106
type: 'text'
73107
}
74108
]
75109
},
76110
{
77-
id: 'webhook',
111+
id: NOTIFICATION_TYPES.WEBHOOK,
78112
label: t('notifications.webhook.label'),
79113
description: t('notifications.webhook.description'),
80114
fields: [
81115
{
82-
id: 'url',
116+
id: FIELD_IDS.URL,
83117
label: t('notifications.webhook.urlLabel'),
84118
placeholder: t('notifications.webhook.urlPlaceholder'),
85119
type: 'text'
@@ -101,7 +135,7 @@ const NotificationIntegrationModal = ({
101135

102136
// Add state for each field in the notification type
103137
type.fields.forEach(field => {
104-
const fieldKey = `${type.id}${field.id.charAt(0).toUpperCase() + field.id.slice(1)}`;
138+
const fieldKey = getFieldKey(type.id, field.id);
105139
state[fieldKey] = monitor?.notifications?.find(n => n.type === type.id)?.[field.id] || "";
106140
});
107141
});
@@ -129,11 +163,26 @@ const NotificationIntegrationModal = ({
129163
}));
130164
};
131165

132-
const handleTestNotification = (type) => {
133-
console.log(`Testing ${type} notification`);
134-
//implement the test notification functionality
166+
const handleTestNotification = async (type) => {
167+
// Get the notification type details
168+
const notificationType = activeNotificationTypes.find(t => t.id === type);
169+
170+
if (typeof notificationType === "undefined") {
171+
return;
172+
}
173+
174+
// Prepare config object based on notification type
175+
const config = {};
176+
177+
// Add each field value to the config object
178+
notificationType.fields.forEach(field => {
179+
const fieldKey = getFieldKey(type, field.id);
180+
config[field.id] = integrations[fieldKey];
181+
});
182+
183+
await sendTestNotification(type, config);
135184
};
136-
185+
137186
const handleSave = () => {
138187
//notifications array for selected integrations
139188
const notifications = [...(monitor?.notifications || [])];
@@ -155,7 +204,7 @@ const NotificationIntegrationModal = ({
155204

156205
// Add each field value to the notification object
157206
type.fields.forEach(field => {
158-
const fieldKey = `${type.id}${field.id.charAt(0).toUpperCase() + field.id.slice(1)}`;
207+
const fieldKey = getFieldKey(type.id, field.id);
159208
notificationObject[field.id] = integrations[fieldKey];
160209
});
161210

@@ -240,6 +289,7 @@ const NotificationIntegrationModal = ({
240289
handleIntegrationChange={handleIntegrationChange}
241290
handleInputChange={handleInputChange}
242291
handleTestNotification={handleTestNotification}
292+
isLoading={loading}
243293
/>
244294
</TabPanel>
245295
))}
@@ -257,13 +307,14 @@ const NotificationIntegrationModal = ({
257307
variant="contained"
258308
color="accent"
259309
onClick={handleSave}
310+
loading={loading}
260311
sx={{
261312
width: 'auto',
262313
minWidth: theme.spacing(60),
263314
px: theme.spacing(8)
264315
}}
265316
>
266-
{t('common.save', 'Save')}
317+
{t('commonSave')}
267318
</Button>
268319
</DialogActions>
269320
</Dialog>

src/Components/NotificationIntegrationModal/TabComponent.jsx src/Components/NotificationIntegrationModal/Components/TabComponent.jsx

+15-6
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@ import React from "react";
22
import {
33
Typography,
44
Box,
5-
Button
5+
Button,
6+
CircularProgress
67
} from "@mui/material";
78
import { useTranslation } from "react-i18next";
89
import { useTheme } from "@emotion/react";
9-
import TextInput from "../../../src/Components/Inputs/TextInput";
10-
import Checkbox from "../../../src/Components/Inputs/Checkbox";
10+
import TextInput from "../../../Components/Inputs/TextInput";
11+
import Checkbox from "../../../Components/Inputs/Checkbox";
1112

1213
const TabComponent = ({
1314
type,
1415
integrations,
1516
handleIntegrationChange,
1617
handleInputChange,
17-
handleTestNotification
18+
handleTestNotification,
19+
isLoading
1820
}) => {
1921
const theme = useTheme();
2022
const { t } = useTranslation();
@@ -55,6 +57,7 @@ const TabComponent = ({
5557
label={t('notifications.enableNotifications', { platform: type.label })}
5658
isChecked={integrations[type.id]}
5759
onChange={(e) => handleIntegrationChange(type.id, e.target.checked)}
60+
disabled={isLoading}
5861
/>
5962
</Box>
6063

@@ -77,7 +80,7 @@ const TabComponent = ({
7780
placeholder={field.placeholder}
7881
value={integrations[fieldKey]}
7982
onChange={(e) => handleInputChange(fieldKey, e.target.value)}
80-
disabled={!integrations[type.id]}
83+
disabled={!integrations[type.id] || isLoading}
8184
/>
8285
</Box>
8386
);
@@ -88,8 +91,14 @@ const TabComponent = ({
8891
variant="text"
8992
color="info"
9093
onClick={() => handleTestNotification(type.id)}
91-
disabled={!integrations[type.id] || !areAllFieldsFilled()}
94+
disabled={!integrations[type.id] || !areAllFieldsFilled() || isLoading}
9295
>
96+
{isLoading ? (
97+
<CircularProgress
98+
size={theme.spacing(8)}
99+
sx={{ mr: theme.spacing(1), color: theme.palette.accent.main}}
100+
/>
101+
) : null}
93102
{t('notifications.testNotification')}
94103
</Button>
95104
</Box>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { useState } from 'react';
2+
import { toast } from 'react-toastify';
3+
import { useTranslation } from "react-i18next";
4+
import { networkService } from '../../../Utils/NetworkService';
5+
6+
// Define constants for notification types to avoid magic values
7+
const NOTIFICATION_TYPES = {
8+
SLACK: 'slack',
9+
DISCORD: 'discord',
10+
TELEGRAM: 'telegram',
11+
WEBHOOK: 'webhook'
12+
};
13+
14+
// Define constants for field IDs
15+
const FIELD_IDS = {
16+
WEBHOOK: 'webhook',
17+
TOKEN: 'token',
18+
CHAT_ID: 'chatId',
19+
URL: 'url'
20+
};
21+
22+
/**
23+
* Custom hook for notification-related operations
24+
*/
25+
const useNotifications = () => {
26+
const [loading, setLoading] = useState(false);
27+
const [error, setError] = useState(undefined);
28+
const { t } = useTranslation();
29+
30+
/**
31+
* Send a test notification
32+
* @param {string} type - The notification type (slack, discord, telegram, webhook)
33+
* @param {object} config - Configuration object with necessary params
34+
*/
35+
const sendTestNotification = async (type, config) => {
36+
setLoading(true);
37+
setError(undefined);
38+
39+
// Validation based on notification type
40+
let payload = { platform: type };
41+
let isValid = true;
42+
let errorMessage = '';
43+
44+
switch(type) {
45+
case NOTIFICATION_TYPES.SLACK:
46+
payload.webhookUrl = config.webhook;
47+
if (typeof payload.webhookUrl === 'undefined' || payload.webhookUrl === '') {
48+
isValid = false;
49+
errorMessage = t('notifications.slack.webhookRequired');
50+
}
51+
break;
52+
53+
case NOTIFICATION_TYPES.DISCORD:
54+
payload.webhookUrl = config.webhook;
55+
if (typeof payload.webhookUrl === 'undefined' || payload.webhookUrl === '') {
56+
isValid = false;
57+
errorMessage = t('notifications.discord.webhookRequired');
58+
}
59+
break;
60+
61+
case NOTIFICATION_TYPES.TELEGRAM:
62+
payload.botToken = config.token;
63+
payload.chatId = config.chatId;
64+
if (typeof payload.botToken === 'undefined' || payload.botToken === '' ||
65+
typeof payload.chatId === 'undefined' || payload.chatId === '') {
66+
isValid = false;
67+
errorMessage = t('notifications.telegram.fieldsRequired');
68+
}
69+
break;
70+
71+
case NOTIFICATION_TYPES.WEBHOOK:
72+
payload.webhookUrl = config.url;
73+
payload.platform = NOTIFICATION_TYPES.SLACK;
74+
if (typeof payload.webhookUrl === 'undefined' || payload.webhookUrl === '') {
75+
isValid = false;
76+
errorMessage = t('notifications.webhook.urlRequired');
77+
}
78+
break;
79+
80+
default:
81+
isValid = false;
82+
errorMessage = t('notifications.unsupportedType');
83+
}
84+
85+
// If validation fails, show error and return
86+
if (isValid === false) {
87+
toast.error(errorMessage);
88+
setLoading(false);
89+
return;
90+
}
91+
92+
try {
93+
const response = await networkService.testNotification({
94+
platform: type,
95+
payload: payload
96+
});
97+
98+
if (response.data.success === true) {
99+
toast.success(t('notifications.testSuccess'));
100+
} else {
101+
throw new Error(response.data.msg || t('notifications.testFailed'));
102+
}
103+
} catch (error) {
104+
const errorMsg = error.response?.data?.msg || error.message || t('notifications.networkError');
105+
toast.error(`${t('notifications.testFailed')}: ${errorMsg}`);
106+
setError(errorMsg);
107+
} finally {
108+
setLoading(false);
109+
}
110+
};
111+
112+
return [
113+
loading,
114+
error,
115+
sendTestNotification
116+
];
117+
};
118+
119+
export default useNotifications;

0 commit comments

Comments
 (0)