///| PR workflow records and merge-gate integration.
///|
pub(all) enum PrWorkflowStatus {
Pending
Running
Success
Failed
} derive(Eq, Debug)
///|
pub fn PrWorkflowStatus::to_string(self : PrWorkflowStatus) -> String {
match self {
Pending => "pending"
Running => "running"
Success => "success"
Failed => "failed"
}
}
///|
pub impl Show for PrWorkflowStatus with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
pub fn parse_pr_workflow_status(value : String) -> PrWorkflowStatus? {
match value.to_lower() {
"pending" => Some(PrWorkflowStatus::Pending)
"running" => Some(PrWorkflowStatus::Running)
"success" | "passed" => Some(PrWorkflowStatus::Success)
"failed" | "failure" => Some(PrWorkflowStatus::Failed)
_ => None
}
}
///|
pub struct PrWorkflowResult {
pr_id : String
task : String
status : PrWorkflowStatus
workspace_fingerprint : String
txn_id : String?
author : String
updated_at : Int64
summary : String
}
///|
pub fn PrWorkflowResult::new(
pr_id : String,
task : String,
status : PrWorkflowStatus,
workspace_fingerprint : String,
author : String,
updated_at : Int64,
txn_id? : String? = None,
summary? : String = "",
) -> PrWorkflowResult {
{
pr_id,
task,
status,
workspace_fingerprint,
txn_id,
author,
updated_at,
summary,
}
}
///|
pub fn PrWorkflowResult::pr_id(self : PrWorkflowResult) -> String {
self.pr_id
}
///|
pub fn PrWorkflowResult::task(self : PrWorkflowResult) -> String {
self.task
}
///|
pub fn PrWorkflowResult::status(self : PrWorkflowResult) -> PrWorkflowStatus {
self.status
}
///|
pub fn PrWorkflowResult::workspace_fingerprint(
self : PrWorkflowResult,
) -> String {
self.workspace_fingerprint
}
///|
pub fn PrWorkflowResult::txn_id(self : PrWorkflowResult) -> String? {
self.txn_id
}
///|
pub fn PrWorkflowResult::author(self : PrWorkflowResult) -> String {
self.author
}
///|
pub fn PrWorkflowResult::updated_at(self : PrWorkflowResult) -> Int64 {
self.updated_at
}
///|
pub fn PrWorkflowResult::summary(self : PrWorkflowResult) -> String {
self.summary
}
///|
fn workflow_json_string(
obj : Map[String, Json],
key : String,
fallback : String,
) -> String {
match obj.get(key) {
Some(Json::String(value)) => value
_ => fallback
}
}
///|
fn with_workflow_timestamp(
workflow : PrWorkflowResult,
updated_at : Int64,
) -> PrWorkflowResult {
PrWorkflowResult::new(
workflow.pr_id(),
workflow.task(),
workflow.status(),
workflow.workspace_fingerprint(),
workflow.author(),
updated_at,
txn_id=workflow.txn_id(),
summary=workflow.summary(),
)
}
///|
pub fn PrWorkflowResult::serialize(self : PrWorkflowResult) -> String {
let obj : Map[String, Json] = Map([])
obj["pr_id"] = Json::string(self.pr_id)
obj["task"] = Json::string(self.task)
obj["status"] = Json::string(self.status.to_string())
obj["workspace_fingerprint"] = Json::string(self.workspace_fingerprint)
obj["author"] = Json::string(self.author)
obj["summary"] = Json::string(self.summary)
match self.txn_id {
Some(txn_id) => obj["txn_id"] = Json::string(txn_id)
None => ()
}
Json::object(obj).stringify()
}
///|
pub fn parse_pr_workflow_result(
text : String,
) -> PrWorkflowResult raise PrError {
let parsed = @json.parse(text) catch {
err =>
raise PrError::InvalidFormat(
"Invalid PR workflow payload JSON: \{err.to_string()}",
)
}
guard parsed is Json::Object(obj) else {
raise PrError::InvalidFormat("PR workflow payload must be JSON object")
}
let pr_id = workflow_json_string(obj, "pr_id", "")
if pr_id.length() == 0 {
raise PrError::InvalidFormat("Missing pr_id in PR workflow payload")
}
let task = workflow_json_string(obj, "task", "")
if task.length() == 0 {
raise PrError::InvalidFormat("Missing task in PR workflow payload")
}
let status_raw = workflow_json_string(obj, "status", "")
guard parse_pr_workflow_status(status_raw) is Some(status) else {
raise PrError::InvalidFormat("Unknown PR workflow status: \{status_raw}")
}
let workspace_fingerprint = workflow_json_string(
obj, "workspace_fingerprint", "",
)
let author = workflow_json_string(obj, "author", "unknown")
let txn_id = match obj.get("txn_id") {
Some(Json::String(value)) =>
if value.length() == 0 {
None
} else {
Some(value)
}
_ => None
}
let summary = workflow_json_string(obj, "summary", "")
PrWorkflowResult::new(
pr_id,
task,
status,
workspace_fingerprint,
author,
0L,
txn_id~,
summary~,
)
}
///|
pub fn pr_workflow_prefix(pr_id : String) -> String {
"hub/pr/" + pr_id + "/workflow/"
}
///|
fn workflow_task_key_suffix(task : String) -> String {
let (id, _compressed) = @bit.create_blob_string("hub-pr-workflow:" + task)
let hex = id.to_hex()
String::unsafe_substring(hex, start=0, end=12)
}
///|
pub fn pr_workflow_key(pr_id : String, task : String) -> String {
pr_workflow_prefix(pr_id) + workflow_task_key_suffix(task)
}
///|
pub fn Hub::record_pr_workflow(
self : Hub,
objects : &@lib.ObjectStore,
refs : &@lib.RefStore,
clock : &@lib.Clock,
pr_id : String,
task : String,
status : PrWorkflowStatus,
workspace_fingerprint : String,
author : String,
txn_id? : String? = None,
summary? : String = "",
) -> PrWorkflowResult raise @bit.GitError {
if task.length() == 0 {
raise @bit.GitError::InvalidObject("PR workflow task is required")
}
if self.get_pr(objects, pr_id) is None {
raise @bit.GitError::InvalidObject("PR not found: \{pr_id}")
}
let draft = PrWorkflowResult::new(
pr_id,
task,
status,
workspace_fingerprint,
author,
clock.now(),
txn_id~,
summary~,
)
let record = self.store.put_record(
objects,
refs,
clock,
pr_workflow_key(pr_id, task),
"hub.pr.workflow",
draft.serialize(),
self.store.node_id(),
)
with_workflow_timestamp(draft, record.timestamp)
}
///|
pub fn Hub::list_pr_workflows(
self : Hub,
objects : &@lib.ObjectStore,
pr_id : String,
) -> Array[PrWorkflowResult] {
let prefix = pr_workflow_prefix(pr_id)
let records = self.store.list_records(objects, prefix)
let latest_by_task : Map[String, PrWorkflowResult] = Map([])
for record in records {
if record.kind != "hub.pr.workflow" {
continue
}
let parsed = parse_pr_workflow_result(record.payload) catch {
_ => continue
}
if parsed.pr_id() != pr_id {
continue
}
let workflow = with_workflow_timestamp(parsed, record.timestamp)
match latest_by_task.get(workflow.task()) {
Some(previous) =>
if previous.updated_at() <= workflow.updated_at() {
latest_by_task[workflow.task()] = workflow
}
None => latest_by_task[workflow.task()] = workflow
}
}
let out : Array[PrWorkflowResult] = []
for _, workflow in latest_by_task {
out.push(workflow)
}
out.sort_by(fn(a, b) {
if a.task() < b.task() {
-1
} else if a.task() > b.task() {
1
} else {
0
}
})
out
}