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

Commit3301212

Browse files
authored
feat: turn off notification via email (#14520)
1 parent5bd19f8 commit3301212

File tree

9 files changed

+153
-14
lines changed

9 files changed

+153
-14
lines changed

‎coderd/database/queries.sql.go

Lines changed: 10 additions & 7 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/queries/notifications.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
-- name: FetchNewMessageMetadata :one
22
-- This is used to build up the notification_message's JSON payload.
33
SELECTnt.nameAS notification_name,
4+
nt.idAS notification_template_id,
45
nt.actionsAS actions,
56
nt.methodAS custom_method,
67
u.idAS user_id,

‎coderd/notifications/dispatch/smtp/html.gotmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<div style="border-top: 1px solid #e2e8f0; color: #475569; font-size: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
2727
<p>&copy;&nbsp;{{ current_year }}&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a href="{{ base_url }}" style="color: #2563eb; text-decoration: none;">{{ base_url }}</a></p>
2828
<p><a href="{{ base_url }}/settings/notifications" style="color: #2563eb; text-decoration: none;">Click here to manage your notification settings</a></p>
29+
<p><a href="{{ base_url }}/settings/notifications?disabled={{ .NotificationTemplateID }}" style="color: #2563eb; text-decoration: none;">Stop receiving emails like this</a></p>
2930
</div>
3031
</div>
3132
</body>

‎coderd/notifications/enqueuer.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,10 @@ func (s *StoreEnqueuer) Enqueue(ctx context.Context, userID, templateID uuid.UUI
121121
// actions which can be taken by the recipient.
122122
func (s*StoreEnqueuer)buildPayload(metadata database.FetchNewMessageMetadataRow,labelsmap[string]string) (*types.MessagePayload,error) {
123123
payload:= types.MessagePayload{
124-
Version:"1.0",
124+
Version:"1.1",
125125

126-
NotificationName:metadata.NotificationName,
126+
NotificationName:metadata.NotificationName,
127+
NotificationTemplateID:metadata.NotificationTemplateID.String(),
127128

128129
UserID:metadata.UserID.String(),
129130
UserEmail:metadata.UserEmail,

‎coderd/notifications/render/gotmpl_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ func TestGoTemplate(t *testing.T) {
5656
"url": "https://mocked-server-address/@johndoe/my-workspace"
5757
}]`,
5858
},
59+
{
60+
name:"render notification template ID",
61+
in:`{{ .NotificationTemplateID }}`,
62+
payload: types.MessagePayload{
63+
NotificationTemplateID:"4e19c0ac-94e1-4532-9515-d1801aa283b2",
64+
},
65+
expectedOutput:"4e19c0ac-94e1-4532-9515-d1801aa283b2",
66+
expectedErr:nil,
67+
},
5968
}
6069

6170
for_,tc:=rangetests {

‎coderd/notifications/types/payload.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ package types
77
typeMessagePayloadstruct {
88
Versionstring`json:"_version"`
99

10-
NotificationNamestring`json:"notification_name"`
10+
NotificationNamestring`json:"notification_name"`
11+
NotificationTemplateIDstring`json:"notification_template_id"`
1112

1213
UserIDstring`json:"user_id"`
1314
UserEmailstring`json:"user_email"`

‎site/src/api/queries/notifications.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,22 @@ export const updateNotificationTemplateMethod = (
136136
UpdateNotificationTemplateMethod
137137
>;
138138
};
139+
140+
exportconstdisableNotification=(
141+
userId:string,
142+
queryClient:QueryClient,
143+
)=>{
144+
return{
145+
mutationFn:async(templateId:string)=>{
146+
constresult=awaitAPI.putUserNotificationPreferences(userId,{
147+
template_disabled_map:{
148+
[templateId]:true,
149+
},
150+
});
151+
returnresult;
152+
},
153+
onSuccess:(data)=>{
154+
queryClient.setQueryData(userNotificationPreferencesKey(userId),data);
155+
},
156+
}satisfiesUseMutationOptions<NotificationPreference[],unknown,string>;
157+
};

‎site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
importtype{Meta,StoryObj}from"@storybook/react";
2-
import{spyOn,userEvent,within}from"@storybook/test";
2+
import{expect,spyOn,userEvent,waitFor,within}from"@storybook/test";
33
import{API}from"api/api";
44
import{
55
notificationDispatchMethodsKey,
66
systemNotificationTemplatesKey,
77
userNotificationPreferencesKey,
88
}from"api/queries/notifications";
9+
import{http,HttpResponse}from"msw";
10+
import{reactRouterParameters}from"storybook-addon-remix-react-router";
911
import{
1012
MockNotificationMethodsResponse,
1113
MockNotificationPreferences,
@@ -19,7 +21,7 @@ import {
1921
}from"testHelpers/storybook";
2022
import{NotificationsPage}from"./NotificationsPage";
2123

22-
constmeta:Meta<typeofNotificationsPage>={
24+
constmeta={
2325
title:"pages/UserSettingsPage/NotificationsPage",
2426
component:NotificationsPage,
2527
parameters:{
@@ -42,7 +44,7 @@ const meta: Meta<typeof NotificationsPage> = {
4244
permissions:{viewDeploymentValues:true},
4345
},
4446
decorators:[withGlobalSnackbar,withAuthProvider,withDashboardProvider],
45-
};
47+
}satisfiesMeta<typeofNotificationsPage>;
4648

4749
exportdefaultmeta;
4850
typeStory=StoryObj<typeofNotificationsPage>;
@@ -76,3 +78,78 @@ export const NonAdmin: Story = {
7678
permissions:{viewDeploymentValues:false},
7779
},
7880
};
81+
82+
// Ensure the selected notification template is enabled before attempting to
83+
// disable it.
84+
constenabledPreference=MockNotificationPreferences.find(
85+
(pref)=>pref.disabled===false,
86+
);
87+
if(!enabledPreference){
88+
thrownewError(
89+
"No enabled notification preference available to test the disabling action.",
90+
);
91+
}
92+
consttemplateToDisable=MockNotificationTemplates.find(
93+
(tpl)=>tpl.id===enabledPreference.id,
94+
);
95+
if(!templateToDisable){
96+
thrownewError("No notification template matches the enabled preference.");
97+
}
98+
99+
exportconstDisableValidTemplate:Story={
100+
parameters:{
101+
reactRouter:reactRouterParameters({
102+
location:{
103+
searchParams:{disabled:templateToDisable.id},
104+
},
105+
}),
106+
},
107+
decorators:[
108+
(Story)=>{
109+
// Since the action occurs during the initial render, we need to spy on
110+
// the API call before the story is rendered. This is done using a
111+
// decorator to ensure the spy is set up in time.
112+
spyOn(API,"putUserNotificationPreferences").mockResolvedValue(
113+
MockNotificationPreferences.map((pref)=>{
114+
if(pref.id===templateToDisable.id){
115+
return{
116+
...pref,
117+
disabled:true,
118+
};
119+
}
120+
returnpref;
121+
}),
122+
);
123+
return<Story/>;
124+
},
125+
],
126+
play:async({ canvasElement})=>{
127+
awaitwithin(document.body).findByText("Notification has been disabled");
128+
constswitchEl=awaitwithin(canvasElement).findByLabelText(
129+
templateToDisable.name,
130+
);
131+
expect(switchEl).not.toBeChecked();
132+
},
133+
};
134+
135+
exportconstDisableInvalidTemplate:Story={
136+
parameters:{
137+
reactRouter:reactRouterParameters({
138+
location:{
139+
searchParams:{disabled:"invalid-template-id"},
140+
},
141+
}),
142+
},
143+
decorators:[
144+
(Story)=>{
145+
// Since the action occurs during the initial render, we need to spy on
146+
// the API call before the story is rendered. This is done using a
147+
// decorator to ensure the spy is set up in time.
148+
spyOn(API,"putUserNotificationPreferences").mockRejectedValue({});
149+
return<Story/>;
150+
},
151+
],
152+
play:async()=>{
153+
awaitwithin(document.body).findByText("Error disabling notification");
154+
},
155+
};

‎site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import ListItemText, { listItemTextClasses } from "@mui/material/ListItemText";
88
importSwitchfrom"@mui/material/Switch";
99
importTooltipfrom"@mui/material/Tooltip";
1010
import{
11+
disableNotification,
1112
notificationDispatchMethods,
1213
selectTemplatesByGroup,
1314
systemNotificationTemplates,
@@ -18,7 +19,7 @@ import type {
1819
NotificationPreference,
1920
NotificationTemplate,
2021
}from"api/typesGenerated";
21-
import{displaySuccess}from"components/GlobalSnackbar/utils";
22+
import{displayError,displaySuccess}from"components/GlobalSnackbar/utils";
2223
import{Loader}from"components/Loader/Loader";
2324
import{Stack}from"components/Stack/Stack";
2425
import{useAuthenticated}from"contexts/auth/RequireAuth";
@@ -28,8 +29,10 @@ import {
2829
methodLabels,
2930
}from"modules/notifications/utils";
3031
import{typeFC,Fragment}from"react";
32+
import{useEffect}from"react";
3133
import{Helmet}from"react-helmet-async";
3234
import{useMutation,useQueries,useQueryClient}from"react-query";
35+
import{useSearchParams}from"react-router-dom";
3336
import{pageTitle}from"utils/page";
3437
import{Section}from"../Section";
3538

@@ -60,6 +63,30 @@ export const NotificationsPage: FC = () => {
6063
constupdatePreferences=useMutation(
6164
updateUserNotificationPreferences(user.id,queryClient),
6265
);
66+
67+
// Notification emails contain a link to disable a specific notification
68+
// template. This functionality is achieved using the query string parameter
69+
// "disabled".
70+
constdisableMutation=useMutation(
71+
disableNotification(user.id,queryClient),
72+
);
73+
const[searchParams]=useSearchParams();
74+
constdisabledId=searchParams.get("disabled");
75+
useEffect(()=>{
76+
if(!disabledId){
77+
return;
78+
}
79+
searchParams.delete("disabled");
80+
disableMutation
81+
.mutateAsync(disabledId)
82+
.then(()=>{
83+
displaySuccess("Notification has been disabled");
84+
})
85+
.catch(()=>{
86+
displayError("Error disabling notification");
87+
});
88+
},[searchParams.delete,disabledId,disableMutation]);
89+
6390
constready=
6491
disabledPreferences.data&&templatesByGroup.data&&dispatchMethods.data;
6592

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp