Setting number custom field value with value function

I’m trying to use the new value function ability of Forge custom fields.
I’m trying to set a number field, but everytime I do, I get the error

The field value must be a number.

shown in the UI.

Here is my code:

Manifest

modules:
  jira:customField:
    - key: customfield-key
      name: Number Custom Field
      description: This custom field displays a number.
      type: number
      value:
        function: render-number
  function:
    - key: render-number
      handler: index.renderNumber

Functions

const calculateNumber= (field, issueId) => {
  const result = { fields: { [field.id]: 5 };
  return JSON.stringify(result);
};

export const renderNumber= (args) => {
  return args.issues.map((issue) => calculateNumber(args.field, issue.id));
};

I am unsure what exactly the return value of a value function is supposed to be. I tried both: with and without JSON.stringify and neither worked.

1 Like

Hi @MaxGroe, thank you for reaching out and for your feedback regarding documentation.

From the docs: https://developer.atlassian.com/platform/forge/manifest-reference/modules/jira-custom-field/#value-function

The function receives a list of issue IDs and returns a list of values for those issues. The values returned must be compatible with the format expected by the Edit Issue REST API operation. For example, for users the value must be an object that contains accountId.

For a number field it should look like below:

export const renderNumber = (args) => {
  return args.issues.map((issue) => calculateNumber(args.field, issue.id));
};

const calculateNumber = (field, issueId) => {
  return 5;
};

We will improve an example in our documentation. Once again, thank you for the feedback!

3 Likes

Thank you so much! @ljarzabek

Hi ljarzabek,

When I write value calculation logic in the function caclulateNumber, there’s always an error:
ERROR 08:17:23.190 96eb1447a1c33cd8 cannot use a hook outside of a component

I don’t know why this pops out. Could you please take a look?

const calculateNumber = (field, issueId) => {
const [changelogs] = useState(() => fetchChangelogsForIssue(issueId));
// value calculation logic here
return result;
}

After changing to async funtion, another error:
“ERROR 07:56:28.410 eeb0b6377c80598c Can’t wrap a non-transferable value (constructor: ‘Promise’)”.

Thanks,
Yong

1 Like

Hi @YY1
The issue here is that you’re using a react hook useState inside of a regular javascript function. You can only use hooks at the top level of a React function (that means from the react function component or another react hook). You can read more about rules of using hooks here: https://reactjs.org/docs/hooks-rules.html .
Instead of doing

const calculateNumber = (field, issueId) => {
const [changelogs] = useState(() => fetchChangelogsForIssue(issueId));

you should make this calculateNumber an async function and fetch the data inside of it. Something like:

const calculateNumber = async (field, issueId) => {
const resp = await fetchChangelogsForIssue(issueId);
....
return yourResult

Let me know if it solves your issue.

Paweł

1 Like

Thanks @PawelRacki

When I changed the codes as below, an error pops out:

Can't wrap a non-transferable value (constructor: 'Promise')
Error: Can't wrap a non-transferable value (constructor: 'Promise')
    at s.wrap (bootstrap.js:1:1677)
    at bootstrap.js:1:2270
    at Array.map (<anonymous>)
    at s.wrapArray (bootstrap.js:1:2257)
    at s.wrap (bootstrap.js:1:1574)
    at bootstrap.js:1:797
    at Array.map (<anonymous>)
    at s.applyIgnored (bootstrap.js:1:784)
    at bootstrap.js:1:8730

Please help me take a look again. Thanks so much!

export async function computeValue(args) {
  const result = await args.issues.map(async (issue) => {
    await calculateNumber(args.field, issue.id);
  });

  return result;
}

const calculateNumber = async (field, issueId) => {
  debugger;
  try {
    const response = await fetchChangelogsForIssue(issueId);
    console.log(response);
    return 5;
  } catch (err) {
    console.log("error: " + err);
  }
}

const fetchChangelogsForIssue = async (issueIdOrKey) => {
  const res = await api
    .asApp()
    .requestJira(route `/rest/api/3/issue/${issueIdOrKey}/changelog`);

  const data = await res.json();
  return data.values;
};

It seems that if we change the function to be async, the values fails to return.

I believe that wrapping your await with Promise.all will solve the issue. So, changing the

export async function computeValue(args) {
  const result = await args.issues.map(async (issue) => {
    await calculateNumber(args.field, issue.id);
  });

to

export async function computeValue(args) {
  const result = await Promise.all(args.issues.map(async (issue) => {
    await calculateNumber(args.field, issue.id);
  }));

EDIT: Ok, so I actually tried your code with the Promise.all and it doesn’t throw errors anymore and the console.log with the response also works fine :slight_smile:

EDIT2: Also, you’re not really returning anything from args.issues.map..... so the value of result will be undefined. So, I’d change it to

export async function computeValue(args) {
  const result = await Promise.all(args.issues.map(async (issue) => await calculateNumber(args.field, issue.id)));

and then transform the data somehow cause most likely you’ll get a validation error, like “The field value must be a string”

2 Likes