Created
February 16, 2025 23:49
-
-
Save astanziola/02251242ba3d9d5531ceb430e61993e3 to your computer and use it in GitHub Desktop.
YouTrack Recurrent Issue Workflow
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * YouTrack Workflow Rule: Automatic Recurrent Issue Creation | |
| * | |
| * This workflow rule automatically creates a new issue when a recurring issue is marked as Done. | |
| * The new issue inherits properties from the completed issue and is scheduled based on the | |
| * recurrence pattern (Daily, Weekly, Monthly, Quarterly, or Yearly). | |
| * | |
| * Behavior: | |
| * - Triggers when an issue with a RecurrencePattern is marked as Done | |
| * - Creates a new issue with the same summary, description, and recurrence pattern | |
| * - Sets the due date based on either: | |
| * a) The original due date if completed early | |
| * b) The completion date if completed late | |
| * - New issue is created in Open state | |
| * | |
| * Required Fields for Issues: | |
| * - RecurrencePattern (enum): Daily, Weekly, Monthly, Quarterly, Yearly | |
| * - State (enum): Done, Open | |
| * - Due Date (date) | |
| * | |
| * Debug Mode: | |
| * - Set debugLog = true to enable detailed logging | |
| * - Logs include guard checks, date calculations, and issue creation steps | |
| */ | |
| const entities = require('@jetbrains/youtrack-scripting-api/entities'); | |
| // Debug flag - set to true to enable debug logging | |
| const debugLog = false; | |
| // Debug logging helper | |
| const debug = (...args) => { | |
| if (debugLog) { | |
| console.log(...args); | |
| } | |
| }; | |
| exports.rule = entities.Issue.onChange({ | |
| title: 'Create next recurrent issue', | |
| guard: (ctx) => { | |
| debug("=== GUARD FUNCTION STARTED ==="); | |
| const issue = ctx.issue; | |
| // Safe logging of issue details | |
| const safeIssueLog = { | |
| id: issue?.id, | |
| summary: issue?.summary, | |
| state: issue?.State?.name, | |
| dueDate: issue?.['Due Date'], | |
| hasRecurrencePattern: !!issue?.RecurrencePattern, | |
| recurrencePattern: issue?.RecurrencePattern?.name | |
| }; | |
| debug("Issue details:", safeIssueLog); | |
| if (issue?.State?.name === 'Done' && issue?.RecurrencePattern?.name) { | |
| debug("Guard passing: issue is Done and has recurrence pattern"); | |
| return true; | |
| } | |
| debug("Guard returning false: conditions not met"); | |
| return false; | |
| }, | |
| action: (ctx) => { | |
| debug("=== ACTION FUNCTION STARTED ==="); | |
| const issue = ctx.issue; | |
| const recurrencePattern = issue.RecurrencePattern; | |
| const completionDate = new Date(); | |
| const originalDueDate = new Date(issue['Due Date']); | |
| debug("Dates:", { | |
| completionDate: completionDate.toISOString(), | |
| originalDueDate: originalDueDate.toISOString(), | |
| isCompletedEarly: completionDate <= originalDueDate | |
| }); | |
| const baseDate = completionDate <= originalDueDate ? originalDueDate : completionDate; | |
| debug("Selected base date for calculation:", baseDate.toISOString()); | |
| let newDueDate = calculateNextDueDate(baseDate, recurrencePattern); | |
| debug("Calculated new due date:", newDueDate.toISOString()); | |
| debug("Creating new issue..."); | |
| const newIssue = new entities.Issue(ctx.currentUser, issue.project, issue.summary); | |
| debug("Copying fields to new issue..."); | |
| newIssue.description = issue.description; | |
| newIssue.fields.State = ctx.State.Open; | |
| newIssue.fields.RecurrencePattern = ctx.RecurrencePattern[recurrencePattern.name]; | |
| newIssue.fields['Due Date'] = newDueDate.getTime(); // Convert to milliseconds timestamp | |
| debug("New issue created with fields:", { | |
| summary: newIssue.summary, | |
| dueDate: newDueDate.toISOString(), | |
| recurrencePattern: recurrencePattern.name, | |
| state: 'Open' | |
| }); | |
| }, | |
| requirements: { | |
| RecurrencePattern: { | |
| type: entities.EnumField.fieldType, | |
| Daily: {}, | |
| Weekly: {}, | |
| Monthly: {}, | |
| Quarterly: {}, | |
| Yearly: {} | |
| }, | |
| State: { | |
| type: entities.State.fieldType, | |
| Done: {}, | |
| Open: {} | |
| }, | |
| 'Due Date': { | |
| type: entities.Field.dateType | |
| } | |
| } | |
| }); | |
| function calculateNextDueDate(baseDate, pattern) { | |
| debug("=== CALCULATE NEXT DUE DATE STARTED ==="); | |
| debug("Inputs:", { | |
| baseDate: baseDate.toISOString(), | |
| pattern: pattern?.name || 'unknown' | |
| }); | |
| const date = new Date(baseDate); | |
| switch(pattern.name.toLowerCase()) { | |
| case 'daily': | |
| date.setDate(date.getDate() + 1); | |
| break; | |
| case 'weekly': | |
| date.setDate(date.getDate() + 7); | |
| break; | |
| case 'monthly': | |
| date.setMonth(date.getMonth() + 1); | |
| break; | |
| case 'quarterly': | |
| date.setMonth(date.getMonth() + 3); | |
| break; | |
| case 'yearly': | |
| date.setFullYear(date.getFullYear() + 1); | |
| break; | |
| default: | |
| debug("ERROR: Unknown pattern:", pattern?.name); | |
| throw new Error(`Unknown recurrence pattern: ${pattern?.name}`); | |
| } | |
| debug("Calculated new date:", date.toISOString()); | |
| return date; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment