Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: notify users on template deprecation #15195

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Oct 24, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DELETE FROM notification_templates WHERE id = 'f40fae84-55a2-42cd-99fa-b41c1ca64894';
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
INSERT INTO notification_templates
(id, name, title_template, body_template, "group", actions)
VALUES (
'f40fae84-55a2-42cd-99fa-b41c1ca64894',
'Template Deprecated',
E'Template ''{{.Labels.template}}'' has been deprecated',
E'Hello {{.UserName}},\n\n'||
E'The template **{{.Labels.template}}** has been deprecated with the following message:\n\n' ||
E'**{{.Labels.message}}**\n\n' ||
E'New workspaces may not be created from this template. Existing workspaces will continue to function normally.',
'Template Events',
'[
{
"label": "See affected workspaces",
"url": "{{base_url}}/workspaces?filter=owner%3Ame+template%3A{{.Labels.template}}"
},
{
"label": "View template",
"url": "{{base_url}}/templates/{{.Labels.organization}}/{{.Labels.template}}"
}
]'::jsonb
);
3 changes: 2 additions & 1 deletion coderd/notifications/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ var (

// Template-related events.
var (
TemplateTemplateDeleted = uuid.MustParse("29a09665-2a4c-403f-9648-54301670e7be")
TemplateTemplateDeleted = uuid.MustParse("29a09665-2a4c-403f-9648-54301670e7be")
TemplateTemplateDeprecated = uuid.MustParse("f40fae84-55a2-42cd-99fa-b41c1ca64894")

TemplateWorkspaceBuildsFailedReport = uuid.MustParse("34a20db2-e9cc-4a93-b0e4-8569699d7a00")
)
14 changes: 14 additions & 0 deletions coderd/notifications/notifications_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,20 @@ func TestNotificationTemplates_Golden(t *testing.T) {
appName: "Custom Application Name",
logoURL: "https://custom.application/logo.png",
},
{
name: "TemplateTemplateDeprecated",
id: notifications.TemplateTemplateDeprecated,
payload: types.MessagePayload{
UserName: "Bobby",
UserEmail: "[email protected]",
UserUsername: "bobby",
Labels: map[string]string{
"template": "alpha",
"message": "This template has been replaced by beta",
"organization": "coder",
},
},
},
}

// We must have a test case for every notification_template. This is enforced below:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
From: [email protected]
To: [email protected]
Subject: Template 'alpha' has been deprecated
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
Date: Fri, 11 Oct 2024 09:03:06 +0000
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
MIME-Version: 1.0

--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8

Hello Bobby,

The template alpha has been deprecated with the following message:

This template has been replaced by beta

New workspaces may not be created from this template. Existing workspaces w=
ill continue to function normally.


See affected workspaces: http://test.com/workspaces?filter=3Downer%3Ame+tem=
plate%3Aalpha

View template: http://test.com/templates/coder/alpha

--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=UTF-8

<!doctype html>
<html lang=3D"en">
<head>
<meta charset=3D"UTF-8" />
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
=3D1.0" />
<title>Template 'alpha' has been deprecated</title>
</head>
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
; background: #f8fafc;">
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
n: left; font-size: 14px; line-height: 1.5;">
<div style=3D"text-align: center;">
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
er Logo" style=3D"height: 40px;" />
</div>
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
argin: 8px 0 32px; line-height: 1.5;">
Template 'alpha' has been deprecated
</h1>
<div style=3D"line-height: 1.5;">
<p>Hello Bobby,</p>

<p>The template <strong>alpha</strong> has been deprecated with the followi=
ng message:</p>

<p><strong>This template has been replaced by beta</strong></p>

<p>New workspaces may not be created from this template. Existing workspace=
s will continue to function normally.</p>
</div>
<div style=3D"text-align: center; margin-top: 32px;">
=20
<a href=3D"http://test.com/workspaces?filter=3Downer%3Ame+template%=
3Aalpha" style=3D"display: inline-block; padding: 13px 24px; background-col=
or: #020617; color: #f8fafc; text-decoration: none; border-radius: 8px; mar=
gin: 0 4px;">
See affected workspaces
</a>
=20
<a href=3D"http://test.com/templates/coder/alpha" style=3D"display:=
inline-block; padding: 13px 24px; background-color: #020617; color: #f8faf=
c; text-decoration: none; border-radius: 8px; margin: 0 4px;">
View template
</a>
=20
</div>
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
ttp://test.com</a></p>
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
r: #2563eb; text-decoration: none;">Click here to manage your notification =
settings</a></p>
<p><a href=3D"http://test.com/settings/notifications?disabled=3Df40=
fae84-55a2-42cd-99fa-b41c1ca64894" style=3D"color: #2563eb; text-decoration=
: none;">Stop receiving emails like this</a></p>
</div>
</div>
</body>
</html>

--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"_version": "1.1",
"msg_id": "00000000-0000-0000-0000-000000000000",
"payload": {
"_version": "1.1",
"notification_name": "Template Deprecated",
"notification_template_id": "00000000-0000-0000-0000-000000000000",
"user_id": "00000000-0000-0000-0000-000000000000",
"user_email": "[email protected]",
"user_name": "Bobby",
"user_username": "bobby",
"actions": [
{
"label": "See affected workspaces",
"url": "http://test.com/workspaces?filter=owner%3Ame+template%3Aalpha"
},
{
"label": "View template",
"url": "http://test.com/templates/coder/alpha"
}
],
"labels": {
"message": "This template has been replaced by beta",
"organization": "coder",
"template": "alpha"
},
"data": null
},
"title": "Template 'alpha' has been deprecated",
"title_markdown": "Template 'alpha' has been deprecated",
"body": "Hello Bobby,\n\nThe template alpha has been deprecated with the following message:\n\nThis template has been replaced by beta\n\nNew workspaces may not be created from this template. Existing workspaces will continue to function normally.",
"body_markdown": "Hello Bobby,\n\nThe template **alpha** has been deprecated with the following message:\n\n**This template has been replaced by beta**\n\nNew workspaces may not be created from this template. Existing workspaces will continue to function normally."
}
42 changes: 42 additions & 0 deletions coderd/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,12 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
return
}

if template.Deprecated != updated.Deprecated && updated.Deprecated != "" {
if err := api.notifyUsersOfTemplateDeprecation(ctx, updated); err != nil {
api.Logger.Error(ctx, "failed to notify users of template deprecation", slog.Error(err))
}
}

if updated.UpdatedAt.IsZero() {
aReq.New = template
rw.WriteHeader(http.StatusNotModified)
Expand All @@ -855,6 +861,42 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplate(updated))
}

func (api *API) notifyUsersOfTemplateDeprecation(ctx context.Context, template database.Template) error {
workspaces, err := api.Database.GetWorkspaces(ctx, database.GetWorkspacesParams{
TemplateIDs: []uuid.UUID{template.ID},
})
if err != nil {
return xerrors.Errorf("get workspaces by template id: %w", err)
}

users := make(map[uuid.UUID]struct{})
for _, workspace := range workspaces {
users[workspace.OwnerID] = struct{}{}
}

errs := []error{}

for userID := range users {
_, err = api.NotificationsEnqueuer.Enqueue(
//nolint:gocritic // We need the system auth context to be able to send the deprecation notification.
dbauthz.AsSystemRestricted(ctx),
userID,
notifications.TemplateTemplateDeprecated,
map[string]string{
"template": template.Name,
"message": template.Deprecated,
"organization": template.OrganizationName,
},
"notify-users-of-template-deprecation",
)
if err != nil {
errs = append(errs, xerrors.Errorf("enqueue notification: %w", err))
}
}

return errors.Join(errs...)
}

// @Summary Get template DAUs by ID
// @ID get-template-daus-by-id
// @Security CoderSessionToken
Expand Down
44 changes: 43 additions & 1 deletion enterprise/coderd/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"net/http"
"slices"
"testing"
"time"

Expand Down Expand Up @@ -38,21 +39,36 @@ func TestTemplates(t *testing.T) {
t.Run("Deprecated", func(t *testing.T) {
t.Parallel()

notifyEnq := &testutil.FakeNotificationsEnqueuer{}
owner, user := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
IncludeProvisionerDaemon: true,
NotificationsEnqueuer: notifyEnq,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureAccessControl: 1,
},
},
})
client, _ := coderdtest.CreateAnotherUser(t, owner, user.OrganizationID, rbac.RoleTemplateAdmin())
client, secondUser := coderdtest.CreateAnotherUser(t, owner, user.OrganizationID, rbac.RoleTemplateAdmin())
otherClient, otherUser := coderdtest.CreateAnotherUser(t, owner, user.OrganizationID, rbac.RoleTemplateAdmin())

version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)

_ = coderdtest.CreateWorkspace(t, owner, template.ID)
_ = coderdtest.CreateWorkspace(t, client, template.ID)

// Create another template for testing that users of another template do not
// get a notification.
secondVersion := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
secondTemplate := coderdtest.CreateTemplate(t, client, user.OrganizationID, secondVersion.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, secondVersion.ID)

_ = coderdtest.CreateWorkspace(t, otherClient, secondTemplate.ID)

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()

Expand All @@ -65,6 +81,32 @@ func TestTemplates(t *testing.T) {
assert.True(t, updated.Deprecated)
assert.NotEmpty(t, updated.DeprecationMessage)

notifs := []*testutil.Notification{}
for _, notif := range notifyEnq.Sent {
if notif.TemplateID == notifications.TemplateTemplateDeprecated {
notifs = append(notifs, notif)
}
}
require.Equal(t, 2, len(notifs))

expectedSentTo := []string{user.UserID.String(), secondUser.ID.String()}
slices.Sort(expectedSentTo)

sentTo := []string{}
for _, notif := range notifs {
sentTo = append(sentTo, notif.UserID.String())
}
slices.Sort(sentTo)

// Require the notification to have only been sent to the expected users
assert.Equal(t, expectedSentTo, sentTo)

// The previous check should verify this but we're double checking that
// the notification wasn't sent to users not using the template.
for _, notif := range notifs {
assert.NotEqual(t, otherUser.ID, notif.UserID)
}

_, err = client.CreateWorkspace(ctx, user.OrganizationID, codersdk.Me, codersdk.CreateWorkspaceRequest{
TemplateID: template.ID,
Name: "foobar",
Expand Down
Loading