Sync src/WorkflowHooks.gs

This commit is contained in:
2026-04-24 17:41:48 +00:00
parent e0538abf25
commit 58dfe8750c
+178
View File
@@ -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}`);
});
}