@@ -1207,6 +1207,10 @@ enum WebhookDeliveryCommands {
12071207 /// List webhook deliveries
12081208 #[ clap( alias = "ls" ) ]
12091209 List ( WebhookDeliveryListArgs ) ,
1210+
1211+ /// Show details on a webhook delivery, including its payload and attempt history.
1212+ #[ clap( alias = "show" ) ]
1213+ Info ( WebhookDeliveryInfoArgs ) ,
12101214}
12111215
12121216#[ derive( Debug , Args , Clone ) ]
@@ -1236,6 +1240,12 @@ struct WebhookDeliveryListArgs {
12361240 after : Option < DateTime < Utc > > ,
12371241}
12381242
1243+ #[ derive( Debug , Args , Clone ) ]
1244+ struct WebhookDeliveryInfoArgs {
1245+ /// The ID of the delivery to show.
1246+ delivery_id : Uuid ,
1247+ }
1248+
12391249impl DbArgs {
12401250 /// Run a `omdb db` subcommand.
12411251 ///
@@ -8130,6 +8140,9 @@ async fn cmd_db_webhook(
81308140 WebhookCommands :: Delivery {
81318141 command : WebhookDeliveryCommands :: List ( args) ,
81328142 } => 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 ,
81338146 WebhookCommands :: Event => {
81348147 Err ( anyhow:: anyhow!( "not yet implemented, sorry!" ) )
81358148 }
@@ -8456,6 +8469,7 @@ async fn cmd_db_webhook_delivery_list(
84568469 check_limit ( & deliveries, fetch_opts. fetch_limit , ctx) ;
84578470
84588471 #[ derive( Tabled ) ]
8472+ #[ tabled( rename_all = "SCREAMING_SNAKE_CASE" ) ]
84598473 struct DeliveryRow {
84608474 id : Uuid ,
84618475 trigger : nexus_db_model:: WebhookDeliveryTrigger ,
@@ -8468,13 +8482,15 @@ async fn cmd_db_webhook_delivery_list(
84688482 }
84698483
84708484 #[ derive( Tabled ) ]
8485+ #[ tabled( rename_all = "SCREAMING_SNAKE_CASE" ) ]
84718486 struct WithEventId < T : Tabled > {
84728487 #[ tabled( inline) ]
84738488 inner : T ,
84748489 event_id : Uuid ,
84758490 }
84768491
84778492 #[ derive( Tabled ) ]
8493+ #[ tabled( rename_all = "SCREAMING_SNAKE_CASE" ) ]
84788494 struct WithRxId < T : Tabled > {
84798495 #[ tabled( inline) ]
84808496 inner : T ,
@@ -8585,6 +8601,171 @@ async fn lookup_webhook_rx(
85858601 . with_context ( || format ! ( "loading webhook_receiver {name_or_id}" ) )
85868602}
85878603
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+
85888769// Format a `chrono::DateTime` in RFC3339 with milliseconds precision and using
85898770// `Z` rather than the UTC offset for UTC timestamps, to save a few characters
85908771// of line width in tabular output.
@@ -8698,3 +8879,11 @@ async fn cmd_db_zpool_set_storage_buffer(
86988879
86998880 Ok ( ( ) )
87008881}
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