Workaround Atlassian Forge limitations to increase my productivity

This article is about my experience participating in yet another Hackathon called Codegeist 2022 organized by Devpost. To take advantage of the Storytelling bonus prize that is up for grabs, this could be a good share for anyone who finds it hard to juggle work and hobbies (joining hackathons).

As you can see, I think I am a seasoned hacker now considering the fact that I managed to build and won quite a few hackathons in the past. One of the key attributes of a developer is the ability to learn and understand new concepts quickly. Regardless of the technology changes, most of the concepts about development (lifecycle) stay the same. This is where we learn about what works and what doesn’t work for us to stay productive.

Without dragging more into it. Here are some of my hacks to get around the limitations and focus on writing the solution.

Summary

Here is a video of me applying what I share in actions. You can read the rest in the later part of my article.

Using the Hello world example

The quickest way to learn is to see the code run on your own system. That said, learn to spike on “Hello World” project quickly to understand the fundamentals and the platform capability. If you’d already have a heap of development experience under your belt, then try to correlate with what you know to speed up the learning.

For the Codegeist, I can’t thank the Atlassian folks enough for providing more than enough resources for us to get started quickly. For example, Getting started on Atlassian Forge and a whole bunch of Example (Hello World) Apps to leverage on.

What’s next, is for us to quickly explore the example to find and learn about what we need.

Modifying the static page to a SPA

I have to admit, I was struggling a lot initially for a few reasons. In all of the examples, we don’t have a good example that shows how to structure multiple frontend (CustomUI) modules within the same project. We can create multiple react-script for each module, but it won’t serve me well to scale my development.

Therefore a quick hack that we did, is to parse the context from the backend to the frontend. And then, using back the basics React to render the component of our choice.


  const renderContext = () => {
    if (context) {
      switch (context.moduleKey) {
        case 'module-1':
          return <ComponentForModule1 context={context} />;
        case 'module-2':
          return <ComponentForModule2 context={context} />;

        // etc...
        default:
          return <>Not found module.</>;
      }
    }
  };

Efficient development tools

I got so used to the hot reload during development that is hard for me to live without it nowadays. Forge tunnel has been good to me without much of an issue. However, not so for the frontend development using the CustomUI.

I know without this, working on the UI will be hell. Sadly, I spend quite a long time trying to debug the wss:// connection to make the hot reload work on my Atlassian instance to no avail.

Given that wss:// for the localhost work just fine. I opted to build the frontend using just the localhost. Then, we hit another problem where @forge/bridge is not able to connect.

And my answer to workaround this is to build a wrapper on top of anything @forge/ with a request call. To get the request back to Forge, we make use of the Web Trigger API to do the trick.

export const invoke = async (name: string, payload?: any) => {
  const _payload = typeof payload === 'object'
    ? JSON.stringify(payload)
    : payload ?? '';

  if (REACT_APP_FRONTEND_DEV_ONLY !== 'true') {
    const ForgeBridge = require('@forge/bridge');
    return ForgeBridge.invoke(name, _payload);
  }
  else {
    const response = await fetch(REACT_APP_MOCK_TRIGGER_URL, {
      method: 'post',
      body: JSON.stringify({ name, payload: _payload }),
    });
    const json = await response.json();
    if (response.status !== 200)
      throw new Error(json.error);

    return json;
  }
};

Bare in mind, this is all just to hack around the development environment. Ultimately, when it comes to the release, we’re using back the original @forge/bridge that we were supposed to.

Forge backend development

Similarly, to keep refreshing the page to trigger the function is not the way to go. That’s why I am also a strong believer in TDD (Test Driven Development). I choose to go with mocha over jest mainly for the --watch performance.

By smartly refactoring your code, we can now focus on writing the logic that improves efficiency during development and making sure the code is testable.


import { ResolverFunction } from './resolver';

const resolver = new Resolver();
Object.keys(ResolveFunc).map(funcName => {
  resolver.define(funcName, ResolveFunc[funcName] as any);
});

export const handler = resolver.getDefinitions();

By smartly refactoring this, we are able to isolate business logic and structure to our code directory for better readability. And then, writing tests as we develop the business logic of the application.


describe('Test', () => {
  it('Fetch and parse space information', async () => {
    const response = await ResolveFunc.getSpaces();
    const results = response.results;
    expect(results).to.have.length;

    const space = results[0];
    expect(space).to.have.property('id');
  });
});

In a nutshell

I think I have shared enough of what I have done. To wrap up, I hope this is able to help someone level up their development game.