Can't deploy ForgUI with @forge/bridge component

I would like to have a button that opens a link in a new page, but I always get an error on forge deploy command.
I did npm install, the bridge folder and its files are existing in the path where they should.

Error: Bundling failed: ./src/index.jsx
Module not found: Error: Can’t resolve ‘@forge/bridge’ in ‘D:\jiraApps\memoQIntegration\src’
resolve ‘@forge/bridge’ in ‘D:\jiraApps\memoQIntegration\src’
Parsed request is a module
using description file: D:\jiraApps\memoQIntegration\package.json (relative path: ./src)
Field ‘browser’ doesn’t contain a valid alias configuration
resolve as module
D:\jiraApps\memoQIntegration\src\node_modules doesn’t exist or is not a directory
D:\jiraApps\node_modules doesn’t exist or is not a directory
D:\node_modules doesn’t exist or is not a directory
looking for modules in D:\jiraApps\memoQIntegration\node_modules
using description file: D:\jiraApps\memoQIntegration\package.json (relative path: ./node_modules)
Field ‘browser’ doesn’t contain a valid alias configuration
using description file: D:\jiraApps\memoQIntegration\node_modules@forge\bridge\package.json (relative path: .)
no extension
Field ‘browser’ doesn’t contain a valid alias configuration
D:\jiraApps\memoQIntegration\node_modules@forge\bridge is not a file
.ts
Field ‘browser’ doesn’t contain a valid alias configuration
D:\jiraApps\memoQIntegration\node_modules@forge\bridge.ts doesn’t exist
.tsx
Field ‘browser’ doesn’t contain a valid alias configuration
D:\jiraApps\memoQIntegration\node_modules@forge\bridge.tsx doesn’t exist
.js
Field ‘browser’ doesn’t contain a valid alias configuration
D:\jiraApps\memoQIntegration\node_modules@forge\bridge.js doesn’t exist
.jsx
Field ‘browser’ doesn’t contain a valid alias configuration
D:\jiraApps\memoQIntegration\node_modules@forge\bridge.jsx doesn’t exist
.json
Field ‘browser’ doesn’t contain a valid alias configuration
D:\jiraApps\memoQIntegration\node_modules@forge\bridge.json doesn’t exist
as directory
existing directory
using path: D:\jiraApps\memoQIntegration\node_modules@forge\bridge\index
using description file: D:\jiraApps\memoQIntegration\node_modules@forge\bridge\package.json (relative path: ./index)
no extension
Field ‘browser’ doesn’t contain a valid alias configuration
D:\jiraApps\memoQIntegration\node_modules@forge\bridge\index doesn’t exist
.ts
Field ‘browser’ doesn’t contain a valid alias configuration
D:\jiraApps\memoQIntegration\node_modules@forge\bridge\index.ts doesn’t exist
.tsx
Field ‘browser’ doesn’t contain a valid alias configuration
D:\jiraApps\memoQIntegration\node_modules@forge\bridge\index.tsx doesn’t exist
.js
Field ‘browser’ doesn’t contain a valid alias configuration
D:\jiraApps\memoQIntegration\node_modules@forge\bridge\index.js doesn’t exist
.jsx
Field ‘browser’ doesn’t contain a valid alias configuration
D:\jiraApps\memoQIntegration\node_modules@forge\bridge\index.jsx doesn’t exist
.json
Field ‘browser’ doesn’t contain a valid alias configuration
D:\jiraApps\memoQIntegration\node_modules@forge\bridge\index.json doesn’t exist
[D:\jiraApps\memoQIntegration\src\node_modules]
[D:\jiraApps\node_modules]
[D:\node_modules]
[D:\jiraApps\memoQIntegration\node_modules@forge\bridge]
[D:\jiraApps\memoQIntegration\node_modules@forge\bridge.ts]
[D:\jiraApps\memoQIntegration\node_modules@forge\bridge.tsx]
[D:\jiraApps\memoQIntegration\node_modules@forge\bridge.js]

@norb_szolnoki can you share your package.json file?
And manifest.yaml just to make sure :blush:

1 Like

@MichalMichalczuk thank you for help, here are the files
Package.json:

{
  "name": "forge-ui-starter",
  "version": "1.0.0",
  "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": "^6.5.1",
    "eslint-plugin-react-hooks": "^2.1.2"
  },
  "dependencies": {
    "@forge/api": "^1.1.0",
    "@forge/bridge": "^1.4.0",
    "@forge/ui": "^0.9.0",
    "global": "^4.4.0",
    "node-forge": "^0.10.0",
    "rm": "^0.1.8"
  }
}

manifest.yml:

modules:
  jira:issuePanel:
    - key: memoqintegration-hello-world-panel
      function: main
      title: memoQIntegration
      icon: https://developer.atlassian.com/platform/forge/images/icons/issue-panel-icon.svg
  function:
    - key: main
      handler: index.run
app:
  id: ari:cloud:ecosystem::app/7c1b58ed-0df9-4ab8-9d71-97af9f7b2afc
  name: memoQIntegration
permissions:
  scopes:
    - read:jira-work

Ah! I didn’t spot that on first run.
@forge/bridge is only allowed to use in Custom UI :crying_cat_face:

Please take a look at docs - https://developer.atlassian.com/platform/forge/runtime-reference/custom-ui-bridge/ , it is not explicitly wrote there - this is something to improve from our site.

Please consider converting your app to Custom UI - https://developer.atlassian.com/platform/forge/custom-ui/

3 Likes

@MichalMichalczuk ,
Thank you so much!
May I ask, if there is a simple sample about how to define a button with a custom URL?
The rest of our integration will be done in an aspx external page, I just pass the ID and title in the URL what the button sends.
In stand alone jira it was worked, but as cloud doesn’t accept the jar file I generated, I have to rewrite this simple button.

Sure :blush:

You can create fresh Custom UI app with forge create or take a look on example of Custom UI app like https://bitbucket.org/atlassian/todo-app-custom-ui/src/master/

Then npm i @forge/bridge in your static folder.
And you can use it as below.
(please make sure you have new version of @forge/brige :wink: )

import { router } from '@forge/bridge';
import Button from '@atlaskit/button';

// then in your component, to open in new tab
<Button onClick={() => router.open('https://atlassian.com')}>🔗 go to Atlassian.com</Button>

// then in your component, to navigate page
<Button onClick={() => router.navigate('https://atlassian.com')}>🔗 go to Atlassian.com</Button>

Hope that helped :slight_smile:

PS You don’t have to use React, in your case it sounds like plain static HTML + a bit of JS is enough.

1 Like

@MichalMichalczuk ,

I don’t know how to thank all of your help!

Just a tiny thing, you must import the button like this:
import Button from ‘@atlaskit/button’;
because the button not exported, I just found a solution by googleing a bit :smiley:
Thank you so much!

Import Button from ‘@atlaskit/button’;

:scream: . Yes you’re right sir :wink:
I updated my previous answer for future reference.

Glad that I helped! Happy Forging :grinning_face_with_smiling_eyes:

1 Like

May I have a bit dumb question, but which is my component file, I really went through the sample of custom UI, but I didn’t find out this.?
The App.js?

Exactly in App.js.

Simplest implementation would be (in App.js)

import React from 'react';
import Button from '@atlaskit/button'
import { router } from '@forge/bridge';

const openPage = () => router.open('https://atlassian.com');

const App = () => {
  return (
    <Button onClick={openPage}>🔗 go to Atlassian.com</Button>
  );
}

export default App;
1 Like

Hi @MichalMichalczuk ,
I’m using also router functions inside a Confluence Custom UI macro.
I got the experience that a popup first ask for permission when having an external URL (like https://google.com).
2021-04-21 14_50
Also in the browser console appears: Refused to display 'https://www.google.com/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.

If I confirm this (click Continue) it opens this in a new tab :-), but in the current tab with my macro this looks like this:
2021-04-21 14_54_

Do I something wrong or is there a chance to avoid this?

Thank you so much, can I use ${issue.key} in the app.js to pass it with the URL?

The prompt with warning is intended, we’d like to inform our users that they will navigate somewhere outside product - to cut out abuses (like some extension send user to page which look like Jira but would like to steal your password :scream: )

But - if you’d like to use relative url (and navigate to other page on Jira on Confluence instance) user won’t get prompted with this popup (since they’re not leaving product)

It is mentioned on router docs.

can I use ${issue.key} in the app.js to pass it with the URL?

While using Custom UI - those are just static files.
So this is just JavaScript and just React, you can use all of those :wink:

3 Likes

Thank you, but I am not sure I understand, if I use ${issue.key} it will applied as a string.
To give you more understanding, in XML I had the following webitem:

  <web-item name="TRANSLATE" i18n-name-key="TRANSLATE.name" key="TRANSLATE" section="jira.issue.tools" weight="1000"> 
    <description key="TRANSLATE.description">The myItem Plugin</description>  
    <label key="TRANSLATE"/>  
    <link linkId="TRANSLATE-link">http://MYURL/submit.aspx?issueId=${issue.id}&amp;issueName=${issue.key}</link> 
  </web-item>

I tried Julians solution from here:

But, I always get an error fetch undefined.

I guess you’re asking about access to context data - like current issue key etc.
What you pasted looks like ASP.NET specific code :wink:

How to get context

Here you can find the reasoning behind “hiding” context, and guide how to access app context in Custom UI.
https://developer.atlassian.com/platform/forge/app-context-security/

CustomUI resources

Here https://developer.atlassian.com/platform/forge/build-a-custom-ui-app-in-jira/ you will find good tutorial how to write CustomUI apps (which also includes resolvers :wink: )

Example app

Please take a look at this example app (it is based on CustomUI), which shows how to utilise issueKey and other context fields ToDo app in CustomUI

In this file - you see the resolvers, which have access to context :wink:

Hi @MichalMichalczuk ,

I hope, this will be my last comment here (:smiley:

I added to src/index.js the following:

const getListKeyFromContext = (context) => {
  console.log(context);
  const { localId: id } = context;
  return id.split('/')[id.split('/').length - 1];
}

resolver.define('get-all', ({ context }) => {
  return getAll(getListKeyFromContext(context));
});

const getAll = async (issueKey) => {
  return await storage.get(issueKey) || [];
}

Then to App.js:

const [issueKey, setKeys] = useState(null);
invoke('get-all').then(setKeys);

But the issueKey variable still empty, there is a documentation about what holds the context and how to retrieve it’s data? If I open the console, Jira drops a ton of warnings so I can’t track them :frowning:

Hey, sorry for late reply.
I’m guessing you’d like to get key of currently open issue :thinking:

you can do it as below

Client code

import React, { useEffect, useState } from 'react';

import { invoke } from '@forge/bridge';

const App = () => {
  const [issueKey, setIssueKey] = useState(null);

  useEffect(() => invoke('getIssueKey').then(setIssueKey), []);

  return (
    <>
      <h1>Current issues key</h1>
      <p>{issueKey}</p>
    </>
  );
};

export default App;

Resolver

import Resolver from '@forge/resolver';

const resolver = new Resolver();

resolver.define('getIssueKey', ({ context }) => {
  const { extensionContext } = context;

  return extensionContext.issueKey;
});

export const handler = resolver.getDefinitions();