Add attachment in Forge Custom UI

Hey Community! I’m trying to create a simple text file as a blob in forge Custom UI and attach it to an existing Jira issue.
My code in the react component:

async function AddAttachmentToIssue(){
  const form = new FormData();
  const blob = new Blob(['HelloWorld!'], {type : 'text/plain'})
  form.append('file', blob, 'file.txt')
  const response = await invoke('addAttachmentToIssue', form);
  console.log(`AddAttachmentToIssue:`, response);
}

And code in the resolver function definitions file:

resolver.define('addAttachmentToIssue', async ({ payload, context }) => {
    const response = await api.asApp().requestJira(route`/rest/api/3/issue/CS-900/attachments`, {
      method: 'POST',
      body: payload,
      headers: {
        'Accept': 'application/json',
        'X-Atlassian-Token': 'no-check',
      }
    });
  return await response;
});

I do get a response: {headers: {…}, ok: false, status: 415, statusText: “Unsupported Media Type”}.”

  1. I checked if the blob file is fine so I download it and it’s ok
  2. I checked if I can upload the text file to the issue (I know but I had to) and I can
    I have no clue why it’s not working;(
2 Likes

Hey @ukaszWiniewski,

We are aware of some issues regarding attachments and the Forge lambda/resolver.

For uploading an attachment from the client-side of a Custom UI app, it may be easiest to use requestJira from the Custom UI bridge. This will make the request directly to the product API on the client-side, rather than via the resolver/lambda.

Custom UI bridge requestJira docs: https://developer.atlassian.com/platform/forge/custom-ui-bridge/requestJira/

1 Like

Hey @kchan!
Thank you for your reply. I did as you suggest but unfortunately, I got the same result.

import { requestJira } from '@forge/bridge';
import FormData from "form-data";

async function AddAttachmentToIssue(){
  const form = new FormData();
  const blob = new Blob(['HelloWorld!'], {type : 'text/plain'})
  form.append('file', blob, 'file.txt')

  const response = await requestJira('/rest/api/2/issue/CS-900/attachments', {
    method: 'POST',
    body: form,
    headers: {
      'Accept': 'application/json',
      'X-Atlassian-Token': 'no-check'
    }
  });
  console.log(`Response:`, response);
}

The result in the console:
Response: {headers: {…}, ok: false, status: 415, statusText: “Unsupported Media Type”, body: “”}

Do you have any ideas for another solution, workaround, or know when this attachment issue might be fixed?

2 Likes

Hey @ukaszWiniewski, I’m reaching out to the team to understand why this is happening. Will get back to you with an answer.

2 Likes

Hi @ukaszWiniewski,

Thanks for your patience. We’ve investigated the issue and found part of our code in @forge/bridge that doesn’t serialize FormData correctly.

We are currently making this fix so that the next version of @forge/bridge will make your code snippet above work.

In the meantime, could you try using the following workaround?

const form = new FormData();
const blob = new Blob(["HelloWorld!"], { type: "text/plain" });
form.append("file", blob, "file.txt");
const req = new Request("", {
  body: form,
  method: "POST",
});
req
  .text()
  .then((body) => {
    requestJira("/rest/api/3/issue/CS-900/attachments", {
      method: "POST",
      body,
      headers: {
        'Content-Type': req.headers.get('content-type'),
        Accept: "application/json",
        "X-Atlassian-Token": "no-check",
      },
    });
  })
  .then((response) => {
    console.log(`Response:`, response);
  });
2 Likes

Hello @kchan , solution with Request.text() works but only for text files. For binary images file uploaded too but it have wrong format. There is code sample for PNG image:

            ///////////////////////////////////////////
            // Create Canvas for draw and upload as image
            var canvas = document.createElement('canvas');

            canvas.id = "canvasImage";
            canvas.width = 100;
            canvas.height = 100;
            canvas.style.zIndex = 8;
            canvas.style.position = "absolute";
            canvas.style.top = 0;
            canvas.style.left = 0;
            document.body.appendChild(canvas)

            var ctx = canvas.getContext("2d");
            ctx.fillStyle = "rgba(0, 255, 0, 1.0)";
            ctx.fillRect(0, 0, 100, 100);
            //////////////

            // Convert canvas to Blob and upload it
            canvas.toBlob((blob) => {

                const form = new FormData();
                form.append("file", blob, "file.png");
                const req = new Request("", {
                  body: form,
                  method: "POST",
                });

                req.text().then((body) => {
                    console.log(body);
                    requestJira("/rest/api/3/issue/VIS-10/attachments", {
                        method: "POST",
                        body,
                        headers: {
                            'Content-Type': req.headers.get('content-type'),
                            Accept: "application/json",
                            "X-Atlassian-Token": "no-check",
                        },
                    })
                    .then((response) => {
                        console.log(`Response:`, response);
                    });
                });
            });
            ///////////////////////////////////////////

(post deleted by author)

hi again, Can anyone suggest a solution to this problem? @kchan