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:
Jordan Ramos
2026-05-22 13:44:25 -06:00
parent 2edf6228ff
commit c19d549ae8
5 changed files with 87 additions and 35 deletions

View File

@@ -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 = [];