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

feat(site): implement notification ui#14175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
BrunoQuaresma merged 34 commits intomainfrombq/user-notifications
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
34 commits
Select commitHold shift + click to select a range
fc282e7
Add base notification settings page to deployment
BrunoQuaresmaJul 29, 2024
997e0d3
Add base notifications components
BrunoQuaresmaJul 29, 2024
73f8471
Bind notifications into the user account notifications page
BrunoQuaresmaAug 2, 2024
33c9ab0
Remove deployment notifications page
BrunoQuaresmaAug 2, 2024
bbc8cbb
Add test for toggling notifications
BrunoQuaresmaAug 2, 2024
7227a42
Update migration
BrunoQuaresmaAug 5, 2024
788de88
Update template notification methods
BrunoQuaresmaAug 5, 2024
1262f7b
Fix types
BrunoQuaresmaAug 5, 2024
e449e3d
Fix remaining type issues
BrunoQuaresmaAug 5, 2024
0154b94
Experience improvements
BrunoQuaresmaAug 5, 2024
aedf159
Fix validation
BrunoQuaresmaAug 5, 2024
1a2efae
Remove BE changes
BrunoQuaresmaAug 6, 2024
a1f363c
Fix FE types
BrunoQuaresmaAug 6, 2024
7412eb9
Fix notifications permissions
BrunoQuaresmaAug 6, 2024
1ff0973
Display webhook info
BrunoQuaresmaAug 6, 2024
2acde04
Merge branch 'main' of https://github.com/coder/coder into bq/user-no…
BrunoQuaresmaAug 6, 2024
7cc7bdb
Add tests to the notifications page
BrunoQuaresmaAug 6, 2024
4956409
Remove unecessary migration
BrunoQuaresmaAug 6, 2024
1c62242
Don't show deployment wide method
BrunoQuaresmaAug 6, 2024
a020619
Fix templates sorting
BrunoQuaresmaAug 6, 2024
0efc40d
Add nav tabs
BrunoQuaresmaAug 6, 2024
1aedb92
Update titles
BrunoQuaresmaAug 6, 2024
786b005
Add tests
BrunoQuaresmaAug 6, 2024
2ecbe5f
Improve product copy
BrunoQuaresmaAug 7, 2024
ec7ab40
Fix notifications visibility
BrunoQuaresmaAug 7, 2024
c986e51
Minor improvements
BrunoQuaresmaAug 7, 2024
e037423
Remove alerts
BrunoQuaresmaAug 8, 2024
341f550
Add alerts when SMTP or Webhook config are enabled but not set
BrunoQuaresmaAug 8, 2024
d97dd82
Apply a few Michaels suggestions
BrunoQuaresmaAug 8, 2024
4399cd6
Merge branch 'main' of https://github.com/coder/coder into bq/user-no…
BrunoQuaresmaAug 9, 2024
d403eed
Simplify state logic for the switch component
BrunoQuaresmaAug 9, 2024
fb02aec
Update copy
BrunoQuaresmaAug 9, 2024
013ccff
Apply PR comments
BrunoQuaresmaAug 9, 2024
7858a5a
Add docs
BrunoQuaresmaAug 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletionscoderd/database/queries.sql.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

3 changes: 2 additions & 1 deletioncoderd/database/queries/notifications.sql
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -170,4 +170,5 @@ WHERE id = @id::uuid;
-- name: GetNotificationTemplatesByKind :many
SELECT *
FROM notification_templates
WHERE kind = @kind::notification_template_kind;
WHERE kind = @kind::notification_template_kind
ORDER BY name ASC;
13 changes: 12 additions & 1 deletionsite/src/@types/storybook.d.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
import * as _storybook_types from "@storybook/react";
import type { QueryKey } from "react-query";
import type { Experiments, FeatureName } from "api/typesGenerated";
import type {
Experiments,
FeatureName,
SerpentOption,
User,
DeploymentValues,
} from "api/typesGenerated";
import type { Permissions } from "contexts/auth/permissions";

declare module "@storybook/react" {
type WebSocketEvent =
Expand All@@ -11,5 +18,9 @@ declare module "@storybook/react" {
experiments?: Experiments;
queries?: { key: QueryKey; data: unknown }[];
webSocket?: WebSocketEvent[];
user?: User;
permissions?: Partial<Permissions>;
deploymentValues?: DeploymentValues;
deploymentOptions?: SerpentOption[];
}
}
43 changes: 43 additions & 0 deletionssite/src/api/api.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2036,6 +2036,49 @@ class ApiMethods {

return response.data;
};

getUserNotificationPreferences = async (userId: string) => {
const res = await this.axios.get<TypesGen.NotificationPreference[] | null>(
`/api/v2/users/${userId}/notifications/preferences`,
);
return res.data ?? [];
};

putUserNotificationPreferences = async (
userId: string,
req: TypesGen.UpdateUserNotificationPreferences,
) => {
const res = await this.axios.put<TypesGen.NotificationPreference[]>(
`/api/v2/users/${userId}/notifications/preferences`,
req,
);
return res.data;
};

getSystemNotificationTemplates = async () => {
const res = await this.axios.get<TypesGen.NotificationTemplate[]>(
`/api/v2/notifications/templates/system`,
);
return res.data;
};

getNotificationDispatchMethods = async () => {
const res = await this.axios.get<TypesGen.NotificationMethodsResponse>(
`/api/v2/notifications/dispatch-methods`,
);
return res.data;
};

updateNotificationTemplateMethod = async (
templateId: string,
req: TypesGen.UpdateNotificationTemplateMethod,
) => {
const res = await this.axios.put<void>(
`/api/v2/notifications/templates/${templateId}/method`,
req,
);
return res.data;
};
}

// This is a hard coded CSRF token/cookie pair for local development. In prod,
Expand Down
138 changes: 138 additions & 0 deletionssite/src/api/queries/notifications.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
import type { QueryClient, UseMutationOptions } from "react-query";
import { API } from "api/api";
import type {
NotificationPreference,
NotificationTemplate,
UpdateNotificationTemplateMethod,
UpdateUserNotificationPreferences,
} from "api/typesGenerated";

export const userNotificationPreferencesKey = (userId: string) => [
"users",
userId,
"notifications",
"preferences",
];

export const userNotificationPreferences = (userId: string) => {
return {
queryKey: userNotificationPreferencesKey(userId),
queryFn: () => API.getUserNotificationPreferences(userId),
};
};

export const updateUserNotificationPreferences = (
userId: string,
queryClient: QueryClient,
) => {
return {
mutationFn: (req) => {
return API.putUserNotificationPreferences(userId, req);
},
onMutate: (data) => {
queryClient.setQueryData(
userNotificationPreferencesKey(userId),
Object.entries(data.template_disabled_map).map(
([id, disabled]) =>
({
id,
disabled,
updated_at: new Date().toISOString(),
}) satisfies NotificationPreference,
),
);
},
} satisfies UseMutationOptions<
NotificationPreference[],
unknown,
UpdateUserNotificationPreferences
>;
};

export const systemNotificationTemplatesKey = [
"notifications",
"templates",
"system",
];

export const systemNotificationTemplates = () => {
return {
queryKey: systemNotificationTemplatesKey,
queryFn: () => API.getSystemNotificationTemplates(),
};
};

export function selectTemplatesByGroup(
data: NotificationTemplate[],
): Record<string, NotificationTemplate[]> {
const grouped = data.reduce(
(acc, tpl) => {
if (!acc[tpl.group]) {
acc[tpl.group] = [];
}
acc[tpl.group].push(tpl);
return acc;
},
{} as Record<string, NotificationTemplate[]>,
);

// Sort templates within each group
for (const group in grouped) {
grouped[group].sort((a, b) => a.name.localeCompare(b.name));
}

// Sort groups by name
const sortedGroups = Object.keys(grouped).sort((a, b) => a.localeCompare(b));
const sortedGrouped: Record<string, NotificationTemplate[]> = {};
for (const group of sortedGroups) {
sortedGrouped[group] = grouped[group];
}

return sortedGrouped;
}

export const notificationDispatchMethodsKey = [
"notifications",
"dispatchMethods",
];

export const notificationDispatchMethods = () => {
return {
staleTime: Infinity,
queryKey: notificationDispatchMethodsKey,
queryFn: () => API.getNotificationDispatchMethods(),
};
};

export const updateNotificationTemplateMethod = (
templateId: string,
queryClient: QueryClient,
) => {
return {
mutationFn: (req: UpdateNotificationTemplateMethod) =>
API.updateNotificationTemplateMethod(templateId, req),
onMutate: (data) => {
const prevData = queryClient.getQueryData<NotificationTemplate[]>(
systemNotificationTemplatesKey,
);
if (!prevData) {
return;
}
queryClient.setQueryData(
systemNotificationTemplatesKey,
prevData.map((tpl) =>
tpl.id === templateId
? {
...tpl,
method: data.method,
}
: tpl,
),
);
},
} satisfies UseMutationOptions<
void,
unknown,
UpdateNotificationTemplateMethod
>;
};
4 changes: 3 additions & 1 deletionsite/src/api/queries/users.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,10 +141,12 @@ export function apiKey(): UseQueryOptions<GenerateAPIKeyResponse> {
};
}

export const hasFirstUserKey = ["hasFirstUser"];

export const hasFirstUser = (userMetadata: MetadataState<User>) => {
return cachedQuery({
metadata: userMetadata,
queryKey:["hasFirstUser"],
queryKey:hasFirstUserKey,
queryFn: API.hasFirstUser,
});
};
Expand Down
29 changes: 29 additions & 0 deletionssite/src/modules/notifications/utils.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
import EmailIcon from "@mui/icons-material/EmailOutlined";
import WebhookIcon from "@mui/icons-material/WebhookOutlined";

// TODO: This should be provided by the auto generated types from codersdk
const notificationMethods = ["smtp", "webhook"] as const;

export type NotificationMethod = (typeof notificationMethods)[number];

export const methodIcons: Record<NotificationMethod, typeof EmailIcon> = {
smtp: EmailIcon,
webhook: WebhookIcon,
};

export const methodLabels: Record<NotificationMethod, string> = {
smtp: "SMTP",
webhook: "Webhook",
};

export const castNotificationMethod = (value: string) => {
if (notificationMethods.includes(value as NotificationMethod)) {
return value as NotificationMethod;
}

throw new Error(
`Invalid notification method: ${value}. Accepted values: ${notificationMethods.join(
", ",
)}`,
);
};
Comment on lines +19 to +29
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Tagging this for now while I look through the other files. I don't fully understand the point of this function, and at the very least, I think that it should be redefined as a type predicate

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This is just easier to use IMO.

Type predicate:

constisNotificationMethod=(v:string):v isNotificationMethod=>{}// Usagevalues.map(v=>{if(!isNotificationMethod(v)){thrownewError("v is not valid")}return<Componentmethod={method}/>})

Cast function

constcastNotificationMethod=(v:string):NotificationMethod=>{}// Usagevalues.map(v=>{constmethod=castNotificationMethod(v)return<Componentmethod={method}/>})

View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,7 @@ export const useDeploySettings = (): DeploySettingsContextValue => {
const context = useContext(DeploySettingsContext);
if (!context) {
throw new Error(
"useDeploySettings should be used inside of DeploySettingsLayout",
"useDeploySettings should be used inside ofDeploySettingsContext orDeploySettingsLayout",
);
}
return context;
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp