///|
pub enum WorkflowStatus {
Draft
Review
Approved
Rejected
Archived
} derive(Debug, Eq)
///|
pub struct WorkflowStep {
name : String
owner : String
status : WorkflowStatus
note : String
} derive(Debug, Eq)
///|
pub fn workflow_step(
name : String,
owner : String,
status : WorkflowStatus,
note : String,
) -> WorkflowStep {
{ name, owner, status, note }
}
///|
pub fn WorkflowStep::is_complete(self : WorkflowStep) -> Bool {
self.status is Approved || self.status is Archived
}
///|
pub fn archive_step(
name : String,
owner : String,
note : String,
) -> WorkflowStep {
workflow_step(name, owner, Archived, note)
}
///|
pub struct ReportWorkflow {
id : String
steps : Array[WorkflowStep]
current : Int
} derive(Debug, Eq)
///|
pub fn report_workflow(
id : String,
steps : Array[WorkflowStep],
) -> ReportWorkflow {
{ id, steps, current: 0 }
}
///|
pub fn ReportWorkflow::count(self : ReportWorkflow) -> Int {
self.steps.length()
}
///|
pub fn ReportWorkflow::current_step(self : ReportWorkflow) -> WorkflowStep? {
self.steps.get(self.current)
}
///|
pub fn ReportWorkflow::completed_count(self : ReportWorkflow) -> Int {
let mut count = 0
for step in self.steps {
if step.is_complete() {
count = count + 1
}
}
count
}
///|
pub fn ReportWorkflow::progress(self : ReportWorkflow) -> Float {
if self.count() == 0 {
0.0
} else {
Float::from_int(self.completed_count()) / Float::from_int(self.count())
}
}
///|
pub fn ReportWorkflow::is_complete(self : ReportWorkflow) -> Bool {
self.count() > 0 && self.completed_count() == self.count()
}
///|
pub fn ReportWorkflow::advance(self : ReportWorkflow) -> ReportWorkflow {
if self.current < self.steps.length() - 1 {
{ ..self, current: self.current + 1 }
} else {
self
}
}
///|
pub fn ReportWorkflow::with_step(
self : ReportWorkflow,
step : WorkflowStep,
) -> ReportWorkflow {
let steps = self.steps.copy()
steps.push(step)
{ ..self, steps, }
}
///|
pub fn ReportWorkflow::approve_current(
self : ReportWorkflow,
note : String,
) -> ReportWorkflow {
if self.current_step() is Some(current) {
let steps = self.steps.copy()
steps[self.current] = { ..current, status: Approved, note }
{ ..self, steps, }
} else {
self
}
}
///|
pub fn ReportWorkflow::reject_current(
self : ReportWorkflow,
note : String,
) -> ReportWorkflow {
if self.current_step() is Some(current) {
let steps = self.steps.copy()
steps[self.current] = { ..current, status: Rejected, note }
{ ..self, steps, }
} else {
self
}
}
///|
pub fn ReportWorkflow::to_table(self : ReportWorkflow) -> ReportTable {
let rows : Array[Array[String]] = []
for index, step in self.steps {
let status = match step.status {
Draft => "draft"
Review => "review"
Approved => "approved"
Rejected => "rejected"
Archived => "archived"
}
rows.push(["\{index}", step.name, step.owner, status, step.note])
}
table(["step", "name", "owner", "status", "note"], rows)
}
///|
pub fn ReportWorkflow::to_section(self : ReportWorkflow) -> ReportSection {
{ title: self.id, kind: Notes, body: self.to_table().to_markdown() }
}
///|
pub fn initial_workflow(id : String, owner : String) -> ReportWorkflow {
report_workflow(id, [
workflow_step("draft", owner, Draft, ""),
workflow_step("technical-review", owner, Review, ""),
workflow_step("acceptance", owner, Review, ""),
])
}
///|
pub fn workflow_gate(workflow : ReportWorkflow) -> Bool {
workflow.is_complete()
}
///|
pub struct AuditEvent {
timestamp : String
actor : String
action : String
object : String
} derive(Debug, Eq)
///|
pub fn audit_event(
timestamp : String,
actor : String,
action : String,
object : String,
) -> AuditEvent {
{ timestamp, actor, action, object }
}
///|
pub struct AuditTrail {
events : Array[AuditEvent]
} derive(Debug, Eq)
///|
pub fn audit_trail(events : Array[AuditEvent]) -> AuditTrail {
{ events, }
}
///|
pub fn AuditTrail::count(self : AuditTrail) -> Int {
self.events.length()
}
///|
pub fn AuditTrail::append(self : AuditTrail, event : AuditEvent) -> AuditTrail {
let events = self.events.copy()
events.push(event)
{ events, }
}
///|
pub fn AuditTrail::actors(self : AuditTrail) -> Array[String] {
let result : Array[String] = []
for event in self.events {
if !result.contains(event.actor) {
result.push(event.actor)
}
}
result
}
///|
pub fn AuditTrail::actions(self : AuditTrail) -> Array[String] {
self.events.map(fn(event) { event.action })
}
///|
pub fn AuditTrail::to_table(self : AuditTrail) -> ReportTable {
let rows : Array[Array[String]] = []
for event in self.events {
rows.push([event.timestamp, event.actor, event.action, event.object])
}
table(["timestamp", "actor", "action", "object"], rows)
}
///|
pub fn AuditTrail::to_markdown(self : AuditTrail) -> String {
self.to_table().to_markdown()
}
///|
pub fn audit_quality_gate(trail : AuditTrail) -> Bool {
trail.count() > 0 && trail.actors().length() > 0
}