New specs: archer-template-library, ccp-metrics-view-restructure, compliance-list-stale-after-sidebar-edit, compliance-metric-estimated-resolution-date, compliance-remediation-display-fix, flexible-jira-ticket-creation, forecast-burndown-chart, granite-loader-export, ivanti-queue-clear-completed-fix, multi-item-jira-ticket, queue-collapsible-sections, vendor-issue-type-dropdown New steering: archer-template-gen.md Updated: migration-registration-check hook, remediation-plan-history spec, gitlab-workflow, tech, versioning steering files
125 lines
7.0 KiB
Markdown
125 lines
7.0 KiB
Markdown
# 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"] }
|
||
]
|
||
}
|
||
```
|