diff --git a/lib/sdf-server/src/service/public/change_sets.rs b/lib/sdf-server/src/service/public/change_sets.rs index cb2c3dceb3..b432bd5431 100644 --- a/lib/sdf-server/src/service/public/change_sets.rs +++ b/lib/sdf-server/src/service/public/change_sets.rs @@ -1,4 +1,5 @@ use axum::{ + extract::Host, http::StatusCode, middleware, response::{IntoResponse, Response}, @@ -11,18 +12,35 @@ use serde_json::json; use si_events::audit_log::AuditLogKind; use thiserror::Error; -use crate::extract::{ - change_set::TargetChangeSetIdFromPath, workspace::WorkspaceDalContext, PosthogEventTracker, -}; use crate::AppState; +use crate::{ + extract::{ + change_set::{ChangeSetDalContext, TargetChangeSetIdFromPath}, + workspace::{WorkspaceAuthorization, WorkspaceDalContext}, + PosthogEventTracker, + }, + service::v2::change_set::post_to_webhook, +}; #[remain::sorted] #[derive(Debug, Error)] pub enum ChangeSetsError { #[error("dal change set error: {0}")] - DalChangeSet(#[from] dal::ChangeSetError), + ChangeSet(#[from] dal::ChangeSetError), + #[error("change set apply error: {0}")] + ChangeSetApply(#[from] dal::ChangeSetApplyError), + #[error("change set service error: {0}")] + ChangeSetService(#[from] crate::service::v2::change_set::Error), + #[error("func error: {0}")] + Func(#[from] dal::FuncError), + #[error("schema error: {0}")] + Schema(#[from] dal::SchemaError), + #[error("schema variant error: {0}")] + SchemaVariant(#[from] dal::SchemaVariantError), #[error("transactions error: {0}")] Transactions(#[from] dal::TransactionsError), + #[error("workspace snapshot error: {0}")] + WorkspaceSnapshot(#[from] dal::WorkspaceSnapshotError), #[error("ws event error: {0}")] WsEvent(#[from] dal::WsEventError), } @@ -42,25 +60,26 @@ pub fn routes() -> Router { Router::new() .nest("/components", super::components::routes()) .nest("/management", super::management::routes()) + .route("/request_approval", post(request_approval)) .route_layer(middleware::from_extractor::()), ) } async fn create_change_set( - WorkspaceDalContext(ctx): WorkspaceDalContext, + WorkspaceDalContext(ref ctx): WorkspaceDalContext, tracker: PosthogEventTracker, Json(payload): Json, ) -> Result> { - let change_set = ChangeSet::fork_head(&ctx, &payload.change_set_name).await?; + let change_set = ChangeSet::fork_head(ctx, &payload.change_set_name).await?; - tracker.track(&ctx, "create_change_set", json!(payload)); + tracker.track(ctx, "create_change_set", json!(payload)); ctx.write_audit_log(AuditLogKind::CreateChangeSet, payload.change_set_name) .await?; - WsEvent::change_set_created(&ctx, change_set.id) + WsEvent::change_set_created(ctx, change_set.id) .await? - .publish_on_commit(&ctx) + .publish_on_commit(ctx) .await?; ctx.commit_no_rebase().await?; @@ -79,3 +98,61 @@ struct CreateChangeSetRequest { struct CreateChangeSetResponse { change_set: ChangeSet, } + +async fn request_approval( + ChangeSetDalContext(ref mut ctx): ChangeSetDalContext, + WorkspaceAuthorization { user, .. }: WorkspaceAuthorization, + tracker: PosthogEventTracker, + Host(host_name): Host, +) -> Result<()> { + let workspace_pk = ctx.workspace_pk()?; + let mut change_set = ctx.change_set()?.clone(); + let change_set_id = change_set.id; + let old_status = change_set.status; + + change_set.request_change_set_approval(ctx).await?; + + tracker.track( + ctx, + "request_change_set_approval", + serde_json::json!({ + "change_set": change_set.id, + }), + ); + // TODO change to get_by_id when https://github.com/systeminit/si/pull/5261 lands + let change_set_view = ChangeSet::get_by_id(ctx, change_set_id) + .await? + .into_frontend_type(ctx) + .await?; + + let change_set_url = format!( + "https://{}/w/{}/{}", + host_name, + ctx.workspace_pk()?, + change_set_id + ); + let message = format!( + "{} requested an approval of change set {}: {}", + user.email(), + change_set_view.name.clone(), + change_set_url + ); + post_to_webhook(ctx, workspace_pk, message.as_str()).await?; + + ctx.write_audit_log( + AuditLogKind::RequestChangeSetApproval { + from_status: old_status.into(), + }, + change_set_view.name.clone(), + ) + .await?; + + WsEvent::change_set_status_changed(ctx, old_status, change_set_view) + .await? + .publish_on_commit(ctx) + .await?; + + ctx.commit().await?; + + Ok(()) +} diff --git a/lib/sdf-server/src/service/public/management.rs b/lib/sdf-server/src/service/public/management.rs index 00123e4143..45d85bc415 100644 --- a/lib/sdf-server/src/service/public/management.rs +++ b/lib/sdf-server/src/service/public/management.rs @@ -32,7 +32,7 @@ pub fn routes() -> Router { } async fn run_prototype( - ChangeSetDalContext(ctx): ChangeSetDalContext, + ChangeSetDalContext(ref ctx): ChangeSetDalContext, tracker: PosthogEventTracker, Path(RunPrototypePath { management_prototype_id: prototype_id, @@ -43,11 +43,10 @@ async fn run_prototype( ) -> Result> { // TODO check that this is a valid prototypeId let mut execution_result = - ManagementPrototype::execute_by_id(&ctx, prototype_id, component_id, view_id.into()) - .await?; + ManagementPrototype::execute_by_id(ctx, prototype_id, component_id, view_id.into()).await?; tracker.track( - &ctx, + ctx, "run_prototype", serde_json::json!({ "how": "/public/management/run_prototype", @@ -68,7 +67,7 @@ async fn run_prototype( if status == ManagementFuncStatus::Ok { if let Some(operations) = operations { created_component_ids = ManagementOperator::new( - &ctx, + ctx, component_id, operations, execution_result, @@ -80,11 +79,11 @@ async fn run_prototype( } } - let func_id = ManagementPrototype::func_id(&ctx, prototype_id).await?; - let func = Func::get_by_id_or_error(&ctx, func_id).await?; + let func_id = ManagementPrototype::func_id(ctx, prototype_id).await?; + let func = Func::get_by_id_or_error(ctx, func_id).await?; WsEvent::management_operations_complete( - &ctx, + ctx, request.request_ulid, func.name.clone(), message.clone(), @@ -92,7 +91,7 @@ async fn run_prototype( created_component_ids, ) .await? - .publish_on_commit(&ctx) + .publish_on_commit(ctx) .await?; ctx.write_audit_log(