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:
@@ -57,6 +57,19 @@ const jiraClient = axios.create({
|
||||
timeout: 60000
|
||||
});
|
||||
|
||||
function parseJqlContext(jql, fallbackProjectKey) {
|
||||
const projectMatch = jql?.match(/\bproject\s*=\s*["']?([A-Z][A-Z0-9_-]*)["']?/i);
|
||||
const parentMatch = jql?.match(/\bparent\s*=\s*["']?([A-Z][A-Z0-9_-]*-\d+)["']?/i);
|
||||
const epicLinkMatch = jql?.match(/["']?Epic Link["']?\s*=\s*["']?([A-Z][A-Z0-9_-]*-\d+)["']?/i);
|
||||
const customEpicMatch = jql?.match(/cf\[(\d+)\]\s*=\s*["']?([A-Z][A-Z0-9_-]*-\d+)["']?/i);
|
||||
|
||||
return {
|
||||
projectKey: (projectMatch?.[1] || fallbackProjectKey || '').toUpperCase(),
|
||||
epicKey: (parentMatch?.[1] || epicLinkMatch?.[1] || customEpicMatch?.[2] || '').toUpperCase(),
|
||||
epicRelation: parentMatch ? 'parent' : (customEpicMatch ? `customfield_${customEpicMatch[1]}` : (epicLinkMatch ? 'epic-link' : null))
|
||||
};
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// DEBUG : Intercepteurs de requêtes
|
||||
// ==========================================
|
||||
@@ -230,6 +243,68 @@ app.post('/api/extract', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Endpoint de création de ticket dans le projet / epic ciblé par la JQL
|
||||
app.post('/api/issues', async (req, res) => {
|
||||
const { username, apiToken, projectKey, jql, summary, dueDate, goLiveDate, goLiveFieldId, assignee } = req.body;
|
||||
|
||||
if (!username || !apiToken || !summary) {
|
||||
return res.status(400).json({ error: "Les champs username, apiToken et summary sont requis." });
|
||||
}
|
||||
|
||||
const context = parseJqlContext(jql, projectKey);
|
||||
if (!context.projectKey) {
|
||||
return res.status(400).json({ error: "Aucun projet n'a été trouvé dans la JQL." });
|
||||
}
|
||||
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${apiToken}`,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
try {
|
||||
const fields = {
|
||||
project: { key: context.projectKey },
|
||||
summary: summary.trim(),
|
||||
issuetype: { name: 'Task' }
|
||||
};
|
||||
|
||||
if (dueDate) fields.duedate = dueDate;
|
||||
if (goLiveDate && goLiveFieldId?.startsWith('customfield_')) fields[goLiveFieldId] = goLiveDate;
|
||||
if (assignee?.trim()) fields.assignee = { name: assignee.trim() };
|
||||
|
||||
if (context.epicKey) {
|
||||
if (context.epicRelation === 'parent') {
|
||||
fields.parent = { key: context.epicKey };
|
||||
} else {
|
||||
let epicFieldId = context.epicRelation;
|
||||
if (epicFieldId === 'epic-link') {
|
||||
const fieldResponse = await jiraClient.get('/rest/api/2/field', { headers });
|
||||
epicFieldId = fieldResponse.data.find(field => field.name === 'Epic Link')?.id;
|
||||
}
|
||||
if (!epicFieldId) {
|
||||
return res.status(400).json({ error: "Le champ Jira 'Epic Link' est introuvable." });
|
||||
}
|
||||
fields[epicFieldId] = context.epicKey;
|
||||
}
|
||||
}
|
||||
|
||||
const response = await jiraClient.post('/rest/api/2/issue', { fields }, { headers });
|
||||
console.log(`[CREATE] Ticket ${response.data.key} créé dans ${context.projectKey}.`);
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
key: response.data.key,
|
||||
url: `${config.jira.url}/browse/${response.data.key}`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[CREATE ERROR]', error.response?.data || error.message);
|
||||
res.status(error.response?.status || 500).json({
|
||||
error: error.response?.data?.errorMessages?.[0] || error.message,
|
||||
details: error.response?.data || null
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Endpoint de mise à jour de ticket
|
||||
app.put('/api/update', async (req, res) => {
|
||||
const { username, apiToken, ticketKey, updates } = req.body;
|
||||
|
||||
Reference in New Issue
Block a user