diff options
| author | Ben Sima <ben@bsima.me> | 2025-11-21 04:02:19 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bsima.me> | 2025-11-21 04:02:19 -0500 |
| commit | 3a6d471da72ce927aaeb65d5c344641c8146ec05 (patch) | |
| tree | fd7b413c218db369de24de192d2850ba5b3cfe2d /Omni | |
| parent | 62728ac31f4a5a86678bf8830bf76b7ebf05436f (diff) | |
| parent | fedb1a8343d04596e5dc0f69b82d6387005430e6 (diff) | |
Merge live into task/t-1rcIwc8
Amp-Thread-ID:
https://ampcode.com/threads/T-7109f8d0-feb4-4a24-bc4b-37743227e2cb
Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'Omni')
| -rw-r--r-- | Omni/Task.hs | 36 | ||||
| -rw-r--r-- | Omni/Task/Core.hs | 46 |
2 files changed, 65 insertions, 17 deletions
diff --git a/Omni/Task.hs b/Omni/Task.hs index 8151487..f3baf15 100644 --- a/Omni/Task.hs +++ b/Omni/Task.hs @@ -45,6 +45,7 @@ Usage: task update <id> <status> [--json] task deps <id> [--json] task tree [<id>] [--json] + task progress <id> [--json] task stats [--epic=<id>] [--json] task export [--flush] task import -i <file> @@ -61,6 +62,7 @@ Commands: update Update task status deps Show dependency tree tree Show task tree (epics with children, or all epics if no ID given) + progress Show progress for an epic stats Show task statistics export Export and consolidate tasks to JSONL import Import tasks from JSONL file @@ -232,6 +234,13 @@ move args tree <- getTaskTree maybeId outputJson tree else showTaskTree maybeId + | args `Cli.has` Cli.command "progress" = do + tid <- getArgText args "id" + if isJsonMode args + then do + progress <- getTaskProgress tid + outputJson progress + else showTaskProgress tid | args `Cli.has` Cli.command "stats" = do maybeEpic <- case Cli.getArg args (Cli.longOption "epic") of Nothing -> pure Nothing @@ -340,20 +349,21 @@ unitTests = child1 <- createTask "Child 1" WorkTask (Just (taskId parent)) Nothing P2 [] -- Manually create a task with .3 suffix to simulate a gap (or deleted task) let child3Id = taskId parent <> ".3" - child3 = Task - { taskId = child3Id, - taskTitle = "Child 3", - taskType = WorkTask, - taskParent = Just (taskId parent), - taskNamespace = Nothing, - taskStatus = Open, - taskPriority = P2, - taskDependencies = [], - taskCreatedAt = taskCreatedAt child1, - taskUpdatedAt = taskUpdatedAt child1 - } + child3 = + Task + { taskId = child3Id, + taskTitle = "Child 3", + taskType = WorkTask, + taskParent = Just (taskId parent), + taskNamespace = Nothing, + taskStatus = Open, + taskPriority = P2, + taskDependencies = [], + taskCreatedAt = taskCreatedAt child1, + taskUpdatedAt = taskUpdatedAt child1 + } saveTask child3 - + -- Create a new child, it should get .4, not .2 child4 <- createTask "Child 4" WorkTask (Just (taskId parent)) Nothing P2 [] taskId child4 Test.@?= taskId parent <> ".4" diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs index 2ff7302..622e8de 100644 --- a/Omni/Task/Core.hs +++ b/Omni/Task/Core.hs @@ -57,6 +57,14 @@ data DependencyType | Related -- Soft relationship, doesn't block deriving (Show, Eq, Generic) +data TaskProgress = TaskProgress + { progressTaskId :: Text, + progressTotal :: Int, + progressCompleted :: Int, + progressPercentage :: Int + } + deriving (Show, Eq, Generic) + instance ToJSON TaskType instance FromJSON TaskType @@ -81,6 +89,10 @@ instance ToJSON Task instance FromJSON Task +instance ToJSON TaskProgress + +instance FromJSON TaskProgress + -- Get the tasks database file path (use test file if TASK_TEST_MODE is set) getTasksFilePath :: IO FilePath getTasksFilePath = do @@ -309,6 +321,32 @@ getDependencyTree tid = do deps = filter (\t -> taskId t `elem` depIds) allTasks in task : concatMap (collectDeps allTasks) deps +-- Get task progress +getTaskProgress :: Text -> IO TaskProgress +getTaskProgress tid = do + tasks <- loadTasks + -- Verify task exists (optional, but good for error handling) + case filter (\t -> taskId t == tid) tasks of + [] -> panic "Task not found" + _ -> do + let children = filter (\child -> taskParent child == Just tid) tasks + total = length children + completed = length <| filter (\child -> taskStatus child == Done) children + percentage = if total == 0 then 0 else (completed * 100) `div` total + pure + TaskProgress + { progressTaskId = tid, + progressTotal = total, + progressCompleted = completed, + progressPercentage = percentage + } + +-- Show task progress +showTaskProgress :: Text -> IO () +showTaskProgress tid = do + progress <- getTaskProgress tid + putText <| "Progress for " <> tid <> ": " <> T.pack (show (progressCompleted progress)) <> "/" <> T.pack (show (progressTotal progress)) <> " (" <> T.pack (show (progressPercentage progress)) <> "%)" + -- Show dependency tree for a task showDependencyTree :: Text -> IO () showDependencyTree tid = do @@ -521,10 +559,10 @@ instance FromJSON TaskStats getTaskStats :: Maybe Text -> IO TaskStats getTaskStats maybeEpicId = do allTasks <- loadTasks - + targetTasks <- case maybeEpicId of Nothing -> pure allTasks - Just epicId -> + Just epicId -> case filter (\t -> taskId t == epicId) allTasks of [] -> panic "Epic not found" _ -> pure <| getAllDescendants allTasks epicId @@ -533,7 +571,7 @@ getTaskStats maybeEpicId = do let readyIds = map taskId globalReady -- Filter ready tasks to only include those in our target set readyCount = length <| filter (\t -> taskId t `elem` readyIds) targetTasks - + tasks = targetTasks total = length tasks open = length <| filter (\t -> taskStatus t == Open) tasks @@ -573,7 +611,7 @@ getTaskStats maybeEpicId = do getAllDescendants :: [Task] -> Text -> [Task] getAllDescendants allTasks parentId = let children = filter (\t -> taskParent t == Just parentId) allTasks - in children ++ concatMap (\t -> getAllDescendants allTasks (taskId t)) children + in children ++ concatMap (getAllDescendants allTasks <. taskId) children -- Show task statistics (human-readable) showTaskStats :: Maybe Text -> IO () |
