Sync Feature: manual #2
@@ -0,0 +1,178 @@
|
|||||||
|
/**
|
||||||
|
* 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}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user