Skip to content

Commit d5434e9

Browse files
committed
refactor: consolidate item update events
1 parent 8797058 commit d5434e9

3 files changed

Lines changed: 102 additions & 171 deletions

File tree

internal/services/event_coordinator.go

Lines changed: 5 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -156,98 +156,21 @@ func (ec *EventCoordinator) EmitItemUpdatedWithContext(original, updated *models
156156
func (ec *EventCoordinator) emitItemUpdatedInternal(original, updated *models.Item, statusChanged, assigneeChanged bool, actorUserID int, fieldChanges []HistoryEntry, actionContext *ActionContext, actorUsername ...string) {
157157
actorName := resolveActorName(actorUserID, actorUsername)
158158

159-
// Construct the item key (e.g., "TST-1")
160-
itemKey := fmt.Sprintf("%s-%d", updated.WorkspaceKey, updated.WorkspaceItemNumber)
161-
162-
// Emit notification events
163159
if ec.notificationService != nil {
164-
// Get status name if status changed
165160
var statusName string
166-
if statusChanged && updated.StatusID != nil {
167-
_ = ec.db.QueryRow("SELECT name FROM statuses WHERE id = ?", *updated.StatusID).Scan(&statusName)
168-
}
169-
170-
// Emit status changed notification
171161
if statusChanged {
172-
ec.notificationService.EmitEvent(&NotificationEvent{
173-
EventType: models.EventStatusChanged,
174-
WorkspaceID: updated.WorkspaceID,
175-
ActorUserID: actorUserID,
176-
ItemID: updated.ID,
177-
AssigneeID: updated.AssigneeID,
178-
CreatorID: original.CreatorID,
179-
Title: "Status Changed",
180-
TemplateData: map[string]any{
181-
"item.title": updated.Title,
182-
"item.key": itemKey,
183-
"item.id": updated.ID,
184-
"status.name": statusName,
185-
"user.name": actorName,
186-
},
187-
})
188-
}
189-
190-
// Emit assignee changed notification
191-
if assigneeChanged {
192-
ec.notificationService.EmitEvent(&NotificationEvent{
193-
EventType: models.EventItemAssigned,
194-
WorkspaceID: updated.WorkspaceID,
195-
ActorUserID: actorUserID,
196-
ItemID: updated.ID,
197-
AssigneeID: updated.AssigneeID,
198-
CreatorID: original.CreatorID,
199-
Title: "Item Assigned",
200-
TemplateData: map[string]any{
201-
"item.title": updated.Title,
202-
"item.key": itemKey,
203-
"item.id": updated.ID,
204-
"user.name": actorName,
205-
},
206-
})
207-
}
208-
209-
// Emit item updated notification (when not status or assignee change)
210-
if !statusChanged && !assigneeChanged {
211-
ec.notificationService.EmitEvent(&NotificationEvent{
212-
EventType: models.EventItemUpdated,
213-
WorkspaceID: updated.WorkspaceID,
214-
ActorUserID: actorUserID,
215-
ItemID: updated.ID,
216-
AssigneeID: updated.AssigneeID,
217-
CreatorID: original.CreatorID,
218-
Title: "Item Updated",
219-
TemplateData: map[string]any{
220-
"item.title": updated.Title,
221-
"item.key": itemKey,
222-
"item.id": updated.ID,
223-
"user.name": actorName,
224-
},
225-
})
162+
statusName, _ = itemUpdateStatusName(ec.db, updated.StatusID)
226163
}
164+
emitItemUpdateNotifications(ec.notificationService.EmitEvent, original, updated,
165+
statusChanged, assigneeChanged, actorUserID, actorName, statusName)
227166
}
228167

229-
// Emit action events for automation
230168
if ec.actionService != nil {
231169
if statusChanged {
232-
event := &models.ActionEvent{
233-
EventType: models.ActionTriggerStatusTransition,
234-
WorkspaceID: updated.WorkspaceID,
235-
ItemID: updated.ID,
236-
ActorUserID: actorUserID,
237-
OldValues: map[string]any{
238-
"status_id": original.StatusID,
239-
},
240-
NewValues: map[string]any{
241-
"status_id": updated.StatusID,
242-
"title": updated.Title,
243-
"assignee_id": updated.AssigneeID,
244-
"creator_id": updated.CreatorID,
245-
},
246-
}
170+
event := newStatusTransitionActionEvent(original, updated, actorUserID)
247171
applyActionContext(event, actionContext)
248172
ec.actionService.EmitActionEvent(event)
249173
} else {
250-
// Build OldValues/NewValues dynamically from field changes
251174
oldVals := make(map[string]any)
252175
newVals := make(map[string]any)
253176
for _, fc := range fieldChanges {
@@ -268,17 +191,7 @@ func (ec *EventCoordinator) emitItemUpdatedInternal(original, updated *models.It
268191
}
269192
}
270193

271-
// Dispatch webhook events
272-
if ec.webhookDispatcher != nil {
273-
if statusChanged {
274-
ec.webhookDispatcher.DispatchEvent("status.changed", updated)
275-
}
276-
if assigneeChanged {
277-
ec.webhookDispatcher.DispatchEvent("item.assigned", updated)
278-
}
279-
// Always dispatch item.updated for any update
280-
ec.webhookDispatcher.DispatchEvent("item.updated", updated)
281-
}
194+
dispatchItemUpdateWebhooks(ec.webhookDispatcher, updated, statusChanged, assigneeChanged)
282195
}
283196

284197
func actionEventFieldName(historyFieldName string) string {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package services
2+
3+
import (
4+
"fmt"
5+
6+
"windshift/internal/database"
7+
"windshift/internal/models"
8+
)
9+
10+
func itemUpdateStatusName(db database.Database, statusID *int) (string, error) {
11+
if db == nil || statusID == nil {
12+
return "", nil
13+
}
14+
var statusName string
15+
err := db.QueryRow("SELECT name FROM statuses WHERE id = ?", *statusID).Scan(&statusName)
16+
return statusName, err
17+
}
18+
19+
func emitItemUpdateNotifications(
20+
notify func(*NotificationEvent),
21+
original, updated *models.Item,
22+
statusChanged, assigneeChanged bool,
23+
actorUserID int,
24+
actorName, statusName string,
25+
) {
26+
if statusChanged {
27+
event := newItemUpdateNotification(models.EventStatusChanged, "Status Changed", original, updated, actorUserID, actorName)
28+
event.TemplateData["status.name"] = statusName
29+
notify(event)
30+
}
31+
if assigneeChanged {
32+
notify(newItemUpdateNotification(models.EventItemAssigned, "Item Assigned", original, updated, actorUserID, actorName))
33+
}
34+
if !statusChanged && !assigneeChanged {
35+
notify(newItemUpdateNotification(models.EventItemUpdated, "Item Updated", original, updated, actorUserID, actorName))
36+
}
37+
}
38+
39+
func newItemUpdateNotification(
40+
eventType, title string,
41+
original, updated *models.Item,
42+
actorUserID int,
43+
actorName string,
44+
) *NotificationEvent {
45+
return &NotificationEvent{
46+
EventType: eventType,
47+
WorkspaceID: updated.WorkspaceID,
48+
ActorUserID: actorUserID,
49+
ItemID: updated.ID,
50+
AssigneeID: updated.AssigneeID,
51+
CreatorID: original.CreatorID,
52+
Title: title,
53+
TemplateData: map[string]any{
54+
"item.title": updated.Title,
55+
"item.key": fmt.Sprintf("%s-%d", updated.WorkspaceKey, updated.WorkspaceItemNumber),
56+
"item.id": updated.ID,
57+
"user.name": actorName,
58+
},
59+
}
60+
}
61+
62+
func newStatusTransitionActionEvent(original, updated *models.Item, actorUserID int) *models.ActionEvent {
63+
return &models.ActionEvent{
64+
EventType: models.ActionTriggerStatusTransition,
65+
WorkspaceID: updated.WorkspaceID,
66+
ItemID: updated.ID,
67+
ActorUserID: actorUserID,
68+
OldValues: map[string]any{"status_id": original.StatusID},
69+
NewValues: map[string]any{
70+
"status_id": updated.StatusID,
71+
"title": updated.Title,
72+
"assignee_id": updated.AssigneeID,
73+
"creator_id": updated.CreatorID,
74+
},
75+
}
76+
}
77+
78+
func dispatchItemUpdateWebhooks(dispatcher WebhookDispatcher, updated *models.Item, statusChanged, assigneeChanged bool) {
79+
if dispatcher == nil {
80+
return
81+
}
82+
if statusChanged {
83+
dispatcher.DispatchEvent("status.changed", updated)
84+
}
85+
if assigneeChanged {
86+
dispatcher.DispatchEvent("item.assigned", updated)
87+
}
88+
dispatcher.DispatchEvent("item.updated", updated)
89+
}

internal/services/legacy_item_events.go

Lines changed: 8 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package services
33
import (
44
"database/sql"
55
"errors"
6-
"fmt"
76
"log/slog"
87

98
"windshift/internal/database"
@@ -40,82 +39,20 @@ func (e *LegacyItemUpdatedEmitter) EmitItemUpdated(original, updated *models.Ite
4039

4140
if e.notify != nil {
4241
var statusName string
43-
if statusChanged && updated.StatusID != nil && e.db != nil {
44-
if err := e.db.QueryRow("SELECT name FROM statuses WHERE id = ?", *updated.StatusID).Scan(&statusName); err != nil && !errors.Is(err, sql.ErrNoRows) {
42+
if statusChanged {
43+
var err error
44+
statusName, err = itemUpdateStatusName(e.db, updated.StatusID)
45+
if err != nil && !errors.Is(err, sql.ErrNoRows) {
4546
slog.Warn("failed to load status name", slog.Int("status_id", *updated.StatusID), slog.Any("error", err))
4647
}
4748
}
48-
itemKey := fmt.Sprintf("%s-%d", updated.WorkspaceKey, updated.WorkspaceItemNumber)
49-
50-
if statusChanged {
51-
e.notify(&NotificationEvent{
52-
EventType: models.EventStatusChanged,
53-
WorkspaceID: updated.WorkspaceID,
54-
ActorUserID: actorUserID,
55-
ItemID: updated.ID,
56-
AssigneeID: updated.AssigneeID,
57-
CreatorID: original.CreatorID,
58-
Title: "Status Changed",
59-
TemplateData: map[string]any{
60-
"item.title": updated.Title,
61-
"item.key": itemKey,
62-
"item.id": updated.ID,
63-
"status.name": statusName,
64-
"user.name": actorName,
65-
},
66-
})
67-
}
68-
if assigneeChanged {
69-
e.notify(&NotificationEvent{
70-
EventType: models.EventItemAssigned,
71-
WorkspaceID: updated.WorkspaceID,
72-
ActorUserID: actorUserID,
73-
ItemID: updated.ID,
74-
AssigneeID: updated.AssigneeID,
75-
CreatorID: original.CreatorID,
76-
Title: "Item Assigned",
77-
TemplateData: map[string]any{
78-
"item.title": updated.Title,
79-
"item.key": itemKey,
80-
"item.id": updated.ID,
81-
"user.name": actorName,
82-
},
83-
})
84-
}
85-
if !statusChanged && !assigneeChanged {
86-
e.notify(&NotificationEvent{
87-
EventType: models.EventItemUpdated,
88-
WorkspaceID: updated.WorkspaceID,
89-
ActorUserID: actorUserID,
90-
ItemID: updated.ID,
91-
AssigneeID: updated.AssigneeID,
92-
CreatorID: original.CreatorID,
93-
Title: "Item Updated",
94-
TemplateData: map[string]any{
95-
"item.title": updated.Title,
96-
"item.key": itemKey,
97-
"item.id": updated.ID,
98-
"user.name": actorName,
99-
},
100-
})
101-
}
49+
emitItemUpdateNotifications(e.notify, original, updated,
50+
statusChanged, assigneeChanged, actorUserID, actorName, statusName)
10251
}
10352

10453
if e.action != nil {
10554
if statusChanged {
106-
e.action.EmitActionEvent(&models.ActionEvent{
107-
EventType: models.ActionTriggerStatusTransition,
108-
WorkspaceID: updated.WorkspaceID,
109-
ItemID: updated.ID,
110-
ActorUserID: actorUserID,
111-
OldValues: map[string]any{"status_id": original.StatusID},
112-
NewValues: map[string]any{
113-
"status_id": updated.StatusID,
114-
"title": updated.Title,
115-
"assignee_id": updated.AssigneeID,
116-
"creator_id": updated.CreatorID,
117-
},
118-
})
55+
e.action.EmitActionEvent(newStatusTransitionActionEvent(original, updated, actorUserID))
11956
} else {
12057
e.action.EmitActionEvent(&models.ActionEvent{
12158
EventType: models.ActionTriggerItemUpdated,
@@ -139,13 +76,5 @@ func (e *LegacyItemUpdatedEmitter) EmitItemUpdated(original, updated *models.Ite
13976
}
14077
}
14178

142-
if e.webhook != nil {
143-
if statusChanged {
144-
e.webhook.DispatchEvent("status.changed", updated)
145-
}
146-
if assigneeChanged {
147-
e.webhook.DispatchEvent("item.assigned", updated)
148-
}
149-
e.webhook.DispatchEvent("item.updated", updated)
150-
}
79+
dispatchItemUpdateWebhooks(e.webhook, updated, statusChanged, assigneeChanged)
15180
}

0 commit comments

Comments
 (0)