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: support custom notifications#19751

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
ssncferreira merged 12 commits intomainfromssncferreira/support_custom_notifications
Sep 11, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
12 commits
Select commitHold shift + click to select a range
2ab1c4d
feat: support custom notifications
ssncferreiraSep 9, 2025
327ec0d
fix: block system users from sending custom notifications
ssncferreiraSep 10, 2025
0d1144a
fix: cli/notificatins_test.go imports
ssncferreiraSep 10, 2025
203668d
chore: add kind 'custom' type to notification template kind enum
ssncferreiraSep 10, 2025
d3ffd1a
chore: update custom notification request with a content object
ssncferreiraSep 10, 2025
938d010
chore: improve cli description
ssncferreiraSep 10, 2025
9a39693
chore: bypass the per-day notification dedupe
ssncferreiraSep 10, 2025
6b8636d
Merge remote-tracking branch 'origin/main' into ssncferreira/support_…
ssncferreiraSep 10, 2025
80c6b22
fix: fix migration numbers
ssncferreiraSep 10, 2025
657a230
chore: address comments
ssncferreiraSep 11, 2025
681a29f
chore: require both title and message
ssncferreiraSep 11, 2025
14e155e
Merge remote-tracking branch 'origin/main' into ssncferreira/support_…
ssncferreiraSep 11, 2025
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
36 changes: 34 additions & 2 deletionscli/notifications.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,17 +16,21 @@ func (r *RootCmd) notifications() *serpent.Command {
Short:"Manage Coder notifications",
Long:"Administrators can use these commands to change notification settings.\n"+FormatExamples(
Example{
Description:"Pause Coder notifications. Administrators can temporarily stop notifiers from dispatching messages in case of the target outage (for example: unavailable SMTP server or Webhook not responding).",
Description:"Pause Coder notifications. Administrators can temporarily stop notifiers from dispatching messages in case of the target outage (for example: unavailable SMTP server or Webhook not responding)",
Command:"coder notifications pause",
},
Example{
Description:"Resume Coder notifications",
Command:"coder notifications resume",
},
Example{
Description:"Send a test notification. Administrators can use this to verify the notification target settings.",
Description:"Send a test notification. Administrators can use this to verify the notification target settings",
Command:"coder notifications test",
},
Example{
Description:"Send a custom notification to the requesting user. Sending notifications targeting other users or groups is currently not supported",
Command:"coder notifications custom\"Custom Title\"\"Custom Message\"",
},
),
Aliases: []string{"notification"},
Handler:func(inv*serpent.Invocation)error {
Expand All@@ -36,6 +40,7 @@ func (r *RootCmd) notifications() *serpent.Command {
r.pauseNotifications(),
r.resumeNotifications(),
r.testNotifications(),
r.customNotifications(),
},
}
returncmd
Expand DownExpand Up@@ -109,3 +114,30 @@ func (r *RootCmd) testNotifications() *serpent.Command {
}
returncmd
}

func (r*RootCmd)customNotifications()*serpent.Command {
client:=new(codersdk.Client)
cmd:=&serpent.Command{
Use:"custom <title> <message>",
Short:"Send a custom notification",
Middleware:serpent.Chain(
serpent.RequireNArgs(2),
r.InitClient(client),
),
Handler:func(inv*serpent.Invocation)error {
err:=client.PostCustomNotification(inv.Context(), codersdk.CustomNotificationRequest{
Content:&codersdk.CustomNotificationContent{
Title:inv.Args[0],
Message:inv.Args[1],
},
})
iferr!=nil {
returnxerrors.Errorf("unable to post custom notification: %w",err)
}

_,_=fmt.Fprintln(inv.Stderr,"A custom notification has been sent.")
returnnil
},
}
returncmd
}
101 changes: 101 additions & 0 deletionscli/notifications_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ import (

"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/notifications"
"github.com/coder/coder/v2/coderd/notifications/notificationstest"
"github.com/coder/coder/v2/codersdk"
Expand DownExpand Up@@ -166,3 +168,102 @@ func TestNotificationsTest(t *testing.T) {
require.Len(t, sent, 0)
})
}

func TestCustomNotifications(t *testing.T) {
t.Parallel()

t.Run("BadRequest", func(t *testing.T) {
t.Parallel()

notifyEnq := &notificationstest.FakeEnqueuer{}

ownerClient := coderdtest.New(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
NotificationsEnqueuer: notifyEnq,
})

// Given: A member user
ownerUser := coderdtest.CreateFirstUser(t, ownerClient)
memberClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, ownerUser.OrganizationID)

// When: The member user attempts to send a custom notification with empty title and message
inv, root := clitest.New(t, "notifications", "custom", "", "")
clitest.SetupConfig(t, memberClient, root)

// Then: an error is expected with no notifications sent
err := inv.Run()
var sdkError *codersdk.Error
require.Error(t, err)
require.ErrorAsf(t, err, &sdkError, "error should be of type *codersdk.Error")
require.Equal(t, http.StatusBadRequest, sdkError.StatusCode())
require.Equal(t, "Invalid request body", sdkError.Message)

sent := notifyEnq.Sent(notificationstest.WithTemplateID(notifications.TemplateTestNotification))
require.Len(t, sent, 0)
})

t.Run("SystemUserNotAllowed", func(t *testing.T) {
t.Parallel()

notifyEnq := &notificationstest.FakeEnqueuer{}

ownerClient, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
NotificationsEnqueuer: notifyEnq,
})

// Given: A system user (prebuilds system user)
_, token := dbgen.APIKey(t, db, database.APIKey{
UserID: database.PrebuildsSystemUserID,
LoginType: database.LoginTypeNone,
})
systemUserClient := codersdk.New(ownerClient.URL)
systemUserClient.SetSessionToken(token)

// When: The system user attempts to send a custom notification
inv, root := clitest.New(t, "notifications", "custom", "Custom Title", "Custom Message")
clitest.SetupConfig(t, systemUserClient, root)

// Then: an error is expected with no notifications sent
err := inv.Run()
var sdkError *codersdk.Error
require.Error(t, err)
require.ErrorAsf(t, err, &sdkError, "error should be of type *codersdk.Error")
require.Equal(t, http.StatusForbidden, sdkError.StatusCode())
require.Equal(t, "Forbidden", sdkError.Message)

sent := notifyEnq.Sent(notificationstest.WithTemplateID(notifications.TemplateTestNotification))
require.Len(t, sent, 0)
})

t.Run("Success", func(t *testing.T) {
t.Parallel()

notifyEnq := &notificationstest.FakeEnqueuer{}

ownerClient := coderdtest.New(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
NotificationsEnqueuer: notifyEnq,
})

// Given: A member user
ownerUser := coderdtest.CreateFirstUser(t, ownerClient)
memberClient, memberUser := coderdtest.CreateAnotherUser(t, ownerClient, ownerUser.OrganizationID)

// When: The member user attempts to send a custom notification
inv, root := clitest.New(t, "notifications", "custom", "Custom Title", "Custom Message")
clitest.SetupConfig(t, memberClient, root)

// Then: we expect a custom notification to be sent to the member user
err := inv.Run()
require.NoError(t, err)

sent := notifyEnq.Sent(notificationstest.WithTemplateID(notifications.TemplateCustomNotification))
require.Len(t, sent, 1)
require.Equal(t, memberUser.ID, sent[0].UserID)
require.Len(t, sent[0].Labels, 2)
require.Equal(t, "Custom Title", sent[0].Labels["custom_title"])
require.Equal(t, "Custom Message", sent[0].Labels["custom_message"])
require.Equal(t, memberUser.ID.String(), sent[0].CreatedBy)
})
}
10 changes: 8 additions & 2 deletionscli/testdata/coder_notifications_--help.golden
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ USAGE:
from
dispatching messages in case of the target outage (for example: unavailable
SMTP
server or Webhook not responding).:
server or Webhook not responding):

$ coder notifications pause

Expand All@@ -22,11 +22,17 @@ USAGE:

- Send a test notification. Administrators can use this to verify the
notification
target settings.:
target settings:

$ coder notifications test

- Send a custom notification to the requesting user. Sending notifications
targeting other users or groups is currently not supported:

$ coder notifications custom "Custom Title" "Custom Message"

SUBCOMMANDS:
custom Send a custom notification
pause Pause notifications
resume Resume notifications
test Send a test notification
Expand Down
9 changes: 9 additions & 0 deletionscli/testdata/coder_notifications_custom_--help.golden
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
coder v0.0.0-devel

USAGE:
coder notifications custom <title> <message>

Send a custom notification

———
Run `coder --help` for a list of global options.
113 changes: 113 additions & 0 deletionscoderd/apidoc/docs.go
View file
Open in desktop

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

Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp