Unable to upload files from jira to custom backend in forge custom UI

Hi all, I am building a Jira plugin and one of my requirements is to upload files to a custom backend.

I am using ReactJS for the custom UI and trying to upload files using form data but I am facing one issue while the request is being sent to the backend nothing is going in the body of the API request.

I tried the same request using Postman, and it’s working fine there.

I have tried all the methods but none is working for me.

Technologies used: React JS for custom UI, Forge for Jira connectivity, node JS for backend.

Can anyone please respond to this

Hi @adeshkumar123, it’s a bit hard to figure out what is going wrong with the information that you’ve provided us. Are you able to provide a code snippet? and more info about your “custom backend”.

one issue while the request is being sent to the backend nothing is going in the body of the API request.

Does this mean that your backend receives the request but doesn’t receive the body of the request?

If you don’t receive the requests at all, one thing that pops to mind is egress controls; have these been enabled for your Forge app?

1 Like

Hi @BoZhang , thanks for the response.
My custom backend is made on node js and express js server. All the functionalities I am handling from my custom backend are working fine. Yes it includes the API calls from the forge app as well. SO my request and all other things are working properly.

But when it comes to sending files, I am using formdata to include files in the body and on the backend I am using multer middleware to handle files. But when I am checking I am not getting any file in the request.

Here you can find the code snippet

  const handleFiles = async (files) => {
    const formData = new FormData();
    console.log('Files to upload:', files);

    files.forEach((file) => {
      formData.append('files', file);
    });

    formData.append('attachableType', entityType);
    formData.append('attachableId', entityId);

    try {
      const response = await invokeApiRemote({
        config: createAttachment(),
        body: formData,
      });

      if (response) {
        console.log('Attachment created successfully:', response);
        onUpload && onUpload(response);
      }
    } catch (error) {
      console.error('Error creating attachment:', error);
      setError(error.message);
    }
  };

and on backend this is how I am handling it

routes.route('/attachments')
    .post(upload.array('files'), AttachmentsController.createPluginAttachment);

and here is my controller

  static async createPluginAttachment(request, response, next) {
    const gainssProjectId = request.currentProject.id;
    const { jiraAccountId } = request.currentUser;


    if (!request.files || request.files.length === 0) {
      return response.status(Controller.HttpStatus.UNPROCESSABLE_ENTITY).json({
        message: 'Please upload at least 1 file.',
      });
    }

      // I am getting returned from here itself

    try {
      const attachable = await Controller.finders.findPolymorphic(
        request.body.attachableType,
        request.body.attachableId
      );

      const uploads = [];
      for (const file of request.files) {
        const uploadedFile = await attachable.createAttachment({
         ...somedata
        });
        uploads.push(uploadedFile);
      }
      console.log("uploaded")
      return response
        .status(Controller.HttpStatus.CREATED)
        .json(attachmentIndexView(uploads));
    } catch (error) {
      console.error('Error creating attachment:', error);
      next(error);
    }
  }

Please let me know if the information provided helps

Here is the invoke function defintion

export const invokeApiRemote = async ({ config, body }) => {
  try {
    const { path, method, headers } = config;
    const fullUrl = `/api/v1/${path}`;

    // Make the API request
    const response = await invokeRemote({
      path: fullUrl,
      method,
      headers: {
        'Content-Type': 'application/json',
        ...headers,
      },
      body,
    });

    // Check if response status indicates an error
    if (response.status >= 200 && response.status < 300) {
      // Successful response
      return response.status === 204 ? { success: true } : response.body;
    }
    const errorMessage = response.body?.error?.message;
    throw new Error(errorMessage);
  } catch (error) {
    throw new Error(
      error.message || 'An unexpected error occurred. Please try again.'
    );
  }
};

Hi @adeshkumar123 , thank you for providing the sample code.
I can see two issues:

  1. I can see that when you call invokeRemote you set the 'Content-Type': 'application/json', this would suggest that the expected body of your request is a JSON, but you are actually sending form-data.
  2. The other problem, which is a blocker, is that invokeRemote via @forge/bridge does not currently support anything but JSON, so even if you send the correct headers, it won’t work.
    However, we have recently released a new variation of invokeRemote which allows you to call a remote from within a Forge function. I need to verify whether or not this variant allows for form-data bodies, but if it works, you could potentially get things working by going from Custom UI -> Backend Forge resolver function -> remote (via invokeRemote).
1 Like

Hi @BoZhang I have tried by removing the application/json but its not working.
How can I use the new functionality of the invokeRemote

@BoZhang is there anything else other then invokeRemove that can I use in the custom UI

Hi @adeshkumar123, sorry I haven’t had a chance to test the invokeRemote path via the Forge resolver function.
From what I can see the alternatives are this:

  • Use fetch API directly from your Custom UI Front end. This will mean that you will not get the Forge Invocation Token you get with invokeRemote and you will have to do something for authentication yourself.
  • Try to use invokeRemote from the Forge resolver (To do this you will have to call a Forge resolver from your Custom UI FE and then call invokeRemote from within the Forge resolver function). I don’t know if this will work, it was what I was going to test.

Okar @BoZhang I will test and let you know

@BoZhang unfortunately that is not working. with normal fetch its giving me CSP errors

Hi, thanks for giving it a go. Sorry to hear it didn’t work.

What errors are you getting with invokeRemote via the Forge resolver function?

When using fetch in the client you will need to include egress permissions for the domain you are trying to call.

1 Like

@BoZhang I have tried both the solutions and I am not getting any error just files are not going in the request at all.

Hi,

unfortunately that is not working. with normal fetch its giving me CSP errors

Is this after applying the egress permissions for calling fetch directly in your Custom UI app?

Can you also provide me with your appId? I can have a look at the logs to see where it’s failing for the invokeRemote solution.

1 Like

Here is my app id 143e5c01-93d3-4430-aec6-3ffe62b88127

Hi, I checked our logs, I could see that your Forge resolver is being called but unfortunately I could not see any egress calls being made by your Forge resolver, suggesting invokeRemote wasn’t called.

Can you please provide me with sample code for your Forge resolver function?

In terms of using fetch directly from your Custom UI app, have you checked whether or not you see the requests in the network section of your browser’s developer tools?

1 Like

Hi @BoZhang
I am really very thankful for the help.
I have used the normal fetch function with the egress permission for client and it worked. The only issue I have is jira tokens are not going into it. So for that I have added another checks and authentication for that particular endpoint.

1 Like