While I have been coding for a long time, I am a relative novice with YouTrack. I have written a "Hello World" action workflow (RJH_Test) and another more useful workflow. The 'useful' workflow does not appear in the Apply Command dialog. When I invoke RJH_Test, it runs but also invokes my useful code too, apparently twice. I am asking three questions because I suspect that they are inter-related.
Question 1: why does my useful code not appear in the dialog?
Question 2: why does my Hello World code invoke my useful code?
Question 3: why do the guard notifications in my useful code run twice?
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
}
}
});
Please check the workflow editor console. If I use the code that you provided here, it shows the "Invalid guard for workflow rule..." error. I suppose you should have the same error. It shows the specific code line that is problematic, so please check it.