Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions api/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ func EventToHealth(event string) models.Health {

type Event struct {
ID uuid.UUID `gorm:"default:generate_ulid()"`
EventID uuid.UUID `json:"event_id"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
Properties types.JSONStringMap `json:"properties"`
Expand All @@ -142,6 +143,7 @@ type Event struct {
func (t Event) ToPostQEvent() models.Event {
return models.Event{
ID: t.ID,
EventID: t.EventID,
Name: t.Name,
Error: t.Error,
Attempts: t.Attempts,
Expand Down
12 changes: 9 additions & 3 deletions cmd/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,17 @@ func triggerExistingNotification(ctx context.Context, flags *sendFlags) error {
}

event := models.Event{
Name: flags.Event,
Name: flags.Event,
EventID: configID,
Properties: map[string]string{
"id": configID.String(),
"description": lo.FromPtr(config.Description),
"status": lo.FromPtr(config.Status),
},
}
if flags.Event == api.EventConfigChanged || flags.Event == api.EventConfigUpdated {
event.Properties["config_id"] = configID.String()
}

celEnv, err := notification.GetEnvForEvent(ctx, event)
if err != nil {
Expand Down Expand Up @@ -158,7 +162,8 @@ func triggerExistingNotification(ctx context.Context, flags *sendFlags) error {
}

payload := notification.NotificationEventPayload{
ID: configID,
ResourceID: configID,
EventID: event.EventID,
EventName: flags.Event,
NotificationID: notifID,
EventCreatedAt: time.Now(),
Expand Down Expand Up @@ -195,12 +200,13 @@ func triggerExistingNotification(ctx context.Context, flags *sendFlags) error {
func enqueueNotification(ctx context.Context, payload notification.NotificationEventPayload) error {
event := models.Event{
Name: api.EventNotificationSend,
EventID: payload.GenerateEventID(),
Properties: payload.AsMap(),
}
if err := ctx.DB().Clauses(events.EventQueueOnConflictClause).Create(&event).Error; err != nil {
return fmt.Errorf("failed to enqueue notification: %w", err)
}
fmt.Printf("Notification enqueued (event_id=%s) - will be processed by running instance\n", event.ID)
fmt.Printf("Notification enqueued (event_id=%s) - will be processed by running instance\n", event.EventID)
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion events/event_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func StartConsumers(ctx context.Context) {

// on conflict clause when inserting new events to the `event_queue` table
var EventQueueOnConflictClause = clause.OnConflict{
Columns: []clause.Column{{Name: "name"}, {Name: "properties"}},
Columns: models.EventQueueUniqueConstraint(),
DoUpdates: clause.Assignments(map[string]any{
"attempts": 0,
"last_attempt": nil,
Expand Down
114 changes: 114 additions & 0 deletions events/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package events

import (
"github.com/flanksource/duty"
dutyAPI "github.com/flanksource/duty/api"
"github.com/flanksource/duty/context"
"github.com/flanksource/duty/models"
"github.com/google/uuid"

"github.com/flanksource/incident-commander/api"
)

type EventResource struct {
Component *models.Component `json:"component,omitempty"`
Config *models.ConfigItem `json:"config,omitempty"`
Check *models.Check `json:"check,omitempty"`
CheckSummary *models.CheckSummary `json:"check_summary,omitempty"`
Canary *models.Canary `json:"canary,omitempty"`
}

func (t *EventResource) AsMap() map[string]any {
output := map[string]any{}

if t.Component != nil {
output["component"] = t.Component.AsMap()
}
if t.Config != nil {
output["config"] = t.Config.AsMap()
}
if t.Check != nil {
output["check"] = t.Check.AsMap()
}
if t.Canary != nil {
output["canary"] = t.Canary.AsMap()
}
if t.CheckSummary != nil {
output["check_summary"] = t.CheckSummary.AsMap()
}

return output
}

// BuildEventResource creates an EventResource from an event by fetching the appropriate models from the database
func BuildEventResource(ctx context.Context, event models.Event) (EventResource, error) {
var eventResource EventResource
switch event.Name {
case api.EventCheckFailed, api.EventCheckPassed:
checkID := event.EventID
var check models.Check
if err := ctx.DB().Where("id = ?", checkID).Limit(1).Find(&check).Error; err != nil {
return eventResource, err
}
if check.ID == uuid.Nil {
return eventResource, dutyAPI.Errorf(dutyAPI.ENOTFOUND, "check(id=%s) not found", checkID)
}
eventResource.Check = &check

if summary, err := duty.CheckSummary(ctx, checkID.String()); err != nil {
return eventResource, err
} else if summary != nil {
eventResource.CheckSummary = summary
}

var canary models.Canary
if err := ctx.DB().Where("id = ?", eventResource.Check.CanaryID).Limit(1).Find(&canary).Error; err != nil {
return eventResource, err
}
if canary.ID == uuid.Nil {
return eventResource, dutyAPI.Errorf(dutyAPI.ENOTFOUND, "canary(id=%s) not found", eventResource.Check.CanaryID)
}
eventResource.Canary = &canary

case api.EventComponentHealthy, api.EventComponentUnhealthy, api.EventComponentWarning, api.EventComponentUnknown:
var component models.Component
if err := ctx.DB().Model(&models.Component{}).Where("id = ?", event.EventID).Limit(1).Find(&component).Error; err != nil {
return eventResource, err
}
if component.ID == uuid.Nil {
return eventResource, dutyAPI.Errorf(dutyAPI.ENOTFOUND, "component(id=%s) not found", event.EventID)
}
eventResource.Component = &component

case api.EventConfigHealthy, api.EventConfigUnhealthy, api.EventConfigWarning, api.EventConfigUnknown, api.EventConfigDegraded:
var config models.ConfigItem
if err := ctx.DB().Model(&models.ConfigItem{}).Where("id = ?", event.EventID).Limit(1).Find(&config).Error; err != nil {
return eventResource, err
}
if config.ID == uuid.Nil {
return eventResource, dutyAPI.Errorf(dutyAPI.ENOTFOUND, "config(id=%s) not found", event.EventID)
}
eventResource.Config = &config

case api.EventConfigCreated, api.EventConfigDeleted:
var config models.ConfigItem
if err := ctx.DB().Model(&models.ConfigItem{}).Where("id = ?", event.EventID).Limit(1).Find(&config).Error; err != nil {
return eventResource, err
}
if config.ID == uuid.Nil {
return eventResource, dutyAPI.Errorf(dutyAPI.ENOTFOUND, "config(id=%s) not found", event.EventID)
}
eventResource.Config = &config

case api.EventConfigChanged, api.EventConfigUpdated:
var config models.ConfigItem
if err := ctx.DB().Model(&models.ConfigItem{}).Where("id = ?", event.Properties["config_id"]).Limit(1).Find(&config).Error; err != nil {
return eventResource, err
}
if config.ID == uuid.Nil {
return eventResource, dutyAPI.Errorf(dutyAPI.ENOTFOUND, "config(id=%s) not found", event.Properties["config_id"])
}
eventResource.Config = &config
}
return eventResource, nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ require (
sigs.k8s.io/yaml v1.6.0
)

// replace github.com/flanksource/duty => ../duty
replace github.com/flanksource/duty => ../duty

// replace github.com/flanksource/clicky => ../clicky

Expand Down
44 changes: 30 additions & 14 deletions incidents/responder/responder_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func RegisterEvents(ctx context.Context) {
// generateResponderAddedAsyncEvent generates async events for each of the configured responder clients
// in the associated team.
func generateResponderAddedAsyncEvent(ctx context.Context, event models.Event) error {
responderID := event.Properties["id"]
responderID := event.EventID.String()

var responder api.Responder
err := ctx.DB().Where("id = ? AND external_id is NULL", responderID).Preload("Incident").Preload("Team").Find(&responder).Error
Expand All @@ -43,13 +43,21 @@ func generateResponderAddedAsyncEvent(ctx context.Context, event models.Event) e
}

if spec.ResponderClients.Jira != nil {
if err := ctx.DB().Clauses(events.EventQueueOnConflictClause).Create(&api.Event{Name: api.EventJiraResponderAdded, Properties: map[string]string{"id": responderID}}).Error; err != nil {
if err := ctx.DB().Clauses(events.EventQueueOnConflictClause).Create(&api.Event{
Name: api.EventJiraResponderAdded,
EventID: event.EventID,
Properties: map[string]string{"id": responderID},
}).Error; err != nil {
return err
}
}

if spec.ResponderClients.MSPlanner != nil {
if err := ctx.DB().Clauses(events.EventQueueOnConflictClause).Create(&api.Event{Name: api.EventMSPlannerResponderAdded, Properties: map[string]string{"id": responderID}}).Error; err != nil {
if err := ctx.DB().Clauses(events.EventQueueOnConflictClause).Create(&api.Event{
Name: api.EventMSPlannerResponderAdded,
EventID: event.EventID,
Properties: map[string]string{"id": responderID},
}).Error; err != nil {
return err
}
}
Expand All @@ -59,7 +67,7 @@ func generateResponderAddedAsyncEvent(ctx context.Context, event models.Event) e

// generateCommentAddedAsyncEvent generates comment.add async events for each of the configured responder clients.
func generateCommentAddedAsyncEvent(ctx context.Context, event models.Event) error {
commentID := event.Properties["id"]
commentID := event.EventID.String()

var comment api.Comment
err := ctx.DB().Where("id = ? AND external_id IS NULL", commentID).First(&comment).Error
Expand All @@ -86,17 +94,25 @@ func generateCommentAddedAsyncEvent(ctx context.Context, event models.Event) err
for _, responder := range responders {
switch responder.Type {
case "jira":
if err := ctx.DB().Clauses(events.EventQueueOnConflictClause).Create(&api.Event{Name: api.EventJiraCommentAdded, Properties: map[string]string{
"responder_id": responder.ID.String(),
"id": commentID,
}}).Error; err != nil {
if err := ctx.DB().Clauses(events.EventQueueOnConflictClause).Create(&api.Event{
Name: api.EventJiraCommentAdded,
EventID: event.EventID,
Properties: map[string]string{
"responder_id": responder.ID.String(),
"id": commentID,
},
}).Error; err != nil {
return err
}
case "ms_planner":
if err := ctx.DB().Clauses(events.EventQueueOnConflictClause).Create(&api.Event{Name: api.EventMSPlannerCommentAdded, Properties: map[string]string{
"responder_id": responder.ID.String(),
"id": commentID,
}}).Error; err != nil {
if err := ctx.DB().Clauses(events.EventQueueOnConflictClause).Create(&api.Event{
Name: api.EventMSPlannerCommentAdded,
EventID: event.EventID,
Properties: map[string]string{
"responder_id": responder.ID.String(),
"id": commentID,
},
}).Error; err != nil {
return err
}
}
Expand Down Expand Up @@ -130,7 +146,7 @@ func handleResponderEvent(ctx context.Context, event models.Event) error {

// TODO: Modify this such that it only notifies the responder mentioned in the event.
func reconcileResponderEvent(ctx context.Context, event models.Event) error {
responderID := event.Properties["id"]
responderID := event.EventID.String()

var responder api.Responder
err := ctx.DB().Where("id = ? AND external_id is NULL", responderID).Preload("Incident").Preload("Team").Find(&responder).Error
Expand All @@ -157,7 +173,7 @@ func reconcileResponderEvent(ctx context.Context, event models.Event) error {

// TODO: Modify this such that it only adds the comment to the particular responder mentioned in the event.
func reconcileCommentEvent(ctx context.Context, event models.Event) error {
commentID := event.Properties["id"]
commentID := event.EventID.String()

var comment api.Comment
err := ctx.DB().Where("id = ? AND external_id IS NULL", commentID).First(&comment).Error
Expand Down
Loading
Loading