@@ -1207,6 +1207,10 @@ enum WebhookDeliveryCommands {
1207
1207
/// List webhook deliveries
1208
1208
#[ clap( alias = "ls" ) ]
1209
1209
List ( WebhookDeliveryListArgs ) ,
1210
+
1211
+ /// Show details on a webhook delivery, including its payload and attempt history.
1212
+ #[ clap( alias = "show" ) ]
1213
+ Info ( WebhookDeliveryInfoArgs ) ,
1210
1214
}
1211
1215
1212
1216
#[ derive( Debug , Args , Clone ) ]
@@ -1236,6 +1240,12 @@ struct WebhookDeliveryListArgs {
1236
1240
after : Option < DateTime < Utc > > ,
1237
1241
}
1238
1242
1243
+ #[ derive( Debug , Args , Clone ) ]
1244
+ struct WebhookDeliveryInfoArgs {
1245
+ /// The ID of the delivery to show.
1246
+ delivery_id : Uuid ,
1247
+ }
1248
+
1239
1249
impl DbArgs {
1240
1250
/// Run a `omdb db` subcommand.
1241
1251
///
@@ -8130,6 +8140,9 @@ async fn cmd_db_webhook(
8130
8140
WebhookCommands :: Delivery {
8131
8141
command : WebhookDeliveryCommands :: List ( args) ,
8132
8142
} => cmd_db_webhook_delivery_list ( datastore, fetch_opts, args) . await ,
8143
+ WebhookCommands :: Delivery {
8144
+ command : WebhookDeliveryCommands :: Info ( args) ,
8145
+ } => cmd_db_webhook_delivery_info ( datastore, fetch_opts, args) . await ,
8133
8146
WebhookCommands :: Event => {
8134
8147
Err ( anyhow:: anyhow!( "not yet implemented, sorry!" ) )
8135
8148
}
@@ -8456,6 +8469,7 @@ async fn cmd_db_webhook_delivery_list(
8456
8469
check_limit ( & deliveries, fetch_opts. fetch_limit , ctx) ;
8457
8470
8458
8471
#[ derive( Tabled ) ]
8472
+ #[ tabled( rename_all = "SCREAMING_SNAKE_CASE" ) ]
8459
8473
struct DeliveryRow {
8460
8474
id : Uuid ,
8461
8475
trigger : nexus_db_model:: WebhookDeliveryTrigger ,
@@ -8468,13 +8482,15 @@ async fn cmd_db_webhook_delivery_list(
8468
8482
}
8469
8483
8470
8484
#[ derive( Tabled ) ]
8485
+ #[ tabled( rename_all = "SCREAMING_SNAKE_CASE" ) ]
8471
8486
struct WithEventId < T : Tabled > {
8472
8487
#[ tabled( inline) ]
8473
8488
inner : T ,
8474
8489
event_id : Uuid ,
8475
8490
}
8476
8491
8477
8492
#[ derive( Tabled ) ]
8493
+ #[ tabled( rename_all = "SCREAMING_SNAKE_CASE" ) ]
8478
8494
struct WithRxId < T : Tabled > {
8479
8495
#[ tabled( inline) ]
8480
8496
inner : T ,
@@ -8585,6 +8601,171 @@ async fn lookup_webhook_rx(
8585
8601
. with_context ( || format ! ( "loading webhook_receiver {name_or_id}" ) )
8586
8602
}
8587
8603
8604
+ async fn cmd_db_webhook_delivery_info (
8605
+ datastore : & DataStore ,
8606
+ fetch_opts : & DbFetchOptions ,
8607
+ args : & WebhookDeliveryInfoArgs ,
8608
+ ) -> anyhow:: Result < ( ) > {
8609
+ use db:: model:: WebhookDeliveryAttempt ;
8610
+ use nexus_db_schema:: schema:: webhook_delivery:: dsl;
8611
+ use nexus_db_schema:: schema:: webhook_delivery_attempt:: dsl as attempt_dsl;
8612
+
8613
+ let WebhookDeliveryInfoArgs { delivery_id } = args;
8614
+ let conn = datastore. pool_connection_for_tests ( ) . await ?;
8615
+ let delivery = dsl:: webhook_delivery
8616
+ . filter ( dsl:: id. eq ( * delivery_id) )
8617
+ . limit ( 1 )
8618
+ . select ( WebhookDelivery :: as_select ( ) )
8619
+ . get_result_async ( & * conn)
8620
+ . await
8621
+ . optional ( )
8622
+ . with_context ( || format ! ( "loading webhook delivery {delivery_id}" ) ) ?
8623
+ . ok_or_else ( || {
8624
+ anyhow:: anyhow!( "no webhook delivery {delivery_id} exists" )
8625
+ } ) ?;
8626
+
8627
+ const ID : & ' static str = "ID" ;
8628
+ const EVENT_ID : & ' static str = "event ID" ;
8629
+ const RECEIVER_ID : & ' static str = "receiver ID" ;
8630
+ const STATE : & ' static str = "state" ;
8631
+ const TRIGGER : & ' static str = "triggered by" ;
8632
+ const ATTEMPTS : & ' static str = "attempts" ;
8633
+ const TIME_CREATED : & ' static str = "created at" ;
8634
+ const TIME_COMPLETED : & ' static str = "completed at" ;
8635
+
8636
+ const DELIVERATOR_ID : & ' static str = "by Nexus" ;
8637
+ const TIME_LEASED : & ' static str = "leased at" ;
8638
+
8639
+ const WIDTH : usize = const_max_len ( & [
8640
+ ID ,
8641
+ EVENT_ID ,
8642
+ RECEIVER_ID ,
8643
+ TRIGGER ,
8644
+ STATE ,
8645
+ TIME_CREATED ,
8646
+ TIME_COMPLETED ,
8647
+ DELIVERATOR_ID ,
8648
+ TIME_LEASED ,
8649
+ ATTEMPTS ,
8650
+ ] ) ;
8651
+
8652
+ let WebhookDelivery {
8653
+ id,
8654
+ event_id,
8655
+ rx_id,
8656
+ triggered_by,
8657
+ attempts,
8658
+ time_created,
8659
+ time_completed,
8660
+ state,
8661
+ deliverator_id,
8662
+ time_leased,
8663
+ } = delivery;
8664
+ println ! ( "\n {:=<80}" , "== DELIVERY " ) ;
8665
+ println ! ( " {ID:>WIDTH$}: {id}" ) ;
8666
+ println ! ( " {EVENT_ID:>WIDTH$}: {event_id}" ) ;
8667
+ println ! ( " {RECEIVER_ID:>WIDTH$}: {rx_id}" ) ;
8668
+ println ! ( " {STATE:>WIDTH$}: {state}" ) ;
8669
+ println ! ( " {TRIGGER:>WIDTH$}: {triggered_by}" ) ;
8670
+ println ! ( " {TIME_CREATED:>WIDTH$}: {time_created}" ) ;
8671
+ println ! ( " {ATTEMPTS}: {}" , attempts. 0 ) ;
8672
+
8673
+ if let Some ( completed) = time_completed {
8674
+ println ! ( "\n {:=<80}" , "== DELIVERY COMPLETED " ) ;
8675
+ println ! ( " {TIME_COMPLETED:>WIDTH$}: {completed}" ) ;
8676
+ if let Some ( leased) = time_leased {
8677
+ println ! ( " {TIME_LEASED:>WIDTH$}: {leased}" ) ;
8678
+ } else {
8679
+ println ! (
8680
+ "/!\\ WEIRD: delivery is completed but has no start timestamp?"
8681
+ ) ;
8682
+ }
8683
+ if let Some ( nexus) = deliverator_id {
8684
+ println ! ( " {DELIVERATOR_ID:>WIDTH$}: {nexus}" ) ;
8685
+ } else {
8686
+ println ! ( "/!\\ WEIRD: delivery is completed but has no Nexus ID?" ) ;
8687
+ }
8688
+ } else if let Some ( leased) = time_leased {
8689
+ println ! ( "\n {:=<80}" , "== DELIVERY IN PROGRESS " ) ;
8690
+ println ! ( " {TIME_LEASED:>WIDTH$}: {leased}" ) ;
8691
+
8692
+ if let Some ( nexus) = deliverator_id {
8693
+ println ! ( " {DELIVERATOR_ID:>WIDTH$}: {nexus}" ) ;
8694
+ } else {
8695
+ println ! (
8696
+ "/!\\ WEIRD: delivery is in progress but has no Nexus ID?"
8697
+ ) ;
8698
+ }
8699
+ } else if let Some ( deliverator) = deliverator_id {
8700
+ println ! (
8701
+ "/!\\ WEIRD: delivery is not completed or in progress but has \
8702
+ Nexus ID {deliverator:?}"
8703
+ ) ;
8704
+ }
8705
+
8706
+ // Okay, now go get attempts for this delivery.
8707
+ let ctx = || format ! ( "listing delivery attempts for {delivery_id}" ) ;
8708
+ let attempts = attempt_dsl:: webhook_delivery_attempt
8709
+ . filter ( attempt_dsl:: delivery_id. eq ( * delivery_id) )
8710
+ . order_by ( attempt_dsl:: attempt. desc ( ) )
8711
+ . limit ( fetch_opts. fetch_limit . get ( ) . into ( ) )
8712
+ . select ( WebhookDeliveryAttempt :: as_select ( ) )
8713
+ . load_async ( & * conn)
8714
+ . await
8715
+ . with_context ( ctx) ?;
8716
+
8717
+ check_limit ( & attempts, fetch_opts. fetch_limit , ctx) ;
8718
+
8719
+ if !attempts. is_empty ( ) {
8720
+ println ! ( "\n {:=<80}" , "== DELIVERY ATTEMPT HISTORY " ) ;
8721
+
8722
+ #[ derive( Tabled ) ]
8723
+ #[ tabled( rename_all = "SCREAMING_SNAKE_CASE" ) ]
8724
+ struct DeliveryAttemptRow {
8725
+ id : Uuid ,
8726
+ #[ tabled( rename = "#" ) ]
8727
+ attempt : u8 ,
8728
+ #[ tabled( display_with = "datetime_rfc3339_concise" ) ]
8729
+ time_created : DateTime < Utc > ,
8730
+ nexus_id : Uuid ,
8731
+ result : db:: model:: WebhookDeliveryAttemptResult ,
8732
+ #[ tabled( display_with = "display_u16_opt" ) ]
8733
+ status : Option < u16 > ,
8734
+ #[ tabled( display_with = "display_time_delta_opt" ) ]
8735
+ duration : Option < chrono:: TimeDelta > ,
8736
+ }
8737
+
8738
+ let rows = attempts. into_iter ( ) . map (
8739
+ |WebhookDeliveryAttempt {
8740
+ id,
8741
+ delivery_id : _,
8742
+ rx_id : _,
8743
+ attempt,
8744
+ result,
8745
+ response_status,
8746
+ response_duration,
8747
+ time_created,
8748
+ deliverator_id,
8749
+ } | DeliveryAttemptRow {
8750
+ id : id. into_untyped_uuid ( ) ,
8751
+ attempt : attempt. 0 ,
8752
+ time_created,
8753
+ nexus_id : deliverator_id. into_untyped_uuid ( ) ,
8754
+ result,
8755
+ status : response_status. map ( |u| u. into ( ) ) ,
8756
+ duration : response_duration,
8757
+ } ,
8758
+ ) ;
8759
+ let mut table = tabled:: Table :: new ( rows) ;
8760
+ table
8761
+ . with ( tabled:: settings:: Style :: empty ( ) )
8762
+ . with ( tabled:: settings:: Padding :: new ( 0 , 1 , 0 , 0 ) ) ;
8763
+ println ! ( "{table}" ) ;
8764
+ }
8765
+
8766
+ Ok ( ( ) )
8767
+ }
8768
+
8588
8769
// Format a `chrono::DateTime` in RFC3339 with milliseconds precision and using
8589
8770
// `Z` rather than the UTC offset for UTC timestamps, to save a few characters
8590
8771
// of line width in tabular output.
@@ -8698,3 +8879,11 @@ async fn cmd_db_zpool_set_storage_buffer(
8698
8879
8699
8880
Ok ( ( ) )
8700
8881
}
8882
+
8883
+ fn display_time_delta_opt ( t : & Option < chrono:: TimeDelta > ) -> String {
8884
+ t. map ( |t| t. to_string ( ) ) . unwrap_or_else ( || "-" . to_string ( ) )
8885
+ }
8886
+
8887
+ fn display_u16_opt ( u : & Option < u16 > ) -> String {
8888
+ u. map ( |u| u. to_string ( ) ) . unwrap_or_else ( || "-" . to_string ( ) )
8889
+ }
0 commit comments