Feel free to open issues / pull requests in case of questions / errors on this exercise

Duration

Between 1 and 2 days

Skills

Javascript / Typescript, React, REST API

License

license

Repository

https://github.com/elements-apps/atlassian-forge-tutorial

Changelog

Date Summary

2021-09-13

Document creation

2022-02-23

Updating deprecated scopes

1. Prerequisites

Before starting this exercise, be sure to:

At this point, we expect the following to be the case:

  • Node.js and Docker are installed on you computer;

  • The Forge CLI is globally installed, and you are logged in using your Atlassian API token;

  • You’ve tested the Forge create, deploy, install and tunnel commands at least once;

  • You’ve created an Atlassian Cloud developer site;

2. About this exercise

The main purpose of this exercise is to explore the main Forge features by creating a simple Jira application that adds generated user information on Jira issues.

And what about Confluence ?
This first exercise only covers Jira, but many features are shared between Jira and Confluence (UI kit, triggers, …​). Currently, only macro are specific to Confluence.

Here is the plan: first thing to do is create an administration page, to allow users configuring our application. Once it’s done, we will listen to issue creations in Jira by using the trigger mechanism. Then, when an issue is created, we will retrieve our application settings from the Forge storage and fetch a remote API to retrieve our user information and store them into the created issue. Finally, the information will be rendered inside the issue, using an issue panel.

The plan

The exercise is split into 4 parts:

  1. Set up the Forge application;

  2. Create the application administration;

  3. Configure the issue creation trigger;

  4. Display the user information in the issue;

At the end of each part (except the Forge setup), you will be asked to implement a solution by yourself. However, a solution is available in this repository if needed.

These exercises will require you to explore the Forge documentation to understand and discover Forge capabilities. If needed, don’t hesitate to ask for help on the Atlassian Developer Community.

3. Let’s code!

3.1. Setup the Forge application

First thing to do is creating our application by using the Forge CLI:

forge create random-user

This command shows you various templates to start a new Forge app. Select Custom UI, then jira-issue-panel by using arrow keys, and press Enter :

Start with a template. Each template contains the required files to be a valid app.

? Select a category: Custom UI
? Select a template: jira-issue-panel

✔ Creating app...

ℹ Downloading template
ℹ Registering app
ℹ Creating environments
ℹ Installing dependencies

✔ Created cat-tachment

Your app is ready to work on, deploy, and install. We created 3 environments you can deploy to: development, staging, production.

Change to directory random-user to see your app files.

This template contains files which will be useful in the last part (Custom UI). Now, if you navigate to the Forge Developer Console, you should see your newly created app.

To continue the setup, go to the ./static/hello-world/ folder, and run the following command:

npm install
npm run build

This part is required to start your Forge app and will be explained later, in the Custom UI part. Ignore it for now and go back to the top of your app directory (cd ../..).

Let’s see files generated by the Forge CLI:

  • ./src folder which contains code relative to Forge functions and UI Kit

    • index.tsx you can ignore its content for now (since it related to the issue view / Custom UI)

  • ./static same, you can ignore for now

  • manifest.yml is the file describing your app

Let’s focus on the manifest.yml file and its different parts:

modules:
  jira:issuePanel: (1)
    - key: random-user-hello-world-panel
      resource: main
      resolver:
        function: resolver
      viewportSize: medium
      title: random-user
      icon: https://developer.atlassian.com/platform/forge/images/issue-panel-icon.svg
  function: (2)
    - key: resolver
      handler: index.handler
resources: (3)
  - key: main
    path: static/hello-world/build
app: (4)
  id: ari:cloud:ecosystem::app/9513507c-5fa1-4e5e-8fee-a5d7ef17a07e
1 modules are locations inside Atlassian products where your app can be displayed. The current module allows our app to appear in the Jira issue.
2 function allows you to define Javascript function as Forge functions. They are used by modules entries to display content. The key property is a unique identifier for the function, and the handler is the "path" to the function. Here, we ask the function handler from the file ./src/index.js.
3 resources are used by Custom UI (so, let’s see this later)
4 app is used to identify your app in the Atlassian Forge infrastructure.
  • The indentation is really important in the YAML language

  • If you copy manifest contents from this exercise, ensure to replace the app.id with the one generated when you created the application. Otherwise, you will have permission issues.

We will add other properties soon in this exercise.

Resource

All available properties of the manifest.yml file can be found on the Forge Documentation.

Now, let’s start creating our administration!

3.2. Create the administration

Let’s build our administration with UI kit. This part of Forge allow you to declare graphical interface using components provided by Atlassian (and only them). And before starting, some setup (again).

Resource

Documentation about UI kit (components, hooks, event) can be found on the Forge documentation.

3.2.1. Adding Typescript

To add Typescript in the UI Kit part of our project, we simply need to run:

npm install typescript @types/node @types/react

and add the tsconfig.json file in our app folder:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2017",
    "sourceMap": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "lib": ["dom", "es2017"],
    "types": ["node", "react"],
    "baseUrl": "./",
    "allowJs": true,
    "jsx": "react",
    "jsxFactory": "ForgeUI.createElement"
  },
  "include": ["./src/**/*"]
}

3.2.2. Declaring our administration

Now, we have to declare our administration in the manifest.yml. Before that, let’s create the function responsible for rendering our administration. In ./src, create the file administration.tsx with the following content:

import ForgeUI, {AdminPage, render, Text} from '@forge/ui';

const App = () => {
    return (
        <AdminPage> (1)
            <Text>Hello, world!</Text>
        </AdminPage>
    );
};

export const run = render(
    <App/>
);
1 Since we want to be displayed as an admin page, our root component have to be <AdminPage>. Each module has its own rooter component to use.

Install the missing dependency (which contains UI kit)

npm install @forge/ui

And now, it’s time to declare the run function from our administration.tsx file. Open the manifest.yml and add the following content:

modules:
  jira:issuePanel:
    - key: random-user-hello-world-panel
      resource: main
      resolver:
        function: resolver
      viewportSize: medium
      title: random-user
      icon: https://developer.atlassian.com/platform/forge/images/issue-panel-icon.svg
  jira:adminPage: (2)
    - key: random-user-administration
      function: run-administration
      title: Random user administration
  function:
    - key: resolver
      handler: index.handler
    - key: run-administration (1)
      handler: administration.run
resources:
  - key: main
    path: static/hello-world/build
app:
  id: ari:cloud:ecosystem::app/9513507c-5fa1-4e5e-8fee-a5d7ef17a07e
1 First thing to do is declaring our function
2 Then, we need to tell Forge we want to be displayed in a Jira administration page

3.2.3. Time to deploy and test!

Now, we should have a something working, let’s test that. Since it’s our first run, we have to execute the forge deploy command to upload our manifest.yml and Forge functions to Forge infrastructure. Then, let’s install our app on a real Jira Cloud instance. Run the forge install command, select Jira and type your Atlassian Cloud instance URL.

? Select a product: Jira

Enter your site. For example, your-domain.atlassian.net

? Enter the site URL: your-instance.atlassian.net

Installing your app onto an Atlassian site.
Press Ctrl+C to cancel.

Your app will be installed with the following scopes:
- read:me

? Do you want to continue? Yes

✔ Install complete!

Your app in the development environment is now installed in Jira on your-instance.atlassian.net

Now, navigate to your Jira instance, open settings (top-left menu) and select "Apps". You should see your app on the left side menu. Click on it, the app is loading and should greet you (and the world)

app menu

3.2.4. Updating the app

Instead of using forge deploy after editing our administration, we can use forge tunnel.

For Mac with ARM processor you will have to use this hack in order to be able to use forge tunnel.

This command will update your app each time a change is detected. Ensure Docker is running on your computer, then run the command:

$ forge tunnel

Tunnel redirects requests you make to your local machine. This occurs for any Atlassian site where your app is installed in the development environment. You will not see requests from other users.
Press Ctrl+C to cancel.

Checking Docker image... 100%
Your Docker image is up to date.

Reloading code...

=== Running forge lint...
No issues found.

=== Bundling code...
App code bundled.

=== Snapshotting functions...
No log output.

App code reloaded.

Listening for requests...

Now, if you refresh the page where your application is displayed, the Forge tunnel should detect the request:

invocation: 3e300f372fab08cb administration.run

Try adding a component to your administration.tsx:

<AdminPage>
    <Text>Hello, world!</Text>
    <Text>No forge deploy needed!</Text>
</AdminPage>

And refresh your page: the new line of text should appear.

3.2.5. Real things start

The setup is now over, it’s time to implement our administration. You have to do this part by yourself, using the resources below. An implementation is available in the ./typescript/random-user directory of this repository. You can use it to compare your solution or as a little help.

What you must do:

In the next part, we will use the randomuser.me API to generate random user information. Using URL parameters, it’s possible to act on generated information. Our administration will simply allow our user to set the gender which will be generated. The API option values are male and female, but you must allow your user to also select a random option in your administration. The selected choice must be loaded and saved using the Forge Storage API, because in the next part, the trigger will rely on it.

Resources:

Additional notes:

  • Everything can be done in the administration.tsx file;

  • Don’t forget to install @forge/api to use storage;

  • You can use any components you want (radio, dropdown, button group, …​) to collect user choice;

  • Check the tunnel output, some errors and associated fix will be displayed here;

  • To apply changes relative to Forge permissions, run forge depoy then forge install --upgrade;

  • If you use console.log, logs will be displayed in the Forge tunnel, and not the browser

admin mockup

And now, it’s trigger time.

3.3. Listening for issue creations

The next part of the exercise is listening for a new issue and attaching generated user information on it. Regarding Typescript, the previous configuration is reused, so no additional steps.

3.3.1. A trigger?

Triggers are functions called when a specific event occurs on an Atlassian product. For example your can listen for Confluence page creation or Jira mentions on issues. Once the event is triggered, Forge calls your function with all the details, and you can do your business.

Resource

Documentation about Forge triggers can be found on the Forge documentation.

3.3.2. Declaring a Forge trigger

Previously we used the manifest.yml to declare our administration. Guess what? Still the same way for triggers:

modules:
  jira:issuePanel:
    - key: random-user-hello-world-panel
      resource: main
      resolver:
        function: resolver
      viewportSize: medium
      title: random-user
      icon: https://developer.atlassian.com/platform/forge/images/issue-panel-icon.svg
  jira:adminPage:
    - key: random-user-administration
      function: run-administration
      title: Random user administration
  trigger: (2)
    - key: issue-created-event (3)
      function: issue-created
      events:
        - avi:jira:created:issue (4)
  function:
    - key: resolver
      handler: index.handler
    - key: run-administration
      handler: administration.run
    - key: issue-created (1)
      handler: trigger.run
resources:
  - key: main
    path: static/hello-world/build
app:
  id: ari:cloud:ecosystem::app/9513507c-5fa1-4e5e-8fee-a5d7ef17a07e
permissions:
  scopes:
    - storage:app (5)
    - read:issue:jira (6)
    - read:issue-type:jira
    - read:user:jira
    - read:project:jira
    - read:status:jira
1 First, we define the function which will be called when the event occurs
2 We add the trigger section
3 and the entry for our associated function
4 Finally, we indicate the events we want to listen to
5 This permission was added for the administration part and will also be required for our trigger (nothing to add)
6 These permissions are required by the avi:jira:created:issue event. We will talk about permissions right after this part.

Don’t forget to create the ./src/trigger.ts file (no need to add .tsx since the trigger doesn’t return graphical content). You should also add the following content, to match our previous declaration in the manifest.yml file.

export async function run(event, context) {
    console.log('Here is the content of the event:');
    console.log(JSON.stringify(event, null, 4));
}

The event parameter contains information related to the received event. So content for a Confluence page created and a Jira issue created are not the same.

Now, run forge deploy to declare the trigger, upgrade your installation using forge install --upgrade since permissions changed and run forge tunnel to watch executions and logs. Then, create a Jira project on your instance (or use an existing one), and create a new issue. The forge tunnel should display something like:

invocation: 805279f4da83daeb trigger.run
INFO    11:49:25.739  805279f4da83daeb  Here is the content of the event:
INFO    11:49:25.740  805279f4da83daeb  {
    "issue": {
        "id": "10046",
        "key": "JIR-37",
        "fields": {
            "summary": "Trigger test !",
            "issuetype": {
                "self": "https://your-instance.atlassian.net/rest/api/2/issuetype/10001",
                "id": "10001",
                "description": "Functionality or a feature expressed as a user goal.",
                "iconUrl": "...",
                "name": "Story",
                "subtask": false,
                "avatarId": 10315,
                "hierarchyLevel": 0
            },
            "creator": {
                "accountId": "5bccf2f82bfc57158b2faa60"
            },
            "created": "2021-10-20T13:49:24.537+0200",
            "project": {
                "self": "https://elements-cga.atlassian.net/rest/api/2/project/10000",
                "id": "10000",
                "key": "JIR",
                "name": "Jiraya",
                "projectTypeKey": "software",
                "simplified": false,
                "avatarUrls": {...}
            },
            "reporter": {
                "accountId": "5bccf2f82bfc57158b2faa60"
            },
            "assignee": null,
            "updated": "2021-10-20T13:49:24.537+0200",
            "status": {
                "self": "https://your-instance.atlassian.net/rest/api/2/status/10000",
                "description": "",
                "iconUrl": "https://your-instance.atlassian.net/",
                "name": "Backlog",
                "id": "10000",
                "statusCategory": {
                    "self": "https://your-instance.atlassian.net/rest/api/2/statuscategory/2",
                    "id": 2,
                    "key": "new",
                    "colorName": "blue-gray",
                    "name": "To Do"
                }
            }
        }
    },
    "atlassianId": "5bccf2f82bfc57158b2faa60",
    "associatedUsers": [
        {
            "accountId": "5bccf2f82bfc57158b2faa60"
        }
    ]
}

In these logs, we can see who created the issue and various fields with their content (id, key, project info, …​).

3.3.3. About permissions

Before coding the trigger part, we must talk about permissions in Forge apps. There are various type of permissions, as listed here:

  • Permissions for using some Forge API, like the storage one (you used it earlier)

  • Permissions to access some product parts, especially when using product REST API

  • Permissions to access external contents. By default you can’t talk with the entire internet, without asking user permissions

Some of these permissions are displayed to the administrator when they are installing your app, others are displayed directly to the end user (if your request concerns personal data for example).

Remember, when you add a new permission you have to redeploy your app (forge deploy) and upgrade existing installations using forge install --upgrade for changes to take effect.

3.3.4. Now, exercise time

Like for the administration, the implementation is up to you again! A possible implementation is available in ./typescript/random-user/src/trigger.ts for help / comparing your solution.

What you must do:

When an issue is created, retrieve the settings in the forge storage. Then, request https://randomuser.me/api to generate random user information. You must take in account the user choice concerning the gender. For more information about the gender parameter, check the randomuser documentation.

The goal is to store the information received from the API into the issue, by using Jira Properties, to retrieve it later.

randomuser.me is a free service, however you can support project with a donation on the website.

Resources:

Additional notes:

  • Again, watch out for permissions

  • Take in account that the storage can be empty if the user hasn’t used the administration yet.

  • Don’t forget to use route with requestJira

  • You can also work with Jira Properties through REST API

Next step, Custom UI!

3.4. Display user information in Jira issue

To conclude this exercise, we will display our generated user into a Jira issue. Instead of UI kit, we will use Custom UI, the second way to build a graphical interface using Forge.

Custom UI is a more open way to build the front part than UI kit. In fact, you can use anything (languages and technologies) you want. The only thing Forge require in the end is static assets (HTML, CSS, JSS, …​).

When we created our Forge app at the beginning of this exercise, we used a Custom UI template. Now it’s time to dig into all the "we will see this later" things.

Resource

Documentation about Custom UI can be found on the Forge documentation.

3.4.1. Custom UI: how does this work?

If you check the ./static/hello-world directory, you will see a standalone React project. In fact, this was generated by the Forge template we used previously. The project you see is based on create-react-app, which provides a ready-to-use React application.

To be recognized as a "Custom UI project", you must declare a resources in the manifest.yml. Hopefully, everything is ready thanks to the Forge template:

modules:
  jira:issuePanel:(2)
    - key: random-user-hello-world-panel
      resource: main
      resolver: (3)
        function: resolver
      viewportSize: medium
      title: random-user
      icon: https://developer.atlassian.com/platform/forge/images/issue-panel-icon.svg
  jira:adminPage:
    - key: random-user-administration
      function: run-administration
      title: Random user administration
  trigger:
    - key: issue-created-event
      function: issue-created
      events:
        - avi:jira:created:issue
  function:
    - key: resolver
      handler: index.handler
    - key: run-administration
      handler: administration.run
    - key: issue-created
      handler: trigger.run
resources: (1)
  - key: main
    path: static/hello-world/build
app:
  id: ari:cloud:ecosystem::app/9513507c-5fa1-4e5e-8fee-a5d7ef17a07e
permissions:
  scopes:
    - storage:app
    - read:issue:jira
    - read:issue-type:jira
    - read:user:jira
    - read:project:jira
    - read:status:jira
  external:
    fetch:
      backend:
        - https://randomuser.me (4)
1 Our resource "main" is declared here. The path tells Forge where static assets of our Custom UI can be found. In our case, the output build directory of our hello-world React application is provided.
2 This part was generated before using forge create, so Forge knows we want to be displayed in an Jira issue panel
3 We tell Forge which function will be used to handle communication with our Custom UI project. This function is also declared in the function part.
4 Permissions added before to fetch the randomuser.me API

Let’s dig in the ./src/index.js file, which is declared as our resolver:

import Resolver from '@forge/resolver';

const resolver = new Resolver(); (1)

resolver.define('getText', (req) => { (2)
  console.log(req);

  return 'Hello, world!';
});

export const handler = resolver.getDefinitions(); (3)
1 A resolver is created here. It will contain functions accessible in our Custom UI project.
2 Here, we define the getText function. The req parameter will contain context about the invocation and also arguments used to call this function.
3 Finally, all functions defined in the resolver are exported

This was for the "Forge side", not let’s see how it’s work in our Custom UI project by opening ./src/static/hello-world/src/App.js:

import React, { useEffect, useState } from 'react';
import { invoke } from '@forge/bridge'; (1)

function App() {
  const [data, setData] = useState(null);

  useEffect(() => {
    invoke<string>('getText', { example: 'my-invoke-variable' }).then(response => setData(response)); (2)
  }, []);

  return (
    <div>
      {data ? data : 'Loading...'}
    </div>
  );
}

export default App;
1 We retrieve the invoke method from the @forge/bridge package, which allow us to call functions defined in the previous resolver
2 Here we call the getText method we declared before. We should receive a javascript Promise with the Hello, world! content.

So, what is this @forge/bridge ? Custom UI has no access to the Forge world by default. This means you can’t directly use packages like @forge/api, contrary to previous parts. This is where @forge/bridge is helpful: as indicated in the name, this package allows you to create a "bridge" between a Custom UI project and our Forge functions. This way, you can continue to use all Forge features.

Let’s do a simple test. Go to your jira instance and open an issue. You should see the Atlassian logo under the issue name. Click on it, and your instance will ask you to allow the app to access Jira in a new tab. You can see that displayed permissions match our manifest.yml. Click on "Accept", and the app should be displayed:

allow issue

As you can see, the "Hello, world!" text from our Forge function is displayed. Here is what happens:

forge bridge

Now, some setup before starting the final part of this exercise.

3.4.2. Adding Typescript

If you check our ./src folder, you can see that index.js doesn’t use Typescript. Since Typescript is already configured in this directory, you can rename this file to index.ts.

  • If you want to rename the index part of the file, ensure to also edit the function part of the manifest.yml

  • If you want to rename the .src/static/hello-word directory, ensure to change the path in the resources part of the manifest.yml

Now regarding our Custom UI project in ./src/static/hello-world, no Typescript is available here. Let’s add it using the following command:

npm install typescript @types/node @types/react @types/react-dom

The package.json file in ./src is related to our Forge functions. The one in ./src/static/hello-world only concerns our Custom UI project.

Once it’s installed, we can replace .js extension of files in ./src/static/hello-world/src with the .tsx one. Like previously, we need to create the tsconfig.json file in ./src/static/hello-world with this content:

{
  "compilerOptions": {
    "target": "es2017",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "downlevelIteration": true
  },
  "include": [
    "src"
  ]
}

Typescript should find errors in App.tsx, so you can replace its content with this one:

import React, { useEffect, useState } from 'react';
import { invoke } from '@forge/bridge';

function App() {
  const [data, setData] = useState<string>();

  useEffect(() => {
    invoke('getText', { example: 'my-invoke-variable' }).then((response) => setData(response as string));
  }, []);

  return (
    <div>
      {data ? data : 'Loading...'}
    </div>
  );
}

export default App;

3.4.3. Working with Forge tunnel and hot reload

Now, each time we modify our Custom UI project, we need to run npm run build to generate our assets inside the ./build folder, then run forge deploy to send assets on the Forge infrastructure. As you see, it takes something like 1 minute to run these two commands. When developing, this will be a problem. That why Forge allow you to bind a local development server to the Forge tunnel. Luckily, create-react-app comes with a dev server! What a good break 👀

Let’s start the server by running this command in ./static/hello-world:

npm start

Now, each time we edit something in our Custom UI project, the assets will be regenerated and available at http://localhost:3000/. If you try to reach this address, you should receive this error:

Uncaught Error:
      Unable to establish a connection with the Custom UI bridge.
      If you are trying to run your app locally, Forge apps only work in the context of Atlassian products. Refer to https://go.atlassian.com/forge-tunneling-with-custom-ui for how to tunnel when using a local development server.

It’s expected: the @forge/bridge package only works on an Atlassian instance.

Then, we must tell Forge tunnel to use our web server to serve our Custom UI content. For this, the Atlassian documentation is clear:

modules:
  jira:issuePanel:
    - key: random-user-hello-world-panel
      resource: main
      resolver:
        function: resolver
      viewportSize: medium
      title: random-user
      icon: https://developer.atlassian.com/platform/forge/images/issue-panel-icon.svg
  jira:adminPage:
    - key: random-user-administration
      function: run-administration
      title: Random user administration
  trigger:
    - key: issue-created-event
      function: issue-created
      events:
        - avi:jira:created:issue
  function:
    - key: resolver
      handler: index.handler
    - key: run-administration
      handler: administration.run
    - key: issue-created
      handler: trigger.run
resources:
  - key: main
    path: static/hello-world/build
    tunnel: (1)
      port: 3000
app:
  id: ari:cloud:ecosystem::app/9513507c-5fa1-4e5e-8fee-a5d7ef17a07e
permissions:
  scopes:
    - storage:app
    - read:issue:jira
    - read:issue-type:jira
    - read:user:jira
    - read:project:jira
    - read:status:jira
  external:
    fetch:
      backend:
        - https://randomuser.me
1 We just need to add the tunnel property under our Custom UI resource. Last thing to add is the port to listen, in our case 3000.

Now, forge deploy the changes, run forge tunnel and open (or reload) an issue view on your Atlassian instance. If it’s working, you should see these logs:

Received proxy request. Serving file index.html for resource main from specified address http://localhost:3000 (1)
CSP violation detected for 'style-src' while serving content at http://localhost:8001/
For an app to share data with external resources or use custom CSP, follow the steps in: https://go.atlassian.com/forge-content-security-and-egress-controls (2)
1 This line indicates that the content is served from your local dev server
2 The second is related to a Content Security Policy (CSP) issue.

3.4.4. Handling CSP

Our Custom UI project has some restrictions regarding the Forge security requirement. As noted by the previous log, our React app generate <style> in the served index.html, which by default is not allowed by Forge.

To avoid CSP, edit the manifest with the following content:

modules:
  jira:issuePanel:
    - key: random-user-hello-world-panel
      resource: main
      resolver:
        function: resolver
      viewportSize: medium
      title: random-user
      icon: https://developer.atlassian.com/platform/forge/images/issue-panel-icon.svg
  jira:adminPage:
    - key: random-user-administration
      function: run-administration
      title: Random user administration
  trigger:
    - key: issue-created-event
      function: issue-created
      events:
        - avi:jira:created:issue
  function:
    - key: resolver
      handler: index.handler
    - key: run-administration
      handler: administration.run
    - key: issue-created
      handler: trigger.run
resources:
  - key: main
    path: static/hello-world/build
    tunnel:
      port: 3000
app:
  id: ari:cloud:ecosystem::app/9513507c-5fa1-4e5e-8fee-a5d7ef17a07e
permissions:
  scopes:
    - storage:app
    - read:issue:jira
    - read:issue-type:jira
    - read:user:jira
    - read:project:jira
    - read:status:jira
  content: (1)
    styles:
      - 'unsafe-inline'
  external:
    fetch:
      backend:
        - https://randomuser.me
1 We tell Forge we need to allow inline CSS

Now, in the ./src/static/hello-world/public/index.html file, add the following line:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="Content-Security-Policy" content="style-src 'self' 'unsafe-inline'" /> (1)
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>
1 We also add the CSP part in our Custom UI project

Since we edit permissions, remember to forge deploy and forge install --upgrade.

After reloading the issue view, our app should be displayed correctly. Try editing App.tsx by adding some text: after saving, your app is directly updated.

Resource

You can find more information about Forge CSP in the Forge documentation.

3.4.5. Final exercise

Time to shine! As usual, do it yourself, solution available if needed in the App.tsx file.

What you must do:

First thing to do is retrieve user information we stored previously in the Jira Issue Properties. Two ways are available to perform this: by invoke`ing a function defined in the resolver or using `requestJira method (also from the @forge/bridge package). Once the data are retrieved, try to display simple information like the generated user picture, it’s name and email.

Resources:

Additional notes:

  • Don’t forget to start your Forge tunnel and dev server

  • Using the requestJira of @forge/bridge perform the request "as the current user" and not in the name of your app.

  • Make sure to check permissions required by the Atlassian API you’re using

  • You can use Atlaskit components to provide an Atlassian-like user experience. Make sure to manage this case.

  • Sometime, some errors can be solved by restarting the Forge tunnel / dev server

  • Displaying images from randomuser.me will require additional egress permission

issue view

Once it’s done: run npm run build in ./src/static/helloword to create an optimized build of your Custom UI project, then run forge deploy. Now you can access your app without using Forge tunnel.

4. Next steps

The goal of this exercise is to cover some interesting features of the Forge framework, but a lot remains. To go further:

  • For the administration part, you can add others API options, like seed, nationality, password, …​ then take these options in account when the trigger is invoked.

  • Speaking of the trigger part, you could use the Forge Storage to store a history of last 10 generated users. This history could also be displayed in the administration as a table containing the ID of the issue and the first and last name of the user. Regarding how you store the data in the Forge Storage, you would be interested in the query part.

  • In our issue panel, you can add a "refresh" button to generate a new user and replace the existing one, using a function from the resolver.

  • Check the Forge Environments. We juste used the development one, but you will have to use production if you want to share your app. You can also push an app to the Atlassian Marketplace, but be aware of Forge quotas and limits;

  • You can also play with Display Conditions, to limit access to our previous issue panel.