Skip to content

feat(tasks): add task-to-task relations (blocks / duplicates / relates)#221

Merged
parth0025 merged 2 commits into
stagingfrom
feat/task-relations
Jun 10, 2026
Merged

feat(tasks): add task-to-task relations (blocks / duplicates / relates)#221
parth0025 merged 2 commits into
stagingfrom
feat/task-relations

Conversation

@parth0025

@parth0025 parth0025 commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds task-to-task relations ("Linked Tasks"): any two tasks in a project can be linked as blocks / blocked_by / duplicates / duplicated_by / relates_to, managed from a new Linked Tasks section in the task detail view.

Dependencies between tasks are currently invisible in AlianHub β€” there is no way to record that one task blocks another or duplicates an existing one. This is one of the first features evaluators coming from Jira / Linear / Plane look for.

Motivation & Context

  • Top item in the Tier 1 roadmap gaps identified in the AlianHub-vs-Plane code comparison (June 2026): relations/dependencies were the biggest visible work-item-model gap.
  • No existing mechanism: confirmed no dependency/relation concept anywhere in Modules/ before this change.

Closes / Relates to: roadmap item "Task relations & dependencies"

Proposed Solution

Backend (58f6375)

  • POST /api/v2/tasks/relations with an explicit action allowlist (add | remove | list) and companyId taken from the verified header β€” same security convention as POST /api/v2/tasks/bulk.
  • New relations mixin on the taskMongo class. A link is stored on both task documents with the inverse type (blocks <-> blocked_by, duplicates <-> duplicated_by, relates_to symmetric), so either side reads its links without a join. One link per task pair.
  • Socket emits for both updated tasks (live board/detail sync) + activity history written to both tasks (messages use TaskKeys, not raw names).
  • Pure rules module relationRules.js (types, inverse map, validators) shared by route, mixin, and tests.
  • relations declared on the strict tasks schema (utils/mongo-handler/schema.js) β€” required since P1-SEC-11 strict mode silently drops undeclared update paths.

Frontend (8a3849c)

  • LinkedTasks.vue rendered below Subtasks in the task detail for every task: rows show relation label + TaskKey + name + status chip, with hover X to unlink.
  • "+ Link Task" flow: relation-type dropdown + debounced search of same-project tasks (existing /api/v1/task/find aggregation; ProjectID wrapped in the objId marker because aggregate $match skips mongoose casting).
  • Live refresh when another client links/unlinks (watches projectData/gettaskDetailData).
  • 15 new en locale keys under Projects.*; the other nine languages fall back to English per the i18n fallbackLocale config.

Scope of Change

  • Frontend
  • Backend
  • API
  • Database
  • Infrastructure
  • Documentation

Testing Strategy

  • Unit tests β€” 17 new tests for the relation rules (tests/task-relations-rules.test.js); full suite 45/45 green
  • Integration tests β€” full matrix run against a real local MongoDB through the app data layer (30/30 checks, see test report comment)
  • Manual testing β€” verified in the browser on a real project (search, add, inverse link, activity log entries, toasts)
  • Not tested

Steps to verify:

  1. Open any task -> Task Details tab -> "Linked Tasks" section below Subtasks.
  2. Click "+ Link Task", pick "Blocked by", search another task by name, click a result β€” toast confirms, row appears.
  3. Open the other task β€” it shows the inverse ("Blocks") row automatically; both Activity Logs have entries.
  4. Remove via X on either side β€” the link disappears from both tasks; a second browser tab updates live without refresh.
  5. Edge cases: self-linking and duplicate links are rejected with clear messages; deleted linked tasks render muted.

Breaking Changes

  • Yes
  • No

Purely additive: new endpoint, new schema field (relations, default []), new UI section. No existing behavior changed.

Documentation Updates

  • README updated
  • API docs updated
  • Inline comments added
  • Not required

Swagger entry for the new endpoint can follow in a docs PR.

Preflight Checklist

  • I have read and understood the Contributing Guidelines for this project.
  • I agree to follow the Code of Conduct that this project adheres to.
  • This PR is submitted against the correct base branch (staging).
  • The scope of this PR is clearly defined and limited.
  • I have performed a self-review of my changes.

πŸ“ Additional Notes

  • Notifications on link/unlink and a "this task is blocked" warning on status change are deliberate follow-ups once this lands.
  • History entries use TaskKeys instead of task names to avoid injecting user-controlled strings into history HTML.

πŸ€– Generated with Claude Code

parth0025 and others added 2 commits June 10, 2026 12:32
Adds linked-task support across the backend:

- POST /api/v2/tasks/relations - add | remove | list actions behind an
  explicit allowlist, with companyId taken from the verified header
  (same convention as POST /api/v2/tasks/bulk)
- relations mixin on taskMongo: a link is stored on BOTH task documents
  with the inverse type (blocks <-> blocked_by, duplicates <->
  duplicated_by, relates_to is symmetric), so either side reads its
  links without a join; one link per task pair
- socket emits for both updated tasks and activity history on both
  sides (messages use TaskKeys, not raw task names)
- pure rules module (relationRules.js) shared by route, mixin, and
  tests; 17 unit tests in tests/task-relations-rules.test.js

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- new LinkedTasks.vue rendered below Subtasks: lists relations with
  type label, TaskKey, name and status chip; add-link flow with a
  relation-type dropdown and debounced same-project task search; live
  refresh via the projectData/gettaskDetailData socket watcher
- task search wraps ProjectID in the backend objId marker (the field
  is an ObjectId and aggregate $match skips mongoose casting)
- declare `relations` on the strict tasks schema so $push persists
  (P1-SEC-11 strict mode silently drops undeclared update paths)
- 15 new en locale keys under Projects.*; other languages fall back
  to en per the i18n fallbackLocale config

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 09f5c964-48ec-4466-a749-9e2372a612b4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • πŸ” Trigger review
✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/task-relations

Comment @coderabbitai help to get the list of available commands and usage tips.

@parth0025 parth0025 self-assigned this Jun 10, 2026
@parth0025 parth0025 requested a review from joshishiv4 June 10, 2026 09:03
@parth0025

Copy link
Copy Markdown
Collaborator Author

πŸ§ͺ Integration test report β€” full feature matrix

Ran against a real local MongoDB through the app''s own data layer (taskMongo mixin + MongoDbCrudOpration, the same code path the endpoint dispatches to). The test created three throwaway RELTEST-* tasks in the test project, exercised the complete matrix, then deleted every trace of itself (3 tasks + 8 history rows; zero residue verified).

Result: 30/30 checks pass.

Validation & guards (8)

  • βœ… rejects missing companyId, malformed taskId, self-link, unknown relation type
  • βœ… rejects nonexistent related task and soft-deleted related task
  • βœ… rejects duplicate link β€” from both directions (one link per task pair)

Write correctness (8)

  • βœ… relations array persists on both documents (strict-schema fix confirmed working)
  • βœ… initiating side stores blocks, related side stores the inverse blocked_by
  • βœ… entries point at each other (taskId cross-check), createdBy recorded, createdAt is a BSON Date
  • βœ… relates_to stored symmetrically on both sides

Read correctness (5)

  • βœ… list returns the link with the related task summary enriched (TaskKey/name/status)
  • βœ… human labels correct on both sides ("blocks" / "is blocked by")
  • βœ… list empty after final removal

History (1)

  • βœ… activity entries written to both tasks on every add and remove β€” 8/8 expected entries found (Key: Task_Relation)

Isolation & removal (8)

  • βœ… company scoping: the same call against another database cannot see the task
  • βœ… remove (from the inverse side) clears both documents; re-remove rejects with "not linked"

Also covered

  • Manual browser testing on the same instance: search picker (incl. the ObjectId objId marker fix), add flow, success toast, activity log rendering β€” verified by @Harmit on tasks WPTP-1/WPTP-2.
  • Unit level: 17 rule tests in tests/task-relations-rules.test.js; full suite 45/45.

πŸ€– Generated with Claude Code

@parth0025 parth0025 merged commit 866d8fa into staging Jun 10, 2026
8 of 14 checks passed
@parth0025 parth0025 deleted the feat/task-relations branch June 10, 2026 09:27
joshishiv4 pushed a commit that referenced this pull request Jun 10, 2026
…s) (#224)

Empty release-trigger commit. The feature itself landed on main in the
#222 promotion, but the squash commit was typed chore(release), which
release-please ignores when computing releases - so no v14.2.0 release
PR was proposed. This commit restates the feature with its original
conventional message so release-please proposes v14.2.0 with a proper
changelog entry.

Refs: #221, #222

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant