Last updated Sep 16, 2024

Your first quest - Part III

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.

Then, in Part II of the quest you customised your app further by accessing application context data via both the front and back end.

Now, in Part III you will personalise your app by accessing and displaying the name of the current user.

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

Call the Confluence Rest API

In this section, you'll modify your app to call the Confluence REST API. Using the requestConfluence bridge method from the @forge/bridge package, you'll use the accountId from the context data you retrieved in part II to lookup the user name using the bulk user lookup api.

The @forge/bridge package simplifies requests to product REST APIs as well as other javascript APIs to interact with the products. The requestConfluence bridge method enables Forge apps to call the Confluence Cloud platform REST API as the current user.

  1. Navigate to the src/frontend directory and open the index.jsx file.
  2. Add requestConfluence to the imports from @forge/bridge at the top of the file, it should now look like:
    1
    2
    import { invoke, view, requestConfluence } from '@forge/bridge'
    
  3. Create two new useState() variables under the existing ones in the App() function; one to track the AccountID, and another to track the Name:
    1
    2
    const [accountID, setAccountID] = useState(null);
    const [name, setName] = useState(null);
    
  4. Modify the getTheme() function created in Part II to also set the accountID with the context by adding the following to the end of the function:
    1
    2
    setAccountID(context.accountId);
    
  5. Next, add a new useEffect() hook to call the bulk user lookup api when the accountID changes:
    1
    2
     useEffect(() => {
       const getUserInfo = async() => {
         if(accountID) {
           let bodyData = `{
             "accountIds": [
               "${accountID}"
             ]
           }`;
    
           const response = await requestConfluence(`/wiki/api/v2/users-bulk`, {
             method: 'POST',
             headers: {
               'Accept': 'application/json',
               'Content-Type': 'application/json'
             },
             body: bodyData
           });
           if(response.status === 200) {
             let data = await response.json();
             console.log(data);
             setName(data.results[0].publicName);
           } else 
           { 
             console.log(response)
           }
         } else {
           console.log("accountID is null")
         }
       }
       getUserInfo();
     }, [accountID]);
    
  6. Finally, update the Heading in the return statement as follows:
    1
    2
    <Heading as="h1">Hello {name ? name: 'World'}!</Heading>
    

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

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

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

  useEffect(() => {
    const getUserInfo = async() => {
      if(accountID) {
        let bodyData = `{
          "accountIds": [
            "${accountID}"
          ]
        }`;

        const response = await requestConfluence(`/wiki/api/v2/users-bulk`, {
          method: 'POST',
          headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
          },
          body: bodyData
        });
        if(response.status === 200) {
          let data = await response.json();
          console.log(data);

          setName(data.results[0].publicName);
        } else 
        { 
          console.log(response)
        }
      } else {
        console.log("accountID is null")
      }
  }

  getUserInfo();

  }, [accountID]);

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

  getTheme();
  }, []);

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

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

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

Update the permissions and re-deploy and re-install your app

You might have noticed in the previous step, you didn't update your apps permissions. When you run a forge deploy, the Forge CLI will automatically run pre-deployment checks before deploying the changes to your environment. As part of those pre-deployment checks forge lint will detect if permissions are missing, and forge lint --fix will add the required permissions to your manifest.yml automatically.

Learn more about permissions in Forge apps in the manifest documentation.

Follow the steps below to see how this process works for your app:

  1. If you haven't already, save the changes to src/frontend/index.jsx
  2. Close your tunnel by pressing Ctrl+C
  3. Deploy your app by running:
    1
    2
    forge deploy
    
  4. As part of pre-deployment, forge lint will detect the missing permissions:
    1
    2
    CHARLIE:Charlies-First-App catlas$ forge deploy
    Deploying your app to the development environment.
    Press Ctrl+C to cancel.
    
    Running forge lint...
    Error: The deploy failed due to errors in the app code. Fix the 
    errors before rerunning forge deploy, or run forge deploy 
    --no-verify to skip the linter.
    
    /Users/mpaisley/test/Charlies-First-App/src/frontend/index.jsx
    20:49   error    Confluence endpoint: POST /users-bulk requires 
    "read:user:confluence" scope  permission-scope-required
    
    /Users/mpaisley/test/Charlies-First-App/src/frontend/index.jsx
    20:49   error    Confluence endpoint: POST /users-bulk requires 
    "read:user:confluence" scope  permission-scope-required
    
    X 2 issues (2 errors, 0 warnings)
      Run forge lint --fix to automatically fix 2 errors and 0 warnings.
    
    Rerunning the command with --verbose may give more details.
    
    
  5. Run forge lint --fix to attempt to automatically fix the permission error:
    1
    2
    forge lint --fix
    
  6. You should see a confirmation that the error was resolved:
    1
    2
    CHARLIE:Charlies-First-App catlas$ forge lint --fix
    ✔ Fixed 2 errors and 0 warnings
    
    Run forge lint to review outstanding errors and warnings
    
  7. If you open the manifest.yml, you'll now see the following permissions have been added:
    1
    2
    permissions:
      scopes:
        - read:user:confluence
    
  8. Attempt to deploy your app with the updated permissions by running:
    1
    2
    forge deploy
    
  9. The Forge CLI will detect that you need to run a forge install --upgrade as the app permissions have changed:
    1
    2
    CHARLIE:Charlies-First-App catlas$ forge deploy
    Deploying your app to the development environment.
    Press Ctrl+C to cancel.
    
    Running forge lint...
    No issues found.
    
    ✔ Deploying Charlies First App to development...
    
    ℹ Packaging app files
    ℹ Uploading app
    ℹ Validating manifest
    ℹ Deploying to environment
    
    ✔ Deployed
    
    Deployed Charlies First App to the development environment.
    
    We've detected new scopes or egress URLs in your app.
    Run forge install --upgrade and restart your tunnel to put them into effect.
    
  10. Upgrade the installation of your app by running:
    1
    2
    forge install --upgrade
    
  11. The Forge CLI will display a list of sites and environments, follow the prompt to select the site to upgrade:
    1
    2
    CHARLIE:Charlies-First-App catlas$ forge install --upgrade
    To upgrade your app to use the latest scopes, select it from the list.
    Press Ctrl+C to cancel.
    ? Select the site or workspace to upgrade:  (Use the Enter key to select)
    ┌───────────────┬────────────────────────┬────────────┬─────────────┬───────────────┐
    │ Environment   │ Site                   │ Product    │ Version     │ Major Version │
    ├───────────────┼────────────────────────┼────────────┼─────────────┼───────────────┤
    │ ❯ development │ cfatlas.atlassian.net  │ Confluence │ Out-of-date │ 2             │
    └───────────────┴────────────────────────┴────────────┴─────────────┴───────────────┘
    
  12. The Forge CLI will display the additional scopes requested by the new version of the app, follow the prompts to confirm you'd like to continue:
    1
    2
    ? Select the site or workspace to upgrade: cfatlas.atlassian.net
    
    Upgrading your app on the Atlassian site.
    
    Your app will be upgraded with the following additional scopes:
    - read:user:confluence
    
    ? Do you want to continue? (y/N) 
    
  13. You'll see a confirmation that the app has been upgraded on the selected site:
    1
    2
    ✔ Upgrade complete!
    
    Your app in the development environment is now the latest in Confluence on cfatlas.atlassian.net.
    
  14. Open the confluence space as you did in Part I & II, (or refresh the page) to see your changes.

Your manifest.yml should look something like this:

1
2
modules:
  confluence:spacePage:
    - key: charlies-first-app-hello-world-space-page
      resource: main
      resolver:
        function: resolver
      render: native
      title: Charlies First App
      route: hello-world
  function:
    - key: resolver
      handler: index.handler
resources:
  - key: main
    path: src/frontend/index.jsx
app:
  runtime:
    name: nodejs20.x
  id: <your-app-id>
permissions:
  scopes:
    - read:user:confluence
Get a Badge

Next steps

You now know many of the basics for building your own Forge Apps. If you'd like to keep learning, head on over to the next tutorial where you'll learn to build a Jira Dashboard Gadget that displays weather information using data from the OpenWeather API.

Feedback

We'd love to hear from you! Please take a minute to provide feedback on Forge Quest so we can continue to build and improve Forge Quest!

Rate this page: