mirror of
https://github.com/coder/coder.git
synced 2026-09-21 12:44:32 +08:00
fix: add fallback icons for notifications (#17013)
Related: https://github.com/coder/internal/issues/522
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/coderd/httpmw"
|
||||
"github.com/coder/coder/v2/coderd/notifications"
|
||||
"github.com/coder/coder/v2/coderd/pubsub"
|
||||
markdown "github.com/coder/coder/v2/coderd/render"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
@@ -28,9 +29,51 @@ const (
|
||||
notificationFormatPlaintext = "plaintext"
|
||||
)
|
||||
|
||||
var fallbackIcons = map[uuid.UUID]string{
|
||||
// workspace related notifications
|
||||
notifications.TemplateWorkspaceCreated: codersdk.FallbackIconWorkspace,
|
||||
notifications.TemplateWorkspaceManuallyUpdated: codersdk.FallbackIconWorkspace,
|
||||
notifications.TemplateWorkspaceDeleted: codersdk.FallbackIconWorkspace,
|
||||
notifications.TemplateWorkspaceAutobuildFailed: codersdk.FallbackIconWorkspace,
|
||||
notifications.TemplateWorkspaceDormant: codersdk.FallbackIconWorkspace,
|
||||
notifications.TemplateWorkspaceAutoUpdated: codersdk.FallbackIconWorkspace,
|
||||
notifications.TemplateWorkspaceMarkedForDeletion: codersdk.FallbackIconWorkspace,
|
||||
notifications.TemplateWorkspaceManualBuildFailed: codersdk.FallbackIconWorkspace,
|
||||
notifications.TemplateWorkspaceOutOfMemory: codersdk.FallbackIconWorkspace,
|
||||
notifications.TemplateWorkspaceOutOfDisk: codersdk.FallbackIconWorkspace,
|
||||
|
||||
// account related notifications
|
||||
notifications.TemplateUserAccountCreated: codersdk.FallbackIconAccount,
|
||||
notifications.TemplateUserAccountDeleted: codersdk.FallbackIconAccount,
|
||||
notifications.TemplateUserAccountSuspended: codersdk.FallbackIconAccount,
|
||||
notifications.TemplateUserAccountActivated: codersdk.FallbackIconAccount,
|
||||
notifications.TemplateYourAccountSuspended: codersdk.FallbackIconAccount,
|
||||
notifications.TemplateYourAccountActivated: codersdk.FallbackIconAccount,
|
||||
notifications.TemplateUserRequestedOneTimePasscode: codersdk.FallbackIconAccount,
|
||||
|
||||
// template related notifications
|
||||
notifications.TemplateTemplateDeleted: codersdk.FallbackIconTemplate,
|
||||
notifications.TemplateTemplateDeprecated: codersdk.FallbackIconTemplate,
|
||||
notifications.TemplateWorkspaceBuildsFailedReport: codersdk.FallbackIconTemplate,
|
||||
}
|
||||
|
||||
func ensureNotificationIcon(notif codersdk.InboxNotification) codersdk.InboxNotification {
|
||||
if notif.Icon != "" {
|
||||
return notif
|
||||
}
|
||||
|
||||
fallbackIcon, ok := fallbackIcons[notif.TemplateID]
|
||||
if !ok {
|
||||
fallbackIcon = codersdk.FallbackIconOther
|
||||
}
|
||||
|
||||
notif.Icon = fallbackIcon
|
||||
return notif
|
||||
}
|
||||
|
||||
// convertInboxNotificationResponse works as a util function to transform a database.InboxNotification to codersdk.InboxNotification
|
||||
func convertInboxNotificationResponse(ctx context.Context, logger slog.Logger, notif database.InboxNotification) codersdk.InboxNotification {
|
||||
return codersdk.InboxNotification{
|
||||
convertedNotif := codersdk.InboxNotification{
|
||||
ID: notif.ID,
|
||||
UserID: notif.UserID,
|
||||
TemplateID: notif.TemplateID,
|
||||
@@ -54,6 +97,8 @@ func convertInboxNotificationResponse(ctx context.Context, logger slog.Logger, n
|
||||
}(),
|
||||
CreatedAt: notif.CreatedAt,
|
||||
}
|
||||
|
||||
return ensureNotificationIcon(convertedNotif)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// keep a safe guard in case of latency to push notifications through websocket
|
||||
select {
|
||||
case notificationCh <- payload.InboxNotification:
|
||||
case notificationCh <- ensureNotificationIcon(payload.InboxNotification):
|
||||
default:
|
||||
api.Logger.Error(ctx, "failed to push consumed notification into websocket handler, check latency")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package coderd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/notifications"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
func TestInboxNotifications_ensureNotificationIcon(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
icon string
|
||||
templateID uuid.UUID
|
||||
expectedIcon string
|
||||
}{
|
||||
{"WorkspaceCreated", "", notifications.TemplateWorkspaceCreated, codersdk.FallbackIconWorkspace},
|
||||
{"UserAccountCreated", "", notifications.TemplateUserAccountCreated, codersdk.FallbackIconAccount},
|
||||
{"TemplateDeleted", "", notifications.TemplateTemplateDeleted, codersdk.FallbackIconTemplate},
|
||||
{"TestNotification", "", notifications.TemplateTestNotification, codersdk.FallbackIconOther},
|
||||
{"TestExistingIcon", "https://cdn.coder.com/icon_notif.png", notifications.TemplateTemplateDeleted, "https://cdn.coder.com/icon_notif.png"},
|
||||
{"UnknownTemplate", "", uuid.New(), codersdk.FallbackIconOther},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
notif := codersdk.InboxNotification{
|
||||
ID: uuid.New(),
|
||||
UserID: uuid.New(),
|
||||
TemplateID: tt.templateID,
|
||||
Title: "notification title",
|
||||
Content: "notification content",
|
||||
Icon: tt.icon,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
notif = ensureNotificationIcon(notif)
|
||||
require.Equal(t, tt.expectedIcon, notif.Icon)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -135,6 +135,9 @@ func TestInboxNotification_Watch(t *testing.T) {
|
||||
|
||||
require.Equal(t, 1, notif.UnreadCount)
|
||||
require.Equal(t, memberClient.ID, notif.Notification.UserID)
|
||||
|
||||
// check for the fallback icon logic
|
||||
require.Equal(t, codersdk.FallbackIconWorkspace, notif.Notification.Icon)
|
||||
})
|
||||
|
||||
t.Run("OK - change format", func(t *testing.T) {
|
||||
@@ -474,8 +477,9 @@ func TestInboxNotifications_List(t *testing.T) {
|
||||
TemplateID: notifications.TemplateWorkspaceOutOfMemory,
|
||||
Title: fmt.Sprintf("Notification %d", i),
|
||||
Actions: json.RawMessage("[]"),
|
||||
Content: fmt.Sprintf("Content of the notif %d", i),
|
||||
CreatedAt: dbtime.Now(),
|
||||
|
||||
Content: fmt.Sprintf("Content of the notif %d", i),
|
||||
CreatedAt: dbtime.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -498,6 +502,68 @@ func TestInboxNotifications_List(t *testing.T) {
|
||||
require.Equal(t, "Notification 14", notifs.Notifications[0].Title)
|
||||
})
|
||||
|
||||
t.Run("OK check icons", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{})
|
||||
firstUser := coderdtest.CreateFirstUser(t, client)
|
||||
client, member := coderdtest.CreateAnotherUser(t, client, firstUser.OrganizationID)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
notifs, err := client.ListInboxNotifications(ctx, codersdk.ListInboxNotificationsRequest{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, notifs)
|
||||
require.Equal(t, 0, notifs.UnreadCount)
|
||||
require.Empty(t, notifs.Notifications)
|
||||
|
||||
for i := range 10 {
|
||||
dbgen.NotificationInbox(t, api.Database, database.InsertInboxNotificationParams{
|
||||
ID: uuid.New(),
|
||||
UserID: member.ID,
|
||||
TemplateID: func() uuid.UUID {
|
||||
switch i {
|
||||
case 0:
|
||||
return notifications.TemplateWorkspaceCreated
|
||||
case 1:
|
||||
return notifications.TemplateWorkspaceMarkedForDeletion
|
||||
case 2:
|
||||
return notifications.TemplateUserAccountActivated
|
||||
case 3:
|
||||
return notifications.TemplateTemplateDeprecated
|
||||
default:
|
||||
return notifications.TemplateTestNotification
|
||||
}
|
||||
}(),
|
||||
Title: fmt.Sprintf("Notification %d", i),
|
||||
Actions: json.RawMessage("[]"),
|
||||
Icon: func() string {
|
||||
if i == 9 {
|
||||
return "https://dev.coder.com/icon.png"
|
||||
}
|
||||
|
||||
return ""
|
||||
}(),
|
||||
Content: fmt.Sprintf("Content of the notif %d", i),
|
||||
CreatedAt: dbtime.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
notifs, err = client.ListInboxNotifications(ctx, codersdk.ListInboxNotificationsRequest{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, notifs)
|
||||
require.Equal(t, 10, notifs.UnreadCount)
|
||||
require.Len(t, notifs.Notifications, 10)
|
||||
|
||||
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.FallbackIconWorkspace, notifs.Notifications[8].Icon)
|
||||
require.Equal(t, codersdk.FallbackIconAccount, notifs.Notifications[7].Icon)
|
||||
require.Equal(t, codersdk.FallbackIconTemplate, notifs.Notifications[6].Icon)
|
||||
require.Equal(t, codersdk.FallbackIconOther, notifs.Notifications[4].Icon)
|
||||
})
|
||||
|
||||
t.Run("OK with template filter", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -541,6 +607,7 @@ func TestInboxNotifications_List(t *testing.T) {
|
||||
require.Len(t, notifs.Notifications, 5)
|
||||
|
||||
require.Equal(t, "Notification 8", notifs.Notifications[0].Title)
|
||||
require.Equal(t, codersdk.FallbackIconWorkspace, notifs.Notifications[0].Icon)
|
||||
})
|
||||
|
||||
t.Run("OK with target filter", func(t *testing.T) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import "github.com/google/uuid"
|
||||
|
||||
// These vars are mapped to UUIDs in the notification_templates table.
|
||||
// TODO: autogenerate these: https://github.com/coder/team-coconut/issues/36
|
||||
// TODO(defelmnq): add fallback icon to coderd/inboxnofication.go when adding a new template
|
||||
|
||||
// Workspace-related events.
|
||||
var (
|
||||
|
||||
@@ -10,6 +10,13 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
FallbackIconWorkspace = "DEFAULT_ICON_WORKSPACE"
|
||||
FallbackIconAccount = "DEFAULT_ICON_ACCOUNT"
|
||||
FallbackIconTemplate = "DEFAULT_ICON_TEMPLATE"
|
||||
FallbackIconOther = "DEFAULT_ICON_OTHER"
|
||||
)
|
||||
|
||||
type InboxNotification struct {
|
||||
ID uuid.UUID `json:"id" format:"uuid"`
|
||||
UserID uuid.UUID `json:"user_id" format:"uuid"`
|
||||
|
||||
Generated
+12
@@ -839,6 +839,18 @@ export interface ExternalAuthUser {
|
||||
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
|
||||
export interface Feature {
|
||||
readonly entitlement: Entitlement;
|
||||
|
||||
Reference in New Issue
Block a user