Skip to content

Commit 148dae1

Browse files
authored
fix: add fallback icons for notifications (coder#17013)
Related: coder/internal#522
1 parent ca414b0 commit 148dae1

File tree

6 files changed

+187
-4
lines changed

6 files changed

+187
-4
lines changed

coderd/inboxnotifications.go

+47-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/coder/coder/v2/coderd/database/dbtime"
1717
"github.com/coder/coder/v2/coderd/httpapi"
1818
"github.com/coder/coder/v2/coderd/httpmw"
19+
"github.com/coder/coder/v2/coderd/notifications"
1920
"github.com/coder/coder/v2/coderd/pubsub"
2021
markdown "github.com/coder/coder/v2/coderd/render"
2122
"github.com/coder/coder/v2/codersdk"
@@ -28,9 +29,51 @@ const (
2829
notificationFormatPlaintext = "plaintext"
2930
)
3031

32+
var fallbackIcons = map[uuid.UUID]string{
33+
// workspace related notifications
34+
notifications.TemplateWorkspaceCreated: codersdk.FallbackIconWorkspace,
35+
notifications.TemplateWorkspaceManuallyUpdated: codersdk.FallbackIconWorkspace,
36+
notifications.TemplateWorkspaceDeleted: codersdk.FallbackIconWorkspace,
37+
notifications.TemplateWorkspaceAutobuildFailed: codersdk.FallbackIconWorkspace,
38+
notifications.TemplateWorkspaceDormant: codersdk.FallbackIconWorkspace,
39+
notifications.TemplateWorkspaceAutoUpdated: codersdk.FallbackIconWorkspace,
40+
notifications.TemplateWorkspaceMarkedForDeletion: codersdk.FallbackIconWorkspace,
41+
notifications.TemplateWorkspaceManualBuildFailed: codersdk.FallbackIconWorkspace,
42+
notifications.TemplateWorkspaceOutOfMemory: codersdk.FallbackIconWorkspace,
43+
notifications.TemplateWorkspaceOutOfDisk: codersdk.FallbackIconWorkspace,
44+
45+
// account related notifications
46+
notifications.TemplateUserAccountCreated: codersdk.FallbackIconAccount,
47+
notifications.TemplateUserAccountDeleted: codersdk.FallbackIconAccount,
48+
notifications.TemplateUserAccountSuspended: codersdk.FallbackIconAccount,
49+
notifications.TemplateUserAccountActivated: codersdk.FallbackIconAccount,
50+
notifications.TemplateYourAccountSuspended: codersdk.FallbackIconAccount,
51+
notifications.TemplateYourAccountActivated: codersdk.FallbackIconAccount,
52+
notifications.TemplateUserRequestedOneTimePasscode: codersdk.FallbackIconAccount,
53+
54+
// template related notifications
55+
notifications.TemplateTemplateDeleted: codersdk.FallbackIconTemplate,
56+
notifications.TemplateTemplateDeprecated: codersdk.FallbackIconTemplate,
57+
notifications.TemplateWorkspaceBuildsFailedReport: codersdk.FallbackIconTemplate,
58+
}
59+
60+
func ensureNotificationIcon(notif codersdk.InboxNotification) codersdk.InboxNotification {
61+
if notif.Icon != "" {
62+
return notif
63+
}
64+
65+
fallbackIcon, ok := fallbackIcons[notif.TemplateID]
66+
if !ok {
67+
fallbackIcon = codersdk.FallbackIconOther
68+
}
69+
70+
notif.Icon = fallbackIcon
71+
return notif
72+
}
73+
3174
// convertInboxNotificationResponse works as a util function to transform a database.InboxNotification to codersdk.InboxNotification
3275
func convertInboxNotificationResponse(ctx context.Context, logger slog.Logger, notif database.InboxNotification) codersdk.InboxNotification {
33-
return codersdk.InboxNotification{
76+
convertedNotif := codersdk.InboxNotification{
3477
ID: notif.ID,
3578
UserID: notif.UserID,
3679
TemplateID: notif.TemplateID,
@@ -54,6 +97,8 @@ func convertInboxNotificationResponse(ctx context.Context, logger slog.Logger, n
5497
}(),
5598
CreatedAt: notif.CreatedAt,
5699
}
100+
101+
return ensureNotificationIcon(convertedNotif)
57102
}
58103

59104
// watchInboxNotifications watches for new inbox notifications and sends them to the client.
@@ -147,7 +192,7 @@ func (api *API) watchInboxNotifications(rw http.ResponseWriter, r *http.Request)
147192

148193
// keep a safe guard in case of latency to push notifications through websocket
149194
select {
150-
case notificationCh <- payload.InboxNotification:
195+
case notificationCh <- ensureNotificationIcon(payload.InboxNotification):
151196
default:
152197
api.Logger.Error(ctx, "failed to push consumed notification into websocket handler, check latency")
153198
}
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package coderd
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/google/uuid"
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/coder/coder/v2/coderd/notifications"
11+
"github.com/coder/coder/v2/codersdk"
12+
)
13+
14+
func TestInboxNotifications_ensureNotificationIcon(t *testing.T) {
15+
t.Parallel()
16+
17+
tests := []struct {
18+
name string
19+
icon string
20+
templateID uuid.UUID
21+
expectedIcon string
22+
}{
23+
{"WorkspaceCreated", "", notifications.TemplateWorkspaceCreated, codersdk.FallbackIconWorkspace},
24+
{"UserAccountCreated", "", notifications.TemplateUserAccountCreated, codersdk.FallbackIconAccount},
25+
{"TemplateDeleted", "", notifications.TemplateTemplateDeleted, codersdk.FallbackIconTemplate},
26+
{"TestNotification", "", notifications.TemplateTestNotification, codersdk.FallbackIconOther},
27+
{"TestExistingIcon", "https://cdn.coder.com/icon_notif.png", notifications.TemplateTemplateDeleted, "https://cdn.coder.com/icon_notif.png"},
28+
{"UnknownTemplate", "", uuid.New(), codersdk.FallbackIconOther},
29+
}
30+
31+
for _, tt := range tests {
32+
tt := tt
33+
34+
t.Run(tt.name, func(t *testing.T) {
35+
t.Parallel()
36+
37+
notif := codersdk.InboxNotification{
38+
ID: uuid.New(),
39+
UserID: uuid.New(),
40+
TemplateID: tt.templateID,
41+
Title: "notification title",
42+
Content: "notification content",
43+
Icon: tt.icon,
44+
CreatedAt: time.Now(),
45+
}
46+
47+
notif = ensureNotificationIcon(notif)
48+
require.Equal(t, tt.expectedIcon, notif.Icon)
49+
})
50+
}
51+
}

coderd/inboxnotifications_test.go

+69-2
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ func TestInboxNotification_Watch(t *testing.T) {
135135

136136
require.Equal(t, 1, notif.UnreadCount)
137137
require.Equal(t, memberClient.ID, notif.Notification.UserID)
138+
139+
// check for the fallback icon logic
140+
require.Equal(t, codersdk.FallbackIconWorkspace, notif.Notification.Icon)
138141
})
139142

140143
t.Run("OK - change format", func(t *testing.T) {
@@ -474,8 +477,9 @@ func TestInboxNotifications_List(t *testing.T) {
474477
TemplateID: notifications.TemplateWorkspaceOutOfMemory,
475478
Title: fmt.Sprintf("Notification %d", i),
476479
Actions: json.RawMessage("[]"),
477-
Content: fmt.Sprintf("Content of the notif %d", i),
478-
CreatedAt: dbtime.Now(),
480+
481+
Content: fmt.Sprintf("Content of the notif %d", i),
482+
CreatedAt: dbtime.Now(),
479483
})
480484
}
481485

@@ -498,6 +502,68 @@ func TestInboxNotifications_List(t *testing.T) {
498502
require.Equal(t, "Notification 14", notifs.Notifications[0].Title)
499503
})
500504

505+
t.Run("OK check icons", func(t *testing.T) {
506+
t.Parallel()
507+
508+
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{})
509+
firstUser := coderdtest.CreateFirstUser(t, client)
510+
client, member := coderdtest.CreateAnotherUser(t, client, firstUser.OrganizationID)
511+
512+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
513+
defer cancel()
514+
515+
notifs, err := client.ListInboxNotifications(ctx, codersdk.ListInboxNotificationsRequest{})
516+
require.NoError(t, err)
517+
require.NotNil(t, notifs)
518+
require.Equal(t, 0, notifs.UnreadCount)
519+
require.Empty(t, notifs.Notifications)
520+
521+
for i := range 10 {
522+
dbgen.NotificationInbox(t, api.Database, database.InsertInboxNotificationParams{
523+
ID: uuid.New(),
524+
UserID: member.ID,
525+
TemplateID: func() uuid.UUID {
526+
switch i {
527+
case 0:
528+
return notifications.TemplateWorkspaceCreated
529+
case 1:
530+
return notifications.TemplateWorkspaceMarkedForDeletion
531+
case 2:
532+
return notifications.TemplateUserAccountActivated
533+
case 3:
534+
return notifications.TemplateTemplateDeprecated
535+
default:
536+
return notifications.TemplateTestNotification
537+
}
538+
}(),
539+
Title: fmt.Sprintf("Notification %d", i),
540+
Actions: json.RawMessage("[]"),
541+
Icon: func() string {
542+
if i == 9 {
543+
return "https://dev.coder.com/icon.png"
544+
}
545+
546+
return ""
547+
}(),
548+
Content: fmt.Sprintf("Content of the notif %d", i),
549+
CreatedAt: dbtime.Now(),
550+
})
551+
}
552+
553+
notifs, err = client.ListInboxNotifications(ctx, codersdk.ListInboxNotificationsRequest{})
554+
require.NoError(t, err)
555+
require.NotNil(t, notifs)
556+
require.Equal(t, 10, notifs.UnreadCount)
557+
require.Len(t, notifs.Notifications, 10)
558+
559+
require.Equal(t, "https://dev.coder.com/icon.png", notifs.Notifications[0].Icon)
560+
require.Equal(t, codersdk.FallbackIconWorkspace, notifs.Notifications[9].Icon)
561+
require.Equal(t, codersdk.FallbackIconWorkspace, notifs.Notifications[8].Icon)
562+
require.Equal(t, codersdk.FallbackIconAccount, notifs.Notifications[7].Icon)
563+
require.Equal(t, codersdk.FallbackIconTemplate, notifs.Notifications[6].Icon)
564+
require.Equal(t, codersdk.FallbackIconOther, notifs.Notifications[4].Icon)
565+
})
566+
501567
t.Run("OK with template filter", func(t *testing.T) {
502568
t.Parallel()
503569

@@ -541,6 +607,7 @@ func TestInboxNotifications_List(t *testing.T) {
541607
require.Len(t, notifs.Notifications, 5)
542608

543609
require.Equal(t, "Notification 8", notifs.Notifications[0].Title)
610+
require.Equal(t, codersdk.FallbackIconWorkspace, notifs.Notifications[0].Icon)
544611
})
545612

546613
t.Run("OK with target filter", func(t *testing.T) {

coderd/notifications/events.go

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import "github.com/google/uuid"
44

55
// These vars are mapped to UUIDs in the notification_templates table.
66
// TODO: autogenerate these: https://github.com/coder/team-coconut/issues/36
7+
// TODO(defelmnq): add fallback icon to coderd/inboxnofication.go when adding a new template
78

89
// Workspace-related events.
910
var (

codersdk/inboxnotification.go

+7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ import (
1010
"github.com/google/uuid"
1111
)
1212

13+
const (
14+
FallbackIconWorkspace = "DEFAULT_ICON_WORKSPACE"
15+
FallbackIconAccount = "DEFAULT_ICON_ACCOUNT"
16+
FallbackIconTemplate = "DEFAULT_ICON_TEMPLATE"
17+
FallbackIconOther = "DEFAULT_ICON_OTHER"
18+
)
19+
1320
type InboxNotification struct {
1421
ID uuid.UUID `json:"id" format:"uuid"`
1522
UserID uuid.UUID `json:"user_id" format:"uuid"`

site/src/api/typesGenerated.ts

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)