Show raw Jira status everywhere instead of mapping to Open/In Progress/Closed
- Drop CHECK constraint on jira_tickets.status to allow any status string - Store raw Jira status directly in status column during sync (remove mapJiraStatusToLocal) - Remove VALID_TICKET_STATUSES validation on create/update endpoints - Remove separate Jira Status column from table (status IS the Jira status now) - Update frontend status badges to color-code dynamically based on status category - Update Open Tickets widget and CVE detail view to use isClosedStatus() helper - Make filter dropdown dynamic based on actual ticket statuses - Add migration script for dropping the constraint on other deployments
This commit is contained in:
@@ -126,7 +126,7 @@ CREATE TABLE IF NOT EXISTS jira_tickets (
|
||||
ticket_key TEXT NOT NULL,
|
||||
url TEXT,
|
||||
summary TEXT,
|
||||
status TEXT DEFAULT 'Open' CHECK (status IN ('Open', 'In Progress', 'Closed')),
|
||||
status TEXT DEFAULT 'Open',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
18
backend/migrations/drop_jira_status_check_constraint.js
Normal file
18
backend/migrations/drop_jira_status_check_constraint.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// Migration: Drop CHECK constraint on jira_tickets.status
|
||||
// Allows storing raw Jira status strings (e.g. "Approval/Handoff", "Prioritizing")
|
||||
// instead of mapping to the limited set of Open/In Progress/Closed.
|
||||
// Idempotent — safe to run multiple times.
|
||||
|
||||
const pool = require('../db');
|
||||
|
||||
async function run() {
|
||||
console.log('[Migration] Dropping jira_tickets_status_check constraint...');
|
||||
await pool.query(`ALTER TABLE jira_tickets DROP CONSTRAINT IF EXISTS jira_tickets_status_check`);
|
||||
console.log('✓ jira_tickets status CHECK constraint dropped (or did not exist)');
|
||||
await pool.end();
|
||||
}
|
||||
|
||||
run().catch(err => {
|
||||
console.error('Migration failed:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -18,7 +18,6 @@ const jiraApi = require('../helpers/jiraApi');
|
||||
|
||||
// Validation helpers
|
||||
const CVE_ID_PATTERN = /^CVE-\d{4}-\d{4,}$/;
|
||||
const VALID_TICKET_STATUSES = ['Open', 'In Progress', 'Closed'];
|
||||
|
||||
function isValidCveId(cveId) {
|
||||
return typeof cveId === 'string' && CVE_ID_PATTERN.test(cveId);
|
||||
@@ -354,12 +353,11 @@ function createJiraTicketsRouter() {
|
||||
|
||||
const jiraStatus = issue.fields.status ? issue.fields.status.name : null;
|
||||
const jiraSummary = issue.fields.summary || ticket.summary;
|
||||
const localStatus = mapJiraStatusToLocal(jiraStatus);
|
||||
|
||||
try {
|
||||
await pool.query(
|
||||
`UPDATE jira_tickets SET summary = $1, status = $2, jira_status = $3, last_synced_at = NOW(), updated_at = NOW() WHERE id = $4`,
|
||||
[jiraSummary, localStatus, jiraStatus, ticket.id]
|
||||
[jiraSummary, jiraStatus || 'Open', jiraStatus, ticket.id]
|
||||
);
|
||||
results.synced++;
|
||||
} catch (dbErr) {
|
||||
@@ -435,11 +433,10 @@ function createJiraTicketsRouter() {
|
||||
const issue = result.data;
|
||||
const jiraStatus = issue.fields.status ? issue.fields.status.name : null;
|
||||
const jiraSummary = issue.fields.summary || ticket.summary;
|
||||
const localStatus = mapJiraStatusToLocal(jiraStatus);
|
||||
|
||||
await pool.query(
|
||||
`UPDATE jira_tickets SET summary = $1, status = $2, jira_status = $3, last_synced_at = NOW(), updated_at = NOW() WHERE id = $4`,
|
||||
[jiraSummary, localStatus, jiraStatus, id]
|
||||
[jiraSummary, jiraStatus || 'Open', jiraStatus, id]
|
||||
);
|
||||
|
||||
logAudit({
|
||||
@@ -563,8 +560,8 @@ function createJiraTicketsRouter() {
|
||||
if (summary && (typeof summary !== 'string' || summary.length > 500)) {
|
||||
return res.status(400).json({ error: 'Summary must be under 500 characters.' });
|
||||
}
|
||||
if (status && !VALID_TICKET_STATUSES.includes(status)) {
|
||||
return res.status(400).json({ error: `Status must be one of: ${VALID_TICKET_STATUSES.join(', ')}` });
|
||||
if (status && typeof status !== 'string') {
|
||||
return res.status(400).json({ error: 'Status must be a string.' });
|
||||
}
|
||||
|
||||
const ticketStatus = status || 'Open';
|
||||
@@ -632,8 +629,8 @@ function createJiraTicketsRouter() {
|
||||
if (summary !== undefined && summary !== null && (typeof summary !== 'string' || summary.length > 500)) {
|
||||
return res.status(400).json({ error: 'Summary must be under 500 characters.' });
|
||||
}
|
||||
if (status !== undefined && !VALID_TICKET_STATUSES.includes(status)) {
|
||||
return res.status(400).json({ error: `Status must be one of: ${VALID_TICKET_STATUSES.join(', ')}` });
|
||||
if (status !== undefined && typeof status !== 'string') {
|
||||
return res.status(400).json({ error: 'Status must be a string.' });
|
||||
}
|
||||
|
||||
const fields = [];
|
||||
|
||||
Reference in New Issue
Block a user