summaryrefslogtreecommitdiff
path: root/Omni
diff options
context:
space:
mode:
Diffstat (limited to 'Omni')
-rw-r--r--Omni/Task.hs42
-rw-r--r--Omni/Task/Core.hs43
2 files changed, 77 insertions, 8 deletions
diff --git a/Omni/Task.hs b/Omni/Task.hs
index d1e672a..24e528b 100644
--- a/Omni/Task.hs
+++ b/Omni/Task.hs
@@ -312,7 +312,47 @@ unitTests =
ready <- getReadyTasks
-- Both should be ready since Related doesn't block
(taskId task1 `elem` map taskId ready) Test.@?= True
- (taskId task2 `elem` map taskId ready) Test.@?= True
+ (taskId task2 `elem` map taskId ready) Test.@?= True,
+ Test.unit "child task gets sequential ID" <| do
+ parent <- createTask "Parent" Epic Nothing Nothing P2 []
+ child1 <- createTask "Child 1" WorkTask (Just (taskId parent)) Nothing P2 []
+ child2 <- createTask "Child 2" WorkTask (Just (taskId parent)) Nothing P2 []
+ taskId child1 Test.@?= taskId parent <> ".1"
+ taskId child2 Test.@?= taskId parent <> ".2",
+ Test.unit "grandchild task gets sequential ID" <| do
+ parent <- createTask "Grandparent" Epic Nothing Nothing P2 []
+ child <- createTask "Parent" Epic (Just (taskId parent)) Nothing P2 []
+ grandchild <- createTask "Grandchild" WorkTask (Just (taskId child)) Nothing P2 []
+ taskId grandchild Test.@?= taskId parent <> ".1.1",
+ Test.unit "siblings of grandchild task get sequential ID" <| do
+ parent <- createTask "Grandparent" Epic Nothing Nothing P2 []
+ child <- createTask "Parent" Epic (Just (taskId parent)) Nothing P2 []
+ grandchild1 <- createTask "Grandchild 1" WorkTask (Just (taskId child)) Nothing P2 []
+ grandchild2 <- createTask "Grandchild 2" WorkTask (Just (taskId child)) Nothing P2 []
+ taskId grandchild1 Test.@?= taskId parent <> ".1.1"
+ taskId grandchild2 Test.@?= taskId parent <> ".1.2",
+ Test.unit "child ID generation skips gaps" <| do
+ parent <- createTask "Parent with gaps" Epic Nothing Nothing P2 []
+ 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
+ }
+ 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"
]
-- | Test CLI argument parsing to ensure docopt string matches actual usage
diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs
index f7b7915..98ef6f9 100644
--- a/Omni/Task/Core.hs
+++ b/Omni/Task/Core.hs
@@ -13,7 +13,8 @@ import qualified Data.ByteString.Lazy.Char8 as BLC
import qualified Data.List as List
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
-import Data.Time (UTCTime, diffTimeToPicoseconds, getCurrentTime, utctDayTime)
+import Data.Time (UTCTime, diffTimeToPicoseconds, getCurrentTime, utctDayTime, utctDay)
+import Data.Time.Calendar (toModifiedJulianDay)
import GHC.Generics ()
import System.Directory (createDirectoryIfMissing, doesFileExist)
import System.Environment (lookupEnv)
@@ -104,13 +105,41 @@ initTaskDb = do
generateId :: IO Text
generateId = do
now <- getCurrentTime
- -- Convert current time to microseconds since midnight
- let dayTime = utctDayTime now
- microseconds = diffTimeToPicoseconds dayTime `div` 1000000
- -- Convert to base62 for shorter IDs
- encoded = toBase62 (fromIntegral microseconds)
+ -- Convert current time to microseconds since epoch (using MJD)
+ let day = utctDay now
+ dayTime = utctDayTime now
+ mjd = toModifiedJulianDay day
+ micros = diffTimeToPicoseconds dayTime `div` 1000000
+ -- Combine MJD and micros to ensure uniqueness across days.
+ -- Multiplier 10^11 (100,000 seconds) is safe for any day length.
+ totalMicros = (mjd * 100000000000) + micros
+ encoded = toBase62 totalMicros
pure <| "t-" <> T.pack encoded
+-- Generate a child ID based on parent ID (e.g. "t-abc.1", "t-abc.1.2")
+-- Finds the next available sequential suffix among existing children.
+generateChildId :: Text -> IO Text
+generateChildId parentId = do
+ tasks <- loadTasks
+ -- Find the max suffix among ALL tasks that look like children (to avoid ID collisions)
+ -- We check all tasks, not just those with taskParent set, because we want to ensure
+ -- ID uniqueness even if the parent link is missing.
+ let suffixes = mapMaybe (getSuffix parentId <. taskId) tasks
+ nextSuffix = case suffixes of
+ [] -> 1
+ s -> maximum s + 1
+ pure <| parentId <> "." <> T.pack (show nextSuffix)
+
+getSuffix :: Text -> Text -> Maybe Int
+getSuffix parent childId =
+ if parent `T.isPrefixOf` childId && T.length childId > T.length parent
+ then
+ let rest = T.drop (T.length parent) childId
+ in if T.head rest == '.'
+ then readMaybe (T.unpack (T.tail rest))
+ else Nothing
+ else Nothing
+
-- Convert number to base62 (0-9, a-z, A-Z)
toBase62 :: Integer -> String
toBase62 0 = "0"
@@ -192,7 +221,7 @@ saveTask task = do
-- Create a new task
createTask :: Text -> TaskType -> Maybe Text -> Maybe Text -> Priority -> [Dependency] -> IO Task
createTask title taskType parent namespace priority deps = do
- tid <- generateId
+ tid <- maybe generateId generateChildId parent
now <- getCurrentTime
let task =
Task