Add vendor-specific issue type dropdown for Jira ticket creation

When the Project Key field contains a vendor project key (e.g. AA_VECIMA),
the Issue Type dropdown switches from STEAM types (Story, Epic, Program,
Project, Reservation, Automation Maintenance) to vendor types (Epic, Story,
Task, Defect, Production Defect/Incident Fix, New Feature, Spike, Release
Candidate, Documentation).

- Add VENDOR_PROJECT_KEYS, VENDOR_ISSUE_TYPES, STEAM_ISSUE_TYPES constants
- Add isVendorProject() and getIssueTypesForProject() pure functions
- Update JiraPage modal with context-aware dropdown and reset on switch
- Update Ivanti queue modal with project_key and issue_type fields
- Add property-based tests for determination logic and state transitions
This commit is contained in:
Jordan Ramos
2026-05-27 15:08:08 -06:00
parent 56e3f5f973
commit 04eb21a7d3
3 changed files with 301 additions and 9 deletions

View File

@@ -4,6 +4,7 @@ import { useAuth } from '../../contexts/AuthContext';
import ConsolidationModal from '../ConsolidationModal';
import { generateConsolidatedSummary, generateConsolidatedDescription, extractFirstCve, extractCommonVendor } from '../../utils/jiraConsolidation';
import { groupQueueItems } from '../../utils/queueGrouping';
import { VENDOR_PROJECT_KEYS, VENDOR_ISSUE_TYPES, STEAM_ISSUE_TYPES, isVendorProject, getIssueTypesForProject } from './JiraPage';
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
@@ -300,7 +301,7 @@ export default function IvantiTodoQueuePage() {
// Single-item Jira creation modal state (Requirement 2.4)
const [showSingleJiraModal, setShowSingleJiraModal] = useState(false);
const [singleJiraItem, setSingleJiraItem] = useState(null);
const [singleJiraForm, setSingleJiraForm] = useState({ cve_id: '', vendor: '', summary: '', description: '', source_context: 'ivanti_queue' });
const [singleJiraForm, setSingleJiraForm] = useState({ cve_id: '', vendor: '', summary: '', description: '', source_context: 'ivanti_queue', project_key: '', issue_type: '' });
const [singleJiraError, setSingleJiraError] = useState(null);
const [singleJiraSaving, setSingleJiraSaving] = useState(false);
const [singleJiraSummaryError, setSingleJiraSummaryError] = useState(null);
@@ -480,6 +481,8 @@ export default function IvantiTodoQueuePage() {
summary: generateConsolidatedSummary(items),
description: generateConsolidatedDescription(items),
source_context: 'ivanti_queue',
project_key: '',
issue_type: '',
});
setSingleJiraError(null);
setSingleJiraSummaryError(null);
@@ -966,6 +969,30 @@ export default function IvantiTodoQueuePage() {
onChange={e => setSingleJiraForm(f => ({ ...f, description: e.target.value }))}
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem' }}>
<div>
<label style={{ fontSize: '0.75rem', color: '#94A3B8', display: 'block', marginBottom: '0.25rem' }}>Project Key (optional)</label>
<input style={STYLES.input} placeholder="Uses .env default" value={singleJiraForm.project_key} onChange={e => {
const newKey = e.target.value.toUpperCase();
const wasVendor = isVendorProject(singleJiraForm.project_key, VENDOR_PROJECT_KEYS);
const isNowVendor = isVendorProject(newKey, VENDOR_PROJECT_KEYS);
setSingleJiraForm(f => ({
...f,
project_key: newKey,
issue_type: (wasVendor !== isNowVendor) ? '' : f.issue_type,
}));
}} />
</div>
<div>
<label style={{ fontSize: '0.75rem', color: '#94A3B8', display: 'block', marginBottom: '0.25rem' }}>Issue Type</label>
<select style={{ ...STYLES.input, cursor: 'pointer' }} value={singleJiraForm.issue_type} onChange={e => setSingleJiraForm(f => ({ ...f, issue_type: e.target.value }))}>
<option value="">Story (default)</option>
{getIssueTypesForProject(singleJiraForm.project_key, VENDOR_PROJECT_KEYS, VENDOR_ISSUE_TYPES, STEAM_ISSUE_TYPES).map(type => (
<option key={type} value={type}>{type}</option>
))}
</select>
</div>
</div>
<button
style={{ ...STYLES.btnSuccess, justifyContent: 'center', marginTop: '0.5rem' }}
onClick={submitSingleJira}