Can't display issuetype icon with custom image

I created an application that, among other things, display multiple issues and the corresponding issue types.

The problem happens when trying to display images of custom icons. I fetch the issue type information and the icon url, but the icon doesn’t load. Inspecting in the network debug panel, I find that the icon image isn’t downloaded due to a 403 error.

What is the correct way to display issue type avatar icon in a custom ui application?

Thank you

1 Like

Hi @gabrieltakeuchi, thank you for reaching out and joining our community.
Did you set up external permission for you app? https://developer.atlassian.com/platform/forge/manifest-reference/permissions/#external-permissions
I believe it can solve your issue.

First, I thank you very much for replying.

I don’t understand how can that help, considering I am not fetching external URLs. Let me explain step by step what I am doing:

  1. I use requestJira to get the contents of an issue via rest API. The returned payload has, among other things, the issue avatar and the corresponding icon URL.

The response payload is something like this:

{
  "key": "ISSUE-1",
  "fields": {
    "issuetype": {
      "iconUrl": "https://api.atlassian.com/ex/jira/<some id>/rest/api/2/universal_avatar/view/type/issuetype/avatar/10552?size=medium"
    }
  }
}
  1. When the issue type has a default icon (for example for issue type “task”), if I just use iconUrl directly on a “img” tag, it works flawlessly

  2. When the issue type has a custom image that I uploaded, the image won’t display. Further inspecting the network requests I see that the request for that image is forbidden (403).

That is not an external URL and I don’t get any CSP errors. In fact, I did some tests using requestJira to fetch that URL and it works, but it’s not that simple to display and image that was fetched by an Ajax invocation. I’m actually trying that, but with limited success. I’m trying something like this:

    public static async getImage(url: string):Promise<string> {
        let apiUrl = url.replace(/.*\/rest/,"/rest"); # here I'm just removing everything before "rest"

        let response = await requestJira(apiUrl, {})

        // @ts-ignore
        return response.blob().then(function(blob) {
            return ImageUtils.blobToBase64(blob).then(function(dataUrl) {
                return dataUrl
            })
        })
    }

export class ImageUtils {
    public static blobToBase64(blob:Blob):Promise<string> {
      return new Promise((resolve, _) => {
        const reader = new FileReader();
        // @ts-ignore
        reader.onloadend = () => resolve(reader.result);
        reader.readAsDataURL(blob);
      });
    }
}

This code actually works for the standard images (the ones I don’t care), but the payload returned for the custom image is somehow corrupted or has some kind of extra encoding I still didn’t figure out.

Anyway, that seems a lot of work just to display a simple issue type icon, which is why I still ask: is there a better way? Is there some standard function buried in the documentation that can ease all this work?

1 Like

Concerning my issue to download the custom icon I believe I figured out the problem and it is probably related to the following issue:

https://ecosystem.atlassian.net/browse/FRGE-531

So, requestJira is fetching the custom image as text, but because the image is a PNG, it is corrupted.

If requestJira is fixed to receive a flag to request binary data, that would work, but it would be even better if forge provided a way to simplify the whole process

Hi @gabrieltakeuchi, I tested it and it seems that there is an issue with authentication. The custom icon OAuth url works neither in requestJira nor in img src. I requested the relevant team to resolve this issue.

1 Like

While I patiently wait for that fix, I managed a very hacky workaround and I’ll post here just in case anyone needs to do something similar and doesn’t have time to wait until Atlassian fixes the issue.

Based on another post, Requesting binary data with requestJira when using the Forge Bridge, I learned that forge functions are able to fetch the binary contents without being changed. With that knowledged, 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/2/universal_avatar/view/type/issuetype/avatar/10552?size=medium"})

Notice that you should not pass the whole avatar URL, you must strip down everything up to /rest.

This invocation will return the dataUrl for the given resource. That will be something like

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.

Of course, it would be so much better if the api returned image URLs with the required tokens, so I could just set that URL directly into the img.src, but at least this solution will get you moving.

Cheers

1 Like

Any news on this subject? The images are still broken

Hi! I’m sorry that this is taking so long. The ticket with the problem was triaged and added to short term backlog. Unfortunately I don’t think there is any work being done yet. I’ll ping people to get some estimates and come back to you.

Hi @gabrieltakeuchi ,
I have the same issue. While waiting jira-team fix it.
This is my way:
Get avatarId from issueType and map it
This domain is current domain of your app. Ex: https://demo.atlassian.net

  const mapIconUrl = (avatarId) => {
    const iconUrl = `${domain}/rest/api/3/universal_avatar/view/type/issuetype/avatar/${avatarId}?size=small&format=png`;
    return iconUrl;
  };

In other project type, maybe avatarId is not found, just use issueType.iconUrl, don’t need map id.