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:
@@ -134,8 +134,28 @@ const STATUS_COLORS = {
|
||||
'Open': '#F59E0B',
|
||||
'In Progress': '#0EA5E9',
|
||||
'Closed': '#10B981',
|
||||
'Done': '#10B981',
|
||||
'Resolved': '#10B981',
|
||||
'Approval/Handoff': '#8B5CF6',
|
||||
'Prioritizing': '#0EA5E9',
|
||||
'In Review': '#0EA5E9',
|
||||
'In Development': '#0EA5E9',
|
||||
'In Testing': '#0EA5E9',
|
||||
};
|
||||
|
||||
// Determine if a status represents a "closed/done" state
|
||||
function isClosedStatus(status) {
|
||||
if (!status) return false;
|
||||
const lower = status.toLowerCase();
|
||||
return ['closed', 'done', 'resolved', 'complete', 'completed', 'cancelled', 'canceled', "won't do", 'declined'].some(s => lower.includes(s));
|
||||
}
|
||||
|
||||
function getStatusColor(status) {
|
||||
if (STATUS_COLORS[status]) return STATUS_COLORS[status];
|
||||
if (isClosedStatus(status)) return '#10B981';
|
||||
return '#F59E0B';
|
||||
}
|
||||
|
||||
const SOURCE_CONTEXT_CONFIG = {
|
||||
cve: { label: 'CVE', color: '#0EA5E9' },
|
||||
archer: { label: 'Archer', color: '#8B5CF6' },
|
||||
@@ -307,7 +327,7 @@ export default function JiraPage() {
|
||||
ticket_key: issue.key,
|
||||
url: jiraUrl,
|
||||
summary: issue.summary || '',
|
||||
status: issue.status === 'Open' || issue.status === 'In Progress' || issue.status === 'Closed' ? issue.status : 'Open',
|
||||
status: issue.status || 'Open',
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
@@ -470,9 +490,8 @@ export default function JiraPage() {
|
||||
|
||||
const counts = {
|
||||
total: tickets.length,
|
||||
open: tickets.filter(t => t.status === 'Open').length,
|
||||
inProgress: tickets.filter(t => t.status === 'In Progress').length,
|
||||
closed: tickets.filter(t => t.status === 'Closed').length,
|
||||
open: tickets.filter(t => !isClosedStatus(t.status)).length,
|
||||
closed: tickets.filter(t => isClosedStatus(t.status)).length,
|
||||
};
|
||||
|
||||
|
||||
@@ -549,7 +568,6 @@ export default function JiraPage() {
|
||||
{[
|
||||
{ label: 'Total', value: counts.total, color: '#0EA5E9' },
|
||||
{ label: 'Open', value: counts.open, color: '#F59E0B' },
|
||||
{ label: 'In Progress', value: counts.inProgress, color: '#0EA5E9' },
|
||||
{ label: 'Closed', value: counts.closed, color: '#10B981' },
|
||||
].map(s => (
|
||||
<div key={s.label} style={STYLES.statCard}>
|
||||
@@ -586,9 +604,9 @@ export default function JiraPage() {
|
||||
onChange={e => setFilterStatus(e.target.value)}
|
||||
>
|
||||
<option value="">All Statuses</option>
|
||||
<option value="Open">Open</option>
|
||||
<option value="In Progress">In Progress</option>
|
||||
<option value="Closed">Closed</option>
|
||||
{[...new Set(tickets.map(t => t.status).filter(Boolean))].sort().map(s => (
|
||||
<option key={s} value={s}>{s}</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
style={{ ...STYLES.input, width: 'auto', minWidth: '140px', cursor: 'pointer' }}
|
||||
@@ -630,7 +648,6 @@ export default function JiraPage() {
|
||||
<th style={STYLES.th}>Source</th>
|
||||
<th style={STYLES.th}>Summary</th>
|
||||
<th style={STYLES.th}>Status</th>
|
||||
<th style={STYLES.th}>Jira Status</th>
|
||||
<th style={STYLES.th}>Last Synced</th>
|
||||
<th style={STYLES.th}>Actions</th>
|
||||
</tr>
|
||||
@@ -677,12 +694,11 @@ export default function JiraPage() {
|
||||
</td>
|
||||
<td style={{ ...STYLES.td, maxWidth: '250px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.summary || '-'}</td>
|
||||
<td style={STYLES.td}>
|
||||
<span style={STYLES.badge(STATUS_COLORS[t.status] || '#94A3B8')}>
|
||||
<span style={{ width: 6, height: 6, borderRadius: '50%', background: STATUS_COLORS[t.status] || '#94A3B8' }} />
|
||||
<span style={STYLES.badge(getStatusColor(t.status))}>
|
||||
<span style={{ width: 6, height: 6, borderRadius: '50%', background: getStatusColor(t.status) }} />
|
||||
{t.status}
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ ...STYLES.td, fontSize: '0.8rem', color: '#CBD5E1' }}>{t.jira_status || '-'}</td>
|
||||
<td style={{ ...STYLES.td, fontSize: '0.75rem', color: '#94A3B8' }}>
|
||||
{t.last_synced_at ? new Date(t.last_synced_at).toLocaleDateString() : 'Never'}
|
||||
</td>
|
||||
@@ -804,6 +820,9 @@ export default function JiraPage() {
|
||||
<option value="Open">Open</option>
|
||||
<option value="In Progress">In Progress</option>
|
||||
<option value="Closed">Closed</option>
|
||||
{form.status && !['Open', 'In Progress', 'Closed'].includes(form.status) && (
|
||||
<option value={form.status}>{form.status}</option>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
<button style={{ ...STYLES.btn, ...STYLES.btnSuccess, justifyContent: 'center', marginTop: '0.5rem' }} onClick={saveTicket} disabled={formSaving}>
|
||||
|
||||
Reference in New Issue
Block a user