# Implementation Plan: Multi-Item Jira Ticket Creation ## Overview This plan implements multi-select on the Ivanti Queue page and a consolidation modal that creates a single Jira ticket from multiple selected queue items. The implementation proceeds from database migration → backend junction endpoints → frontend aggregation logic → frontend selection UI → consolidation modal → ticket link badges. ## Tasks - [x] 1. Database migration for junction table - [x] 1.1 Create migration file `backend/migrations/add_multi_item_jira_ticket.js` - Verify `jira_tickets` and `ivanti_todo_queue` tables exist; exit with error if missing - Create `jira_ticket_queue_items` table with columns: id (SERIAL PRIMARY KEY), jira_ticket_id (INTEGER NOT NULL REFERENCES jira_tickets(id)), queue_item_id (INTEGER NOT NULL REFERENCES ivanti_todo_queue(id)), created_at (TIMESTAMPTZ DEFAULT NOW()) - Add UNIQUE constraint on (jira_ticket_id, queue_item_id) - Add index on queue_item_id - Add index on jira_ticket_id - Ensure full idempotency — safe to run multiple times - _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5_ - [x] 2. Backend junction table endpoints - [x] 2.1 Add `POST /api/jira-tickets/:id/queue-items` endpoint in `backend/routes/jiraTickets.js` - Validate `queue_item_ids` is a non-empty array of integers - Verify the jira_ticket exists - Verify all referenced queue items exist - Insert rows into `jira_ticket_queue_items` with ON CONFLICT DO NOTHING - Return 201 with linked_count - Require Admin or Standard_User group - _Requirements: 5.3, 6.1, 6.2_ - [x] 2.2 Add `GET /api/ivanti/todo-queue/ticket-links` endpoint in `backend/routes/ivantiTodoQueue.js` - Join `jira_ticket_queue_items` with `jira_tickets` to get ticket_key and url - Filter by queue items belonging to the authenticated user - Return a map of queue_item_id → { ticket_key, jira_url } - _Requirements: 6.3, 6.4_ - [x] 3. Frontend aggregation utility functions - [x] 3.1 Create `frontend/src/utils/jiraConsolidation.js` with pure functions - `generateConsolidatedSummary(items)` — format: `[N findings] vendor - title`, truncated to 255 chars - `generateConsolidatedDescription(items)` — structured description grouped by vendor - `extractFirstCve(items)` — first CVE from first item with non-empty cves_json - `extractCommonVendor(items)` — common vendor if all same, empty string otherwise - _Requirements: 3.1, 3.2, 4.1, 4.2, 4.3, 4.4, 4.6, 4.7_ - [ ]* 3.2 Write property tests for aggregation functions (Properties 1–6) - **Property 1: Summary format and truncation** — starts with `[N findings]`, at most 255 chars, contains correct vendor label - **Property 2: Description includes all items** — every item's finding_title appears in output - **Property 3: Description groups by vendor** — items with same vendor are contiguous - **Property 4: Description header contains count** — output contains the item count - **Property 5: First CVE extraction** — returns first CVE from first item with CVEs, or empty string - **Property 6: Common vendor extraction** — returns vendor when all same, empty when different - **Validates: Requirements 3.1, 3.2, 4.1, 4.2, 4.3, 4.4, 4.6, 4.7** - File: `backend/__tests__/jira-consolidation-aggregation.property.test.js` - [x] 4. Frontend multi-select mode on Ivanti Queue page - [x] 4.1 Add selection mode toggle and checkbox UI to `frontend/src/components/pages/IvantiQueuePage.js` - Add "Select" toggle button to page toolbar - Show checkboxes on each queue item row when selection mode active - Add "Select All" checkbox in table header - Display selection count indicator - Clear selections when selection mode deactivated - Preserve selections on scroll/re-render within same session - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6_ - [x] 4.2 Add floating action bar with "Create Jira Ticket" button - Render floating bar when selection mode active and at least 1 item selected - Disable "Create Jira Ticket" button when no items selected - Route to existing single-item modal when exactly 1 item selected - Route to new Consolidation Modal when 2+ items selected - _Requirements: 2.1, 2.2, 2.3, 2.4_ - [x] 5. Frontend Consolidation Modal - [x] 5.1 Create `frontend/src/components/ConsolidationModal.js` - Accept selected queue items as props - Call aggregation functions to pre-populate summary, description, cve_id, vendor - Lock source_context to `ivanti_queue` (read-only display) - Display item count in modal header/subtitle - Render scrollable preview list of selected items (finding_title + hostname) - Allow removing individual items from selection (minimum 2 required) - Disable submit and show message when fewer than 2 items remain - Editable summary field with required validation (max 255 chars) - Editable description textarea - Editable CVE ID and Vendor fields (optional) - On submit: POST to create-in-jira, then POST to junction endpoint - On success: close modal, show success toast, trigger queue page refresh - On error: display error message, preserve form values - _Requirements: 3.1, 3.2, 3.3, 3.4, 4.1, 4.5, 5.1, 5.2, 5.4, 5.5, 8.1, 8.2, 8.3, 8.4, 8.5_ - [x] 6. Frontend ticket link badges on queue items - [x] 6.1 Fetch and display ticket link badges on Ivanti Queue page - On page load, fetch `GET /api/ivanti/todo-queue/ticket-links` - For each queue item with an association, render a ticket key badge (e.g., "VULN-789") - Clicking badge opens Jira URL in new tab - Refresh links after successful consolidated ticket creation - _Requirements: 6.3, 6.4, 6.5_ - [ ] 7. Backend property test for junction insert invariant - [ ]* 7.1 Write property test for junction row count (Property 7) - **Property 7: Junction table row count equals selected item count** — for N items, exactly N rows inserted - **Validates: Requirements 5.3, 6.2** - File: `backend/__tests__/jira-consolidation-junction.property.test.js` - [x] 8. Final checkpoint - Ensure all tests pass, ask the user if questions arise. ## Notes - Tasks marked with `*` are optional property-based tests that can be skipped for faster MVP - The existing `POST /api/jira-tickets/create-in-jira` endpoint is reused without modification - The aggregation functions are pure and extracted into a utility module for easy testing - The junction table insert happens as a second request after ticket creation — if it fails, the ticket still exists in Jira (partial success scenario handled with warning) - The Consolidation Modal is a separate component from the existing Creation Modal to avoid overcomplicating the single-item flow ## Task Dependency Graph ```json { "waves": [ { "id": 0, "tasks": ["1.1"] }, { "id": 1, "tasks": ["2.1", "2.2", "3.1"] }, { "id": 2, "tasks": ["3.2", "4.1"] }, { "id": 3, "tasks": ["4.2", "5.1"] }, { "id": 4, "tasks": ["6.1", "7.1"] } ] } ```