Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitc1cecf4

Browse files
BrunoQuaresmaEmyrk
authored andcommitted
fix(site): only show method warning if some template is using it (#14565)
Previously, we were showing the warning regardless of whether a template was using the misconfigured notification method or not. However, we realized this could be too noisy, so we decided to display the warning only when the user has a template configured to use the misconfigured method.
1 parent52cf266 commitc1cecf4

File tree

5 files changed

+587
-451
lines changed

5 files changed

+587
-451
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
importtype{Meta,StoryObj}from"@storybook/react";
2+
import{spyOn,userEvent,within}from"@storybook/test";
3+
import{API}from"api/api";
4+
import{selectTemplatesByGroup}from"api/queries/notifications";
5+
importtype{DeploymentValues}from"api/typesGenerated";
6+
import{MockNotificationTemplates}from"testHelpers/entities";
7+
import{NotificationEvents}from"./NotificationEvents";
8+
import{baseMeta}from"./storybookUtils";
9+
10+
constmeta:Meta<typeofNotificationEvents>={
11+
title:"pages/DeploymentSettings/NotificationsPage/NotificationEvents",
12+
component:NotificationEvents,
13+
args:{
14+
defaultMethod:"smtp",
15+
availableMethods:["smtp","webhook"],
16+
templatesByGroup:selectTemplatesByGroup(MockNotificationTemplates),
17+
deploymentValues:baseMeta.parameters.deploymentValues,
18+
},
19+
...baseMeta,
20+
};
21+
22+
exportdefaultmeta;
23+
24+
typeStory=StoryObj<typeofNotificationEvents>;
25+
26+
exportconstSMTPNotConfigured:Story={
27+
args:{
28+
deploymentValues:{
29+
notifications:{
30+
webhook:{
31+
endpoint:"https://example.com",
32+
},
33+
email:{
34+
smarthost:"",
35+
},
36+
},
37+
}asDeploymentValues,
38+
},
39+
};
40+
41+
exportconstWebhookNotConfigured:Story={
42+
args:{
43+
deploymentValues:{
44+
notifications:{
45+
webhook:{
46+
endpoint:"",
47+
},
48+
email:{
49+
smarthost:"smtp.example.com",
50+
from:"bob@localhost",
51+
hello:"localhost",
52+
},
53+
},
54+
}asDeploymentValues,
55+
},
56+
};
57+
58+
exportconstToggle:Story={
59+
play:async({ canvasElement})=>{
60+
spyOn(API,"updateNotificationTemplateMethod").mockResolvedValue();
61+
constuser=userEvent.setup();
62+
constcanvas=within(canvasElement);
63+
consttmpl=MockNotificationTemplates[4];
64+
constoption=awaitcanvas.findByText(tmpl.name);
65+
constli=option.closest("li");
66+
if(!li){
67+
thrownewError("Could not find li");
68+
}
69+
consttoggleButton=within(li).getByRole("button",{
70+
name:"Webhook",
71+
});
72+
awaituser.click(toggleButton);
73+
awaitwithin(document.body).findByText("Notification method updated");
74+
},
75+
};
76+
77+
exportconstToggleError:Story={
78+
play:async({ canvasElement})=>{
79+
spyOn(API,"updateNotificationTemplateMethod").mockRejectedValue({});
80+
constuser=userEvent.setup();
81+
constcanvas=within(canvasElement);
82+
consttmpl=MockNotificationTemplates[4];
83+
constoption=awaitcanvas.findByText(tmpl.name);
84+
constli=option.closest("li");
85+
if(!li){
86+
thrownewError("Could not find li");
87+
}
88+
consttoggleButton=within(li).getByRole("button",{
89+
name:"Webhook",
90+
});
91+
awaituser.click(toggleButton);
92+
awaitwithin(document.body).findByText(
93+
"Failed to update notification method",
94+
);
95+
},
96+
};
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
importtype{Interpolation,Theme}from"@emotion/react";
2+
importButtonfrom"@mui/material/Button";
3+
importCardfrom"@mui/material/Card";
4+
importDividerfrom"@mui/material/Divider";
5+
importListfrom"@mui/material/List";
6+
importListItemfrom"@mui/material/ListItem";
7+
importListItemText,{listItemTextClasses}from"@mui/material/ListItemText";
8+
importToggleButtonfrom"@mui/material/ToggleButton";
9+
importToggleButtonGroupfrom"@mui/material/ToggleButtonGroup";
10+
importTooltipfrom"@mui/material/Tooltip";
11+
import{getErrorMessage}from"api/errors";
12+
import{
13+
typeselectTemplatesByGroup,
14+
updateNotificationTemplateMethod,
15+
}from"api/queries/notifications";
16+
importtype{DeploymentValues}from"api/typesGenerated";
17+
import{Alert}from"components/Alert/Alert";
18+
import{displayError,displaySuccess}from"components/GlobalSnackbar/utils";
19+
import{Stack}from"components/Stack/Stack";
20+
import{
21+
typeNotificationMethod,
22+
castNotificationMethod,
23+
methodIcons,
24+
methodLabels,
25+
}from"modules/notifications/utils";
26+
import{typeFC,Fragment}from"react";
27+
import{useMutation,useQueryClient}from"react-query";
28+
import{docs}from"utils/docs";
29+
30+
typeNotificationEventsProps={
31+
defaultMethod:NotificationMethod;
32+
availableMethods:NotificationMethod[];
33+
templatesByGroup:ReturnType<typeofselectTemplatesByGroup>;
34+
deploymentValues:DeploymentValues;
35+
};
36+
37+
exportconstNotificationEvents:FC<NotificationEventsProps>=({
38+
defaultMethod,
39+
availableMethods,
40+
templatesByGroup,
41+
deploymentValues,
42+
})=>{
43+
// Webhook
44+
consthasWebhookNotifications=Object.values(templatesByGroup)
45+
.flat()
46+
.some((t)=>t.method==="webhook");
47+
constwebhookValues=deploymentValues.notifications?.webhook??{};
48+
constisWebhookConfigured=requiredFieldsArePresent(webhookValues,[
49+
"endpoint",
50+
]);
51+
52+
// SMTP
53+
consthasSMTPNotifications=Object.values(templatesByGroup)
54+
.flat()
55+
.some((t)=>t.method==="smtp");
56+
constsmtpValues=deploymentValues.notifications?.email??{};
57+
constisSMTPConfigured=requiredFieldsArePresent(smtpValues,[
58+
"smarthost",
59+
"from",
60+
"hello",
61+
]);
62+
63+
return(
64+
<Stackspacing={4}>
65+
{hasWebhookNotifications&&!isWebhookConfigured&&(
66+
<Alert
67+
severity="warning"
68+
actions={
69+
<Button
70+
variant="text"
71+
size="small"
72+
component="a"
73+
target="_blank"
74+
rel="noreferrer"
75+
href={docs("/admin/notifications#webhook")}
76+
>
77+
Read the docs
78+
</Button>
79+
}
80+
>
81+
Webhook notifications are enabled, but not properly configured.
82+
</Alert>
83+
)}
84+
85+
{hasSMTPNotifications&&!isSMTPConfigured&&(
86+
<Alert
87+
severity="warning"
88+
actions={
89+
<Button
90+
variant="text"
91+
size="small"
92+
component="a"
93+
target="_blank"
94+
rel="noreferrer"
95+
href={docs("/admin/notifications#smtp-email")}
96+
>
97+
Read the docs
98+
</Button>
99+
}
100+
>
101+
SMTP notifications are enabled but not properly configured.
102+
</Alert>
103+
)}
104+
105+
{Object.entries(templatesByGroup).map(([group,templates])=>(
106+
<Card
107+
key={group}
108+
variant="outlined"
109+
css={{background:"transparent",width:"100%"}}
110+
>
111+
<List>
112+
<ListItemcss={styles.listHeader}>
113+
<ListItemTextcss={styles.listItemText}primary={group}/>
114+
</ListItem>
115+
116+
{templates.map((tpl,i)=>{
117+
constvalue=castNotificationMethod(tpl.method||defaultMethod);
118+
constisLastItem=i===templates.length-1;
119+
120+
return(
121+
<Fragmentkey={tpl.id}>
122+
<ListItem>
123+
<ListItemText
124+
css={styles.listItemText}
125+
primary={tpl.name}
126+
/>
127+
<MethodToggleGroup
128+
templateId={tpl.id}
129+
options={availableMethods}
130+
value={value}
131+
/>
132+
</ListItem>
133+
{!isLastItem&&<Divider/>}
134+
</Fragment>
135+
);
136+
})}
137+
</List>
138+
</Card>
139+
))}
140+
</Stack>
141+
);
142+
};
143+
144+
functionrequiredFieldsArePresent(
145+
obj:Record<string,string|undefined>,
146+
fields:string[],
147+
):boolean{
148+
returnfields.every((field)=>Boolean(obj[field]));
149+
}
150+
151+
typeMethodToggleGroupProps={
152+
templateId:string;
153+
options:NotificationMethod[];
154+
value:NotificationMethod;
155+
};
156+
157+
constMethodToggleGroup:FC<MethodToggleGroupProps>=({
158+
value,
159+
options,
160+
templateId,
161+
})=>{
162+
constqueryClient=useQueryClient();
163+
constupdateMethodMutation=useMutation(
164+
updateNotificationTemplateMethod(templateId,queryClient),
165+
);
166+
167+
return(
168+
<ToggleButtonGroup
169+
exclusive
170+
value={value}
171+
size="small"
172+
aria-label="Notification method"
173+
css={styles.toggleGroup}
174+
onChange={async(_,method)=>{
175+
try{
176+
awaitupdateMethodMutation.mutateAsync({
177+
method,
178+
});
179+
displaySuccess("Notification method updated");
180+
}catch(error){
181+
displayError(
182+
getErrorMessage(error,"Failed to update notification method"),
183+
);
184+
}
185+
}}
186+
>
187+
{options.map((method)=>{
188+
constIcon=methodIcons[method];
189+
constlabel=methodLabels[method];
190+
return(
191+
<Tooltipkey={method}title={label}>
192+
<ToggleButton
193+
value={method}
194+
css={styles.toggleButton}
195+
onClick={(e)=>{
196+
// Retain the value if the user clicks the same button, ensuring
197+
// at least one value remains selected.
198+
if(method===value){
199+
e.preventDefault();
200+
e.stopPropagation();
201+
return;
202+
}
203+
}}
204+
>
205+
<Iconaria-label={label}/>
206+
</ToggleButton>
207+
</Tooltip>
208+
);
209+
})}
210+
</ToggleButtonGroup>
211+
);
212+
};
213+
214+
conststyles={
215+
listHeader:(theme)=>({
216+
background:theme.palette.background.paper,
217+
borderBottom:`1px solid${theme.palette.divider}`,
218+
}),
219+
listItemText:{
220+
[`& .${listItemTextClasses.primary}`]:{
221+
fontSize:14,
222+
fontWeight:500,
223+
},
224+
[`& .${listItemTextClasses.secondary}`]:{
225+
fontSize:14,
226+
},
227+
},
228+
toggleGroup:(theme)=>({
229+
border:`1px solid${theme.palette.divider}`,
230+
borderRadius:4,
231+
}),
232+
toggleButton:(theme)=>({
233+
border:0,
234+
borderRadius:4,
235+
fontSize:16,
236+
padding:"4px 8px",
237+
color:theme.palette.text.disabled,
238+
239+
"&:hover":{
240+
color:theme.palette.text.primary,
241+
},
242+
243+
"& svg":{
244+
fontSize:"inherit",
245+
},
246+
}),
247+
}asRecord<string,Interpolation<Theme>>;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp