Add multi-item Jira ticket creation from Ivanti Queue

Select multiple queue items and create a single consolidated Jira ticket
with aggregated summary and description. Adds multi-select mode with
checkboxes, floating action bar, consolidation modal, and junction table
to track which queue items contributed to each ticket.

- Migration: jira_ticket_queue_items junction table
- POST /api/jira-tickets/:id/queue-items endpoint
- GET /api/ivanti/todo-queue/ticket-links endpoint
- ConsolidationModal component with aggregation logic
- IvantiTodoQueuePage with selection mode and ticket link badges
- Pure utility functions for summary/description generation
- 34 tests passing (backend + frontend)
This commit is contained in:
Jordan Ramos
2026-05-22 11:12:45 -06:00
parent 704432788c
commit 6b805ee633
10 changed files with 2281 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
// Migration: Add multi-item Jira ticket junction table
// - Creates jira_ticket_queue_items table linking jira_tickets to ivanti_todo_queue items
// - Adds UNIQUE constraint on (jira_ticket_id, queue_item_id)
// - Adds indexes on queue_item_id and jira_ticket_id
// Idempotent — safe to run multiple times.
const pool = require('../db');
async function run() {
console.log('Starting multi-item Jira ticket migration...');
// Verify prerequisite tables exist
const { rows: jiraTable } = await pool.query(`
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'jira_tickets'
`);
if (jiraTable.length === 0) {
console.error('✗ jira_tickets table does not exist. Cannot proceed.');
process.exit(1);
}
console.log('✓ jira_tickets table exists');
const { rows: queueTable } = await pool.query(`
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'ivanti_todo_queue'
`);
if (queueTable.length === 0) {
console.error('✗ ivanti_todo_queue table does not exist. Cannot proceed.');
process.exit(1);
}
console.log('✓ ivanti_todo_queue table exists');
// Create junction table
await pool.query(`
CREATE TABLE IF NOT EXISTS jira_ticket_queue_items (
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(),
UNIQUE (jira_ticket_id, queue_item_id)
)
`);
console.log('✓ jira_ticket_queue_items table created (or already exists)');
// Add index on queue_item_id for efficient lookup of tickets by queue item
await pool.query(`
CREATE INDEX IF NOT EXISTS idx_jira_ticket_queue_items_queue_item
ON jira_ticket_queue_items(queue_item_id)
`);
console.log('✓ queue_item_id index created (or already exists)');
// Add index on jira_ticket_id for efficient lookup of queue items by ticket
await pool.query(`
CREATE INDEX IF NOT EXISTS idx_jira_ticket_queue_items_ticket
ON jira_ticket_queue_items(jira_ticket_id)
`);
console.log('✓ jira_ticket_id index created (or already exists)');
console.log('Migration complete.');
process.exit(0);
}
run().catch(err => {
console.error('Migration failed:', err.message);
process.exit(1);
});

View File

@@ -23,6 +23,7 @@ const POSTGRES_MIGRATIONS = [
'add_compliance_item_history.js',
'add_jira_sync_columns_pg.js',
'add_flexible_jira_ticket_creation.js',
'add_multi_item_jira_ticket.js',
];
async function runAll() {