mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: set icons for each type of notification (#17115)
Each notification type will have an icon to represent the context: <img width="503" alt="Screenshot 2025-03-26 at 13 44 35" src="https://github.com/user-attachments/assets/1187c1c0-1043-4a32-b105-a7f91b52f8ca" /> This depends on https://github.com/coder/coder/pull/17013
This commit is contained in:
@@ -31,30 +31,30 @@ const (
|
|||||||
|
|
||||||
var fallbackIcons = map[uuid.UUID]string{
|
var fallbackIcons = map[uuid.UUID]string{
|
||||||
// workspace related notifications
|
// workspace related notifications
|
||||||
notifications.TemplateWorkspaceCreated: codersdk.FallbackIconWorkspace,
|
notifications.TemplateWorkspaceCreated: codersdk.InboxNotificationFallbackIconWorkspace,
|
||||||
notifications.TemplateWorkspaceManuallyUpdated: codersdk.FallbackIconWorkspace,
|
notifications.TemplateWorkspaceManuallyUpdated: codersdk.InboxNotificationFallbackIconWorkspace,
|
||||||
notifications.TemplateWorkspaceDeleted: codersdk.FallbackIconWorkspace,
|
notifications.TemplateWorkspaceDeleted: codersdk.InboxNotificationFallbackIconWorkspace,
|
||||||
notifications.TemplateWorkspaceAutobuildFailed: codersdk.FallbackIconWorkspace,
|
notifications.TemplateWorkspaceAutobuildFailed: codersdk.InboxNotificationFallbackIconWorkspace,
|
||||||
notifications.TemplateWorkspaceDormant: codersdk.FallbackIconWorkspace,
|
notifications.TemplateWorkspaceDormant: codersdk.InboxNotificationFallbackIconWorkspace,
|
||||||
notifications.TemplateWorkspaceAutoUpdated: codersdk.FallbackIconWorkspace,
|
notifications.TemplateWorkspaceAutoUpdated: codersdk.InboxNotificationFallbackIconWorkspace,
|
||||||
notifications.TemplateWorkspaceMarkedForDeletion: codersdk.FallbackIconWorkspace,
|
notifications.TemplateWorkspaceMarkedForDeletion: codersdk.InboxNotificationFallbackIconWorkspace,
|
||||||
notifications.TemplateWorkspaceManualBuildFailed: codersdk.FallbackIconWorkspace,
|
notifications.TemplateWorkspaceManualBuildFailed: codersdk.InboxNotificationFallbackIconWorkspace,
|
||||||
notifications.TemplateWorkspaceOutOfMemory: codersdk.FallbackIconWorkspace,
|
notifications.TemplateWorkspaceOutOfMemory: codersdk.InboxNotificationFallbackIconWorkspace,
|
||||||
notifications.TemplateWorkspaceOutOfDisk: codersdk.FallbackIconWorkspace,
|
notifications.TemplateWorkspaceOutOfDisk: codersdk.InboxNotificationFallbackIconWorkspace,
|
||||||
|
|
||||||
// account related notifications
|
// account related notifications
|
||||||
notifications.TemplateUserAccountCreated: codersdk.FallbackIconAccount,
|
notifications.TemplateUserAccountCreated: codersdk.InboxNotificationFallbackIconAccount,
|
||||||
notifications.TemplateUserAccountDeleted: codersdk.FallbackIconAccount,
|
notifications.TemplateUserAccountDeleted: codersdk.InboxNotificationFallbackIconAccount,
|
||||||
notifications.TemplateUserAccountSuspended: codersdk.FallbackIconAccount,
|
notifications.TemplateUserAccountSuspended: codersdk.InboxNotificationFallbackIconAccount,
|
||||||
notifications.TemplateUserAccountActivated: codersdk.FallbackIconAccount,
|
notifications.TemplateUserAccountActivated: codersdk.InboxNotificationFallbackIconAccount,
|
||||||
notifications.TemplateYourAccountSuspended: codersdk.FallbackIconAccount,
|
notifications.TemplateYourAccountSuspended: codersdk.InboxNotificationFallbackIconAccount,
|
||||||
notifications.TemplateYourAccountActivated: codersdk.FallbackIconAccount,
|
notifications.TemplateYourAccountActivated: codersdk.InboxNotificationFallbackIconAccount,
|
||||||
notifications.TemplateUserRequestedOneTimePasscode: codersdk.FallbackIconAccount,
|
notifications.TemplateUserRequestedOneTimePasscode: codersdk.InboxNotificationFallbackIconAccount,
|
||||||
|
|
||||||
// template related notifications
|
// template related notifications
|
||||||
notifications.TemplateTemplateDeleted: codersdk.FallbackIconTemplate,
|
notifications.TemplateTemplateDeleted: codersdk.InboxNotificationFallbackIconTemplate,
|
||||||
notifications.TemplateTemplateDeprecated: codersdk.FallbackIconTemplate,
|
notifications.TemplateTemplateDeprecated: codersdk.InboxNotificationFallbackIconTemplate,
|
||||||
notifications.TemplateWorkspaceBuildsFailedReport: codersdk.FallbackIconTemplate,
|
notifications.TemplateWorkspaceBuildsFailedReport: codersdk.InboxNotificationFallbackIconTemplate,
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureNotificationIcon(notif codersdk.InboxNotification) codersdk.InboxNotification {
|
func ensureNotificationIcon(notif codersdk.InboxNotification) codersdk.InboxNotification {
|
||||||
@@ -64,7 +64,7 @@ func ensureNotificationIcon(notif codersdk.InboxNotification) codersdk.InboxNoti
|
|||||||
|
|
||||||
fallbackIcon, ok := fallbackIcons[notif.TemplateID]
|
fallbackIcon, ok := fallbackIcons[notif.TemplateID]
|
||||||
if !ok {
|
if !ok {
|
||||||
fallbackIcon = codersdk.FallbackIconOther
|
fallbackIcon = codersdk.InboxNotificationFallbackIconOther
|
||||||
}
|
}
|
||||||
|
|
||||||
notif.Icon = fallbackIcon
|
notif.Icon = fallbackIcon
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ func TestInboxNotifications_ensureNotificationIcon(t *testing.T) {
|
|||||||
templateID uuid.UUID
|
templateID uuid.UUID
|
||||||
expectedIcon string
|
expectedIcon string
|
||||||
}{
|
}{
|
||||||
{"WorkspaceCreated", "", notifications.TemplateWorkspaceCreated, codersdk.FallbackIconWorkspace},
|
{"WorkspaceCreated", "", notifications.TemplateWorkspaceCreated, codersdk.InboxNotificationFallbackIconWorkspace},
|
||||||
{"UserAccountCreated", "", notifications.TemplateUserAccountCreated, codersdk.FallbackIconAccount},
|
{"UserAccountCreated", "", notifications.TemplateUserAccountCreated, codersdk.InboxNotificationFallbackIconAccount},
|
||||||
{"TemplateDeleted", "", notifications.TemplateTemplateDeleted, codersdk.FallbackIconTemplate},
|
{"TemplateDeleted", "", notifications.TemplateTemplateDeleted, codersdk.InboxNotificationFallbackIconTemplate},
|
||||||
{"TestNotification", "", notifications.TemplateTestNotification, codersdk.FallbackIconOther},
|
{"TestNotification", "", notifications.TemplateTestNotification, codersdk.InboxNotificationFallbackIconOther},
|
||||||
{"TestExistingIcon", "https://cdn.coder.com/icon_notif.png", notifications.TemplateTemplateDeleted, "https://cdn.coder.com/icon_notif.png"},
|
{"TestExistingIcon", "https://cdn.coder.com/icon_notif.png", notifications.TemplateTemplateDeleted, "https://cdn.coder.com/icon_notif.png"},
|
||||||
{"UnknownTemplate", "", uuid.New(), codersdk.FallbackIconOther},
|
{"UnknownTemplate", "", uuid.New(), codersdk.InboxNotificationFallbackIconOther},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ func TestInboxNotification_Watch(t *testing.T) {
|
|||||||
require.Equal(t, memberClient.ID, notif.Notification.UserID)
|
require.Equal(t, memberClient.ID, notif.Notification.UserID)
|
||||||
|
|
||||||
// check for the fallback icon logic
|
// check for the fallback icon logic
|
||||||
require.Equal(t, codersdk.FallbackIconWorkspace, notif.Notification.Icon)
|
require.Equal(t, codersdk.InboxNotificationFallbackIconWorkspace, notif.Notification.Icon)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("OK - change format", func(t *testing.T) {
|
t.Run("OK - change format", func(t *testing.T) {
|
||||||
@@ -557,11 +557,11 @@ func TestInboxNotifications_List(t *testing.T) {
|
|||||||
require.Len(t, notifs.Notifications, 10)
|
require.Len(t, notifs.Notifications, 10)
|
||||||
|
|
||||||
require.Equal(t, "https://dev.coder.com/icon.png", notifs.Notifications[0].Icon)
|
require.Equal(t, "https://dev.coder.com/icon.png", notifs.Notifications[0].Icon)
|
||||||
require.Equal(t, codersdk.FallbackIconWorkspace, notifs.Notifications[9].Icon)
|
require.Equal(t, codersdk.InboxNotificationFallbackIconWorkspace, notifs.Notifications[9].Icon)
|
||||||
require.Equal(t, codersdk.FallbackIconWorkspace, notifs.Notifications[8].Icon)
|
require.Equal(t, codersdk.InboxNotificationFallbackIconWorkspace, notifs.Notifications[8].Icon)
|
||||||
require.Equal(t, codersdk.FallbackIconAccount, notifs.Notifications[7].Icon)
|
require.Equal(t, codersdk.InboxNotificationFallbackIconAccount, notifs.Notifications[7].Icon)
|
||||||
require.Equal(t, codersdk.FallbackIconTemplate, notifs.Notifications[6].Icon)
|
require.Equal(t, codersdk.InboxNotificationFallbackIconTemplate, notifs.Notifications[6].Icon)
|
||||||
require.Equal(t, codersdk.FallbackIconOther, notifs.Notifications[4].Icon)
|
require.Equal(t, codersdk.InboxNotificationFallbackIconOther, notifs.Notifications[4].Icon)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("OK with template filter", func(t *testing.T) {
|
t.Run("OK with template filter", func(t *testing.T) {
|
||||||
@@ -607,7 +607,7 @@ func TestInboxNotifications_List(t *testing.T) {
|
|||||||
require.Len(t, notifs.Notifications, 5)
|
require.Len(t, notifs.Notifications, 5)
|
||||||
|
|
||||||
require.Equal(t, "Notification 8", notifs.Notifications[0].Title)
|
require.Equal(t, "Notification 8", notifs.Notifications[0].Title)
|
||||||
require.Equal(t, codersdk.FallbackIconWorkspace, notifs.Notifications[0].Icon)
|
require.Equal(t, codersdk.InboxNotificationFallbackIconWorkspace, notifs.Notifications[0].Icon)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("OK with target filter", func(t *testing.T) {
|
t.Run("OK with target filter", func(t *testing.T) {
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FallbackIconWorkspace = "DEFAULT_ICON_WORKSPACE"
|
InboxNotificationFallbackIconWorkspace = "DEFAULT_ICON_WORKSPACE"
|
||||||
FallbackIconAccount = "DEFAULT_ICON_ACCOUNT"
|
InboxNotificationFallbackIconAccount = "DEFAULT_ICON_ACCOUNT"
|
||||||
FallbackIconTemplate = "DEFAULT_ICON_TEMPLATE"
|
InboxNotificationFallbackIconTemplate = "DEFAULT_ICON_TEMPLATE"
|
||||||
FallbackIconOther = "DEFAULT_ICON_OTHER"
|
InboxNotificationFallbackIconOther = "DEFAULT_ICON_OTHER"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InboxNotification struct {
|
type InboxNotification struct {
|
||||||
|
|||||||
Generated
+12
-12
@@ -839,18 +839,6 @@ export interface ExternalAuthUser {
|
|||||||
readonly name: string;
|
readonly name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/inboxnotification.go
|
|
||||||
export const FallbackIconAccount = "DEFAULT_ICON_ACCOUNT";
|
|
||||||
|
|
||||||
// From codersdk/inboxnotification.go
|
|
||||||
export const FallbackIconOther = "DEFAULT_ICON_OTHER";
|
|
||||||
|
|
||||||
// From codersdk/inboxnotification.go
|
|
||||||
export const FallbackIconTemplate = "DEFAULT_ICON_TEMPLATE";
|
|
||||||
|
|
||||||
// From codersdk/inboxnotification.go
|
|
||||||
export const FallbackIconWorkspace = "DEFAULT_ICON_WORKSPACE";
|
|
||||||
|
|
||||||
// From codersdk/deployment.go
|
// From codersdk/deployment.go
|
||||||
export interface Feature {
|
export interface Feature {
|
||||||
readonly entitlement: Entitlement;
|
readonly entitlement: Entitlement;
|
||||||
@@ -1124,6 +1112,18 @@ export interface InboxNotificationAction {
|
|||||||
readonly url: string;
|
readonly url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// From codersdk/inboxnotification.go
|
||||||
|
export const InboxNotificationFallbackIconAccount = "DEFAULT_ICON_ACCOUNT";
|
||||||
|
|
||||||
|
// From codersdk/inboxnotification.go
|
||||||
|
export const InboxNotificationFallbackIconOther = "DEFAULT_ICON_OTHER";
|
||||||
|
|
||||||
|
// From codersdk/inboxnotification.go
|
||||||
|
export const InboxNotificationFallbackIconTemplate = "DEFAULT_ICON_TEMPLATE";
|
||||||
|
|
||||||
|
// From codersdk/inboxnotification.go
|
||||||
|
export const InboxNotificationFallbackIconWorkspace = "DEFAULT_ICON_WORKSPACE";
|
||||||
|
|
||||||
// From codersdk/insights.go
|
// From codersdk/insights.go
|
||||||
export type InsightsReportInterval = "day" | "week";
|
export type InsightsReportInterval = "day" | "week";
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const avatarVariants = cva(
|
|||||||
},
|
},
|
||||||
variant: {
|
variant: {
|
||||||
default: null,
|
default: null,
|
||||||
icon: null,
|
icon: "[&_svg]:size-full",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { InboxAvatar } from "./InboxAvatar";
|
||||||
|
|
||||||
|
const meta: Meta<typeof InboxAvatar> = {
|
||||||
|
title: "modules/notifications/NotificationsInbox/InboxAvatar",
|
||||||
|
component: InboxAvatar,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof InboxAvatar>;
|
||||||
|
|
||||||
|
export const Custom: Story = {
|
||||||
|
args: {
|
||||||
|
icon: "/icon/git.svg",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EmptyIcon: Story = {
|
||||||
|
args: {
|
||||||
|
icon: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FallbackWorkspace: Story = {
|
||||||
|
args: {
|
||||||
|
icon: "DEFAULT_ICON_WORKSPACE",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FallbackAccount: Story = {
|
||||||
|
args: {
|
||||||
|
icon: "DEFAULT_ICON_ACCOUNT",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FallbackTemplate: Story = {
|
||||||
|
args: {
|
||||||
|
icon: "DEFAULT_ICON_TEMPLATE",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FallbackOther: Story = {
|
||||||
|
args: {
|
||||||
|
icon: "DEFAULT_ICON_OTHER",
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
InboxNotificationFallbackIconAccount,
|
||||||
|
InboxNotificationFallbackIconOther,
|
||||||
|
InboxNotificationFallbackIconTemplate,
|
||||||
|
InboxNotificationFallbackIconWorkspace,
|
||||||
|
} from "api/typesGenerated";
|
||||||
|
import { Avatar } from "components/Avatar/Avatar";
|
||||||
|
import {
|
||||||
|
InfoIcon,
|
||||||
|
LaptopIcon,
|
||||||
|
LayoutTemplateIcon,
|
||||||
|
UserIcon,
|
||||||
|
} from "lucide-react";
|
||||||
|
import type { FC } from "react";
|
||||||
|
import type React from "react";
|
||||||
|
|
||||||
|
const InboxNotificationFallbackIcons = [
|
||||||
|
InboxNotificationFallbackIconAccount,
|
||||||
|
InboxNotificationFallbackIconWorkspace,
|
||||||
|
InboxNotificationFallbackIconTemplate,
|
||||||
|
InboxNotificationFallbackIconOther,
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type InboxNotificationFallbackIcon =
|
||||||
|
(typeof InboxNotificationFallbackIcons)[number];
|
||||||
|
|
||||||
|
const fallbackIcons: Record<InboxNotificationFallbackIcon, React.ReactNode> = {
|
||||||
|
DEFAULT_ICON_WORKSPACE: <LaptopIcon />,
|
||||||
|
DEFAULT_ICON_ACCOUNT: <UserIcon />,
|
||||||
|
DEFAULT_ICON_TEMPLATE: <LayoutTemplateIcon />,
|
||||||
|
DEFAULT_ICON_OTHER: <InfoIcon />,
|
||||||
|
};
|
||||||
|
|
||||||
|
type InboxAvatarProps = {
|
||||||
|
icon: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InboxAvatar: FC<InboxAvatarProps> = ({ icon }) => {
|
||||||
|
if (icon === "") {
|
||||||
|
return <Avatar variant="icon">{fallbackIcons.DEFAULT_ICON_OTHER}</Avatar>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInboxNotificationFallbackIcon(icon)) {
|
||||||
|
return <Avatar variant="icon">{fallbackIcons[icon]}</Avatar>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Avatar variant="icon" src={icon} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isInboxNotificationFallbackIcon(
|
||||||
|
icon: string,
|
||||||
|
): icon is InboxNotificationFallbackIcon {
|
||||||
|
return (InboxNotificationFallbackIcons as readonly string[]).includes(icon);
|
||||||
|
}
|
||||||
@@ -61,6 +61,7 @@ export const Markdown: Story = {
|
|||||||
url: "https://dev.coder.com/workspaces?filter=template%3Acoder-with-ai",
|
url: "https://dev.coder.com/workspaces?filter=template%3Acoder-with-ai",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
icon: "DEFAULT_ICON_TEMPLATE",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import type { InboxNotification } from "api/typesGenerated";
|
import type { InboxNotification } from "api/typesGenerated";
|
||||||
import { Avatar } from "components/Avatar/Avatar";
|
|
||||||
import { Button } from "components/Button/Button";
|
import { Button } from "components/Button/Button";
|
||||||
import { Link } from "components/Link/Link";
|
import { Link } from "components/Link/Link";
|
||||||
import { SquareCheckBig } from "lucide-react";
|
import { SquareCheckBig } from "lucide-react";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import { Link as RouterLink } from "react-router-dom";
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
import { cn } from "utils/cn";
|
|
||||||
import { relativeTime } from "utils/time";
|
import { relativeTime } from "utils/time";
|
||||||
|
import { InboxAvatar } from "./InboxAvatar";
|
||||||
|
|
||||||
type InboxItemProps = {
|
type InboxItemProps = {
|
||||||
notification: InboxNotification;
|
notification: InboxNotification;
|
||||||
@@ -25,7 +24,7 @@ export const InboxItem: FC<InboxItemProps> = ({
|
|||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<Avatar fallback="AR" />
|
<InboxAvatar icon={notification.icon} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3 flex-1">
|
<div className="flex flex-col gap-3 flex-1">
|
||||||
|
|||||||
@@ -4261,7 +4261,7 @@ export const MockNotification: TypesGen.InboxNotification = {
|
|||||||
template_id: MockTemplate.id,
|
template_id: MockTemplate.id,
|
||||||
targets: [],
|
targets: [],
|
||||||
title: "User account created",
|
title: "User account created",
|
||||||
icon: "user",
|
icon: "DEFAULT_ICON_ACCOUNT",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MockNotifications: TypesGen.InboxNotification[] = [
|
export const MockNotifications: TypesGen.InboxNotification[] = [
|
||||||
|
|||||||
Reference in New Issue
Block a user