From af0407157bff20ccfcedf9e4a5a46f6667f1b0ad Mon Sep 17 00:00:00 2001 From: Amir Raminfar Date: Wed, 21 Jan 2026 10:18:48 -0800 Subject: [PATCH] chore: cleans up types for notificaitons (#4367) --- graph/schema.resolvers.go | 5 +- .../notification/dispatcher/dispatcher.go | 4 +- internal/notification/dispatcher/webhook.go | 3 +- internal/notification/manager.go | 21 +++++---- internal/notification/types.go | 46 ++++--------------- types/{types.go => beacon.go} | 0 types/notification.go | 32 +++++++++++++ 7 files changed, 60 insertions(+), 51 deletions(-) rename types/{types.go => beacon.go} (100%) create mode 100644 types/notification.go diff --git a/graph/schema.resolvers.go b/graph/schema.resolvers.go index 33785bc7..4eb09219 100644 --- a/graph/schema.resolvers.go +++ b/graph/schema.resolvers.go @@ -14,6 +14,7 @@ import ( "github.com/amir20/dozzle/internal/notification" "github.com/amir20/dozzle/internal/notification/dispatcher" "github.com/amir20/dozzle/internal/releases" + "github.com/amir20/dozzle/types" "github.com/expr-lang/expr" "github.com/expr-lang/expr/vm" ) @@ -195,7 +196,7 @@ func (r *mutationResolver) PreviewExpression(ctx context.Context, input model.Pr // Compile and test container expression var containerProgram *vm.Program if input.ContainerExpression != "" { - program, err := expr.Compile(input.ContainerExpression, expr.Env(notification.Container{})) + program, err := expr.Compile(input.ContainerExpression, expr.Env(types.NotificationContainer{})) if err != nil { errStr := err.Error() result.ContainerError = &errStr @@ -207,7 +208,7 @@ func (r *mutationResolver) PreviewExpression(ctx context.Context, input model.Pr // Compile and test log expression var logProgram *vm.Program if input.LogExpression != nil && *input.LogExpression != "" { - program, err := expr.Compile(*input.LogExpression, expr.Env(notification.Log{})) + program, err := expr.Compile(*input.LogExpression, expr.Env(types.NotificationLog{})) if err != nil { errStr := err.Error() result.LogError = &errStr diff --git a/internal/notification/dispatcher/dispatcher.go b/internal/notification/dispatcher/dispatcher.go index 30d981a6..92f25126 100644 --- a/internal/notification/dispatcher/dispatcher.go +++ b/internal/notification/dispatcher/dispatcher.go @@ -2,10 +2,12 @@ package dispatcher import ( "context" + + "github.com/amir20/dozzle/types" ) // Dispatcher is responsible for sending notifications to external systems type Dispatcher interface { // Send sends a notification to the configured destination - Send(ctx context.Context, notification any) error + Send(ctx context.Context, notification types.Notification) error } diff --git a/internal/notification/dispatcher/webhook.go b/internal/notification/dispatcher/webhook.go index d7c40803..87d1cda6 100644 --- a/internal/notification/dispatcher/webhook.go +++ b/internal/notification/dispatcher/webhook.go @@ -10,6 +10,7 @@ import ( "text/template" "time" + "github.com/amir20/dozzle/types" "github.com/rs/zerolog/log" ) @@ -46,7 +47,7 @@ func NewWebhookDispatcher(name, url, templateStr string) (*WebhookDispatcher, er } // Send sends a notification to the webhook URL -func (w *WebhookDispatcher) Send(ctx context.Context, notification any) error { +func (w *WebhookDispatcher) Send(ctx context.Context, notification types.Notification) error { var payload []byte var err error diff --git a/internal/notification/manager.go b/internal/notification/manager.go index c440eb4b..abcbda28 100644 --- a/internal/notification/manager.go +++ b/internal/notification/manager.go @@ -10,6 +10,7 @@ import ( "github.com/amir20/dozzle/internal/container" "github.com/amir20/dozzle/internal/notification/dispatcher" + "github.com/amir20/dozzle/types" "github.com/expr-lang/expr" "github.com/puzpuzpuz/xsync/v4" "github.com/rs/zerolog/log" @@ -75,7 +76,7 @@ func (m *Manager) AddSubscription(sub *Subscription) error { // Compile container expression if provided if sub.ContainerExpression != "" { - program, err := expr.Compile(sub.ContainerExpression, expr.Env(Container{})) + program, err := expr.Compile(sub.ContainerExpression, expr.Env(types.NotificationContainer{})) if err != nil { return fmt.Errorf("failed to compile container expression: %w", err) } @@ -84,7 +85,7 @@ func (m *Manager) AddSubscription(sub *Subscription) error { // Compile log expression if provided if sub.LogExpression != "" { - program, err := expr.Compile(sub.LogExpression, expr.Env(Log{})) + program, err := expr.Compile(sub.LogExpression, expr.Env(types.NotificationLog{})) if err != nil { return fmt.Errorf("failed to compile log expression: %w", err) } @@ -122,7 +123,7 @@ func (m *Manager) RemoveSubscription(id int) { func (m *Manager) ReplaceSubscription(sub *Subscription) error { // Compile container expression if provided if sub.ContainerExpression != "" { - program, err := expr.Compile(sub.ContainerExpression, expr.Env(Container{})) + program, err := expr.Compile(sub.ContainerExpression, expr.Env(types.NotificationContainer{})) if err != nil { return fmt.Errorf("failed to compile container expression: %w", err) } @@ -131,7 +132,7 @@ func (m *Manager) ReplaceSubscription(sub *Subscription) error { // Compile log expression if provided if sub.LogExpression != "" { - program, err := expr.Compile(sub.LogExpression, expr.Env(Log{})) + program, err := expr.Compile(sub.LogExpression, expr.Env(types.NotificationLog{})) if err != nil { return fmt.Errorf("failed to compile log expression: %w", err) } @@ -196,7 +197,7 @@ func (m *Manager) UpdateSubscription(id int, updates map[string]any) error { } case "containerExpression": if exprStr, ok := value.(string); ok { - program, err := expr.Compile(exprStr, expr.Env(Container{})) + program, err := expr.Compile(exprStr, expr.Env(types.NotificationContainer{})) if err != nil { updateErr = fmt.Errorf("failed to compile container expression: %w", err) return nil, xsync.CancelOp @@ -207,7 +208,7 @@ func (m *Manager) UpdateSubscription(id int, updates map[string]any) error { case "logExpression": if exprStr, ok := value.(string); ok { if exprStr != "" { - program, err := expr.Compile(exprStr, expr.Env(Log{})) + program, err := expr.Compile(exprStr, expr.Env(types.NotificationLog{})) if err != nil { updateErr = fmt.Errorf("failed to compile log expression: %w", err) return nil, xsync.CancelOp @@ -354,7 +355,7 @@ func (m *Manager) processLogEvent(logEvent *container.LogEvent) { log.Debug().Str("containerID", notificationContainer.ID).Interface("log", notificationLog.Message).Msg("Matched subscription") // Create notification - notification := Notification{ + notification := types.Notification{ ID: fmt.Sprintf("%s-%d", c.ID, time.Now().UnixNano()), Container: notificationContainer, Log: notificationLog, @@ -370,7 +371,7 @@ func (m *Manager) processLogEvent(logEvent *container.LogEvent) { } // sendNotification sends a notification using the dispatcher -func (m *Manager) sendNotification(d dispatcher.Dispatcher, notification Notification, id int) { +func (m *Manager) sendNotification(d dispatcher.Dispatcher, notification types.Notification, id int) { ctx, cancel := context.WithTimeout(m.ctx, 30*time.Second) defer cancel() @@ -454,7 +455,7 @@ func (m *Manager) LoadConfig(r io.Reader) error { func (m *Manager) loadSubscription(sub *Subscription) error { // Compile container expression if provided if sub.ContainerExpression != "" { - program, err := expr.Compile(sub.ContainerExpression, expr.Env(Container{})) + program, err := expr.Compile(sub.ContainerExpression, expr.Env(types.NotificationContainer{})) if err != nil { return fmt.Errorf("failed to compile container expression: %w", err) } @@ -463,7 +464,7 @@ func (m *Manager) loadSubscription(sub *Subscription) error { // Compile log expression if provided if sub.LogExpression != "" { - program, err := expr.Compile(sub.LogExpression, expr.Env(Log{})) + program, err := expr.Compile(sub.LogExpression, expr.Env(types.NotificationLog{})) if err != nil { return fmt.Errorf("failed to compile log expression: %w", err) } diff --git a/internal/notification/types.go b/internal/notification/types.go index 696d7bbf..75a0044c 100644 --- a/internal/notification/types.go +++ b/internal/notification/types.go @@ -6,33 +6,15 @@ import ( "time" "github.com/amir20/dozzle/internal/container" + "github.com/amir20/dozzle/types" "github.com/expr-lang/expr" "github.com/expr-lang/expr/vm" "github.com/puzpuzpuz/xsync/v4" ) -// Notification represents a notification event that can be filtered and sent -type Notification struct { - ID string `json:"id"` - Container Container `json:"container"` - Log Log `json:"log"` - Timestamp time.Time `json:"timestamp"` -} - -// Container represents a simplified container structure optimized for expr filtering -type Container struct { - ID string `json:"id" expr:"id"` - Name string `json:"name" expr:"name"` - Image string `json:"image" expr:"image"` - State string `json:"state" expr:"state"` - Health string `json:"health" expr:"health"` - Host string `json:"host" expr:"host"` - Labels map[string]string `json:"labels" expr:"labels"` -} - -// FromContainerModel converts internal container.Container to notification.Container -func FromContainerModel(c container.Container) Container { - return Container{ +// FromContainerModel converts internal container.Container to types.NotificationContainer +func FromContainerModel(c container.Container) types.NotificationContainer { + return types.NotificationContainer{ ID: c.ID, Name: c.Name, Image: c.Image, @@ -43,21 +25,11 @@ func FromContainerModel(c container.Container) Container { } } -// Log represents a log entry with message that can be string or object -type Log struct { - ID uint32 `json:"id" expr:"id"` - Message any `json:"message" expr:"message"` // string for simple/grouped logs, map for complex logs - Timestamp int64 `json:"timestamp" expr:"timestamp"` - Level string `json:"level" expr:"level"` - Stream string `json:"stream" expr:"stream"` - Type string `json:"type" expr:"type"` -} - -// FromLogEvent converts container.LogEvent to notification.Log -func FromLogEvent(l container.LogEvent) Log { +// FromLogEvent converts container.LogEvent to types.NotificationLog +func FromLogEvent(l container.LogEvent) types.NotificationLog { message := extractMessage(l) - return Log{ + return types.NotificationLog{ ID: l.Id, Message: message, Timestamp: l.Timestamp, @@ -138,7 +110,7 @@ type Config struct { } // MatchesContainer checks if a container matches this subscription's container filter -func (s *Subscription) MatchesContainer(c Container) bool { +func (s *Subscription) MatchesContainer(c types.NotificationContainer) bool { if s.ContainerProgram == nil { return false } @@ -153,7 +125,7 @@ func (s *Subscription) MatchesContainer(c Container) bool { } // MatchesLog checks if a log matches this subscription's log filter -func (s *Subscription) MatchesLog(l Log) bool { +func (s *Subscription) MatchesLog(l types.NotificationLog) bool { if s.LogProgram == nil { return false } diff --git a/types/types.go b/types/beacon.go similarity index 100% rename from types/types.go rename to types/beacon.go diff --git a/types/notification.go b/types/notification.go new file mode 100644 index 00000000..18725bb1 --- /dev/null +++ b/types/notification.go @@ -0,0 +1,32 @@ +package types + +import "time" + +// Notification represents a notification event that can be filtered and sent +type Notification struct { + ID string `json:"id"` + Container NotificationContainer `json:"container"` + Log NotificationLog `json:"log"` + Timestamp time.Time `json:"timestamp"` +} + +// NotificationContainer represents a simplified container structure for notifications +type NotificationContainer struct { + ID string `json:"id" expr:"id"` + Name string `json:"name" expr:"name"` + Image string `json:"image" expr:"image"` + State string `json:"state" expr:"state"` + Health string `json:"health" expr:"health"` + Host string `json:"host" expr:"host"` + Labels map[string]string `json:"labels" expr:"labels"` +} + +// NotificationLog represents a log entry with message that can be string or object +type NotificationLog struct { + ID uint32 `json:"id" expr:"id"` + Message any `json:"message" expr:"message"` // string for simple/grouped logs, map for complex logs + Timestamp int64 `json:"timestamp" expr:"timestamp"` + Level string `json:"level" expr:"level"` + Stream string `json:"stream" expr:"stream"` + Type string `json:"type" expr:"type"` +}