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

Commit21942af

Browse files
feat(site): implement notification ui (#14175)
1 parentaaa5174 commit21942af

File tree

21 files changed

+1324
-6
lines changed

21 files changed

+1324
-6
lines changed

‎coderd/database/queries.sql.go‎

Lines changed: 1 addition & 0 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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,4 +170,5 @@ WHERE id = @id::uuid;
170170
-- name: GetNotificationTemplatesByKind :many
171171
SELECT*
172172
FROM notification_templates
173-
WHERE kind= @kind::notification_template_kind;
173+
WHERE kind= @kind::notification_template_kind
174+
ORDER BY nameASC;

‎site/src/@types/storybook.d.ts‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import*as_storybook_typesfrom"@storybook/react";
22
importtype{QueryKey}from"react-query";
3-
importtype{Experiments,FeatureName}from"api/typesGenerated";
3+
importtype{
4+
Experiments,
5+
FeatureName,
6+
SerpentOption,
7+
User,
8+
DeploymentValues,
9+
}from"api/typesGenerated";
10+
importtype{Permissions}from"contexts/auth/permissions";
411

512
declare module"@storybook/react"{
613
typeWebSocketEvent=
@@ -11,5 +18,9 @@ declare module "@storybook/react" {
1118
experiments?:Experiments;
1219
queries?:{key:QueryKey;data:unknown}[];
1320
webSocket?:WebSocketEvent[];
21+
user?:User;
22+
permissions?:Partial<Permissions>;
23+
deploymentValues?:DeploymentValues;
24+
deploymentOptions?:SerpentOption[];
1425
}
1526
}

‎site/src/api/api.ts‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2036,6 +2036,49 @@ class ApiMethods {
20362036

20372037
returnresponse.data;
20382038
};
2039+
2040+
getUserNotificationPreferences=async(userId:string)=>{
2041+
constres=awaitthis.axios.get<TypesGen.NotificationPreference[]|null>(
2042+
`/api/v2/users/${userId}/notifications/preferences`,
2043+
);
2044+
returnres.data??[];
2045+
};
2046+
2047+
putUserNotificationPreferences=async(
2048+
userId:string,
2049+
req:TypesGen.UpdateUserNotificationPreferences,
2050+
)=>{
2051+
constres=awaitthis.axios.put<TypesGen.NotificationPreference[]>(
2052+
`/api/v2/users/${userId}/notifications/preferences`,
2053+
req,
2054+
);
2055+
returnres.data;
2056+
};
2057+
2058+
getSystemNotificationTemplates=async()=>{
2059+
constres=awaitthis.axios.get<TypesGen.NotificationTemplate[]>(
2060+
`/api/v2/notifications/templates/system`,
2061+
);
2062+
returnres.data;
2063+
};
2064+
2065+
getNotificationDispatchMethods=async()=>{
2066+
constres=awaitthis.axios.get<TypesGen.NotificationMethodsResponse>(
2067+
`/api/v2/notifications/dispatch-methods`,
2068+
);
2069+
returnres.data;
2070+
};
2071+
2072+
updateNotificationTemplateMethod=async(
2073+
templateId:string,
2074+
req:TypesGen.UpdateNotificationTemplateMethod,
2075+
)=>{
2076+
constres=awaitthis.axios.put<void>(
2077+
`/api/v2/notifications/templates/${templateId}/method`,
2078+
req,
2079+
);
2080+
returnres.data;
2081+
};
20392082
}
20402083

20412084
// This is a hard coded CSRF token/cookie pair for local development. In prod,
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
importtype{QueryClient,UseMutationOptions}from"react-query";
2+
import{API}from"api/api";
3+
importtype{
4+
NotificationPreference,
5+
NotificationTemplate,
6+
UpdateNotificationTemplateMethod,
7+
UpdateUserNotificationPreferences,
8+
}from"api/typesGenerated";
9+
10+
exportconstuserNotificationPreferencesKey=(userId:string)=>[
11+
"users",
12+
userId,
13+
"notifications",
14+
"preferences",
15+
];
16+
17+
exportconstuserNotificationPreferences=(userId:string)=>{
18+
return{
19+
queryKey:userNotificationPreferencesKey(userId),
20+
queryFn:()=>API.getUserNotificationPreferences(userId),
21+
};
22+
};
23+
24+
exportconstupdateUserNotificationPreferences=(
25+
userId:string,
26+
queryClient:QueryClient,
27+
)=>{
28+
return{
29+
mutationFn:(req)=>{
30+
returnAPI.putUserNotificationPreferences(userId,req);
31+
},
32+
onMutate:(data)=>{
33+
queryClient.setQueryData(
34+
userNotificationPreferencesKey(userId),
35+
Object.entries(data.template_disabled_map).map(
36+
([id,disabled])=>
37+
({
38+
id,
39+
disabled,
40+
updated_at:newDate().toISOString(),
41+
})satisfiesNotificationPreference,
42+
),
43+
);
44+
},
45+
}satisfiesUseMutationOptions<
46+
NotificationPreference[],
47+
unknown,
48+
UpdateUserNotificationPreferences
49+
>;
50+
};
51+
52+
exportconstsystemNotificationTemplatesKey=[
53+
"notifications",
54+
"templates",
55+
"system",
56+
];
57+
58+
exportconstsystemNotificationTemplates=()=>{
59+
return{
60+
queryKey:systemNotificationTemplatesKey,
61+
queryFn:()=>API.getSystemNotificationTemplates(),
62+
};
63+
};
64+
65+
exportfunctionselectTemplatesByGroup(
66+
data:NotificationTemplate[],
67+
):Record<string,NotificationTemplate[]>{
68+
constgrouped=data.reduce(
69+
(acc,tpl)=>{
70+
if(!acc[tpl.group]){
71+
acc[tpl.group]=[];
72+
}
73+
acc[tpl.group].push(tpl);
74+
returnacc;
75+
},
76+
{}asRecord<string,NotificationTemplate[]>,
77+
);
78+
79+
// Sort templates within each group
80+
for(constgroupingrouped){
81+
grouped[group].sort((a,b)=>a.name.localeCompare(b.name));
82+
}
83+
84+
// Sort groups by name
85+
constsortedGroups=Object.keys(grouped).sort((a,b)=>a.localeCompare(b));
86+
constsortedGrouped:Record<string,NotificationTemplate[]>={};
87+
for(constgroupofsortedGroups){
88+
sortedGrouped[group]=grouped[group];
89+
}
90+
91+
returnsortedGrouped;
92+
}
93+
94+
exportconstnotificationDispatchMethodsKey=[
95+
"notifications",
96+
"dispatchMethods",
97+
];
98+
99+
exportconstnotificationDispatchMethods=()=>{
100+
return{
101+
staleTime:Infinity,
102+
queryKey:notificationDispatchMethodsKey,
103+
queryFn:()=>API.getNotificationDispatchMethods(),
104+
};
105+
};
106+
107+
exportconstupdateNotificationTemplateMethod=(
108+
templateId:string,
109+
queryClient:QueryClient,
110+
)=>{
111+
return{
112+
mutationFn:(req:UpdateNotificationTemplateMethod)=>
113+
API.updateNotificationTemplateMethod(templateId,req),
114+
onMutate:(data)=>{
115+
constprevData=queryClient.getQueryData<NotificationTemplate[]>(
116+
systemNotificationTemplatesKey,
117+
);
118+
if(!prevData){
119+
return;
120+
}
121+
queryClient.setQueryData(
122+
systemNotificationTemplatesKey,
123+
prevData.map((tpl)=>
124+
tpl.id===templateId
125+
?{
126+
...tpl,
127+
method:data.method,
128+
}
129+
:tpl,
130+
),
131+
);
132+
},
133+
}satisfiesUseMutationOptions<
134+
void,
135+
unknown,
136+
UpdateNotificationTemplateMethod
137+
>;
138+
};

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,12 @@ export function apiKey(): UseQueryOptions<GenerateAPIKeyResponse> {
141141
};
142142
}
143143

144+
exportconsthasFirstUserKey=["hasFirstUser"];
145+
144146
exportconsthasFirstUser=(userMetadata:MetadataState<User>)=>{
145147
returncachedQuery({
146148
metadata:userMetadata,
147-
queryKey:["hasFirstUser"],
149+
queryKey:hasFirstUserKey,
148150
queryFn:API.hasFirstUser,
149151
});
150152
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
importEmailIconfrom"@mui/icons-material/EmailOutlined";
2+
importWebhookIconfrom"@mui/icons-material/WebhookOutlined";
3+
4+
// TODO: This should be provided by the auto generated types from codersdk
5+
constnotificationMethods=["smtp","webhook"]asconst;
6+
7+
exporttypeNotificationMethod=(typeofnotificationMethods)[number];
8+
9+
exportconstmethodIcons:Record<NotificationMethod,typeofEmailIcon>={
10+
smtp:EmailIcon,
11+
webhook:WebhookIcon,
12+
};
13+
14+
exportconstmethodLabels:Record<NotificationMethod,string>={
15+
smtp:"SMTP",
16+
webhook:"Webhook",
17+
};
18+
19+
exportconstcastNotificationMethod=(value:string)=>{
20+
if(notificationMethods.includes(valueasNotificationMethod)){
21+
returnvalueasNotificationMethod;
22+
}
23+
24+
thrownewError(
25+
`Invalid notification method:${value}. Accepted values:${notificationMethods.join(
26+
", ",
27+
)}`,
28+
);
29+
};

‎site/src/pages/DeploySettingsPage/DeploySettingsLayout.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const useDeploySettings = (): DeploySettingsContextValue => {
2424
constcontext=useContext(DeploySettingsContext);
2525
if(!context){
2626
thrownewError(
27-
"useDeploySettings should be used inside of DeploySettingsLayout",
27+
"useDeploySettings should be used inside ofDeploySettingsContext orDeploySettingsLayout",
2828
);
2929
}
3030
returncontext;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp