feat: add Jira ticket creation modal
Release notes: create Jira tickets from the header, targeting the project and epic found in JQL, with title, dates, and assignee.
This commit is contained in:
+105
-1
@@ -408,6 +408,102 @@ function enableUpdateBtn(ticketKey) {
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
|
||||
// --- TICKET CREATION ---
|
||||
function getCreateIssueContext() {
|
||||
const jql = document.getElementById('jqlQuery').value.trim();
|
||||
const fallbackProject = document.getElementById('projectKey').value.trim().toUpperCase();
|
||||
const projectMatch = jql.match(/\bproject\s*=\s*["']?([A-Z][A-Z0-9_-]*)["']?/i);
|
||||
const epicMatch = jql.match(/(?:["']?Epic Link["']?|\bparent|cf\[\d+\])\s*=\s*["']?([A-Z][A-Z0-9_-]*-\d+)["']?/i);
|
||||
const configuredFields = parseCustomFields(document.getElementById('customFields').value.trim());
|
||||
const dateField = configuredFields.find(field => /date|go live|production/i.test(field.label)) || configuredFields[0] || null;
|
||||
|
||||
return {
|
||||
jql,
|
||||
projectKey: (projectMatch?.[1] || fallbackProject).toUpperCase(),
|
||||
epicKey: epicMatch?.[1]?.toUpperCase() || '',
|
||||
dateField
|
||||
};
|
||||
}
|
||||
|
||||
function openCreateIssueModal() {
|
||||
const username = document.getElementById('username').value.trim();
|
||||
const apiToken = document.getElementById('apiToken').value.trim();
|
||||
const context = getCreateIssueContext();
|
||||
|
||||
if (!username || !apiToken) {
|
||||
showToast('Enter your Jira credentials first.', 'error');
|
||||
return;
|
||||
}
|
||||
if (!context.projectKey) {
|
||||
showToast('Add a project to the JQL query first.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
saveSettings();
|
||||
document.getElementById('createIssueTarget').textContent = context.epicKey
|
||||
? `Project ${context.projectKey} · Epic ${context.epicKey}`
|
||||
: `Project ${context.projectKey}`;
|
||||
document.getElementById('createIssueSummary').value = '';
|
||||
document.getElementById('createIssueDueDate').value = '';
|
||||
document.getElementById('createIssueGoLiveDate').value = '';
|
||||
document.getElementById('createIssueAssignee').value = '';
|
||||
|
||||
const customDateInput = document.getElementById('createIssueGoLiveDate');
|
||||
document.getElementById('createIssueGoLiveLabel').textContent = context.dateField?.label || 'Custom Date';
|
||||
customDateInput.disabled = !context.dateField;
|
||||
customDateInput.title = context.dateField ? '' : 'Configure a custom date field in Extraction Settings';
|
||||
|
||||
document.getElementById('createIssueModal').classList.add('visible');
|
||||
document.getElementById('createIssueSummary').focus();
|
||||
}
|
||||
|
||||
function closeCreateIssueModal() {
|
||||
document.getElementById('createIssueModal').classList.remove('visible');
|
||||
}
|
||||
|
||||
async function createIssue() {
|
||||
const summary = document.getElementById('createIssueSummary').value.trim();
|
||||
if (!summary) {
|
||||
showToast('The ticket title is required.', 'error');
|
||||
document.getElementById('createIssueSummary').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const context = getCreateIssueContext();
|
||||
const btn = document.getElementById('createIssueSaveBtn');
|
||||
const originalContent = btn.innerHTML;
|
||||
btn.innerHTML = '<span class="spinner"></span> Creating...';
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/issues`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
username: document.getElementById('username').value.trim(),
|
||||
apiToken: document.getElementById('apiToken').value.trim(),
|
||||
projectKey: context.projectKey,
|
||||
jql: context.jql,
|
||||
summary,
|
||||
dueDate: document.getElementById('createIssueDueDate').value || null,
|
||||
goLiveDate: document.getElementById('createIssueGoLiveDate').value || null,
|
||||
goLiveFieldId: context.dateField?.id || null,
|
||||
assignee: document.getElementById('createIssueAssignee').value.trim() || null
|
||||
})
|
||||
});
|
||||
const data = await response.json();
|
||||
if (!response.ok) throw new Error(data.error || `HTTP Error ${response.status}`);
|
||||
|
||||
closeCreateIssueModal();
|
||||
showToast(`Ticket ${data.key} created!`, 'success');
|
||||
} catch (error) {
|
||||
showToast(error.message, 'error');
|
||||
} finally {
|
||||
btn.innerHTML = originalContent;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleUpdateClick(ticketKey, goLiveColName) {
|
||||
const dueInput = document.getElementById(`due-${ticketKey}`);
|
||||
const dueDate = dueInput && dueInput.value ? dueInput.value : null;
|
||||
@@ -964,7 +1060,10 @@ function clearResults() { allTickets=[]; filteredTickets=[]; document.getElement
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if((e.ctrlKey||e.metaKey)&&e.key==='Enter'){e.preventDefault();startExtraction()}
|
||||
if(e.key === 'Escape') closeDateEditModal();
|
||||
if(e.key === 'Escape') {
|
||||
closeDateEditModal();
|
||||
closeCreateIssueModal();
|
||||
}
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initTheme();
|
||||
@@ -975,5 +1074,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
modalOverlay.addEventListener('click', e => { if (e.target === modalOverlay) closeDateEditModal(); });
|
||||
}
|
||||
|
||||
const createIssueOverlay = document.getElementById('createIssueModal');
|
||||
if (createIssueOverlay) {
|
||||
createIssueOverlay.addEventListener('click', e => { if (e.target === createIssueOverlay) closeCreateIssueModal(); });
|
||||
}
|
||||
|
||||
console.log("Settings loaded from local storage.");
|
||||
});
|
||||
Reference in New Issue
Block a user