diff --git a/internal/pkg/project/ignore/file_test.go b/internal/pkg/project/ignore/file_test.go index cb3013543b..f4844dd857 100644 --- a/internal/pkg/project/ignore/file_test.go +++ b/internal/pkg/project/ignore/file_test.go @@ -109,5 +109,13 @@ func newTestRegistry(t *testing.T) *registry.Registry { } assert.NoError(t, r.Set(row2)) + // Notification 1 (under config 345, component keboola.foo) + notification1Key := model.NotificationKey{BranchID: 123, ComponentID: "keboola.foo", ConfigID: `345`, ID: `sub-001`} + notification1 := &model.NotificationState{ + NotificationManifest: &model.NotificationManifest{NotificationKey: notification1Key}, + Local: &model.Notification{NotificationKey: notification1Key}, + } + assert.NoError(t, r.Set(notification1)) + return r } diff --git a/internal/pkg/project/ignore/ignore.go b/internal/pkg/project/ignore/ignore.go index 4420d2c415..19b82aa6fa 100644 --- a/internal/pkg/project/ignore/ignore.go +++ b/internal/pkg/project/ignore/ignore.go @@ -6,7 +6,7 @@ import ( "github.com/keboola/keboola-as-code/internal/pkg/utils/errors" ) -func (f *File) IgnoreConfigsOrRows() error { +func (f *File) IgnoreObjects() error { return f.applyIgnoredPatterns() } @@ -44,13 +44,21 @@ func (f *File) applyIgnorePattern(ignoreConfig string) error { } parts := strings.Split(ignoreConfig, "/") - switch len(parts) { - case 2: + switch { + case len(parts) == 2: // Ignore config by componentID/configID. configID, componentID := parts[1], parts[0] f.state.IgnoreConfig(configID, componentID) - case 3: - // Ignore specific config row. + case len(parts) == 3 && parts[2] == "notifications": + // Ignore all notifications for a config: componentID/configID/notifications. + componentID, configID := parts[0], parts[1] + f.state.IgnoreNotificationsForConfig(configID, componentID) + case len(parts) == 4 && parts[2] == "notifications": + // Ignore a specific notification: componentID/configID/notifications/notificationID. + componentID, configID, notificationID := parts[0], parts[1], parts[3] + f.state.IgnoreNotification(configID, componentID, notificationID) + case len(parts) == 3: + // Ignore specific config row: componentID/configID/rowID. configID, rowID := parts[1], parts[2] f.state.IgnoreConfigRow(configID, rowID) default: diff --git a/internal/pkg/project/ignore/ignore_test.go b/internal/pkg/project/ignore/ignore_test.go index 541aeca877..4838b69fc7 100644 --- a/internal/pkg/project/ignore/ignore_test.go +++ b/internal/pkg/project/ignore/ignore_test.go @@ -11,7 +11,7 @@ import ( "github.com/keboola/keboola-as-code/internal/pkg/filesystem/aferofs" ) -func TestFile_IgnoreConfigsOrRows(t *testing.T) { +func TestFile_IgnoreObjects(t *testing.T) { t.Parallel() ctx := t.Context() @@ -23,7 +23,7 @@ func TestFile_IgnoreConfigsOrRows(t *testing.T) { file, err := LoadFile(ctx, fs, registry, "foo/bar1") require.NoError(t, err) - require.NoError(t, file.IgnoreConfigsOrRows()) + require.NoError(t, file.IgnoreObjects()) assert.Len(t, registry.IgnoredConfigRows(), 1) assert.Len(t, registry.IgnoredConfigs(), 1) @@ -31,7 +31,45 @@ func TestFile_IgnoreConfigsOrRows(t *testing.T) { assert.Equal(t, "345", registry.IgnoredConfigs()[0].ID.String()) } -func TestFile_IgnoreConfigsOrRows_Branch(t *testing.T) { +func TestFile_IgnoreObjects_AllNotificationsForConfig(t *testing.T) { + t.Parallel() + + ctx := t.Context() + r := newTestRegistry(t) + fs := aferofs.NewMemoryFs() + + require.NoError(t, fs.WriteFile(ctx, filesystem.NewRawFile(`foo/bar1`, "keboola.foo/345/notifications"))) + + file, err := LoadFile(ctx, fs, r, "foo/bar1") + require.NoError(t, err) + + require.NoError(t, file.IgnoreObjects()) + + ignored := r.IgnoredNotifications() + require.Len(t, ignored, 1) + assert.Equal(t, "sub-001", string(ignored[0].ID)) +} + +func TestFile_IgnoreObjects_SpecificNotification(t *testing.T) { + t.Parallel() + + ctx := t.Context() + r := newTestRegistry(t) + fs := aferofs.NewMemoryFs() + + require.NoError(t, fs.WriteFile(ctx, filesystem.NewRawFile(`foo/bar1`, "keboola.foo/345/notifications/sub-001"))) + + file, err := LoadFile(ctx, fs, r, "foo/bar1") + require.NoError(t, err) + + require.NoError(t, file.IgnoreObjects()) + + ignored := r.IgnoredNotifications() + require.Len(t, ignored, 1) + assert.Equal(t, "sub-001", string(ignored[0].ID)) +} + +func TestFile_IgnoreObjects_Branch(t *testing.T) { t.Parallel() ctx := t.Context() @@ -43,14 +81,14 @@ func TestFile_IgnoreConfigsOrRows_Branch(t *testing.T) { file, err := LoadFile(ctx, fs, r, "foo/bar1") require.NoError(t, err) - require.NoError(t, file.IgnoreConfigsOrRows()) + require.NoError(t, file.IgnoreObjects()) ignored := r.IgnoredBranches() require.Len(t, ignored, 1) assert.Equal(t, "123", ignored[0].ID.String()) } -func TestFile_IgnoreConfigsOrRows_BranchWithSlashInName(t *testing.T) { +func TestFile_IgnoreObjects_BranchWithSlashInName(t *testing.T) { t.Parallel() ctx := t.Context() @@ -62,7 +100,7 @@ func TestFile_IgnoreConfigsOrRows_BranchWithSlashInName(t *testing.T) { file, err := LoadFile(ctx, fs, r, "foo/bar1") require.NoError(t, err) - require.NoError(t, file.IgnoreConfigsOrRows()) + require.NoError(t, file.IgnoreObjects()) // Branch "feature/foo" should be ignored, not misidentified as a config-row pattern. ignored := r.IgnoredBranches() @@ -109,6 +147,27 @@ func Test_applyIgnoredPatterns(t *testing.T) { }, wantErr: assert.Error, }, + { + name: "notifications all pattern", + args: args{ + pattern: "keboola.foo/345/notifications", + }, + wantErr: assert.NoError, + }, + { + name: "notifications specific pattern", + args: args{ + pattern: "keboola.foo/345/notifications/sub-001", + }, + wantErr: assert.NoError, + }, + { + name: "notifications too long pattern", + args: args{ + pattern: "keboola.foo/345/notifications/sub-001/extra", + }, + wantErr: assert.Error, + }, { name: "short pattern", args: args{ diff --git a/internal/pkg/state/registry/registry.go b/internal/pkg/state/registry/registry.go index f9b33ebd84..c96da4b19c 100644 --- a/internal/pkg/state/registry/registry.go +++ b/internal/pkg/state/registry/registry.go @@ -199,6 +199,11 @@ func (s *Registry) NullIgnoredBranchStates() { v.SetLocalState(nil) v.SetRemoteState(nil) } + case *model.NotificationState: + if _, ok := ignoredKeys[model.BranchKey{ID: v.BranchID}]; ok { + v.SetLocalState(nil) + v.SetRemoteState(nil) + } } } } @@ -293,6 +298,37 @@ func (s *Registry) Notifications() (notifications []*model.NotificationState) { return notifications } +func (s *Registry) IgnoreNotificationsForConfig(configID, componentID string) { + for _, obj := range s.All() { + if v, ok := obj.(*model.NotificationState); ok { + if v.ConfigID.String() == configID && v.ComponentID.String() == componentID { + v.Ignore = true + } + } + } +} + +func (s *Registry) IgnoreNotification(configID, componentID, notificationID string) { + for _, obj := range s.All() { + if v, ok := obj.(*model.NotificationState); ok { + if v.ConfigID.String() == configID && v.ComponentID.String() == componentID && string(v.ID) == notificationID { + v.Ignore = true + } + } + } +} + +func (s *Registry) IgnoredNotifications() (notifications []*model.NotificationState) { + for _, obj := range s.All() { + if v, ok := obj.(*model.NotificationState); ok { + if v.Ignore { + notifications = append(notifications, v) + } + } + } + return notifications +} + func (s *Registry) NotificationsFrom(config model.ConfigKey) (notifications []*model.NotificationState) { for _, object := range s.All() { if v, ok := object.(*model.NotificationState); ok { diff --git a/internal/pkg/state/registry/registry_test.go b/internal/pkg/state/registry/registry_test.go index 58d323d3c7..7d9298e685 100644 --- a/internal/pkg/state/registry/registry_test.go +++ b/internal/pkg/state/registry/registry_test.go @@ -247,6 +247,73 @@ func TestNullIgnoredBranchStates(t *testing.T) { assert.Empty(t, s.ConfigRows()) } +func TestNullIgnoredBranchStates_WithNotification(t *testing.T) { + t.Parallel() + s := newTestState(t, knownpaths.NewNop(t.Context())) + + // Add a notification under branch 123 / config 345. + notifKey := NotificationKey{BranchID: 123, ComponentID: "keboola.foo", ConfigID: `345`, ID: `sub-001`} + notif := &NotificationState{ + NotificationManifest: &NotificationManifest{NotificationKey: notifKey}, + Local: &Notification{NotificationKey: notifKey}, + } + require.NoError(t, s.Set(notif)) + + // 7 objects now. + assert.Len(t, s.All(), 7) + + s.IgnoreBranch("Main") + s.NullIgnoredBranchStates() + + // The notification under branch 123 must also be nulled. + assert.Len(t, s.All(), 1) + assert.Empty(t, s.Notifications()) +} + +func TestIgnoreNotificationsForConfig(t *testing.T) { + t.Parallel() + s := newTestState(t, knownpaths.NewNop(t.Context())) + + notifKey := NotificationKey{BranchID: 123, ComponentID: "keboola.foo", ConfigID: `345`, ID: `sub-001`} + notif := &NotificationState{ + NotificationManifest: &NotificationManifest{NotificationKey: notifKey}, + Local: &Notification{NotificationKey: notifKey}, + } + require.NoError(t, s.Set(notif)) + + assert.Empty(t, s.IgnoredNotifications()) + + s.IgnoreNotificationsForConfig("345", "keboola.foo") + + ignored := s.IgnoredNotifications() + require.Len(t, ignored, 1) + assert.Equal(t, `sub-001`, string(ignored[0].ID)) +} + +func TestIgnoreNotification(t *testing.T) { + t.Parallel() + s := newTestState(t, knownpaths.NewNop(t.Context())) + + notifKey := NotificationKey{BranchID: 123, ComponentID: "keboola.foo", ConfigID: `345`, ID: `sub-001`} + notif := &NotificationState{ + NotificationManifest: &NotificationManifest{NotificationKey: notifKey}, + Local: &Notification{NotificationKey: notifKey}, + } + require.NoError(t, s.Set(notif)) + + assert.Empty(t, s.IgnoredNotifications()) + + // Wrong notification ID — should not match. + s.IgnoreNotification("345", "keboola.foo", "sub-999") + assert.Empty(t, s.IgnoredNotifications()) + + // Correct ID — should match. + s.IgnoreNotification("345", "keboola.foo", "sub-001") + ignored := s.IgnoredNotifications() + require.Len(t, ignored, 1) + assert.Equal(t, `sub-001`, string(ignored[0].ID)) +} + func TestIgnoreBranch(t *testing.T) { t.Parallel() s := newTestState(t, knownpaths.NewNop(t.Context())) diff --git a/pkg/lib/operation/project/sync/pull/operation.go b/pkg/lib/operation/project/sync/pull/operation.go index 561419bf1b..0a20058e0b 100644 --- a/pkg/lib/operation/project/sync/pull/operation.go +++ b/pkg/lib/operation/project/sync/pull/operation.go @@ -59,11 +59,11 @@ func Run(ctx context.Context, projectState *project.State, o Options, d dependen return err } - if err = file.IgnoreConfigsOrRows(); err != nil { + if err = file.IgnoreObjects(); err != nil { return err } - ignoreBranchesConfigsAndRows(projectState) + ignoreObjects(projectState) } // Diff @@ -156,7 +156,7 @@ func Run(ctx context.Context, projectState *project.State, o Options, d dependen return nil } -func ignoreBranchesConfigsAndRows(projectState *project.State) { +func ignoreObjects(projectState *project.State) { for _, v := range projectState.IgnoredConfigRows() { v.SetRemoteState(nil) } @@ -165,6 +165,10 @@ func ignoreBranchesConfigsAndRows(projectState *project.State) { v.SetRemoteState(nil) } + for _, v := range projectState.IgnoredNotifications() { + v.SetRemoteState(nil) + } + // Null both states for ignored branches so they are invisible to the diff. projectState.NullIgnoredBranchStates() } diff --git a/pkg/lib/operation/project/sync/push/operation.go b/pkg/lib/operation/project/sync/push/operation.go index b366a49340..26145a995b 100644 --- a/pkg/lib/operation/project/sync/push/operation.go +++ b/pkg/lib/operation/project/sync/push/operation.go @@ -76,10 +76,16 @@ func Run(ctx context.Context, projectState *project.State, o Options, d dependen return err } - if err = file.IgnoreConfigsOrRows(); err != nil { + if err = file.IgnoreObjects(); err != nil { return err } + // Null both states for directly-ignored notifications. + for _, v := range projectState.IgnoredNotifications() { + v.SetLocalState(nil) + v.SetRemoteState(nil) + } + // Make ignored branches invisible to the push diff. projectState.NullIgnoredBranchStates() }