diff --git a/app/web/src/components/AuditLogHeader.vue b/app/web/src/components/AuditLogHeader.vue index 48459eb9d7..9a05bdf42f 100644 --- a/app/web/src/components/AuditLogHeader.vue +++ b/app/web/src/components/AuditLogHeader.vue @@ -10,7 +10,7 @@ header.id !== 'json' && 'cursor-pointer hover:underline', header.id === 'json' && 'w-8 px-2xs', - themeClasses('bg-shade-0', 'bg-shade-100'), + themeClasses('bg-neutral-400 text-black', 'bg-shade-100'), ) " @mousedown="startActive" diff --git a/app/web/src/components/Workspace/WorkspaceAuditLog.vue b/app/web/src/components/Workspace/WorkspaceAuditLog.vue index c0d5c946cb..9e57cc9413 100644 --- a/app/web/src/components/Workspace/WorkspaceAuditLog.vue +++ b/app/web/src/components/Workspace/WorkspaceAuditLog.vue @@ -106,10 +106,10 @@ clsx( 'h-lg text-sm', rowCollapseState[Number(row.id)] - ? 'bg-action-300' + ? themeClasses('bg-action-200', 'bg-action-900') : themeClasses( - 'odd:bg-neutral-200 even:bg-neutral-100 hover:bg-action-200', - 'odd:bg-neutral-700 even:bg-neutral-800 hover:bg-action-200', + 'odd:bg-neutral-200 even:bg-neutral-100 hover:border hover:border-action-500', + 'odd:bg-neutral-700 even:bg-neutral-800 hover:border hover:border-action-300', ), ) " @@ -190,7 +190,9 @@ const DEFAULT_FILTERS = { excludeSystemUser: false, kindFilter: [], serviceFilter: [], - changeSetFilter: [changeSetsStore.selectedChangeSetId], + changeSetFilter: changeSetsStore.headSelected + ? [] + : [changeSetsStore.selectedChangeSetId], userFilter: [], } as LogFilters; const currentFilters = ref({ ...DEFAULT_FILTERS }); diff --git a/lib/dal/src/audit_logging.rs b/lib/dal/src/audit_logging.rs index 7363e17466..5a2c7a82cb 100644 --- a/lib/dal/src/audit_logging.rs +++ b/lib/dal/src/audit_logging.rs @@ -226,13 +226,12 @@ async fn assemble_for_list( .map_err(Box::new)? .ok_or(AuditLoggingError::ChangeSetNotFound(change_set_id.into()))?; match change_set.status { - ChangeSetStatus::Abandoned - | ChangeSetStatus::Failed - | ChangeSetStatus::Rejected => { + ChangeSetStatus::Failed | ChangeSetStatus::Rejected => { trace!(?change_set.status, ?change_set.id, "skipping change set for audit log assembly due to status"); return Ok(None); } - ChangeSetStatus::Applied + ChangeSetStatus::Abandoned + | ChangeSetStatus::Applied | ChangeSetStatus::Approved | ChangeSetStatus::NeedsAbandonApproval | ChangeSetStatus::NeedsApproval diff --git a/lib/dal/src/context.rs b/lib/dal/src/context.rs index 1946dd1ae9..0779f2380b 100644 --- a/lib/dal/src/context.rs +++ b/lib/dal/src/context.rs @@ -582,6 +582,11 @@ impl DalContext { } pub async fn commit_no_rebase(&self) -> TransactionsResult<()> { + // Since we are not rebasing, we need to write the final message and flush all + // pending audit logs. + self.write_audit_log_final_message().await?; + self.publish_pending_audit_logs(None, None).await?; + if self.blocking { self.blocking_commit_internal(DelayedRebaseWithReply::NoUpdates) .await?; diff --git a/lib/dal/src/job/consumer.rs b/lib/dal/src/job/consumer.rs index ccd97c8876..28c833b405 100644 --- a/lib/dal/src/job/consumer.rs +++ b/lib/dal/src/job/consumer.rs @@ -15,6 +15,7 @@ use crate::billing_publish::BillingPublishError; use crate::diagram::DiagramError; use crate::prop::PropError; use crate::validation::ValidationError; +use crate::FuncError; use crate::{ action::prototype::ActionPrototypeError, action::ActionError, attribute::value::AttributeValueError, @@ -49,6 +50,8 @@ pub enum JobConsumerError { DependentValueUpdate(#[from] DependentValueUpdateError), #[error("diagram error: {0}")] Diagram(#[from] DiagramError), + #[error("func error: {0}")] + Func(#[from] FuncError), #[error("Invalid job arguments. Expected: {0} Actual: {1:?}")] InvalidArguments(String, Vec), #[error("std io error: {0}")] diff --git a/lib/dal/src/job/definition/action.rs b/lib/dal/src/job/definition/action.rs index c554c045e6..27da5816fe 100644 --- a/lib/dal/src/job/definition/action.rs +++ b/lib/dal/src/job/definition/action.rs @@ -5,7 +5,7 @@ use std::{ use async_trait::async_trait; use serde::{Deserialize, Serialize}; -use si_events::{ActionResultState, FuncRunId}; +use si_events::{audit_log::AuditLogKind, ActionResultState, FuncRunId}; use telemetry::prelude::*; use telemetry_utils::metric; use veritech_client::{ActionRunResultSuccess, ResourceStatus}; @@ -24,7 +24,8 @@ use crate::{ }, producer::{JobProducer, JobProducerResult}, }, - AccessBuilder, ActionPrototypeId, Component, ComponentId, DalContext, Visibility, WsEvent, + AccessBuilder, ActionPrototypeId, Component, ComponentId, DalContext, Func, Visibility, + WsEvent, }; #[derive(Debug, Deserialize, Serialize)] @@ -212,11 +213,14 @@ async fn process_execution( ) -> JobConsumerResult<()> { let prototype_id = Action::prototype_id(ctx, action_id).await?; let prototype = ActionPrototype::get_by_id(ctx, prototype_id).await?; + let func_id = ActionPrototype::func_id(ctx, prototype_id).await?; + let func = Func::get_by_id_or_error(ctx, func_id).await?; let component_id = Action::component_id(ctx, action_id) .await? .ok_or(ActionError::ComponentNotFoundForAction(action_id))?; let component = Component::get_by_id(ctx, component_id).await?; + let mut success = false; if let Some(run_result) = action_run_result { // Set the resource if we have a payload, regardless of status *and* assemble a // summary @@ -235,6 +239,8 @@ async fn process_execution( } if run_result.status == ResourceStatus::Ok { + success = true; + // Remove `ActionId` from graph as the execution succeeded Action::remove_by_id(ctx, action_id).await?; @@ -294,6 +300,19 @@ async fn process_execution( .publish_on_commit(ctx) .await?; + ctx.write_audit_log( + AuditLogKind::ActionRun { + prototype_id: prototype_id.into(), + action_kind: prototype.kind.into(), + func_id: func.id.into(), + func_name: func.name.clone(), + func_display_name: func.display_name, + run_status: success, + }, + func.name, + ) + .await?; + // Send the rebase request with the resource updated (if applicable) ctx.commit().await?; ctx.update_snapshot_to_visibility().await?; diff --git a/lib/sdf-server/src/service/action/cancel.rs b/lib/sdf-server/src/service/action/cancel.rs index cc932d6b66..0de9e45292 100644 --- a/lib/sdf-server/src/service/action/cancel.rs +++ b/lib/sdf-server/src/service/action/cancel.rs @@ -1,7 +1,10 @@ use axum::Json; +use dal::action::prototype::ActionPrototype; use dal::action::Action; +use dal::Func; use dal::{action::ActionId, Visibility, WsEvent}; use serde::{Deserialize, Serialize}; +use si_events::audit_log::AuditLogKind; use super::ActionResult; use crate::extract::{AccessBuilder, HandlerContext}; @@ -21,6 +24,22 @@ pub async fn cancel( ) -> ActionResult<()> { let ctx = builder.build(request_ctx.build(request.visibility)).await?; for action_id in request.ids { + let prototype_id = Action::prototype_id(&ctx, action_id).await?; + let prototype = ActionPrototype::get_by_id(&ctx, prototype_id).await?; + let func_id = ActionPrototype::func_id(&ctx, prototype_id).await?; + let func = Func::get_by_id_or_error(&ctx, func_id).await?; + ctx.write_audit_log( + AuditLogKind::CancelAction { + prototype_id: prototype_id.into(), + action_kind: prototype.kind.into(), + func_id: func_id.into(), + func_display_name: func.display_name, + func_name: func.name.clone(), + }, + func.name, + ) + .await?; + Action::remove_by_id(&ctx, action_id).await?; } WsEvent::action_list_updated(&ctx) diff --git a/lib/sdf-server/src/service/action/put_on_hold.rs b/lib/sdf-server/src/service/action/put_on_hold.rs index e59a97d4f0..40410d2ae1 100644 --- a/lib/sdf-server/src/service/action/put_on_hold.rs +++ b/lib/sdf-server/src/service/action/put_on_hold.rs @@ -1,9 +1,10 @@ use axum::Json; use dal::{ - action::{Action, ActionId, ActionState}, - Visibility, WsEvent, + action::{prototype::ActionPrototype, Action, ActionId, ActionState}, + Func, Visibility, WsEvent, }; use serde::{Deserialize, Serialize}; +use si_events::audit_log::AuditLogKind; use super::ActionResult; use crate::{ @@ -36,6 +37,23 @@ pub async fn put_on_hold( } Action::set_state(&ctx, action.id(), ActionState::OnHold).await?; + + let prototype_id = Action::prototype_id(&ctx, action.id()).await?; + let prototype = ActionPrototype::get_by_id(&ctx, prototype_id).await?; + let func_id = ActionPrototype::func_id(&ctx, prototype_id).await?; + let func = Func::get_by_id_or_error(&ctx, func_id).await?; + + ctx.write_audit_log( + AuditLogKind::PutActionOnHold { + prototype_id: prototype.id().into(), + action_kind: prototype.kind.into(), + func_id: func_id.into(), + func_display_name: func.display_name, + func_name: func.name.clone(), + }, + func.name, + ) + .await?; } WsEvent::action_list_updated(&ctx) .await? diff --git a/lib/sdf-server/src/service/change_set/abandon_change_set.rs b/lib/sdf-server/src/service/change_set/abandon_change_set.rs index ef80433d09..be11d2e826 100644 --- a/lib/sdf-server/src/service/change_set/abandon_change_set.rs +++ b/lib/sdf-server/src/service/change_set/abandon_change_set.rs @@ -4,6 +4,7 @@ use axum::{ }; use dal::{change_set::ChangeSet, ChangeSetId}; use serde::{Deserialize, Serialize}; +use si_events::audit_log::AuditLogKind; use super::ChangeSetResult; use crate::{ @@ -41,6 +42,8 @@ pub async fn abandon_change_set( .await? .ok_or(ChangeSetError::ChangeSetNotFound)?; + let name = change_set.clone().name; + ctx.update_visibility_and_snapshot_to_visibility(change_set.id) .await?; change_set.abandon(&ctx).await?; @@ -56,6 +59,9 @@ pub async fn abandon_change_set( }), ); + ctx.write_audit_log(AuditLogKind::AbandonChangeset {}, name) + .await?; + ctx.commit_no_rebase().await?; Ok(()) diff --git a/lib/sdf-server/src/service/change_set/add_action.rs b/lib/sdf-server/src/service/change_set/add_action.rs index 85ebd643ec..4a22751a74 100644 --- a/lib/sdf-server/src/service/change_set/add_action.rs +++ b/lib/sdf-server/src/service/change_set/add_action.rs @@ -4,9 +4,10 @@ use dal::{ prototype::{ActionKind, ActionPrototype}, Action, }, - ActionPrototypeId, ChangeSet, Component, ComponentId, Visibility, + ActionPrototypeId, ChangeSet, Component, ComponentId, Func, Visibility, }; use serde::{Deserialize, Serialize}; +use si_events::audit_log::AuditLogKind; use super::ChangeSetResult; use crate::{ @@ -53,6 +54,8 @@ pub async fn add_action( let component = Component::get_by_id(&ctx, request.component_id).await?; let action = Action::new(&ctx, request.prototype_id, Some(request.component_id)).await?; + let func_id = ActionPrototype::func_id(&ctx, prototype.id).await?; + let func = Func::get_by_id_or_error(&ctx, func_id).await?; track( &posthog_client, &ctx, @@ -68,6 +71,17 @@ pub async fn add_action( }), ); // todo add ws event here + ctx.write_audit_log( + AuditLogKind::AddAction { + prototype_id: prototype.id().into(), + action_kind: prototype.kind.into(), + func_id: func_id.into(), + func_display_name: func.display_name, + func_name: func.name.clone(), + }, + func.name, + ) + .await?; ctx.commit().await?; diff --git a/lib/sdf-server/src/service/change_set/apply_change_set.rs b/lib/sdf-server/src/service/change_set/apply_change_set.rs index 2c88ecd3dc..f1cf8c0129 100644 --- a/lib/sdf-server/src/service/change_set/apply_change_set.rs +++ b/lib/sdf-server/src/service/change_set/apply_change_set.rs @@ -4,6 +4,7 @@ use axum::{ }; use dal::{change_set::ChangeSet, Func, Schema, SchemaVariant, Visibility}; use serde::{Deserialize, Serialize}; +use si_events::audit_log::AuditLogKind; use crate::{ extract::{AccessBuilder, HandlerContext, PosthogClient}, @@ -85,6 +86,9 @@ pub async fn apply_change_set( }), ); + ctx.write_audit_log(AuditLogKind::ApplyChangeset {}, "-".to_string()) + .await?; + // // If anything fails with uploading the workspace backup module, just log it. We shouldn't // // have the change set apply itself fail because of this. // tokio::task::spawn( diff --git a/lib/sdf-server/src/service/change_set/create_change_set.rs b/lib/sdf-server/src/service/change_set/create_change_set.rs index 8d7e54f5d2..19c5489e8d 100644 --- a/lib/sdf-server/src/service/change_set/create_change_set.rs +++ b/lib/sdf-server/src/service/change_set/create_change_set.rs @@ -3,6 +3,7 @@ use axum::Json; use dal::change_set::ChangeSet; use dal::WsEvent; use serde::{Deserialize, Serialize}; +use si_events::audit_log::AuditLogKind; use super::ChangeSetResult; use crate::{ @@ -45,10 +46,16 @@ pub async fn create_change_set( &host_name, "create_change_set", serde_json::json!({ - "change_set_name": change_set_name, + "change_set_name": change_set_name.clone(), }), ); + ctx.write_audit_log( + AuditLogKind::CreateChangeset {}, + change_set_name.to_string(), + ) + .await?; + WsEvent::change_set_created(&ctx, change_set.id) .await? .publish_on_commit(&ctx) diff --git a/lib/si-events-rs/src/audit_log/v2.rs b/lib/si-events-rs/src/audit_log/v2.rs index 03d039ff89..988c8d2f64 100644 --- a/lib/si-events-rs/src/audit_log/v2.rs +++ b/lib/si-events-rs/src/audit_log/v2.rs @@ -1,7 +1,10 @@ use serde::{Deserialize, Serialize}; use strum::Display; -use crate::{Actor, AttributeValueId, ChangeSetId, ComponentId, PropId, SchemaVariantId, SecretId}; +use crate::{ + ActionKind, ActionPrototypeId, Actor, AttributeValueId, ChangeSetId, ComponentId, FuncId, + PropId, SchemaVariantId, SecretId, +}; #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct AuditLogV2 { @@ -50,4 +53,36 @@ pub enum AuditLogKindV2 { after_secret_name: Option, after_secret_id: Option, }, + ApplyChangeset, + AddAction { + prototype_id: ActionPrototypeId, + action_kind: ActionKind, + func_id: FuncId, + func_display_name: Option, + func_name: String, + }, + PutActionOnHold { + prototype_id: ActionPrototypeId, + action_kind: ActionKind, + func_id: FuncId, + func_display_name: Option, + func_name: String, + }, + CancelAction { + prototype_id: ActionPrototypeId, + action_kind: ActionKind, + func_id: FuncId, + func_display_name: Option, + func_name: String, + }, + CreateChangeset, + AbandonChangeset, + ActionRun { + prototype_id: ActionPrototypeId, + action_kind: ActionKind, + func_id: FuncId, + func_display_name: Option, + func_name: String, + run_status: bool, + }, } diff --git a/lib/si-frontend-types-rs/src/audit_log.rs b/lib/si-frontend-types-rs/src/audit_log.rs index 0622c2ce91..311392afdb 100644 --- a/lib/si-frontend-types-rs/src/audit_log.rs +++ b/lib/si-frontend-types-rs/src/audit_log.rs @@ -1,7 +1,7 @@ use serde::Serialize; use si_events::{ - audit_log::AuditLogKind, AttributeValueId, ChangeSetId, ComponentId, PropId, SchemaVariantId, - SecretId, UserPk, + audit_log::AuditLogKind, ActionKind, ActionPrototypeId, AttributeValueId, ChangeSetId, + ComponentId, FuncId, PropId, SchemaVariantId, SecretId, UserPk, }; use strum::EnumDiscriminants; @@ -73,6 +73,45 @@ pub enum AuditLogDeserializedMetadata { after_secret_name: Option, after_secret_id: Option, }, + #[serde(rename_all = "camelCase")] + ApplyChangeSet {}, + #[serde(rename_all = "camelCase")] + AbandonChangeSet {}, + #[serde(rename_all = "camelCase")] + CreateChangeSet {}, + #[serde(rename_all = "camelCase")] + AddAction { + prototype_id: ActionPrototypeId, + action_kind: ActionKind, + func_id: FuncId, + func_display_name: Option, + func_name: String, + }, + #[serde(rename_all = "camelCase")] + PutActionOnHold { + prototype_id: ActionPrototypeId, + action_kind: ActionKind, + func_id: FuncId, + func_display_name: Option, + func_name: String, + }, + #[serde(rename_all = "camelCase")] + CancelAction { + prototype_id: ActionPrototypeId, + action_kind: ActionKind, + func_id: FuncId, + func_display_name: Option, + func_name: String, + }, + #[serde(rename_all = "camelCase")] + ActionRun { + prototype_id: ActionPrototypeId, + action_kind: ActionKind, + func_id: FuncId, + func_display_name: Option, + func_name: String, + run_status: bool, + }, } impl AuditLogDeserializedMetadata { @@ -83,9 +122,16 @@ impl AuditLogDeserializedMetadata { match discrim { Discrim::CreateComponent => ("Created", "Component"), Discrim::DeleteComponent => ("Deleted", "Component"), - Discrim::UpdatePropertyEditorValue => ("Updated property in Component", "Property"), + Discrim::UpdatePropertyEditorValue => ("Updated Component", "Property"), + Discrim::ApplyChangeSet => ("Applied", "Change Set"), + Discrim::CreateChangeSet => ("Created", "Change Set"), + Discrim::AbandonChangeSet => ("Abandoned", "Change Set"), + Discrim::PutActionOnHold => ("Paused", "Action"), + Discrim::CancelAction => ("Removed", "Action"), + Discrim::AddAction => ("Enqueued", "Action"), + Discrim::ActionRun => ("Ran", "Action"), Discrim::UpdatePropertyEditorValueForSecret => { - ("Updated secret in Component", "Property for Secret") + ("Updated Component", "Property for Secret") } } } @@ -162,6 +208,63 @@ impl From for AuditLogDeserializedMetadata { after_secret_name, after_secret_id, }, + AuditLogKind::ApplyChangeset {} => Self::ApplyChangeSet {}, + AuditLogKind::CreateChangeset {} => Self::CreateChangeSet {}, + AuditLogKind::AbandonChangeset {} => Self::AbandonChangeSet {}, + AuditLogKind::AddAction { + prototype_id, + action_kind, + func_id, + func_display_name, + func_name, + } => Self::AddAction { + prototype_id, + action_kind, + func_id, + func_display_name, + func_name, + }, + AuditLogKind::PutActionOnHold { + prototype_id, + action_kind, + func_id, + func_display_name, + func_name, + } => Self::PutActionOnHold { + prototype_id, + action_kind, + func_id, + func_display_name, + func_name, + }, + AuditLogKind::CancelAction { + prototype_id, + action_kind, + func_id, + func_display_name, + func_name, + } => Self::CancelAction { + prototype_id, + action_kind, + func_id, + func_display_name, + func_name, + }, + AuditLogKind::ActionRun { + prototype_id, + action_kind, + func_id, + func_display_name, + func_name, + run_status, + } => Self::ActionRun { + prototype_id, + action_kind, + func_id, + func_display_name, + func_name, + run_status, + }, } } }