Custom fields on create Issue screen is unstable

Hi Atlassian Staff in the community,

Recently, custom field inline edit on create issue view is supported for jira:customFieldType module.
And, the old dialog edit is deprecated.

I thought our app can provide better UX with it and I tried to adopt it.
But it seems the custom field inline edit on create issue view is very unstable.

  1. The error “Unable to save the value for this custom field” is occur sometimes. I think it is related to focus change because I feel it frequently happens when blur focus.
  2. Old value is saved when modifying a text input in create screen.
    e.g. set “ABC” to a text field then change the value to “ABCDEF” and click create immediately. The saved value will be “ABC”.
  3. Create issue rarely doesn’t complete (the spinner stays rotating) when submitting the form.

The simple code below can reproduce 1. and 2.
I couldn’t determine how to reproduce 3.

export const EditText = () => {
  const {extensionContext: {fieldValue}} = useProductContext();

  const [val] = useState(async () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(fieldValue);
      }, 3000);
    });
  });

  const onSubmit = values => {
    return values.text;
  };

  return (
    <CustomFieldEdit onSubmit={onSubmit}>
      <TextField name="text" label="Text:" defaultValue={val}/>
    </CustomFieldEdit>
  );
};

Could someone help me with this problem?
I can’t update @forge/ui package without resolving this issue.

2 Likes

Hi @takafumiohtake
Thank you for the feedback. We’re working on the fix right now and we’ll let you know when it will be ready to be tested once it’s deployed. Sorry for your inconvenience.

Paweł

2 Likes

Hello again :wave:
The fix has been deployed. @takafumiohtake can you verify if everything works fine on your side?

Hi @PawelRacki

Thank you for the quick fix.

I confirmed that the problem 2 is resolved.

But, the problem 1 and 3 seem still remain.
To reproduce problem 1,

  • Focus on a text field, that is a Forge custom field
  • Leave the field blank and blur the focus
  • Submit.
    After problem 1 happens, problem 3 also happens when submitting the form.
    error1and3

And, this fix seems to have made a degraded bug.
When an invalid value is submitted the first time, the submission fails with no error messages.
degraded

Regards,
Takafumi

Hi @takafumiohtake
Thanks for the reproduction steps and gifs. They will be very helpful in debugging. We’ll start working on this right away and I’ll let you know once these issues are resolved.

1 Like

Hi again @takafumiohtake
We deployed the fix for the issue. Can you verify if everything works fine on your side?

Hi @PawelRacki,

Thank you for the fix.
I have tested them.
Now problem 1 and 3 seems to be fixed.

However, I found that problem2 still happens with checkboxes and radio buttons.
Could you take a look?

Video (Check three options in the create screen but the last checking is ignored.)

old_value

Demo code

export const EditRadio = () => {
  const {extensionContext: {fieldValue}} = useProductContext();

  const [val] = useState(async () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(fieldValue);
      }, 3000);
    });
  });

  const onSubmit = values => {
    return values.radio;
  };

  return (
      <CustomFieldEdit onSubmit={onSubmit}>
        <RadioGroup name="radio" label="">
          <Radio label={"radio1"} value={"radio1"} defaultChecked={val === "radio1"} />
          <Radio label={"radio2"} value={"radio2"} defaultChecked={val === "radio2"} />
          <Radio label={"radio3"} value={"radio3"} defaultChecked={val === "radio3"} />
        </RadioGroup>
      </CustomFieldEdit>
  );
};

export const EditCheck= () => {
  const {extensionContext: {fieldValue}} = useProductContext();

  const [val] = useState(async () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(fieldValue ? JSON.parse(fieldValue) : []);
      }, 3000);
    });
  });

  const onSubmit = values => {
    return JSON.stringify(values.multiSelect);
  };

  return (
      <CustomFieldEdit onSubmit={onSubmit}>
        <CheckboxGroup name="multiSelect" label="">
          <Checkbox label={"check1"} value={"check1"} defaultChecked={val.includes("check1")} />
          <Checkbox label={"check2"} value={"check2"} defaultChecked={val.includes("check2")} />
          <Checkbox label={"check3"} value={"check3"} defaultChecked={val.includes("check3")} />
        </CheckboxGroup>
      </CustomFieldEdit>
  );
};

Regards,
Takafumi

2 Likes

Hi @takafumiohtake :wave:
Glad that the fixes helped with the previous issues. Thanks for bringing up the issue with radio buttons and checkboxes. Will investigate it a get back to you once the issue is resolved

Paweł

2 Likes

Hi @takafumiohtake,
Our fix has reached production so could you verify if everything works fine on your side?

Regards,
Magdalena

Hi @MagdalenaRogulska ,

Thank you for the fix.
However, custom fields show “Unexpected error in app” when I enter a value into the field.
I changed nothing to my demo code.
Could you check it?

error0720

Regards,
Takafumi

Thank you @takafumiohtake for checking the fix.
I’ll see what is going on ASAP and reach out to you.

Regards,
Magdalena

1 Like

Hi @MagdalenaRogulska

I found another bug.
When I edit field values in the create issue screen with Firefox, navigation to “https://xxxx.atlassian.net/<current_path>?text=<entered_text>” happens.

error0721

Regards,
Takafumi

Hi @takafumiohtake
Just to verify, the code to the broken field looks like that:

import ForgeUI, {CustomFieldEdit, TextField, useProductContext, useState} from "@forge/ui";

export const Edit = () => {
  const {extensionContext: {fieldValue}} = useProductContext();

  const [val] = useState(async () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(fieldValue);
      }, 3000);
    });
  });

  const onSubmit = values => {
    return values.text;
  };

  return (
    <CustomFieldEdit onSubmit={onSubmit}>
      <TextField name="text" label="Text:" defaultValue={val}/>
    </CustomFieldEdit>
  );
};

right? I’m asking cause I’ve created a test field with this code and it works fine on my Jira instance.

Regards,
Paweł

Hi @PawelRacki

Thank you for checking it.
Current my entire demo code is,

src/editText.jsx

import ForgeUI, {CustomFieldEdit, TextField, DatePicker, Select, Option, RadioGroup, Radio, CheckboxGroup, Checkbox, useProductContext, useState} from "@forge/ui";

export const EditText = () => {
  const {extensionContext: {fieldValue}} = useProductContext();

  const [val] = useState(async () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(fieldValue);
      }, 3000);
    });
  });

  const onSubmit = values => {
    return values.text;
  };

  return (
    <CustomFieldEdit onSubmit={onSubmit}>
      <TextField name="text" label="" defaultValue={val}/>
    </CustomFieldEdit>
  );
};

export const EditDate = () => {
  const {extensionContext: {fieldValue}} = useProductContext();

  const [val] = useState(async () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(fieldValue);
      }, 3000);
    });
  });

  const onSubmit = values => {
    return values.text;
  };

  return (
      <CustomFieldEdit onSubmit={onSubmit}>
        <DatePicker name="text" label="" defaultValue={val}/>
      </CustomFieldEdit>
  );
};

export const EditSelect= () => {
  const {extensionContext: {fieldValue}} = useProductContext();

  const [val] = useState(async () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(fieldValue ? JSON.parse(fieldValue) : []);
      }, 3000);
    });
  });

  const onSubmit = values => {
    return JSON.stringify(values.select);
  };

  return (
      <CustomFieldEdit onSubmit={onSubmit}>
        <Select name="select" label="" isMulti>
          <Option label={"select1"} value={"select11"} defaultChecked={val.includes("select1")} />
          <Option label={"select2"} value={"select22"} defaultChecked={val.includes("select2")} />
          <Option label={"select3"} value={"select32"} defaultChecked={val.includes("select3")} />
        </Select>
      </CustomFieldEdit>
  );
};

export const EditRadio = () => {
  const {extensionContext: {fieldValue}} = useProductContext();

  const [val] = useState(async () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(fieldValue);
      }, 3000);
    });
  });

  const onSubmit = values => {
    return values.radio;
  };

  return (
      <CustomFieldEdit onSubmit={onSubmit}>
        <RadioGroup name="radio" label="">
          <Radio label={"radio1"} value={"radio1"} defaultChecked={val === "radio1"} />
          <Radio label={"radio2"} value={"radio2"} defaultChecked={val === "radio2"} />
          <Radio label={"radio3"} value={"radio3"} defaultChecked={val === "radio3"} />
        </RadioGroup>
      </CustomFieldEdit>
  );
};

export const EditCheck= () => {
  const {extensionContext: {fieldValue}} = useProductContext();

  const [val] = useState(async () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(fieldValue ? JSON.parse(fieldValue) : []);
      }, 3000);
    });
  });

  const onSubmit = values => {
    return JSON.stringify(values.multiSelect);
  };

  return (
      <CustomFieldEdit onSubmit={onSubmit}>
        <CheckboxGroup name="multiSelect" label="">
          <Checkbox label={"check1"} value={"check1"} defaultChecked={val.includes("check1")} />
          <Checkbox label={"check2"} value={"check2"} defaultChecked={val.includes("check2")} />
          <Checkbox label={"check3"} value={"check3"} defaultChecked={val.includes("check3")} />
        </CheckboxGroup>
      </CustomFieldEdit>
  );
};

src/index.jsx

import ForgeUI, {render} from "@forge/ui";

import {View} from "./view";
import {EditText, EditDate, EditSelect, EditRadio, EditCheck} from "./editText";

export const runView = render(<View/>);
export const runEdit = render(<EditText/>);
export const runEditDate = render(<EditDate/>);
export const runEditSelect = render(<EditSelect/>);
export const runEditRadio = render(<EditRadio/>);
export const runEditCheck = render(<EditCheck/>);

src/view.jsx

import ForgeUI, {useProductContext, CustomField, Text} from "@forge/ui";

export const View = () => {
  const {extensionContext: {fieldValue}} = useProductContext();

  return (
    <CustomField>
      <Text>{fieldValue}</Text>
    </CustomField>
  );
};

manifest.yml

modules:
  jira:customFieldType:
    - key: cft-test-hello-world
      name: cft-test
      description: text
      type: string
      validation:
        expression: value == null || !!value.match("^[A-Za-z]+$")
        errorMessage: The value must consist only of letters
      function: main
      edit:
        function: edit
    - key: cft-test-date
      name: cft-test-date
      description: date
      type: string
      function: main
      edit:
        function: edit-date
    - key: cft-test-select
      name: cft-test-select
      description: select
      type: string
      function: main
      edit:
        function: edit-select
    - key: cft-test-radio
      name: cft-test-radio
      description: radio
      type: string
      function: main
      edit:
        function: edit-radio
    - key: cft-test-check
      name: cft-test-check
      description: check
      type: string
      function: main
      edit:
        function: edit-check
  function:
    - key: main
      handler: index.runView
    - key: edit
      handler: index.runEdit
    - key: edit-date
      handler: index.runEditDate
    - key: edit-select
      handler: index.runEditSelect
    - key: edit-radio
      handler: index.runEditRadio
    - key: edit-check
      handler: index.runEditCheck
app:
  id: ari:cloud:ecosystem::app/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

package.json

{
  "name": "jira-custom-field-type-ui-kit",
  "version": "1.0.4",
  "main": "index.js",
  "license": "MIT",
  "private": true,
  "scripts": {
    "lint": "./node_modules/.bin/eslint src/**/* || npm run --silent hook-errors",
    "hook-errors": "echo '\\x1b[31mThe build failed because a Forge UI hook is being used incorrectly. Forge UI hooks follow the same rules as React Hooks but have their own API definitions. See the Forge documentation for details on how to use Forge UI hooks.\n' && exit 1"
  },
  "devDependencies": {
    "eslint": "^7.32.0",
    "eslint-plugin-react-hooks": "^4.2.0"
  },
  "dependencies": {
    "@forge/ui": "^1.2.1"
  }
}

environment
OS: Windows 10.
Forge cli version: 4.5.1
Browser:
image

I can reproduce the problem now while disabling the browser cache, so I think it’s not a temporary incident. And I can reproduce it every time I open a new issue create screen.
And re-deploying didn’t resolve the problem.
I could reproduce it on another site (there are no apps except for the demo app).
It can be reproduced with my actual product code.

If you need additional information, please let me know.

Regards,
Takafumi

@PawelRacki , @MagdalenaRogulska

By the way, why the edit function of the UI kit is called every time when the value is changed? I think calling the function when the form is submitted is enough.

  • The behavior of edit dialog is the same as my suggestion. So I think no users who use the custom field module expect the current behavior of the create issue screen.
  • I think all bugs I reported are related to this behavior. So, the current behavior would make the feature too complicated.
  • Current behavior increases the number of calling functions. It will affect the easiness of reaching the quota.

If the current design is for complex UI, it should be done by Custom UI. You provide Custom UI for simple use cases.
In addition, the UI kit function calling lag is a critical defect of UX when building reactive UI. So, I think you should recommend Custom UI for reactive UI for custom fields.

It’s just my opinion. But, I hope the design team considers it.

Regards,
Takafumi

Hi @takafumiohtake,
Thanks for all the valuable remarks. We will take all of them into consideration.

Regards
Magdalena

1 Like

Hello @takafumiohtake
Thanks for all the feedback. I’ve been trying to reproduce the broken behaviour with Unexpected error in app but with no success. I’ve used the same code as you do and also asked to do the same some of my teammates and yet it worked fine for us. Could you please share what does the error contain in your graphql request?

About the UI kit being called every time the value is changed, the main we’re doing it so we don’t have to wait with the issue creation for all of the submits coming from forge custom fields and instead have values already stored. However we’ll give it another thought bearing in mind the feedback you provided.

Regards,
Paweł

1 Like

Helllo @PawelRacki

Thank you for your reply.

I’ve been trying to reproduce the broken behaviour with Unexpected error in app but with no success. I’ve used the same code as you do and also asked to do the same some of my teammates and yet it worked fine for us. Could you please share what does the error contain in your graphql request?

I can’t also reproduce the issue that causes the “Unexpected error in app” error now. So I think something was changed. And it’s fixed.
And, the problem 2 related to checkboxes and radio buttons also seems to be fixed.
Thank you so much.

However, the navigation issue with Firefox still remains.

About the UI kit being called every time the value is changed, the main we’re doing it so we don’t have to wait with the issue creation for all of the submits coming from forge custom fields and instead have values already stored. However we’ll give it another thought bearing in mind the feedback you provided.

I didn’t have the point of view. Thank you for telling me.
I hope you make the best choice for both end users and developers.

Regards,
Takafumi

1 Like

Hi @takafumiohtake

Great to hear that the “Unexpected error” issue no longer appears. About broken Firefox experience - I’ve created a public issue [FRGE-791] - Ecosystem Jira so you can track it. I’ll also create a ticket in my teams backlog to fix this issue as soon as we can.
Also, once again, thank you for all the feedback you provided us. There was a lot of very valuable input for us and we’re very grateful for that.

Regards,
Paweł

1 Like

Hi @PawelRacki

Thank you for creating the issue.
I watched and voted for it.
I hope it will be fixed in the near future.

Regards,
Takafumi

2 Likes