UP | HOME
Ravi Sagar | Home | Blog

Create hello world app using Forge

Table of Contents

1 Create the app

  • Navigate to the directory where you want to create the app
forge create
  • Enter the name of the app: hello-world-app
  • Select a category: UI kit
  • Select a template: jira-issue-panel

A new folder will be created.

cd hello-world-app

2 jira-issue-panel template

ls hello-world-app

3 Change the panel title

Go to the manifest.yml and modify the title.

4 Install the app

Run this command from the top level directory.

forge deploy

Then install the app.

forge install

Select a product: Jira Enter the site URL: abc-dev.atlassian.net Then continue.

5 Check your app

Go to Manage apps section of your Jira instance and you will find your app installed on the instance.

Now create an issue in a project, create a project if not already done. You will find a button on top called Forge app for Ravi or whatever title you used in the manifest.yml file above.

Cool isn't it? How long it took you to create, deploy and install the app?

6 Login to Forge non interactively

forge login --email $FORGE_EMAIL --token $FORGE_TOKEN --non-interactive

6.1 Error: We couldn't log you in with those credentials

We couldn't log you in with those credentials. Check your email address and token before rerunning the command.

7 Further changes to the app.

You can do that using forge deploy. So let us say you change the title, so after you run this command the changes will be reflected back.

This is a manual way of deploying the changes.

First make sure docker is running.

sudo systemctl start docker

Then run forge tunnel.

At this stage you may encounter errors related to forge/cli, just follow the instructions on the screen.

  • For me it didn't work properly. If I run docker with sudo then it works but not with normal user. Not a big problem because I can always use forge deploy.
  • Also run sudo forge tunnel if get Cannot pull the tunnel image. error.

I tried to pull the image directly using docker pull atlassian/forge-tunnel:latest

8 Calling Jira API

Now instead of display a static text, let us consume an end point.

8.1 Install @forge/api

npm install @forge/api

8.2 Make changes in your code

8.3 Run forge tunnel if not running already

When you add comments and refresh your page then you will see a message in your console showing number of comments.

8.4 Forge might ask to update permission.

If Forge complains about the permissions just follows its instructions.

8.5 Show last comment

Modify the code from Atlassian to display last comment along with number of comments.

const fetchLastCommentForIssue = async (issueId) => {
  const res = await api
    .asApp()
    .requestJira(`/rest/api/3/issue/${issueId}/comment`);

  const data = await res.json();
  if (data.comments.length==0) {
    return "No comments yet, be the first one!"
  } else {
    const numberOfComments = data.comments.length
    const lastComment = data.comments[numberOfComments-1].body.content[0].content[0].text
    console.log(lastComment);
    return lastComment;
  }
};

9 Make API calls on behalf of a user

In your index.jsx file change .asApp() to .asUser(). So your function will look like this.

const fetchLastCommentForIssue = async (issueId) => {
  const res = await api
    .asUser()
    .requestJira(`/rest/api/3/issue/${issueId}/comment`);

  const data = await res.json();
  if (data.comments.length==0) {
    return "No comments yet, be the first one!"
  } else {
    const numberOfComments = data.comments.length
    const lastComment = data.comments[numberOfComments-1].body.content[0].content[0].text
    console.log(lastComment);
    return lastComment;
  }
};

Now just run sudo forge tunnel again and refresh the issue. You will get a button to allow access.

10 Forge logs

If you run forge logs then you can check the recent log items. Can be helpful for debugging.

INFO    2021-03-28T14:09:01.909Z e4d5f610-047b-4e01-9942-813e53732c20 Number of
comments on this issue: 4
INFO    2021-03-28T14:10:18.192Z f2b0e068-aa1a-40e0-b3f3-8f999a443158 Number of
comments on this issue: 4
INFO    2021-03-28T14:10:50.324Z 8b63305e-cf41-4d13-ad6b-a915da77c15f Number of
comments on this issue: 4
INFO    2021-03-28T14:11:27.599Z 605b4de8-70e4-473a-b160-a465dbdcf49b Number of
comments on this issue: 4

11 Display current project's name

Let us display current issue's project name as well in the panel.

const fetchProjectFromIssue = async (issueId) => {
  const res = await api
    .asUser()
    .requestJira(`/rest/api/3/issue/${issueId}`);

    const data = await res.json();

    return data.fields.project.name;

};
//call this method to get the value
  const projectName = useState(async ()=> await fetchProjectFromIssue(context.platformContext.issueKey));

This is how your code will look like now.

import ForgeUI, { render, Fragment, Text, IssuePanel, useProductContext, useState } from '@forge/ui';

const App = () => {
  const context = useProductContext();
  const [comments] = useState(async () => await fetchCommentsForIssue(context.platformContext.issueKey));
  const lastComment = useState(async ()=> await fetchLastCommentForIssue(context.platformContext.issueKey));
  const projectName = useState(async ()=> await fetchProjectFromIssue(context.platformContext.issueKey));

  const message = `Number of comments on this issue: ${comments.length}`;
  console.log(message);
  console.log(lastComment);
  console.log(projectName);

  return (
    <Fragment>
      <Text>Hello world! Great it works.</Text>
      <Text>{message}</Text>
      <Text>{lastComment[0]}</Text>
      <Text>{projectName[0]}</Text>
    </Fragment>
  );
};

export const run = render(
  <IssuePanel>
    <App />
  </IssuePanel>
);

const fetchCommentsForIssue = async (issueId) => {
  const res = await api
    .asUser()
    .requestJira(`/rest/api/3/issue/${issueId}/comment`);

  const data = await res.json();
  return data.comments;
};


const fetchLastCommentForIssue = async (issueId) => {
  const res = await api
    .asUser()
    .requestJira(`/rest/api/3/issue/${issueId}/comment`);

  const data = await res.json();
  if (data.comments.length==0) {
    return "No comments yet, be the first one!"
  } else {
    const numberOfComments = data.comments.length
    const lastComment = data.comments[numberOfComments-1].body.content[0].content[0].text
    console.log(lastComment);
    return lastComment;
  }
};

const fetchProjectFromIssue = async (issueId) => {
  const res = await api
    .asUser()
    .requestJira(`/rest/api/3/issue/${issueId}`);

    const data = await res.json();

    return data.fields.project.name;

};

12 Forge tunnel debug

Run forge tunnel debug and open the url to check debugging. Add debugger; in your code as well before that.

13 Custom icon in manifest.yml

I tried local relative path but it didn't work but then I tried an online path like this.

icon: https://www.sparxsys.com/sites/default/files/sparxsys_favicon_0.png and it worked after uninstalling the app, then doing forge deploy and forge install.

14 Publish Forge apps

14.1 First deploy the app to production and then install it.

Details here: https://developer.atlassian.com/platform/forge/staging-and-production-apps/

forge deploy -e production
forge install -e production

14.2 Send your app for approval

Once you have deployed and installed the app you can go to your vendor account and submit the app for approval. For there was already an app version, so I just had to go to the existing app and create a new version of the app for cloud.

14.3 Set pricing when your send your app for approval

I also set the app pricing during the approval

15 Check node and npm version

node --version
npm --version

16 Forge errors

16.1 Error: error command sh -c node-gyp rebuild

https://github.com/nodejs/node-gyp/issues/2272

sudo npm install --global node-gyp@latest
npm config set node_gyp $(npm prefix -g)/lib/node_modules/node-gyp/bin/node-gyp.js

16.2 Error: npm ERR! `nodegyp` is not a valid npm option

16.3 Error: sh: line 1: react-scripts: command not found

Try adding '.env' file in root directory of your project and add this to file -

SKIPPREFLIGHTCHECK=true

I created this file in the react project not my forge app root.

16.4 Error: error:0308010C:digital envelope routines::unsupported

npm audit fix --force

16.5 Error: npm ERR! Missing script: "build"

16.6 Error: Keytar error detected

Error: Keytar error detected: Error spawning command line “dbus-launch –autolaunch=1dabfe8cd581451db7ec4cd4b3f8461b –binary-syntax –close-stderr”: Child process exited with code 1 Something went wrong while accessing Secret Service API.

I tried this forge settings set usage-analytics true but it didn't work.