Skip to content

Commit b222bc7

Browse files
authored
Merge pull request #94 from unisoncomputing/cp/status-update-notifications
Status update notifications
2 parents 3df0779 + 7116864 commit b222bc7

File tree

20 files changed

+429
-187
lines changed

20 files changed

+429
-187
lines changed

src/Share/BackgroundJobs/Webhooks/Worker.hs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import Share.BackgroundJobs.Webhooks.Queries qualified as WQ
2727
import Share.BackgroundJobs.Workers (newWorker)
2828
import Share.ChatApps (Author (..))
2929
import Share.ChatApps qualified as ChatApps
30+
import Share.Contribution (displayContributionStatus)
3031
import Share.Env qualified as Env
3132
import Share.IDs
3233
import Share.IDs qualified as IDs
@@ -41,6 +42,7 @@ import Share.Notifications.Webhooks.Secrets qualified as Webhooks
4142
import Share.Postgres qualified as PG
4243
import Share.Postgres.Notifications qualified as Notif
4344
import Share.Prelude
45+
import Share.Ticket (displayTicketStatus)
4446
import Share.Utils.Logging qualified as Logging
4547
import Share.Utils.URI (URIParam (..))
4648
import Share.Web.Authorization qualified as AuthZ
@@ -308,8 +310,16 @@ buildWebhookRequest webhookId uri event defaultPayload = do
308310
HydratedProjectContributionCreatedPayload payload -> do
309311
let mkPretext pbShorthand = "New Contribution in " <> IDs.toText pbShorthand
310312
contributionChatMessage event author mainLink payload mkPretext
311-
HydratedProjectContributionUpdatedPayload payload -> do
312-
let mkPretext pbShorthand = "Updated Contribution in " <> IDs.toText pbShorthand
313+
HydratedProjectContributionStatusUpdatedPayload payload (StatusUpdatePayload {oldStatus, newStatus}) -> do
314+
let mkPretext pbShorthand =
315+
Text.unwords
316+
[ "Contribution in",
317+
IDs.toText pbShorthand,
318+
"changed from",
319+
displayContributionStatus oldStatus,
320+
"to",
321+
displayContributionStatus newStatus
322+
]
313323
contributionChatMessage event author mainLink payload mkPretext
314324
HydratedProjectContributionCommentPayload payload comment -> do
315325
let mkPretext pbShorthand = "New Comment on Contribution in " <> IDs.toText pbShorthand
@@ -321,8 +331,16 @@ buildWebhookRequest webhookId uri event defaultPayload = do
321331
HydratedProjectTicketCreatedPayload payload -> do
322332
let mkPretext projectShorthand = "New Ticket in " <> IDs.toText projectShorthand
323333
ticketChatMessage event author mainLink payload mkPretext
324-
HydratedProjectTicketUpdatedPayload payload -> do
325-
let mkPretext projectShorthand = "Updated Ticket in " <> IDs.toText projectShorthand
334+
HydratedProjectTicketStatusUpdatedPayload payload (StatusUpdatePayload {oldStatus, newStatus}) -> do
335+
let mkPretext projectShorthand =
336+
Text.unwords
337+
[ "Ticket in",
338+
IDs.toText projectShorthand,
339+
"changed from",
340+
displayTicketStatus oldStatus,
341+
"to",
342+
displayTicketStatus newStatus
343+
]
326344
ticketChatMessage event author mainLink payload mkPretext
327345
HydratedProjectTicketCommentPayload payload comment -> do
328346
let mkPretext projectShorthand = "New Comment on Ticket in " <> IDs.toText projectShorthand

src/Share/Contribution.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ instance Aeson.FromJSON ContributionStatus where
3636
"merged" -> pure Merged
3737
_ -> fail "Invalid contribution status"
3838

39+
displayContributionStatus :: ContributionStatus -> Text
40+
displayContributionStatus = \case
41+
Draft -> "Draft"
42+
InReview -> "In Review"
43+
Closed -> "Closed"
44+
Merged -> "Merged"
45+
3946
instance Hasql.EncodeValue ContributionStatus where
4047
encodeValue =
4148
PG.encodeValue & contramap \case

src/Share/Notifications/Queries.hs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -332,10 +332,14 @@ hydrateEventPayload = \case
332332
(ProjectData {projectId})
333333
(ContributionData {contributionId, fromBranchId, toBranchId, contributorUserId}) -> do
334334
HydratedProjectContributionCreatedPayload <$> hydrateContributionPayload contributionId projectId fromBranchId toBranchId contributorUserId
335-
ProjectContributionUpdatedData
335+
ProjectContributionStatusUpdatedData
336336
(ProjectData {projectId})
337-
(ContributionData {contributionId, fromBranchId, toBranchId, contributorUserId}) -> do
338-
HydratedProjectContributionUpdatedPayload <$> hydrateContributionPayload contributionId projectId fromBranchId toBranchId contributorUserId
337+
(ContributionData {contributionId, fromBranchId, toBranchId, contributorUserId})
338+
(StatusUpdateData {oldStatus, newStatus}) -> do
339+
let statusPayload = StatusUpdatePayload {oldStatus, newStatus}
340+
HydratedProjectContributionStatusUpdatedPayload
341+
<$> hydrateContributionPayload contributionId projectId fromBranchId toBranchId contributorUserId
342+
<*> pure statusPayload
339343
ProjectContributionCommentData
340344
(ProjectData {projectId})
341345
(ContributionData {contributionId, fromBranchId, toBranchId, contributorUserId})
@@ -347,10 +351,14 @@ hydrateEventPayload = \case
347351
(ProjectData {projectId})
348352
(TicketData {ticketId, ticketAuthorUserId}) -> do
349353
HydratedProjectTicketCreatedPayload <$> hydrateTicketPayload projectId ticketId ticketAuthorUserId
350-
ProjectTicketUpdatedData
354+
ProjectTicketStatusUpdatedData
351355
(ProjectData {projectId})
352-
(TicketData {ticketId, ticketAuthorUserId}) -> do
353-
HydratedProjectTicketUpdatedPayload <$> hydrateTicketPayload projectId ticketId ticketAuthorUserId
356+
(TicketData {ticketId, ticketAuthorUserId})
357+
(StatusUpdateData {oldStatus, newStatus}) -> do
358+
let statusPayload = StatusUpdatePayload {oldStatus, newStatus}
359+
HydratedProjectTicketStatusUpdatedPayload
360+
<$> hydrateTicketPayload projectId ticketId ticketAuthorUserId
361+
<*> pure statusPayload
354362
ProjectTicketCommentData
355363
(ProjectData {projectId})
356364
(TicketData {ticketId, ticketAuthorUserId})

src/Share/Notifications/Types.hs

Lines changed: 78 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module Share.Notifications.Types
1515
ContributionData (..),
1616
TicketData (..),
1717
CommentData (..),
18+
StatusUpdateData (..),
1819
NotificationHubEntry (..),
1920
NotificationStatus (..),
2021
DeliveryMethodId (..),
@@ -31,6 +32,7 @@ module Share.Notifications.Types
3132
CommentPayload (..),
3233
ProjectBranchUpdatedPayload (..),
3334
ProjectContributionPayload (..),
35+
StatusUpdatePayload (..),
3436
eventTopic,
3537
hydratedEventTopic,
3638
eventData_,
@@ -62,52 +64,52 @@ import Share.Web.Share.DisplayInfo.Types (UserDisplayInfo)
6264
data NotificationTopic
6365
= ProjectBranchUpdated
6466
| ProjectContributionCreated
65-
| ProjectContributionUpdated
67+
| ProjectContributionStatusUpdated
6668
| ProjectContributionComment
6769
| ProjectTicketCreated
68-
| ProjectTicketUpdated
70+
| ProjectTicketStatusUpdated
6971
| ProjectTicketComment
7072
deriving (Eq, Show, Ord)
7173

7274
instance PG.EncodeValue NotificationTopic where
7375
encodeValue = HasqlEncoders.enum \case
7476
ProjectBranchUpdated -> "project:branch:updated"
7577
ProjectContributionCreated -> "project:contribution:created"
76-
ProjectContributionUpdated -> "project:contribution:updated"
78+
ProjectContributionStatusUpdated -> "project:contribution:updated"
7779
ProjectContributionComment -> "project:contribution:comment"
7880
ProjectTicketCreated -> "project:ticket:created"
79-
ProjectTicketUpdated -> "project:ticket:updated"
81+
ProjectTicketStatusUpdated -> "project:ticket:updated"
8082
ProjectTicketComment -> "project:ticket:comment"
8183

8284
instance PG.DecodeValue NotificationTopic where
8385
decodeValue = HasqlDecoders.enum \case
8486
"project:branch:updated" -> Just ProjectBranchUpdated
8587
"project:contribution:created" -> Just ProjectContributionCreated
86-
"project:contribution:updated" -> Just ProjectContributionUpdated
88+
"project:contribution:updated" -> Just ProjectContributionStatusUpdated
8789
"project:contribution:comment" -> Just ProjectContributionComment
8890
"project:ticket:created" -> Just ProjectTicketCreated
89-
"project:ticket:updated" -> Just ProjectTicketUpdated
91+
"project:ticket:updated" -> Just ProjectTicketStatusUpdated
9092
"project:ticket:comment" -> Just ProjectTicketComment
9193
_ -> Nothing
9294

9395
instance Aeson.ToJSON NotificationTopic where
9496
toJSON = \case
9597
ProjectBranchUpdated -> "project:branch:updated"
9698
ProjectContributionCreated -> "project:contribution:created"
97-
ProjectContributionUpdated -> "project:contribution:updated"
99+
ProjectContributionStatusUpdated -> "project:contribution:updated"
98100
ProjectContributionComment -> "project:contribution:comment"
99101
ProjectTicketCreated -> "project:ticket:created"
100-
ProjectTicketUpdated -> "project:ticket:updated"
102+
ProjectTicketStatusUpdated -> "project:ticket:updated"
101103
ProjectTicketComment -> "project:ticket:comment"
102104

103105
instance Aeson.FromJSON NotificationTopic where
104106
parseJSON = Aeson.withText "NotificationTopic" \case
105107
"project:branch:updated" -> pure ProjectBranchUpdated
106108
"project:contribution:created" -> pure ProjectContributionCreated
107-
"project:contribution:updated" -> pure ProjectContributionUpdated
109+
"project:contribution:updated" -> pure ProjectContributionStatusUpdated
108110
"project:contribution:comment" -> pure ProjectContributionComment
109111
"project:ticket:created" -> pure ProjectTicketCreated
110-
"project:ticket:updated" -> pure ProjectTicketUpdated
112+
"project:ticket:updated" -> pure ProjectTicketStatusUpdated
111113
"project:ticket:comment" -> pure ProjectTicketComment
112114
s -> fail $ "Invalid notification topic: " <> Text.unpack s
113115

@@ -290,15 +292,35 @@ instance Aeson.FromJSON ContributionData where
290292
contributorUserId <- o .: "contributorUserId"
291293
pure ContributionData {contributionId, fromBranchId, toBranchId, contributorUserId}
292294

295+
data StatusUpdateData status
296+
= StatusUpdateData
297+
{ oldStatus :: status,
298+
newStatus :: status
299+
}
300+
deriving (Eq, Show)
301+
302+
instance (ToJSON status) => ToJSON (StatusUpdateData status) where
303+
toJSON StatusUpdateData {oldStatus, newStatus} =
304+
Aeson.object
305+
[ "oldStatus" .= oldStatus,
306+
"newStatus" .= newStatus
307+
]
308+
309+
instance (FromJSON status) => FromJSON (StatusUpdateData status) where
310+
parseJSON = Aeson.withObject "StatusUpdateData" \o -> do
311+
oldStatus <- o .: "oldStatus"
312+
newStatus <- o .: "newStatus"
313+
pure StatusUpdateData {oldStatus, newStatus}
314+
293315
-- The bare-bones Notification Event Data that's actually stored in the database.
294316
-- It holds unhydrated IDs.
295317
data NotificationEventData
296318
= ProjectBranchUpdatedData ProjectData BranchData
297319
| ProjectContributionCreatedData ProjectData ContributionData
298-
| ProjectContributionUpdatedData ProjectData ContributionData
320+
| ProjectContributionStatusUpdatedData ProjectData ContributionData (StatusUpdateData ContributionStatus)
299321
| ProjectContributionCommentData ProjectData ContributionData CommentData
300322
| ProjectTicketCreatedData ProjectData TicketData
301-
| ProjectTicketUpdatedData ProjectData TicketData
323+
| ProjectTicketStatusUpdatedData ProjectData TicketData (StatusUpdateData TicketStatus)
302324
| ProjectTicketCommentData ProjectData TicketData CommentData
303325
deriving stock (Eq, Show)
304326

@@ -313,10 +335,10 @@ instance Aeson.ToJSON NotificationEventData where
313335
body = case ned of
314336
ProjectBranchUpdatedData project branch -> Aeson.toJSON (project :++ branch)
315337
ProjectContributionCreatedData project c -> Aeson.toJSON (project :++ c)
316-
ProjectContributionUpdatedData project contr -> Aeson.toJSON (project :++ contr)
338+
ProjectContributionStatusUpdatedData project contr status -> Aeson.toJSON (project :++ contr :++ status)
317339
ProjectContributionCommentData project contr comm -> Aeson.toJSON (project :++ contr :++ comm)
318340
ProjectTicketCreatedData project ticket -> Aeson.toJSON (project :++ ticket)
319-
ProjectTicketUpdatedData project ticket -> Aeson.toJSON (project :++ ticket)
341+
ProjectTicketStatusUpdatedData project ticket status -> Aeson.toJSON (project :++ ticket :++ status)
320342
ProjectTicketCommentData project ticket comm -> Aeson.toJSON (project :++ ticket :++ comm)
321343

322344
instance PG.EncodeValue NotificationEventData where
@@ -325,10 +347,10 @@ instance PG.EncodeValue NotificationEventData where
325347
& contramap \case
326348
ProjectBranchUpdatedData project branch -> Aeson.toJSON (project :++ branch)
327349
ProjectContributionCreatedData project contr -> Aeson.toJSON (project :++ contr)
328-
ProjectContributionUpdatedData project contr -> Aeson.toJSON (project :++ contr)
350+
ProjectContributionStatusUpdatedData project contr status -> Aeson.toJSON (project :++ contr :++ status)
329351
ProjectContributionCommentData project contr comm -> Aeson.toJSON (project :++ contr :++ comm)
330352
ProjectTicketCreatedData project ticket -> Aeson.toJSON (project :++ ticket)
331-
ProjectTicketUpdatedData project ticket -> Aeson.toJSON (project :++ ticket)
353+
ProjectTicketStatusUpdatedData project ticket status -> Aeson.toJSON (project :++ ticket :++ status)
332354
ProjectTicketCommentData project ticket comm -> Aeson.toJSON (project :++ ticket :++ comm)
333355

334356
instance Hasql.DecodeRow NotificationEventData where
@@ -342,18 +364,18 @@ instance Hasql.DecodeRow NotificationEventData where
342364
ProjectContributionCreated -> do
343365
(project :++ contr) <- parseJsonData jsonData
344366
pure $ ProjectContributionCreatedData project contr
345-
ProjectContributionUpdated -> do
346-
(project :++ contr) <- parseJsonData jsonData
347-
pure $ ProjectContributionUpdatedData project contr
367+
ProjectContributionStatusUpdated -> do
368+
(project :++ contr :++ status) <- parseJsonData jsonData
369+
pure $ ProjectContributionStatusUpdatedData project contr status
348370
ProjectContributionComment -> do
349371
(project :++ contr :++ comm) <- parseJsonData jsonData
350372
pure $ ProjectContributionCommentData project contr comm
351373
ProjectTicketCreated -> do
352374
(project :++ ticket) <- parseJsonData jsonData
353375
pure $ ProjectTicketCreatedData project ticket
354-
ProjectTicketUpdated -> do
355-
(project :++ ticket) <- parseJsonData jsonData
356-
pure $ ProjectTicketUpdatedData project ticket
376+
ProjectTicketStatusUpdated -> do
377+
(project :++ ticket :++ status) <- parseJsonData jsonData
378+
pure $ ProjectTicketStatusUpdatedData project ticket status
357379
ProjectTicketComment -> do
358380
(project :++ ticket :++ comm) <- parseJsonData jsonData
359381
pure $ ProjectTicketCommentData project ticket comm
@@ -366,10 +388,10 @@ eventTopic :: NotificationEventData -> NotificationTopic
366388
eventTopic = \case
367389
ProjectBranchUpdatedData {} -> ProjectBranchUpdated
368390
ProjectContributionCreatedData {} -> ProjectContributionCreated
369-
ProjectContributionUpdatedData {} -> ProjectContributionUpdated
391+
ProjectContributionStatusUpdatedData {} -> ProjectContributionStatusUpdated
370392
ProjectContributionCommentData {} -> ProjectContributionComment
371393
ProjectTicketCreatedData {} -> ProjectTicketCreated
372-
ProjectTicketUpdatedData {} -> ProjectTicketUpdated
394+
ProjectTicketStatusUpdatedData {} -> ProjectTicketStatusUpdated
373395
ProjectTicketCommentData {} -> ProjectTicketComment
374396

375397
-- | Description of a notifiable event.
@@ -754,6 +776,26 @@ instance FromJSON ProjectContributionPayload where
754776
contributionInfo <- o .: "contribution"
755777
pure ProjectContributionPayload {projectInfo, contributionInfo}
756778

779+
data StatusUpdatePayload status
780+
= StatusUpdatePayload
781+
{ oldStatus :: status,
782+
newStatus :: status
783+
}
784+
deriving (Eq, Show)
785+
786+
instance (ToJSON status) => ToJSON (StatusUpdatePayload status) where
787+
toJSON StatusUpdatePayload {oldStatus, newStatus} =
788+
Aeson.object
789+
[ "oldStatus" .= oldStatus,
790+
"newStatus" .= newStatus
791+
]
792+
793+
instance (FromJSON status) => FromJSON (StatusUpdatePayload status) where
794+
parseJSON = Aeson.withObject "StatusUpdatedPayload" \o -> do
795+
oldStatus <- o .: "oldStatus"
796+
newStatus <- o .: "newStatus"
797+
pure StatusUpdatePayload {oldStatus, newStatus}
798+
757799
data HydratedEvent = HydratedEvent
758800
{ hydratedEventPayload :: HydratedEventPayload,
759801
hydratedEventLink :: URI
@@ -766,10 +808,10 @@ instance ToJSON HydratedEvent where
766808
payload = case hydratedEventPayload of
767809
HydratedProjectBranchUpdatedPayload p -> Aeson.toJSON p
768810
HydratedProjectContributionCreatedPayload p -> Aeson.toJSON p
769-
HydratedProjectContributionUpdatedPayload p -> Aeson.toJSON p
811+
HydratedProjectContributionStatusUpdatedPayload p status -> Aeson.toJSON (p :++ status)
770812
HydratedProjectContributionCommentPayload p comm -> Aeson.toJSON (p :++ comm)
771813
HydratedProjectTicketCreatedPayload p -> Aeson.toJSON p
772-
HydratedProjectTicketUpdatedPayload p -> Aeson.toJSON p
814+
HydratedProjectTicketStatusUpdatedPayload p status -> Aeson.toJSON (p :++ status)
773815
HydratedProjectTicketCommentPayload p comm -> Aeson.toJSON (p :++ comm)
774816
in Aeson.object
775817
[ "payload" .= payload,
@@ -784,12 +826,16 @@ instance FromJSON HydratedEvent where
784826
hydratedEventPayload <- case kind of
785827
ProjectBranchUpdated -> HydratedProjectBranchUpdatedPayload <$> o .: "payload"
786828
ProjectContributionCreated -> HydratedProjectContributionCreatedPayload <$> o .: "payload"
787-
ProjectContributionUpdated -> HydratedProjectContributionUpdatedPayload <$> o .: "payload"
829+
ProjectContributionStatusUpdated -> do
830+
(p :++ status) <- o .: "payload"
831+
pure $ HydratedProjectContributionStatusUpdatedPayload p status
788832
ProjectContributionComment -> do
789833
(p :++ comm) <- o .: "payload"
790834
pure $ HydratedProjectContributionCommentPayload p comm
791835
ProjectTicketCreated -> HydratedProjectTicketCreatedPayload <$> o .: "payload"
792-
ProjectTicketUpdated -> HydratedProjectTicketUpdatedPayload <$> o .: "payload"
836+
ProjectTicketStatusUpdated -> do
837+
(p :++ status) <- o .: "payload"
838+
pure $ HydratedProjectTicketStatusUpdatedPayload p status
793839
ProjectTicketComment -> do
794840
(p :++ comm) <- o .: "payload"
795841
pure $ HydratedProjectTicketCommentPayload p comm
@@ -799,19 +845,19 @@ instance FromJSON HydratedEvent where
799845
data HydratedEventPayload
800846
= HydratedProjectBranchUpdatedPayload ProjectBranchUpdatedPayload
801847
| HydratedProjectContributionCreatedPayload ProjectContributionPayload
802-
| HydratedProjectContributionUpdatedPayload ProjectContributionPayload
848+
| HydratedProjectContributionStatusUpdatedPayload ProjectContributionPayload (StatusUpdatePayload ContributionStatus)
803849
| HydratedProjectContributionCommentPayload ProjectContributionPayload CommentPayload
804850
| HydratedProjectTicketCreatedPayload ProjectTicketPayload
805-
| HydratedProjectTicketUpdatedPayload ProjectTicketPayload
851+
| HydratedProjectTicketStatusUpdatedPayload ProjectTicketPayload (StatusUpdatePayload TicketStatus)
806852
| HydratedProjectTicketCommentPayload ProjectTicketPayload CommentPayload
807853
deriving stock (Show, Eq)
808854

809855
hydratedEventTopic :: HydratedEvent -> NotificationTopic
810856
hydratedEventTopic (HydratedEvent {hydratedEventPayload}) = case hydratedEventPayload of
811857
HydratedProjectBranchUpdatedPayload _ -> ProjectBranchUpdated
812858
HydratedProjectContributionCreatedPayload _ -> ProjectContributionCreated
813-
HydratedProjectContributionUpdatedPayload _ -> ProjectContributionUpdated
859+
HydratedProjectContributionStatusUpdatedPayload _ _ -> ProjectContributionStatusUpdated
814860
HydratedProjectContributionCommentPayload _ _ -> ProjectContributionComment
815861
HydratedProjectTicketCreatedPayload _ -> ProjectTicketCreated
816-
HydratedProjectTicketUpdatedPayload _ -> ProjectTicketUpdated
862+
HydratedProjectTicketStatusUpdatedPayload _ _ -> ProjectTicketStatusUpdated
817863
HydratedProjectTicketCommentPayload _ _ -> ProjectTicketComment

0 commit comments

Comments
 (0)