/** * WorkflowHooks - High-level orchestrator for business workflows. * Core app files should call these functions instead of Clients directly. */ function workflowStartFeature(featureCode, taskId) { const note = `๐Ÿš€ Feature [${featureCode}] has been started in SchoolHub.`; const res = createVikunjaComment(taskId, note); return res.ok ? { ok: true } : { ok: false, message: res.message }; } function workflowMarkInternalTest(featureCode, taskId, note) { const fullNote = `๐Ÿงช Internal Testing for [${featureCode}]: ${note}`; const res = createVikunjaComment(taskId, fullNote); return res.ok ? { ok: true } : { ok: false, message: res.message }; } function workflowMarkDeploySuccess(featureCode, taskId, deployInfo) { // 1. Add deployment confirmation comment const comment = `โœ… Deployed to Production: ${deployInfo || 'No additional info'}`; createVikunjaComment(taskId, comment); // 2. Automatically mark task as done using the workflow helper // We pass a minimal meta object to resolve the task markWorkflowTaskDone_({ featureCode, title: 'Deployment' }, true); return { ok: true }; } /** * Private helper to mark a workflow task as done or reopened. * Resolves the task ID via ensureWorkflowTask to ensure consistency. */ function markWorkflowTaskDone_(featureMeta, isDone) { try { const ensureRes = ensureWorkflowTask(featureMeta); if (!ensureRes.ok || !ensureRes.taskId) { console.warn(`Workflow Done Status Warning: Could not resolve task for ${featureMeta.featureCode}`); return; } const taskId = ensureRes.taskId; const patch = { done: isDone }; const res = updateVikunjaTask(taskId, patch); if (res.ok) { console.log(`Workflow Done Status: Task ${taskId} marked as ${isDone ? 'DONE' : 'OPEN'}`); } else { console.warn(`Workflow Done Status Failure: ${res.message}`); } } catch (e) { console.error(`Workflow Done Status Error: ${e.message}`); } } function workflowCreateRelease(featureCode, taskId, releaseInfo) { const { tagName, name, body } = releaseInfo; // 1. Create Release in Gitea const gRes = createReleaseTag(tagName, name, body, null); if (!gRes.ok) return { ok: false, message: `Gitea Release Failed: ${gRes.message}` }; // 2. Link Gitea release back to Vikunja task const config = getGiteaConfig(); const releaseUrl = `${config.baseUrl}/repos/${config.owner}/${config.repo}/releases/tags/${tagName}`; const vRes = createVikunjaComment(taskId, `๐Ÿ“ฆ Release created: ${releaseUrl}`); return vRes.ok ? { ok: true } : { ok: false, message: vRes.message }; } /** * Ensures a Vikunja task exists for a specific feature/bug. * If it exists, returns it. If not, creates it with calculated priority and labels. * * @param {Object} featureMeta - { featureCode, title, type, module, impactArea, severity, requestedBy, source } */ function ensureWorkflowTask(featureMeta) { try { const { featureCode, title } = featureMeta; if (!featureCode || !title) throw new Error('featureCode and title are required for task assurance.'); // 1. Try to find existing task const existingTask = findExistingWorkflowTask_(featureMeta); if (existingTask) { return { ok: true, taskId: existingTask.id, state: 'EXISTING', priority: existingTask.priority, labels: existingTask.labels || [] }; } // 2. Derive Priority and Labels const priority = deriveWorkflowPriority_(featureMeta); const labels = deriveWorkflowLabels_(featureMeta); // 3. Create Task const config = getVikunjaConfig(); const taskPayload = { title: `[${featureCode}] ${title}`, description: `Source: ${featureMeta.source || 'System'}\nRequested by: ${featureMeta.requestedBy || 'Unknown'}\nModule: ${featureMeta.module || 'General'}`, projectId: config.projectId, priority: priority }; const createRes = createVikunjaTask(taskPayload); if (!createRes.ok) throw new Error(`Task creation failed: ${createRes.message}`); const taskId = createRes.data.id; // 4. Attach Labels attachLabelsToTask_(taskId, labels); return { ok: true, taskId: taskId, state: 'CREATED', priority: priority, labels: labels }; } catch (e) { console.error(`Workflow Task Assurance Error: ${e.message}`); return { ok: false, message: e.message }; } } function findExistingWorkflowTask_(featureMeta) { const searchRes = searchVikunjaTasks(featureMeta.featureCode); if (searchRes.ok && Array.isArray(searchRes.data)) { // Find exact match of [CODE] in title return searchRes.data.find(t => t.title.includes(`[${featureMeta.featureCode}]`)); } return null; } function deriveWorkflowPriority_(featureMeta) { const { type, module, severity } = featureMeta; const highPrioKeywords = ['auth', 'login', 'otp', 'admin', 'rbac', 'security', 'payment']; const modValue = (module || '').toLowerCase(); // Rule 1: Critical severity OR critical modules = Priority 5 (Highest) const isCriticalModule = highPrioKeywords.some(kw => modValue.includes(kw)); if (severity === 'critical' || isCriticalModule) return 5; // Rule 2: Bugfixes OR High severity = Priority 4 if (type === 'bugfix' || severity === 'high') return 4; // Rule 3: Features OR Improvements = Priority 3 if (type === 'feature' || type === 'improvement') return 3; // Rule 4: Refactors OR Docs OR Low severity = Priority 2 if (type === 'refactor' || type === 'docs' || severity === 'low') return 2; // Rule 5: Default = Priority 1 (Lowest) return 1; } function deriveWorkflowLabels_(featureMeta) { const labels = []; if (featureMeta.type) labels.push(`type:${featureMeta.type}`); if (featureMeta.module) labels.push(`mod:${featureMeta.module}`); if (featureMeta.impactArea) labels.push(`impact:${featureMeta.impactArea}`); if (featureMeta.severity) labels.push(`sev:${featureMeta.severity}`); return labels; } function attachLabelsToTask_(taskId, labels) { labels.forEach(label => { const res = addVikunjaLabel(taskId, label); if (!res.ok) console.warn(`Workflow Label Warning: ${res.message}`); }); }