Auto deployments
This commit is contained in:
parent
212fea579f
commit
cc92cf681c
8 changed files with 484 additions and 6 deletions
97
src/todo.rs
97
src/todo.rs
|
|
@ -5,6 +5,7 @@ use uuid::Uuid;
|
|||
use crate::enums::{ToDoSource, ToDoStatus};
|
||||
use crate::errors::ValidationError;
|
||||
use crate::{CommunicationType, CommunicationValue, DataTypes, DataValue};
|
||||
use std::collections::HashSet;
|
||||
|
||||
const MAX_TITLE_LENGTH: usize = 500;
|
||||
const MAX_DESCRIPTION_LENGTH: usize = 10000;
|
||||
|
|
@ -12,14 +13,22 @@ const MIN_PRIORITY: u32 = 1;
|
|||
const MAX_PRIORITY: u32 = 1000;
|
||||
const DEFAULT_PRIORITY: u32 = 100;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Dependency {
|
||||
pub todo_id: Uuid,
|
||||
pub board_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ToDo {
|
||||
pub id: Uuid,
|
||||
pub board_id: Uuid,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub status: ToDoStatus,
|
||||
pub priority: u32,
|
||||
pub depends_on: Vec<Uuid>,
|
||||
pub cross_board_depends_on: Vec<Dependency>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub created_by: Uuid,
|
||||
|
|
@ -30,6 +39,7 @@ pub struct ToDo {
|
|||
pub source: ToDoSource,
|
||||
pub automation_chain_id: Option<Uuid>,
|
||||
pub tags: Vec<String>,
|
||||
pub estimated_tokens: u32,
|
||||
}
|
||||
|
||||
impl Default for ToDo {
|
||||
|
|
@ -37,11 +47,13 @@ impl Default for ToDo {
|
|||
let now = Utc::now();
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
board_id: Uuid::nil(),
|
||||
title: String::new(),
|
||||
description: String::new(),
|
||||
status: ToDoStatus::default(),
|
||||
priority: DEFAULT_PRIORITY,
|
||||
depends_on: Vec::new(),
|
||||
cross_board_depends_on: Vec::new(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
created_by: Uuid::nil(),
|
||||
|
|
@ -52,6 +64,7 @@ impl Default for ToDo {
|
|||
source: ToDoSource::User,
|
||||
automation_chain_id: None,
|
||||
tags: Vec::new(),
|
||||
estimated_tokens: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,11 +74,13 @@ impl ToDo {
|
|||
let now = Utc::now();
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
board_id: Uuid::nil(),
|
||||
title: title.trim().to_string(),
|
||||
description,
|
||||
status: ToDoStatus::default(),
|
||||
priority: DEFAULT_PRIORITY,
|
||||
depends_on: Vec::new(),
|
||||
cross_board_depends_on: Vec::new(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
created_by,
|
||||
|
|
@ -76,6 +91,7 @@ impl ToDo {
|
|||
source: ToDoSource::User,
|
||||
automation_chain_id: None,
|
||||
tags: Vec::new(),
|
||||
estimated_tokens: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,12 +125,56 @@ impl ToDo {
|
|||
}
|
||||
|
||||
fn has_circular_dependency(&self) -> bool {
|
||||
if self.depends_on.is_empty() {
|
||||
return false;
|
||||
// Direct self-dependency
|
||||
if self.depends_on.contains(&self.id) {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Performs full graph cycle detection given a map of todo_id -> dependencies.
|
||||
/// Returns the ID of the first todo that would participate in a cycle, or None.
|
||||
pub fn detect_cycle_in_graph(deps: &std::collections::HashMap<Uuid, Vec<Uuid>>) -> Option<Uuid> {
|
||||
let mut visited: HashSet<Uuid> = HashSet::new();
|
||||
let mut in_stack: HashSet<Uuid> = HashSet::new();
|
||||
|
||||
fn dfs(
|
||||
node: Uuid,
|
||||
deps: &std::collections::HashMap<Uuid, Vec<Uuid>>,
|
||||
visited: &mut HashSet<Uuid>,
|
||||
in_stack: &mut HashSet<Uuid>,
|
||||
) -> Option<Uuid> {
|
||||
if in_stack.contains(&node) {
|
||||
return Some(node);
|
||||
}
|
||||
if visited.contains(&node) {
|
||||
return None;
|
||||
}
|
||||
visited.insert(node);
|
||||
in_stack.insert(node);
|
||||
|
||||
if let Some(children) = deps.get(&node) {
|
||||
for child in children {
|
||||
if let Some(cycle_node) = dfs(*child, deps, visited, in_stack) {
|
||||
return Some(cycle_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
in_stack.remove(&node);
|
||||
None
|
||||
}
|
||||
|
||||
for node in deps.keys() {
|
||||
if !visited.contains(node) {
|
||||
if let Some(cycle_node) = dfs(*node, deps, &mut visited, &mut in_stack) {
|
||||
return Some(cycle_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_status(&mut self, status: ToDoStatus) -> Result<(), ValidationError> {
|
||||
let valid_transition = match (&self.status, &status) {
|
||||
(ToDoStatus::Pending, ToDoStatus::InProgress) => true,
|
||||
|
|
@ -350,4 +410,37 @@ mod tests {
|
|||
let result = todo.update(None, None, Some(0));
|
||||
assert!(matches!(result, Err(ValidationError::InvalidPriority)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_circular_dependency_self_reference() {
|
||||
let mut todo = ToDo::default();
|
||||
todo.depends_on = vec![todo.id];
|
||||
assert!(todo.has_circular_dependency());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_circular_dependency_none() {
|
||||
let todo = ToDo::default();
|
||||
assert!(!todo.has_circular_dependency());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_detect_cycle_in_graph_simple_cycle() {
|
||||
let id_a = Uuid::new_v4();
|
||||
let id_b = Uuid::new_v4();
|
||||
let mut deps = std::collections::HashMap::new();
|
||||
deps.insert(id_a, vec![id_b]);
|
||||
deps.insert(id_b, vec![id_a]);
|
||||
assert!(ToDo::detect_cycle_in_graph(&deps).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_detect_cycle_in_graph_no_cycle() {
|
||||
let id_a = Uuid::new_v4();
|
||||
let id_b = Uuid::new_v4();
|
||||
let mut deps = std::collections::HashMap::new();
|
||||
deps.insert(id_a, vec![id_b]);
|
||||
deps.insert(id_b, vec![]);
|
||||
assert!(ToDo::detect_cycle_in_graph(&deps).is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue