-
Notifications
You must be signed in to change notification settings - Fork 321
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP: use new change set approvals table
- Add change set approvals table with integration test - Collect approval status for a change set (only current approvals right now and without the validity check) - Add new approval route Co-authored-by: Jacob Helwig <[email protected]> Co-authored-by: John Obelenus <[email protected]> Signed-off-by: Nick Gerace <[email protected]>
- Loading branch information
1 parent
68eca14
commit 0899cc2
Showing
13 changed files
with
339 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ use crate::{ | |
WorkspaceError, | ||
}; | ||
|
||
pub mod approval; | ||
pub mod event; | ||
pub mod status; | ||
pub mod view; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
use chrono::{DateTime, Utc}; | ||
use serde::{Deserialize, Serialize}; | ||
use si_data_pg::{PgError, PgRow}; | ||
use si_id::{ChangeSetApprovalId, ChangeSetId, UserPk}; | ||
use telemetry::prelude::*; | ||
use thiserror::Error; | ||
|
||
pub use si_events::ChangeSetApprovalStatus; | ||
|
||
use crate::{DalContext, HistoryActor, TransactionsError}; | ||
|
||
#[derive(Debug, Error)] | ||
pub enum ChangeSetApprovalError { | ||
#[error("invalid user for creating a change set approval")] | ||
InvalidUserForCreation, | ||
#[error("pg error: {0}")] | ||
Pg(#[from] PgError), | ||
#[error("strum parse error: {0}")] | ||
StrumParse(#[from] strum::ParseError), | ||
#[error("transactions error: {0}")] | ||
Transactions(#[from] TransactionsError), | ||
} | ||
|
||
type Result<T> = std::result::Result<T, ChangeSetApprovalError>; | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
pub struct ChangeSetApproval { | ||
id: ChangeSetApprovalId, | ||
created_at: DateTime<Utc>, | ||
updated_at: DateTime<Utc>, | ||
change_set_id: ChangeSetId, | ||
status: ChangeSetApprovalStatus, | ||
user_id: UserPk, | ||
checksum: String, | ||
} | ||
|
||
impl TryFrom<PgRow> for ChangeSetApproval { | ||
type Error = ChangeSetApprovalError; | ||
|
||
fn try_from(value: PgRow) -> std::result::Result<Self, Self::Error> { | ||
let status_string: String = value.try_get("status")?; | ||
let status = ChangeSetApprovalStatus::try_from(status_string.as_str())?; | ||
Ok(Self { | ||
id: value.try_get("id")?, | ||
created_at: value.try_get("created_at")?, | ||
updated_at: value.try_get("updated_at")?, | ||
change_set_id: value.try_get("change_set_id")?, | ||
status, | ||
user_id: value.try_get("user_id")?, | ||
checksum: value.try_get("checksum")?, | ||
}) | ||
} | ||
} | ||
|
||
impl ChangeSetApproval { | ||
#[instrument(name = "change_set.approval.new", level = "info", skip_all)] | ||
pub async fn new( | ||
ctx: &DalContext, | ||
status: ChangeSetApprovalStatus, | ||
checksum: String, | ||
) -> Result<Self> { | ||
let change_set_id = ctx.change_set_id(); | ||
let user_id = match ctx.history_actor() { | ||
HistoryActor::User(user_id) => user_id, | ||
HistoryActor::SystemInit => return Err(ChangeSetApprovalError::InvalidUserForCreation), | ||
}; | ||
let row = ctx | ||
.txns() | ||
.await? | ||
.pg() | ||
.query_one( | ||
"INSERT INTO change_set_approvals (change_set_id, status, user_id, checksum) VALUES ($1, $2, $3, $4) RETURNING *", | ||
&[&change_set_id, &status, &user_id, &checksum] | ||
) | ||
.await?; | ||
Self::try_from(row) | ||
} | ||
|
||
pub fn id(&self) -> ChangeSetApprovalId { | ||
self.id | ||
} | ||
|
||
pub fn status(&self) -> ChangeSetApprovalStatus { | ||
self.status | ||
} | ||
|
||
pub fn user_id(&self) -> UserPk { | ||
self.user_id | ||
} | ||
|
||
pub fn checksum(&self) -> &str { | ||
self.checksum.as_str() | ||
} | ||
|
||
#[instrument(name = "change_set.approval.list", level = "info", skip_all)] | ||
pub async fn list(ctx: &DalContext) -> Result<Vec<Self>> { | ||
let change_set_id = ctx.change_set_id(); | ||
let rows = ctx | ||
.txns() | ||
.await? | ||
.pg() | ||
.query( | ||
"SELECT * from change_set_approvals WHERE change_set_id = $1 ORDER BY id ASC", | ||
&[&change_set_id], | ||
) | ||
.await?; | ||
let mut approvals = Vec::with_capacity(rows.len()); | ||
for row in rows { | ||
approvals.push(Self::try_from(row)?); | ||
} | ||
Ok(approvals) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
CREATE change_set_approvals | ||
( | ||
id ident primary key NOT NULL DEFAULT ident_create_v1(), | ||
created_at timestamp with time zone NOT NULL DEFAULT CLOCK_TIMESTAMP(), | ||
updated_at timestamp with time zone NOT NULL DEFAULT CLOCK_TIMESTAMP(), | ||
change_set_id ident NOT NULL, | ||
status text NOT NULL, | ||
user_id ident NOT NULL, | ||
checksum text NOT NULL | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
use dal::change_set::approval::{ChangeSetApproval, ChangeSetApprovalStatus}; | ||
use dal::DalContext; | ||
use dal_test::color_eyre::eyre::OptionExt; | ||
use dal_test::{test, Result}; | ||
use pretty_assertions_sorted::assert_eq; | ||
|
||
#[test] | ||
async fn new(ctx: &mut DalContext) -> Result<()> { | ||
let status = ChangeSetApprovalStatus::Approved; | ||
// FIXME(nick): use a real checksum here. | ||
let checksum = "FIXME".to_string(); | ||
|
||
let new_approval = ChangeSetApproval::new(ctx, status, checksum).await?; | ||
assert_eq!( | ||
status, // expectd | ||
new_approval.status() // actual | ||
); | ||
|
||
let mut approvals = ChangeSetApproval::list(ctx).await?; | ||
let approval = approvals.pop().ok_or_eyre("unexpected empty approvals")?; | ||
assert!(approvals.is_empty()); | ||
assert_eq!( | ||
new_approval.status(), // expected | ||
approval.status() // actual | ||
); | ||
assert_eq!( | ||
new_approval.id(), // expected | ||
approval.id() // actual | ||
); | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
lib/sdf-server/src/service/v2/change_set/approval_status.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
use axum::{ | ||
extract::{Host, OriginalUri, Path, State}, | ||
Json, | ||
}; | ||
use dal::{change_set::approval::ChangeSetApproval, ChangeSet, ChangeSetId, WorkspacePk}; | ||
|
||
use super::{AppState, Error, Result}; | ||
use crate::{ | ||
extract::{AccessBuilder, HandlerContext, PosthogClient}, | ||
track, | ||
}; | ||
|
||
pub async fn approval_status( | ||
HandlerContext(builder): HandlerContext, | ||
AccessBuilder(access_builder): AccessBuilder, | ||
Path((_workspace_pk, change_set_id)): Path<(WorkspacePk, ChangeSetId)>, | ||
) -> Result<Json<si_frontend_types::ChangeSetApprovals>> { | ||
let ctx = builder | ||
.build(access_builder.build(change_set_id.into())) | ||
.await?; | ||
|
||
let approvals = ChangeSetApproval::list(&ctx).await?; | ||
let mut current = Vec::with_capacity(approvals.len()); | ||
for approval in approvals { | ||
current.push(si_frontend_types::ChangeSetApproval { | ||
user_id: approval.user_id(), | ||
status: approval.status(), | ||
// FIXME(nick): make this real! | ||
is_valid: true, | ||
}) | ||
} | ||
|
||
Ok(Json(si_frontend_types::ChangeSetApprovals { | ||
// FIXME(nick): get requirements. | ||
required: Vec::new(), | ||
current, | ||
})) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
use axum::{ | ||
extract::{Host, OriginalUri, Path}, | ||
Json, | ||
}; | ||
use dal::{change_set::approval::ChangeSetApproval, ChangeSet, ChangeSetId, WorkspacePk, WsEvent}; | ||
use serde::Deserialize; | ||
use si_events::{audit_log::AuditLogKind, ChangeSetApprovalStatus}; | ||
|
||
use super::{post_to_webhook, Error, Result}; | ||
use crate::{ | ||
extract::{AccessBuilder, HandlerContext, PosthogClient}, | ||
track, | ||
}; | ||
|
||
#[derive(Debug, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct Request { | ||
pub status: ChangeSetApprovalStatus, | ||
} | ||
|
||
pub async fn approve( | ||
HandlerContext(builder): HandlerContext, | ||
AccessBuilder(request_ctx): AccessBuilder, | ||
PosthogClient(posthog_client): PosthogClient, | ||
OriginalUri(original_uri): OriginalUri, | ||
Host(host_name): Host, | ||
Path((workspace_pk, change_set_id)): Path<(WorkspacePk, ChangeSetId)>, | ||
Json(request): Json<Request>, | ||
) -> Result<()> { | ||
let ctx = builder | ||
.build(request_ctx.build(change_set_id.into())) | ||
.await?; | ||
|
||
// Ensure that DVU roots are empty before continuing? | ||
// todo(brit): maybe we can get away without this. Ex: Approve a PR before tests finish | ||
if !ctx | ||
.workspace_snapshot()? | ||
.get_dependent_value_roots() | ||
.await? | ||
.is_empty() | ||
{ | ||
// TODO(nick): we should consider requiring this check in integration tests too. Why did I | ||
// not do this at the time of writing? Tests have multiple ways to call "apply", whether | ||
// its via helpers or through the change set methods directly. In addition, they test | ||
// for success and failure, not solely for success. We should still do this, but not within | ||
// the PR corresponding to when this message was written. | ||
return Err(Error::DvuRootsNotEmpty(ctx.change_set_id())); | ||
} | ||
|
||
let change_set = ChangeSet::find(&ctx, ctx.visibility().change_set_id) | ||
.await? | ||
.ok_or(Error::ChangeSetNotFound(ctx.change_set_id()))?; | ||
// FIXME(nick): put the real checksum here. | ||
ChangeSetApproval::new(&ctx, request.status, "FIXME".to_string()).await?; | ||
|
||
track( | ||
&posthog_client, | ||
&ctx, | ||
&original_uri, | ||
&host_name, | ||
"approve_change_set_apply", | ||
serde_json::json!({ | ||
"merged_change_set": change_set.id, | ||
}), | ||
); | ||
ctx.write_audit_log( | ||
AuditLogKind::ApproveChangeSetApply { | ||
from_status: change_set.status.into(), | ||
}, | ||
change_set.name, | ||
) | ||
.await?; | ||
|
||
ctx.commit().await?; | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
use postgres_types::ToSql; | ||
use serde::{Deserialize, Serialize}; | ||
use strum::{AsRefStr, Display, EnumString}; | ||
|
||
#[remain::sorted] | ||
#[derive( | ||
AsRefStr, Deserialize, Serialize, Debug, Display, EnumString, PartialEq, Eq, Copy, Clone, ToSql, | ||
)] | ||
pub enum ChangeSetApprovalStatus { | ||
Approved, | ||
} | ||
|
||
#[remain::sorted] | ||
#[derive(Debug, Clone, Deserialize, Serialize)] | ||
pub enum ChangeSetApprovalKind { | ||
Func, | ||
SchemaVariant, | ||
View, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.