Last updated Sep 16, 2024

Your first quest - Part II

In Part I of the quest you used the Forge CLI to create a forge app based on one of the many available templates, you then customised the app and added a new module.

Now, in Part II of the quest you will customise your app further by accessing application context data via both the front and back end. You will also learn how to automatically deploy the app, and troubleshoot the app using console logging.

This is part II in this tutorial. Complete Part I before working on this page.

Automatically deploy app changes

In part I of the quest, you needed to run forge deploy to update your app. Tunneling allows you to speed up development by avoiding the need to redeploy each code change, and by seeing each invocation as it executes. The Forge tunnel works similarly to hot reloading, so any changes you make to your app code can be viewed on your Atlassian site or Bitbucket workspace without losing the current app state. You don’t need to run any other commands; you only need to refresh the page.

To use the forge tunnel command, Docker must be set up and running. To learn about Docker, visit the Docker getting started guides. If you don't want to run Docker, you can redeploy your app after each code change with the forge deploy command as described in Part I.

  1. Once Docker is set up, you can start tunneling by running:

    1
    2
    forge tunnel
    

    You should see output similar to:

    1
    2
    Tunnel redirects requests you make to your local machine. This occurs for any 
    Atlassian site where your app is installed in the specific 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.
    Tunnel uses an anonymous ngrok account, which has a time limit of 2 hours. After 
    that time, you'll need to restart your tunnel.
    
    === Running forge lint...
    No issues found.
    
    === Bundling code...
    ✔ Functions bundled.
    ✔ Resources bundled.
    
    === Snapshotting functions...
    No log output.
    
    Listening for requests...
    
    Reloading code...
    
    === Running forge lint...
    No issues found.
    
    === Bundling code...
    ✔ Resources bundled.
    
    Listening for requests...
    

    You can now automatically deploy changes to your codebase and install packages, while tunneling. These changes appear on the Atlassian site or Bitbucket workspace where your app is installed.

  2. When you are ready to close the tunnel, press Control + C.

The forge tunnel command only forwards traffic when the user (in Jira, Confluence, Jira Service Management or Bitbucket) matches the Forge CLI user. For security reasons, you can’t see the traffic of other users.

For important caveats on how forge tunnel works, see Tunneling.

Use context to customise your app

In this section, you will modify your app to get the application context. Using the Product bridge API from the @forge/bridge package, you will get the theme for the location the app is currently loaded in and display it using a StatusLozenge.

You can learn more about themes in Design tokens and theming

The @forge/bridge package allows UI Kit apps to securely integrate with Atlassian products.

You might have noticed that the app created by the forge CLI is already using the invoke method. This means we can skip installing the npm package in this step.

  1. Navigate to the src/frontend directory.

  2. Open the index.jsx file. The content of the file should be as follows:

    1
    2
    import React, { useEffect, useState } from 'react';
    import ForgeReconciler, { Heading, Text } from '@forge/react';
    import { invoke } from '@forge/bridge';
    
    const App = () => {
      const [data, setData] = useState(null);
    
      useEffect(() => {
        invoke('getText', { example: 'my-invoke-variable' }).then(setData);
      }, []);
    
      return (
        <>
          <Heading as="h1">Hello <name>!</Heading>
          <Text>{data ? data : 'Loading...'}</Text>
        </>
      );
    };
    
    ForgeReconciler.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    );
    
  3. Add the view method to the import ... from '@forge/bridge'; statement on line 3:

    1
    2
    import { invoke, view } from '@forge/bridge';
    
  4. Copy the following code into the App method to create a function that uses the getContext method as part of the view object using the @forge/bridge package:

    1
    2
    const [theme, setTheme] = useState(null);
    
     useEffect(() => {
       const getTheme = async() => {
         const context = await view.getContext();
         setTheme(context.theme.colorMode);
       }
    
     getTheme();
    }, []);
    

    This function uses view.getContext() to get contextual information about the current environment in which the app is running. The data received depends on the module the app is being used in. In this example, we're interested in the theme colorMode so that's the information saved in the function.

  5. To see the full result returned from view.getContext(), add a console.log(context); on the line below.

    To view console.log() results logged from your src/frontend/index.jsx open your browsers developer console.
    For console.log() results logged elsewhere in your app, check your forge tunnel session.

  6. Add Lozenge to the import ... from '@forge/react'; statement on line 2:

    1
    2
    import ForgeReconciler, { Heading, Lozenge, Text } from '@forge/react';
    
  7. Add the following line to the Return statement for the App method:

    1
    2
    <Text>Current theme: <Lozenge>{theme ? theme : 'Loading...'}</Lozenge></Text>
    

    This will display the current colorMode in a statusLozenge in your app.

  8. Save your changes and then wait for the app to be bundled in your terminal window.

  9. Open the confluence space as you did in Part I, (or refresh the page) to see your changes.

    Note: When you refresh the page after opening your browsers developer console you should see something like in the log:

    1
    2
    {localId: '<the local id>', cloudId: '<the cloud id>', environmentId: 
    '<the environment id>', environmentType: 'DEVELOPMENT', moduleKey: 
    'my-first-quest-hello-world-space-page', …}  
    

Your src/frontend/index.jsx should look like this:

1
2
import React, { useEffect, useState } from 'react';
import ForgeReconciler, { Heading, Lozenge, Text } from '@forge/react';
import { invoke, view } from '@forge/bridge';

const App = () => {
  const [data, setData] = useState(null);
  const [theme, setTheme] = useState(null);

  useEffect(() => {
    const getTheme = async() => {
      const context = await view.getContext();
      console.log(context);
      setTheme(context.theme.colorMode);
    }

    getTheme();
  }, []);

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

  return (
    <>
      <Heading as="h1">Hello <name>!</Heading>
      <Text>{data ? data : 'Loading...'}</Text>
      <Text>Current theme: <Lozenge>{theme ? theme : 'Loading...'}</Lozenge></Text>
    </>
  );
};

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Create a resolver function

In this section, you will modify the back end of the app to access context data and pass that data back to be displayed in your app.

  1. Navigate to the src/resolvers directory for your app.
  2. Open the index.js file, the contents should be as follows:
    1
    2
     import Resolver from '@forge/resolver';
    
     const resolver = new Resolver();
    
     resolver.define('getText', (req) => {
       console.log(req);
    
       return 'Hello, world!';
     });
    
     export const handler = resolver.getDefinitions();
    
  3. Replace the code on line 8 with:
    1
    2
      return 'Welcome to the ' + req.context.extension.space.key + ' space!';
    
    This will look at the request context which is shared with your resolver when it is called from the front end, including the space key.
  4. Save your changes and then wait for the app to be bundled in your terminal window.
  5. Open the confluence space as you did in Part I, (or refresh the page) to see your changes.

Watch your forge tunnel as you reload your confluence space page, to see the data logged by the resolver. Notice the data looks a little different to the data displayed in the log to the developer console of your browser in the previous section - that's because in this case all of the request data is being logged - not just the context.

Your `src/resolvers/index.js` should look like this:
1
2
mport Resolver from '@forge/resolver';

const resolver = new Resolver();

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

  return 'Welcome to the ' + req.context.extension.space.key + ' space!';
});

export const handler = resolver.getDefinitions();

Close the tunnel and deploy the app

After confirming the app works locally, deploy it so that it continues working after the tunnel is closed.

  1. Close your tunnel by pressing Ctrl+C
  2. Deploy your app by running:
    1
    2
    forge deploy
    
  3. Refresh the page where your app is installed to verify it is working as expected.

Next step

In the next part of this quest you will personalise your app by displaying the current users name, which you'll get by calling the Confluence API. You'll also learn about modifying the manifest to add the required permissions, and re-deploying an app with new permissions.

Rate this page: