Skip to content

Commit 66f3b05

Browse files
committed
feat: ungate notifications
1 parent e36e199 commit 66f3b05

12 files changed

Lines changed: 373 additions & 508 deletions

File tree

api/v1/api.gen.go

Lines changed: 198 additions & 198 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1/api.yaml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4026,7 +4026,7 @@ paths:
40264026
/notification-routes:
40274027
get:
40284028
summary: "List notification routes"
4029-
description: "Returns global and workspace notification channel routes. Notification channels and rules require an active Dagu license or trial. Developer, manager, or admin only."
4029+
description: "Returns global and workspace notification channel routes. Developer, manager, or admin only."
40304030
operationId: "listNotificationRoutes"
40314031
tags:
40324032
- "notifications"
@@ -4040,7 +4040,7 @@ paths:
40404040
schema:
40414041
$ref: "#/components/schemas/NotificationRouteSetListResponse"
40424042
"403":
4043-
description: "Forbidden - requires an active Dagu license or trial"
4043+
description: "Forbidden - insufficient permissions"
40444044
content:
40454045
application/json:
40464046
schema:
@@ -4069,7 +4069,7 @@ paths:
40694069
schema:
40704070
$ref: "#/components/schemas/NotificationRouteSet"
40714071
"403":
4072-
description: "Forbidden - requires an active Dagu license or trial"
4072+
description: "Forbidden - insufficient permissions"
40734073
content:
40744074
application/json:
40754075
schema:
@@ -4109,7 +4109,7 @@ paths:
41094109
schema:
41104110
$ref: "#/components/schemas/Error"
41114111
"403":
4112-
description: "Forbidden - requires an active Dagu license or trial"
4112+
description: "Forbidden - insufficient permissions"
41134113
content:
41144114
application/json:
41154115
schema:
@@ -4149,7 +4149,7 @@ paths:
41494149
schema:
41504150
$ref: "#/components/schemas/NotificationRouteSet"
41514151
"403":
4152-
description: "Forbidden - requires an active Dagu license or trial"
4152+
description: "Forbidden - insufficient permissions"
41534153
content:
41544154
application/json:
41554155
schema:
@@ -4200,7 +4200,7 @@ paths:
42004200
schema:
42014201
$ref: "#/components/schemas/Error"
42024202
"403":
4203-
description: "Forbidden - requires an active Dagu license or trial"
4203+
description: "Forbidden - insufficient permissions"
42044204
content:
42054205
application/json:
42064206
schema:
@@ -4235,7 +4235,7 @@ paths:
42354235
schema:
42364236
$ref: "#/components/schemas/NotificationChannelListResponse"
42374237
"403":
4238-
description: "Forbidden - requires an active Dagu license or trial"
4238+
description: "Forbidden - insufficient permissions"
42394239
content:
42404240
application/json:
42414241
schema:
@@ -4277,7 +4277,7 @@ paths:
42774277
schema:
42784278
$ref: "#/components/schemas/Error"
42794279
"403":
4280-
description: "Forbidden - requires an active Dagu license or trial"
4280+
description: "Forbidden - insufficient permissions"
42814281
content:
42824282
application/json:
42834283
schema:
@@ -4317,7 +4317,7 @@ paths:
43174317
schema:
43184318
$ref: "#/components/schemas/Error"
43194319
"403":
4320-
description: "Forbidden - requires an active Dagu license or trial"
4320+
description: "Forbidden - insufficient permissions"
43214321
content:
43224322
application/json:
43234323
schema:
@@ -4370,7 +4370,7 @@ paths:
43704370
schema:
43714371
$ref: "#/components/schemas/Error"
43724372
"403":
4373-
description: "Forbidden - requires an active Dagu license or trial"
4373+
description: "Forbidden - insufficient permissions"
43744374
content:
43754375
application/json:
43764376
schema:
@@ -4411,7 +4411,7 @@ paths:
44114411
schema:
44124412
$ref: "#/components/schemas/Error"
44134413
"403":
4414-
description: "Forbidden - requires an active Dagu license or trial"
4414+
description: "Forbidden - insufficient permissions"
44154415
content:
44164416
application/json:
44174417
schema:
@@ -5286,7 +5286,7 @@ paths:
52865286
schema:
52875287
$ref: "#/components/schemas/Error"
52885288
"403":
5289-
description: "Forbidden - notification channels require an active Dagu license or trial"
5289+
description: "Forbidden - insufficient permissions"
52905290
content:
52915291
application/json:
52925292
schema:

internal/cmd/process/scheduler.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func NewScheduler(cfg SchedulerConfig) (*scheduler.Scheduler, error) {
9898
} else {
9999
sched.SetEventCollector(collector)
100100
}
101-
if notificationMonitor := newNotificationMonitor(ctx, cfg.Config, dagStore, cfg.LicenseManager, cfg.EventService); notificationMonitor != nil {
101+
if notificationMonitor := newNotificationMonitor(ctx, cfg.Config, dagStore, cfg.EventService); notificationMonitor != nil {
102102
sched.SetNotificationMonitor(notificationMonitor)
103103
}
104104
if incidentMonitor := newIncidentMonitor(ctx, cfg.Config, cfg.LicenseManager, cfg.EventService); incidentMonitor != nil {
@@ -132,7 +132,6 @@ func newNotificationMonitor(
132132
ctx context.Context,
133133
cfg *config.Config,
134134
dagStore exec.DAGStore,
135-
licenseManager *license.Manager,
136135
eventService *eventstore.Service,
137136
) *chatbridge.NotificationMonitor {
138137
encKey, encErr := crypto.ResolveKey(cfg.Paths.DataDir)
@@ -153,16 +152,9 @@ func newNotificationMonitor(
153152
logger.Warn(ctx, "Failed to create notification settings store", tag.Error(err))
154153
return nil
155154
}
156-
var checker license.Checker
157-
if licenseManager != nil {
158-
checker = licenseManager.Checker()
159-
}
160155
notificationService := notificationservice.New(
161156
store,
162157
dagStore,
163-
notificationservice.WithReusableChannelsEnabled(func() bool {
164-
return license.HasActiveLicense(checker)
165-
}),
166158
)
167159
stateFile := filepath.Join(cfg.Paths.DataDir, "notifications", "monitor-state.json")
168160
return chatbridge.NewNotificationMonitor(

internal/service/frontend/api/v1/api.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -719,11 +719,6 @@ var (
719719
Code: api.ErrorCodeForbidden,
720720
Message: "Audit logs require a Dagu Pro license",
721721
}
722-
errReusableNotificationChannelsNotLicensed = &Error{
723-
HTTPStatus: http.StatusForbidden,
724-
Code: api.ErrorCodeForbidden,
725-
Message: "Notification channels and rules require an active Dagu license or trial",
726-
}
727722
errIncidentManagementNotLicensed = &Error{
728723
HTTPStatus: http.StatusForbidden,
729724
Code: api.ErrorCodeForbidden,
@@ -784,16 +779,6 @@ func (a *API) requireLicensedAudit() error {
784779
return nil
785780
}
786781

787-
func (a *API) requireLicensedReusableNotificationChannels() error {
788-
if a.licenseManager == nil {
789-
return errReusableNotificationChannelsNotLicensed
790-
}
791-
if !license.HasActiveLicense(a.licenseManager.Checker()) {
792-
return errReusableNotificationChannelsNotLicensed
793-
}
794-
return nil
795-
}
796-
797782
func (a *API) requireLicensedIncidentManagement() error {
798783
if a.licenseManager == nil {
799784
return errIncidentManagementNotLicensed

internal/service/frontend/api/v1/notification_channels_license_test.go

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@ import (
2121
"github.com/stretchr/testify/require"
2222
)
2323

24-
func TestNotificationChannels_RequireActiveLicense(t *testing.T) {
24+
func TestNotificationChannels_AvailableWithoutLicense(t *testing.T) {
2525
t.Parallel()
2626

2727
server := test.SetupServer(t)
28-
server.Client().Get("/api/v1/notification-channels").
29-
ExpectStatus(http.StatusForbidden).Send(t)
28+
resp := server.Client().Get("/api/v1/notification-channels").
29+
ExpectStatus(http.StatusOK).Send(t)
30+
31+
var result api.NotificationChannelListResponse
32+
resp.Unmarshal(t, &result)
33+
assert.Empty(t, result.Channels)
3034
}
3135

3236
func TestNotificationChannels_AcceptExistingLicenseWithoutFeatureClaim(t *testing.T) {
@@ -43,20 +47,22 @@ func TestNotificationChannels_AcceptExistingLicenseWithoutFeatureClaim(t *testin
4347
assert.Empty(t, result.Channels)
4448
}
4549

46-
func TestNotificationRoutes_RequireActiveLicense(t *testing.T) {
50+
func TestNotificationRoutes_AvailableWithoutLicense(t *testing.T) {
4751
t.Parallel()
4852

4953
server := test.SetupServer(t)
50-
server.Client().Get("/api/v1/notification-routes/global").
51-
ExpectStatus(http.StatusForbidden).Send(t)
54+
resp := server.Client().Get("/api/v1/notification-routes/global").
55+
ExpectStatus(http.StatusOK).Send(t)
56+
57+
var result api.NotificationRouteSet
58+
resp.Unmarshal(t, &result)
59+
assert.Equal(t, api.NotificationRouteScopeGlobal, result.Scope)
5260
}
5361

5462
func TestNotificationRoutes_GlobalAndWorkspaceRouteSets(t *testing.T) {
5563
t.Parallel()
5664

57-
server := test.SetupServer(t,
58-
test.WithServerOptions(frontend.WithLicenseManager(license.NewTestManager())),
59-
)
65+
server := test.SetupServer(t)
6066

6167
channelResp := server.Client().Post("/api/v1/notification-channels", api.NotificationChannelInput{
6268
Name: "Ops Webhook",
@@ -172,35 +178,34 @@ func testValue[T any](value *T) T {
172178
return *value
173179
}
174180

175-
func TestDAGNotifications_UnlicensedSubscriptionUpdates(t *testing.T) {
181+
func TestDAGNotifications_SubscriptionUpdatesWithoutLicense(t *testing.T) {
176182
t.Parallel()
177183

178184
tests := []struct {
179-
name string
180-
subscriptions *[]api.NotificationSubscriptionInput
181-
wantStatus int
182-
wantSubscriptions int
185+
name string
186+
subscriptions *[]api.NotificationSubscriptionInput
187+
wantSubscriptionIDs []string
188+
wantSubscriptionRefs []string
183189
}{
184190
{
185-
name: "omitted subscriptions preserves existing reusable subscription",
186-
subscriptions: nil,
187-
wantStatus: http.StatusOK,
188-
wantSubscriptions: 1,
191+
name: "omitted subscriptions preserves existing reusable subscription",
192+
subscriptions: nil,
193+
wantSubscriptionIDs: []string{"subscription-1"},
194+
wantSubscriptionRefs: []string{"channel-1"},
189195
},
190196
{
191-
name: "empty subscriptions is still gated",
192-
subscriptions: &[]api.NotificationSubscriptionInput{},
193-
wantStatus: http.StatusForbidden,
194-
wantSubscriptions: 1,
197+
name: "empty subscriptions clears existing reusable subscriptions",
198+
subscriptions: &[]api.NotificationSubscriptionInput{},
195199
},
196200
{
197-
name: "non-empty subscriptions is gated",
201+
name: "non-empty subscriptions replaces reusable subscriptions",
198202
subscriptions: &[]api.NotificationSubscriptionInput{{
203+
Id: new("subscription-2"),
199204
ChannelId: "channel-1",
200205
Enabled: true,
201206
}},
202-
wantStatus: http.StatusForbidden,
203-
wantSubscriptions: 1,
207+
wantSubscriptionIDs: []string{"subscription-2"},
208+
wantSubscriptionRefs: []string{"channel-1"},
204209
},
205210
}
206211

@@ -217,21 +222,26 @@ func TestDAGNotifications_UnlicensedSubscriptionUpdates(t *testing.T) {
217222
Enabled: true,
218223
Events: []api.NotificationEventType{api.NotificationEventTypeDagRunFailed},
219224
Targets: []api.NotificationTargetInput{},
220-
// nil means an older/unlicensed client is not managing reusable
221-
// subscriptions; non-nil means it is trying to replace them.
225+
// nil means an older client is not managing reusable subscriptions;
226+
// non-nil means it is trying to replace them.
222227
Subscriptions: tt.subscriptions,
223-
}).ExpectStatus(tt.wantStatus).Send(t)
224-
225-
if tt.wantStatus == http.StatusOK {
226-
var settings api.DAGNotificationSettings
227-
response.Unmarshal(t, &settings)
228-
require.Len(t, settings.Subscriptions, tt.wantSubscriptions)
228+
}).ExpectStatus(http.StatusOK).Send(t)
229+
230+
var apiSettings api.DAGNotificationSettings
231+
response.Unmarshal(t, &apiSettings)
232+
require.Len(t, apiSettings.Subscriptions, len(tt.wantSubscriptionIDs))
233+
for i, wantID := range tt.wantSubscriptionIDs {
234+
assert.Equal(t, wantID, apiSettings.Subscriptions[i].Id)
235+
assert.Equal(t, tt.wantSubscriptionRefs[i], apiSettings.Subscriptions[i].ChannelId)
229236
}
230237

231238
settings, err := store.GetByDAGName(context.Background(), dagName)
232239
require.NoError(t, err)
233-
require.Len(t, settings.Subscriptions, tt.wantSubscriptions)
234-
assert.Equal(t, "subscription-1", settings.Subscriptions[0].ID)
240+
require.Len(t, settings.Subscriptions, len(tt.wantSubscriptionIDs))
241+
for i, wantID := range tt.wantSubscriptionIDs {
242+
assert.Equal(t, wantID, settings.Subscriptions[i].ID)
243+
assert.Equal(t, tt.wantSubscriptionRefs[i], settings.Subscriptions[i].ChannelID)
244+
}
235245
})
236246
}
237247
}

0 commit comments

Comments
 (0)