Download and display Jira issue attachments with Forge

Hey there :slight_smile:

I’m trying to display Jira issue attachments (images) within my Custom UI.

I’m using this function to obtain a blob URL. Afterwards I use the URL as the src attribute of an img tag to display the image. Unfortunately the resulting image is broken.

export async function fetchAttachmentById({attachmentId}: { attachmentId: string }) {
    const response = await requestJira("/rest/api/3/attachment/content/" + attachmentId);
    if (!response.ok || !response.body) {
        throw new Error("Failed request.");
    }
    const blob = await response.blob();
    return URL.createObjectURL(blob);
}

The dev tools show that the Forge bridge downloads a file. But its content seems to be broken. Maybe the content gets corrupted through the PostMessage API Forge uses under the hood?

I’ve tried multiple things (Also fiddled around with the “redirect” param of the REST Api to get a direct URL from the location header, but the header gets omitted.)

A working code sample would be great. Or maybe an Atlassian could have a look at this behavior to confirm this is a bug.

Please help! :slight_smile:

1 Like

@JulianWolf did you ever get this resolved?

I’m trying to fetch the thumbnail directly from the url given in the REST API response from Jira (not even trying to look up the content and building the url myself) and I’m running into the same issue.

/Daniel

Hi @JulianWolf ,
What do you mean by the image is broken?
Do you see anything on your response? Are you able to get anything out of the response.blob()?

Best,
Gabriel

This could also be related to the mediaApiKey not being returned, as mentioned on this thread.

This has been reported here.

Best,
Gabriel

Hey @danielwester & @GabrielDias

thank you for reaching out.

No, unfortunately this has not been resolved yet. Currently I don’t know how to fetch and display an image that is a Jira attachment with Custom UI (ugly workaround at the end).

@GabrielDias yes, I see something in the response. tl;dr: The image data seems to be corrupted. I assume that because it contains plenty � characters.

More information about my analysis: when the Custom UI client calls the api endpoint (see code above) the following things happen:

  1. The inner iFrame tells the host frame via postMessage to make the according call.

  2. Then the host frame makes the according request for our iFrame which results in a redirect (303)

  3. The outer frame follows the according redirect and downloads the image. Chrome shows the image, everythings is fine until here

  4. Now I assume annoying things happen. The host frame passes the response back to our iFrame via postMessage api. According to the chrome logs this data is corrupted (see the ��� at the bottom).

I’m not entirely sure but I guess the Forge magic that happens in the host frame should convert the data to base64 first before passing it to the postMessage API (which isn’t compatible with binary data like images).

Summary

@GabrielDias I don’t know if this has anything to do with the mediaApiKey you mentioned. Any working example of how to render an attached Jira image would be appreciated.

@danielwester No, this hasn’t been resolved yet. I ended up, staying on Connect in my case. But there is an ugly workaround. You can download the attachment in a Forge backend function, then convert it in your backend code to a base64 string and pass it to the Custom UI. This is incredibly slow and unnecessarily pipes the whole data through a AWS lambda. It worked. But I don’t think that this scales or works with really large data. You could also convert the image to base64 before uploading it as an attachment. Users would assume that it is a txt file when viewing the Jira issue, but then your app could directly fetch it from Custom UI. (:nauseated_face:.)

Hope this helps.

Cheers
Julian

2 Likes

@JulianWolf

I had a similar issue, but instead of fetching attachments, I was trying to fetch issueType icon images when the user setup a custom image. I worked out a “hacky” solution that works for my case, so I believe it might work in your case as well:

Based on another post, Requesting binary data with requestJira when using the Forge Bridge, I learned that forge functions in the “backend” are able to fetch the binary contents without “corrupting” it. In fact, forge/@bridge corrupts the image because it interprets the binary response as text (related issue) .

With that knowledge, I wrote the following forge function (not frontend, this a “backend” function, defined in a forge module).

import api, {route} from "@forge/api";
const resolver = new Resolver();
resolver.define('requestJiraAsDataUrl', async(req) => {
    let restApi = req.payload.restApi
    let response =  await api.asUser().requestJira(route `${restApi}`, {})
    let contentType = response.headers.get('content-type')
    const arrayBuffer = await response.arrayBuffer();
    const base64 = Buffer.from(arrayBuffer).toString('base64')
    return `data:${contentType};base64,${base64}`
})

This function can be invoked from the CustomUI using @forge/bridge invoke function, like this:

let dataUrl:string = await invoke("requestJiraAsDataUrl", {restApi: "/rest/api/3/attachment/content/" + attachmentId})

This invocation will return the dataUrl for the given attachment. That will be something like this:

data:image/png;charset=UTF-8;base64,iVBORw0KGgoAAAANSUhEUgAAADA...

This works because await api.asUser().requestJira has the required credentials in the request to fetch image.

Finally, all you have to do is set y our img.src with the returned value and, hopefully, that will work

Cheers

I go there but not find the proper solution there.

Hi @gabrieltakeuchi, I’m the author of the bug report that you linked - thanks for posting your workaround, but unfortunately this doesn’t work for large data as @JulianWolf mentions. When trying to request a 1 MB image I see this error returned:

{
    "timestamp": 1645387235637,
    "path": "/central/graphql",
    "status": 413,
    "error": "Payload Too Large",
    "message": "GraphQl query and variables must not exceed 512000 bytes in size",
    "requestId": "3f2b214a-963",
    "exception": "io.atlassian.graphql.gateway.http.RequestTooLargeException",
    "request_id": "UNKNOWN",
    "query": "unknown",
    "operation": "unspecified",
    "schema": "unspecified",
    "errorSource": "GRAPHQL_GATEWAY"
}