From a641a8b9cd613bfed1d86a4455ce70b40d28f42e Mon Sep 17 00:00:00 2001 From: someone <2+someone@noreply.localhost> Date: Fri, 24 Apr 2026 22:40:00 +0000 Subject: [PATCH] Sync src/WorkflowHooks.gs --- src/WorkflowHooks.gs | 178 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 src/WorkflowHooks.gs diff --git a/src/WorkflowHooks.gs b/src/WorkflowHooks.gs new file mode 100644 index 0000000..19e29a1 --- /dev/null +++ b/src/WorkflowHooks.gs @@ -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}`); + }); +}