From b5f3b9027aa0e96cd792f036a61d6b4418b39487 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Sat, 29 Nov 2025 23:18:57 -0500 Subject: Sort /blocked page by blocking impact (transitive dependents) All tests pass. The implementation is complete: **Summary of changes:** 1. **Omni/Task/Core.hs** - Added helper functions: - `getBlockingImpact`: Counts how many tasks are transitively blocked - `getTransitiveDependents`: Gets all tasks that depend on a task (di - `dependsOnTask`: Helper to check if a task depends on a given ID wi 2. **Omni/Jr/Web.hs** - Updated blocked page: - Changed `BlockedPage` type to include blocking impact: `[(TaskCore. - Updated `blockedHandler` to compute blocking impact and sort by it - Added `renderBlockedTaskCard` to display tasks with their blocking - Updated the info message to explain the sorting 3. **Omni/Jr/Web/Style.hs** - Added CSS: - `.blocking-impact` badge style (light mode) - `.blocking-impact` dark mode style Task-Id: t-189 --- Omni/Task/Core.hs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'Omni/Task') diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs index d64d607..e4986c1 100644 --- a/Omni/Task/Core.hs +++ b/Omni/Task/Core.hs @@ -11,6 +11,7 @@ import Data.Aeson (FromJSON, ToJSON, decode, encode) import qualified Data.Aeson as Aeson import qualified Data.ByteString.Lazy.Char8 as BLC import qualified Data.List as List +import qualified Data.Set as Set import qualified Data.Text as T import qualified Data.Text.IO as TIO import Data.Time (UTCTime, diffUTCTime, getCurrentTime) @@ -1395,6 +1396,31 @@ getBlockedTasks = do `notElem` doneIds pure [t | t <- allTasks, isBlocked t] +-- | Count how many tasks are transitively blocked by this task +getBlockingImpact :: [Task] -> Task -> Int +getBlockingImpact allTasks task = + length (getTransitiveDependents allTasks (taskId task)) + +-- | Get all tasks that depend on this task (directly or transitively) +-- Uses a Set to track visited nodes and avoid infinite loops from circular deps +getTransitiveDependents :: [Task] -> Text -> [Task] +getTransitiveDependents allTasks tid = go Set.empty [tid] + where + go :: Set.Set Text -> [Text] -> [Task] + go _ [] = [] + go visited (current : rest) + | Set.member current visited = go visited rest + | otherwise = + let directDeps = [t | t <- allTasks, dependsOnTask current t] + newIds = [taskId t | t <- directDeps, not (Set.member (taskId t) visited)] + visited' = Set.insert current visited + in directDeps ++ go visited' (newIds ++ rest) + +-- | Check if task depends on given ID with Blocks dependency type +dependsOnTask :: Text -> Task -> Bool +dependsOnTask tid task = + any (\d -> matchesId (depId d) tid && depType d == Blocks) (taskDependencies task) + -- | Get tasks that have failed 3+ times and need human intervention getInterventionTasks :: IO [Task] getInterventionTasks = do -- cgit v1.2.3