EVENT: Forge new frontiers: The Weavy + Atlassian Forge hackathonRead more
Quickstart

Add Weavy to Confluence using Atlassian Forge

Here's an end-to-end example of adding Weavy to Confluence as a macro using Atlassian Forge.

These code snippets include finding who is logged in to Confluence, syncing that user to Weavy, and getting an access token.

Have Forge CLI ready, set the parameters to unlock the tutorial, and get started.

Don't have the parameters? Sign up for free or sign in and set up your environment to get the parameters.

 Looking for how to add Weavy to Jira?

1. Create a Forge app

This example creates a Forge app for Confluence; the template used here is a macro.
Open a terminal and create new Forge app
forge create
  • Enter a name for the Forge app (preferably something with Weavy so it's easy to find later)
  • Select Custom UI as Category
  • Select Confluence as Product
  • Select confluence-macro as the template

2. Install the Weavy UIKit

Open up your project in VS Code or similar. The project consists of a server part (src/index.js) and a client part written in React (static/hello-world).

Open up a terminal and go to the /static/hello-world folder. Install the Weavy UI kit.

Run this in the /static/hello-world folder
npm install @weavy/uikit-web

3. Modify index.js

Syncing users and getting the access token should always be done from the server side. Forge uses a bridge that allows you to define backend functions that can be called from the front end.

Copy and paste into index.js, found in the /src folder
import Resolver from '@forge/resolver';
import api, { fetch, route } from '@forge/api';

const resolver = new Resolver();

let _tokens = [];
const apiKey = "********************";
const backend = "WEAVY_URL";

resolver.define("token", async (req, res) => {
    // get confluence user's account id
    const accountId = req.context.accountId;

    // check if we already have a token stored for the user or if we need to refresh the token
    if ((!req.payload.refresh || req.payload.refresh === "false") && _tokens.find((t) => t.accountId === accountId)) {
        res.json({ access_token: _tokens.find((t) => t.accountId === accountId).access_token });
    } else {
        // get an access token from weavy backend
        let response = await fetch(`${backend}/api/users/${accountId}/tokens`, {
            method: 'POST',
            headers: {
                'content-type': 'application/json',
                'Authorization': `Bearer ${apiKey}`
            },
            body: JSON.stringify({ expires_in: 3600 })
        });

        if (response.ok) {
            let data = await response.json();
            // store the access token so we don't need to call the api every time (unless we need to refresh the token)
            _tokens = [..._tokens.filter((t) => t.accountId !== accountId), { accountId: accountId, access_token: data.access_token }];
            return data.access_token;
        } else {
            return ({ message: "Could not get access token from server!", accountId: accountId, error: response })
        }
    }
});

resolver.define("syncUser", async (req, res) => {
    // get confluence user data
    const userResponse = await api.asApp().requestConfluence(route`/wiki/rest/api/user?accountId=${req.context.accountId}`, {
        headers: {
            'Accept': 'application/json'
        }
    });

    var user = await userResponse.json();
  
    // user's account id - we will use this as the 'uid' when syncing the user to Weavy
    let uid = user.accountId;

    // user's name
    let name = user.displayName;

    // sync the user to Weavy
    let response = await fetch(`${backend}/api/users/${uid}`, {
        method: 'PUT',
        headers: {
            'content-type': 'application/json',
            'Authorization': `Bearer ${apiKey}`
        },
        body: JSON.stringify({ name: name })
    });
     
    return uid;

});

resolver.define("getUniqueId", async (req, res) => {
  // get contextual id from confluence content  
  return `${req.payload.type}--confluence-content-${req.context.extension.content.id}`;
});

export const handler = resolver.getDefinitions();
  • Line 10 - endpoint to get an access token for the user in Weavy
  • Line 39 - endpoint to add user information to Weavy based on who is logged into the Atlassian account
  • Line 69 - endpoint to get a unique ID for the building block, making it contextual

4. Modify app.js

The client part is in the App.js file where we add the Weavy building block.

If you want to test a different building block, change the selected block in the parameter input in the header.

Copy and paste into app.js, found in the /static/hello-world/src folder
import React, { useEffect, useState } from 'react';
import { invoke } from '@forge/bridge';
import { Weavy } from "@weavy/uikit-web";

function App() {  
  const [loaded, setLoaded] = useState(false);
  const [uid, setUid] = useState(null);

  useEffect(() => {
    const init = async () => {      
      const weavy = new Weavy();
      weavy.url = "WEAVY_URL";
      weavy.tokenFactory = async (refresh) => {
        return await invoke('token', { refresh: refresh });
      };

      // sync user
      await invoke('syncUser', {});  

      // get a unique id for the app based on confluence page content
      let uniqueId = await invoke('getUniqueId', { type: 'chat'});  
      setUid(uniqueId);

      setLoaded(true);      
    }

    init();
  }, []);

  if (!loaded) {
    return 'Loading...';
  }
  
  return (
    <div style={{height: '500px', overflow: 'auto'}}>                
      {uid && 
        <wy-chat uid={uid}></wy-chat>      
      }      
    </div>
  );
}

export default App;
  • Line 21 - get a unique ID for the app based on Confluence page content
  • Line 37 - use that UID when rendering the building block, making it contextual

5. Set runtime version

For Weavy to work with Forge at this moment, we need to use the Forge runtime legacy version, to do so you need to set app.runtime.name to sandbox

Set runtime version
app:
  id: .....
  runtime:
    name: sandbox
Please update if it says anything other than sandbox for the runtime.

6. Update manifest.yml

Forge requires some configuration in the manifest.yml for the app to have the correct permissions. 
Add the following at the end of the current manifest.yml file
permissions:
  external:
    frames: 
      - "*.weavy.io"
    scripts:
      - "*.weavy.io"
    images:
      - "*.weavy.io"
    fetch:
      backend:
        - "*.weavy.io"
        - wss://*.weavy.io
      client:
        - "*.weavy.io"
        - wss://*.weavy.io
  content:
    scripts:
      - 'unsafe-hashes'
      - 'unsafe-eval'
      - 'unsafe-inline'
      - 'blob:'
    styles:
      - 'unsafe-inline'
  scopes:
    - read:confluence-user

7. Build and Deploy

First, we need to build the React project. Go to /static/hello-world folder and run:
Run in the /static/hello-world folder
npm run build

If you make any changes to the React part, you must build every time before you re-deploy the Forge app.

The next step is to deploy the package to Atlassian. Go to the root folder of the project and run:

Run in the root folder of your project
forge deploy

When successfully deployed, the last part is to install it - if the macro is already installed add --upgrade to the install command. Select Confluence when asked where to install it. Then enter your Atlassian URL - for example your-domain.atlassian.net.

forge install

8. Take it for a test run

Navigate to your Confluence  site - your-domain.atlassian.net - and verify that the app works.

The confluence-macro can be tested by creating a new Page and entering /Weavy - or whatever you named the macro to - in the editor.

Unlock the tutorial with your Weavy API key.