workflowyoutrack

YouTrack Command Workflow calling a different Workflow


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?

Apply Command Dialog

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
    }
  }
});

Solution

  • 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.