Workflow doesn't work ?

Hey there, 

Context : two dev teams, one estimate in time, the other in points. 
To smooth monitoring, I've created two workflows : 

  • A - Conversion of story points into time estimation : this one works perfectly ✅
  • B - Conversion of time estimation into story points : this one doesn't work ❌

I don't understand the issue with B, as I've used the same logic as in A, changing the fiels names and value accordingly. 
I'll copy below B json model if it helps.

Anybody got an idea ? 

 

{"title":"Conversion estimation temps > Story Point","rules":[{"name":"vfe-1752584770913-0.1229433011321801","title":"On-change 1","triggerType":"onChange","cron":"0 0 0 * * ?","schedule":"","search":"","command":"","properties":{"action":[{"id":"1","name":"ifOperator","params":{"if":[{"id":"2","name":"checkField","params":{"issue":{"type":"Issue","wayOfSearch":"ctx","contextItem":"Issue"},"itemName":"Field","mode":"is","field":"Estimation","fieldType":"period","isMultiValue":false,"state":"1h"}}],"then":[{"id":"3","name":"changeIssueFieldValue","params":{"issue":{"type":"Issue","wayOfSearch":"ctx","contextItem":"Issue"},"userValue":{"type":"User","wayOfSearch":"ctx","contextItem":"User"},"fieldName":"Story points","fieldType":"integer","isMultiValue":false,"value":["1"],"state":"set"}}],"else":[{"id":"4","name":"ifOperator","params":{"if":[{"id":"5","name":"checkField","params":{"issue":{"type":"Issue","wayOfSearch":"ctx","contextItem":"Issue"},"itemName":"Field","mode":"is","field":"Estimation","fieldType":"period","isMultiValue":false,"state":"2h"}}],"then":[{"id":"6","name":"changeIssueFieldValue","params":{"issue":{"type":"Issue","wayOfSearch":"ctx","contextItem":"Issue"},"userValue":{"type":"User","wayOfSearch":"ctx","contextItem":"User"},"fieldName":"Story points","fieldType":"integer","isMultiValue":false,"value":["2"],"state":"set"}}],"else":[{"id":"7","name":"ifOperator","params":{"if":[{"id":"8","name":"checkField","params":{"issue":{"type":"Issue","wayOfSearch":"ctx","contextItem":"Issue"},"itemName":"Field","mode":"is","field":"Estimation","fieldType":"period","isMultiValue":false,"state":"3h"}}],"then":[{"id":"9","name":"changeIssueFieldValue","params":{"issue":{"type":"Issue","wayOfSearch":"ctx","contextItem":"Issue"},"userValue":{"type":"User","wayOfSearch":"ctx","contextItem":"User"},"fieldName":"Story points","fieldType":"integer","isMultiValue":false,"value":["3"],"state":"set"}}],"else":[{"id":"10","name":"ifOperator","params":{"if":[{"id":"11","name":"checkField","params":{"issue":{"type":"Issue","wayOfSearch":"ctx","contextItem":"Issue"},"itemName":"Field","mode":"is","field":"Estimation","fieldType":"period","isMultiValue":false,"state":"1d"}}],"then":[{"id":"12","name":"changeIssueFieldValue","params":{"issue":{"type":"Issue","wayOfSearch":"ctx","contextItem":"Issue"},"userValue":{"type":"User","wayOfSearch":"ctx","contextItem":"User"},"fieldName":"Story points","fieldType":"integer","isMultiValue":false,"value":["5"],"state":"set"}}],"else":[{"id":"13","name":"ifOperator","params":{"if":[{"id":"14","name":"checkField","params":{"issue":{"type":"Issue","wayOfSearch":"ctx","contextItem":"Issue"},"itemName":"Field","mode":"is","field":"Estimation","fieldType":"period","isMultiValue":false,"state":"2d"}}],"then":[{"id":"15","name":"changeIssueFieldValue","params":{"issue":{"type":"Issue","wayOfSearch":"ctx","contextItem":"Issue"},"userValue":{"type":"User","wayOfSearch":"ctx","contextItem":"User"},"fieldName":"Story points","fieldType":"integer","isMultiValue":false,"value":["8"],"state":"set"}}],"else":[]}}]}}]}}]}}]}}],"guard":[{"id":"1","name":"checkField","params":{"issue":{"type":"Issue","wayOfSearch":"ctx","contextItem":"Issue"},"itemName":"Field","mode":"changed","field":"Estimation","fieldType":"period","isMultiValue":false}}]}}]}
0
5 comments

Hi Marion Morazzani!

Can you please share the code for workflow B as well? This will give us better understanding of the conversion mechanism you chose and what might need correcting.

Also, please describe the issue in more detail - how exactly does the workflow not work? Does it fail with an error or do you see some other unexpected result?

0

Hi Julia Bernikova ! Thank you for looking into this !

What I meant by “not working” is that when I change the “Estimation" field, the “Story points” field doesn't update. I have no failure message or unexpected result, it's like the workflow isn't even active (it is in this project).

With Workflow A, everything is going well, when I update “Story points”, “Estimation” updates automatically as expected. 

Workflow B is to be used in a different project than Workflow A, could this be a factor ?

I'll share the code below.

/* eslint-disable */
var entities = require('@jetbrains/youtrack-scripting-api/entities');
var workflow = require('@jetbrains/youtrack-scripting-api/workflow');



exports.rule = entities.Issue.onChange({
  title: "On-change 1",
  guard: function(ctx) {
    const logger = new Logger(ctx.traceEnabled);


    // --- #1 checkField ---
    const issue_0 = ctx.issue;

    function checkFieldEstimationPeriod_0() {
      const issueField = issue_0.fields["Estimation"];

      return issue_0.fields.isChanged(ctx.EstimationPeriod);
    }

    const checkFieldFn_0 = () => {


      return checkFieldEstimationPeriod_0()
    };


    try {
      return (
        checkFieldFn_0()
      );
    } catch (err) {
      if (err?.message?.includes('has no value')) {
        logger.error('Failed to execute guard', err);
        return false;
      }
      throw err;
    }

  },
  action: function(ctx) {
    const logger = new Logger(ctx.traceEnabled);


    // --- #1 ifOperator ---

    // --- #2 checkField ---
    const issue_1 = ctx.issue;

    function checkFieldEstimationPeriod_1() {
      const issueField = issue_1.fields["Estimation"];

      return issue_1.is(ctx.EstimationPeriod, "1h");
    }

    const checkFieldFn_1 = () => {


      return checkFieldEstimationPeriod_1()
    };


    if (checkFieldFn_1()) {


      // --- #3 changeIssueFieldValue ---
      const issue_2 = ctx.issue;
      logger.log("Mode: set");
      logger.log("Current value:", issue_2.fields['Story points']);

      const changeIssueFieldValueFn_0 = () => {


        issue_2.fields['Story points'] = "1";
      };

      changeIssueFieldValueFn_0();

    } else {


      // --- #4 ifOperator ---

      // --- #5 checkField ---
      const issue_3 = ctx.issue;

      function checkFieldEstimationPeriod_2() {
        const issueField = issue_3.fields["Estimation"];

        return issue_3.is(ctx.EstimationPeriod, "2h");
      }

      const checkFieldFn_2 = () => {


        return checkFieldEstimationPeriod_2()
      };


      if (checkFieldFn_2()) {


        // --- #6 changeIssueFieldValue ---
        const issue_4 = ctx.issue;
        logger.log("Mode: set");
        logger.log("Current value:", issue_4.fields['Story points']);

        const changeIssueFieldValueFn_1 = () => {


          issue_4.fields['Story points'] = "2";
        };

        changeIssueFieldValueFn_1();

      } else {


        // --- #7 ifOperator ---

        // --- #8 checkField ---
        const issue_5 = ctx.issue;

        function checkFieldEstimationPeriod_3() {
          const issueField = issue_5.fields["Estimation"];

          return issue_5.is(ctx.EstimationPeriod, "3h");
        }

        const checkFieldFn_3 = () => {


          return checkFieldEstimationPeriod_3()
        };


        if (checkFieldFn_3()) {


          // --- #9 changeIssueFieldValue ---
          const issue_6 = ctx.issue;
          logger.log("Mode: set");
          logger.log("Current value:", issue_6.fields['Story points']);

          const changeIssueFieldValueFn_2 = () => {


            issue_6.fields['Story points'] = "3";
          };

          changeIssueFieldValueFn_2();

        } else {


          // --- #10 ifOperator ---

          // --- #11 checkField ---
          const issue_7 = ctx.issue;

          function checkFieldEstimationPeriod_4() {
            const issueField = issue_7.fields["Estimation"];

            return issue_7.is(ctx.EstimationPeriod, "1d");
          }

          const checkFieldFn_4 = () => {


            return checkFieldEstimationPeriod_4()
          };


          if (checkFieldFn_4()) {


            // --- #12 changeIssueFieldValue ---
            const issue_8 = ctx.issue;
            logger.log("Mode: set");
            logger.log("Current value:", issue_8.fields['Story points']);

            const changeIssueFieldValueFn_3 = () => {


              issue_8.fields['Story points'] = "5";
            };

            changeIssueFieldValueFn_3();

          } else {


            // --- #13 ifOperator ---

            // --- #14 checkField ---
            const issue_9 = ctx.issue;

            function checkFieldEstimationPeriod_5() {
              const issueField = issue_9.fields["Estimation"];

              return issue_9.is(ctx.EstimationPeriod, "2d");
            }

            const checkFieldFn_5 = () => {


              return checkFieldEstimationPeriod_5()
            };


            if (checkFieldFn_5()) {


              // --- #15 changeIssueFieldValue ---
              const issue_10 = ctx.issue;
              logger.log("Mode: set");
              logger.log("Current value:", issue_10.fields['Story points']);

              const changeIssueFieldValueFn_4 = () => {


                issue_10.fields['Story points'] = "8";
              };

              changeIssueFieldValueFn_4();

            } else {

            }

            const ifOperatorFn_0 = () => {



            };

            ifOperatorFn_0();

          }

          const ifOperatorFn_1 = () => {



          };

          ifOperatorFn_1();

        }

        const ifOperatorFn_2 = () => {



        };

        ifOperatorFn_2();

      }

      const ifOperatorFn_3 = () => {



      };

      ifOperatorFn_3();

    }

    const ifOperatorFn_4 = () => {



    };

    ifOperatorFn_4();

  },
  requirements: {
    EstimationPeriod: {
      name: "Estimation",
      type: entities.Field.periodType
    },
    StorypointsInteger: {
      name: "Story points",
      type: entities.Field.integerType
    }
  }
});

function Logger(useDebug = true) {
  return {
    log: (...args) => useDebug && console.log(...args),
    warn: (...args) => useDebug && console.warn(...args),
    error: (...args) => useDebug && console.error(...args)
  }
}

 

0

Hi Marion Morazzani,

It seems that the workflow was first created in the visual constructor and then converted to JavaScript. I believe it used to be a sequence of the “IF” blocks, that's why it only converts the time estimate for a few predefined values (1h, 2h, 3h, 1d, 2d) and doesn't account for anything in between. So, I'd suggest starting from scratch for easier troubleshooting. Here's an example of a rule converting time estimation into story points:

const entities = require('@jetbrains/youtrack-scripting-api/entities');

exports.rule = entities.Issue.onChange({
  title: 'Estimate_conversion',
  guard: (ctx) => {
    return ctx.issue.fields.isChanged(ctx.Estimation);
  },
  action: (ctx) => {
    const issue = ctx.issue;
    
    const period = issue.fields.Estimation;
    const estimationHours = !period ? 0 : period.getWeeks() * 7 * 24 + period.getDays() * 24 + period.getHours() + period.getMinutes() / 60;
    let storyPoints = 0;

    
    // Converting hours to story points
    switch (true) {
      case (estimationHours >= 7 * 24):
        // Anything above 1 week is 21 story points
        storyPoints = 21;
        break;
      case (estimationHours >= 4 * 24):
        storyPoints = 13;
        break;
      case (estimationHours >= 2 * 24):
        storyPoints = 8;
        break;
      case (estimationHours >= 24):
        storyPoints = 5;
        break;
      case (estimationHours >= 3):
        storyPoints = 3;
        break;
      case (estimationHours >= 2):
        storyPoints = 2;
        break;
      case (estimationHours >= 0.25):
        // Anything below 15 minutes is 0 story points
        storyPoints = 1;
        break;
    }
    
    issue.fields.StoryPoints = storyPoints;
    
  },
  requirements: {
    Estimation: {
      name: "Estimation",
      type: entities.Field.periodType
    },
    StoryPoints: {
      name: "Story points",
      type: entities.Field.integerType
    }
  }
});

The code is quite basic, but it should let you achieve the task. Feel free to change the conversion logic and let me know if any questions occur!

0

Hi Julia Bernikova thank you for taking the time to help !

Yes, the code I shared was converted from visual editor, as I am not skilled enough to use JS directly 😅
I'm managed to implement the workflow you share and it works perfectly !  Thank you so much !
I'll use it to improve my code reading/writing and try composing the next workflow directly in JS.

Thanks again !
 

0

Marion Morazzani,

I am happy to hear the workflow works, thanks for the update!

Just so you know, workflow conversion is a completely valid approach to getting more familiar with the JS code under the hood. So, please feel free to continue doing that as long as it makes composing workflows easier. My comment was purely about this particular case where correcting the converted workflow would be more laborious than starting fresh.

You can also check out our documentation for more real-life JS workflows: Use Cases for Workflows. And don't hesitate to reach out if any questions occur!

0

Please sign in to leave a comment.