YouTrack命令工作流异常:未显示、误触发及重复通知问题咨询
YouTrack工作流问题排查
问题描述
我有多年编码经验,但刚接触YouTrack工具。写了两个动作工作流:一个是名为RJH_Test的Hello World示例,另一个是给工单标题加前缀的实用工作流。现在遇到三个问题:
- 实用工作流没出现在「Apply Command」对话框里,原因是什么?
- 调用
RJH_Test工作流时,为什么会同时触发实用工作流? - 实用工作流里的guard通知为什么会运行两次?
相关代码
const entities = require('@jetbrains/youtrack-scripting-api/entities'); const workflow = require('@jetbrains/youtrack-scripting-api/workflow'); exports.rule = entities.Issue.action({ title: "Action 2", command: 'RJH_Test', action: function(ctx) { const logger = new Logger(ctx.traceEnabled); // --- #1 notifyUser --- const message_0 = `Hello Earthling #2`; logger.log("Show alert with message", message_0); const notifyUserFn_0 = () => { workflow.message(message_0); }; // notifyUserFn_0(); } }); /** * This workflow rule changes the subject of an ticket. * The trigger is when the command text is invoked. * The subject is prefixed with the following inside square brackets: * The code character of the priority followed by one space * the customer name, maximum 25 characters, no hyphens, whole words, followed by only one space * the due date in ISO format * * Predefined field 'summary', must be lower case, is the title of the issue * Custom field "Priority (SUP)" is type enum (single) with 4 values configured. These are in the form of text followed by a single capital letter inside normal brackets "()" * Custom field "Customer" is type enum (single) with 400 values currently configured. These are often lengthy and contain hyphens * Custom field "Due Date" is type date, to be reformated as an ISO date. **/ /* Load global manifest constants */ const entities = require('@jetbrains/youtrack-scripting-api/entities'); const workflow = require('@jetbrains/youtrack-scripting-api/workflow'); function getWorkflowContext(ctx) { return { logger: new Logger(ctx.traceEnabled), ticketThis: ctx.issue, customerField: ctx.CustomerEnum, dueDateField: ctx.DueDateField }; } exports.rule = entities.Issue.action({ title: "Custom Action to prefix the Subject when the command is invoked", description: "Prefixes the Subject only when the command is invoked.", command: 'ticket-prefix', /* The GUARD section checks for the fields needed by ACTION and that the subject has not been updated already */ guard: function(ctx) { workflow.message("Starting Guard"); const { logger, ticketThis, customerField, dueDateField } = getWorkflowContext(ctx); const priorityValue = ctx.issue.fields["Priority (SUP)"]; // Get the selected value if (!priorityValue) { workflow.message("Error: Priority field has no selected value."); } else { // workflow.message(`Priority Selected Value: ${priorityValue.name}`); } const priorityCodeCharacterMatch = priorityValue.name.match(/\((.)\)/); // workflow.message("CodeCharacterMatch:" + priorityCodeCharacterMatch); // logger.log("CodeCharacterMatch:" + priorityCodeCharacterMatch); // console.log("CodeCharacterMatch:" + priorityCodeCharacterMatch); const priorityCodeCharacter = priorityCodeCharacterMatch ? priorityCodeCharacterMatch[1] : null; if (!priorityCodeCharacter) { workflow.message("Unable to extract the code character from Priority (SUP)."); logger.error("Unable to extract the code character from Priority (SUP)."); return false; }else{ // workflow.message(`Extracted Priority Code: ${priorityCodeCharacter}`); } if (!customerField) { workflow.message("Customer field has no value."); logger.error("Customer field has no value."); return false; } if (!dueDateField) { workflow.message("Due Date field has no value."); logger.error("Due Date field has no value."); return false; } const existingSummary = ticketThis.summary; // workflow.message("Summary= " + existingSummary); if (existingSummary.startsWith("[")) { workflow.message("Subject already has a prefix. Skipping."); logger.warn("Subject already has a prefix. Skipping."); return false; } workflow.message("Ending Guard"); return true; // Ensure the guard explicitly returns a boolean }, /* ACTION: this can only run if GUARD has returned logical TRUE, and not thrown an error. */ action: function(ctx) { const { logger, ticketThis, customerField, dueDateField } = getWorkflowContext(ctx); logger.log("Mode: set"); logger.log("Current Subject:", ticketThis.fields.Summary); workflow.message("Starting Action"); var existingSummary = ticketThis.summary; logger.log("Summary= " + existingSummary); console.log("Summary= " + existingSummary); workflow.message("Summary= " + existingSummary); const priorityValue = ctx.issue.fields["Priority (SUP)"]; // Get the selected value const priorityCodeCharacterMatch = priorityValue.name.match(/\((.)\)/); // if missing handled in the guard section const priorityCodeCharacter = priorityCodeCharacterMatch ? priorityCodeCharacterMatch[1] : null; let processedCustomerName = customerField.name .replace(/-/g, ' ') // Replace hyphens with spaces .replace(/\s+/g, ' ') // Replace multiple spaces with a single space .trim(); // Remove leading and trailing spaces if (processedCustomerName.length > 25) { const truncated = processedCustomerName.slice(0, 25); const lastSpaceIndex = truncated.lastIndexOf(' '); processedCustomerName = lastSpaceIndex > 0 ? truncated.slice(0, lastSpaceIndex) : truncated; } logger.log(`Processed customer name: "${processedCustomerName}"`); const isoDueDate = new Date(dueDateField).toISOString().split('T')[0]; // Convert the date to ISO format logger.log(`Formatted Due Date (ISO): ${isoDueDate}`); // Build new prefix const prefix = `[${priorityCodeCharacter} ${isoDueDate}][${processedCustomerName}]`; logger.log(`Prefix: ${prefix}`); ticketThis.summary = prefix + " " + ticketThis.summary; logger.log("Replaced Subject:", ticketThis.fields.summary); }, /* REQUIREMENTS: this section ensures that the field is defined and of the correct type. The GUARD section however needs to check the field contents are useful.*/ requirements: { PrioritySUPEnum: { name: "Priority (SUP)", type: entities.EnumField.fieldType }, CustomerEnum: { name: "Customer", type: entities.EnumField.fieldType }, DueDateField: { name: "Due Date", type: entities.Field.dateType } } });
问题解答
1. 实用工作流未出现在Apply Command对话框的原因
- 项目与权限配置缺失:检查工作流是否已关联到目标项目,以及当前用户是否拥有执行该工作流的权限(在YouTrack的「Workflow Administration」中确认项目绑定和权限设置)。
- 工作流未发布:未发布的工作流不会显示在命令列表中,需确保实用工作流已完成发布操作。
- Requirements字段校验失败:工作流的
requirements部分定义了三个必填字段,如果当前项目未配置Priority (SUP)、Customer或Due Date,工作流会被禁用。可在工作流的「Validation」标签页查看字段缺失的错误提示。
2. 调用RJH_Test时触发实用工作流的原因
- 脚本文件合并问题:两个工作流写在同一个脚本文件中,YouTrack会将它们视为同一规则集合,执行时会同时触发。需将两个工作流拆分为独立的脚本文件,分别发布。
- 触发规则配置错误:若实用工作流的触发类型被误设为「工单更新」而非仅绑定
ticket-prefix命令,会在RJH_Test执行(工单发生变更)时被触发。确认实用工作流的触发类型为action且仅绑定指定命令。
3. Guard通知运行两次的原因
- YouTrack预检查机制:YouTrack在显示命令选项前,会预执行一次guard判断工作流是否可用;用户实际执行命令时,会再次执行guard做最终校验,因此会出现两次通知输出。
- 优化方案:通过
ctx.isCommandExecution区分预检查和实际执行,仅在实际执行时显示通知:guard: function(ctx) { // 仅在实际执行命令时输出通知 if (ctx.isCommandExecution) { workflow.message("Starting Guard"); } // 其余逻辑保持不变 }
内容的提问来源于stack exchange,提问作者Robert Howe
相关产品推荐
相关产品推荐

