If you frequently work with Confluence pages, you may find yourself spending a lot of time manually categorizing and organizing content. This can be a time-consuming process that takes away from other important tasks. With an app that extracts keywords from your Confluence pages, you can save time and streamline the organization process. Instead of sifting through large quantities of information, simply let the app do the work for you by identifying key themes and topics within your pages.
In this tutorial, we will build a Forge app that integrates with OpenAI APIs to extract keywords from the content of a Confluence page.
Make sure you have the following:
The tutorial uses these components to build the app: permissions, requestConfluence, useProductContext, and invoke.
An Atlassian cloud developer site lets you install and test your app on Confluence and Jira products set up for you. If you don't have one yet, set it up now:
You can install your app to multiple Atlassian sites. However, app data won't be shared between separate Atlassian sites, products, or Forge environments.
The limits on the numbers of users you can create are as follows:
The Atlassian Marketplace doesn't currently support cross-product apps. If your app supports multiple products, you can publish two separate listings on the Marketplace, but your app won't be able to make API calls across different products and instances/installations.
The GIF above is an example of how the app will work. When a user clicks Keyword extractor in the three dots menu, the extracted keywords will be added to the page as Confluence labels.
You can find the source code for this demo here.
A high-level outline of how the app works is:
Let’s see how to build this app.
Assuming your development environment is set up, you can get right to it. Follow these steps:
forge create
.keyword-extractor
.UI kit
,
the Confluence
product, and then the template confluence-content-action
.forge deploy
.forge install
to install the app on your Confluence instance. You’ll be asked to provide
a destination site.manifest.yml
1 2modules: confluence:contentAction: - key: keyword-extractor resource: main resolver: function: resolver render: native title: keyword-extractor function: - key: resolver handler: index.handler resources: - key: main path: src/frontend/index.jsx app: runtime: name: nodejs22.x id: <your-app-id> permissions: scopes: - read:confluence-content.summary - write:confluence-content external: fetch: backend: - https://api.openai.com
To call certain Confluence and external APIs, you need to give your app permission to do so.
This is done by adding scopes and external permissions
to the app’s manifest.yml
file. This app will call three APIs:
read:page:confluence
permission is required to call this API.write:confluence-content
permissions is required to call this API.external.fetch.backend
permission is used to define external domains your Forge functions can talk to.index.jsx
with the main top-level logic for your appNavigate to src/frontend/index.jsx
. This contains the top-level code which calls other functions to interact with the Confluence and ChatGPT APIs. You will add to this file as you work through the tutorial.
index.jsx
1 2import React, { useState, useEffect } from 'react'; import ForgeReconciler, { useProductContext } from '@forge/react'; import { invoke } from '@forge/bridge'; const App = () => { // Get the confluence page id const context = useProductContext(); const contentId = context?.extension?.content?.id; // Use states to store page data asynchronously const [data, setData] = useState(); const [keywords, setKeywords] = useState(); const [response, setResponse] = useState(); // Fetch and store page data useEffect(() => { if(contentId){ invoke('getContent', { contentId }).then(setData); } }, [contentId]); console.log(data); // Define a prompt to be used for the OpenAI API const prompt = `Here is the data:"${data}" Give me the 5 most important keywords from the text. Return the results in the form of a JavaScript array. The response shouldn't contain anything apart from the array. No extra text or JavaScript formatting.` // Call OpenAI API and store the result (keywords) useEffect(() => { if(prompt){ invoke('callOpenAI', prompt).then(setKeywords); } }, [prompt]); console.log("Prompt response - " + keywords); // Use state to add the extracted keywords as labels to the current page useEffect(() => { if(keywords){ invoke('addKeywordsToLabels', {keywords, contentId}).then(setResponse); } }, [keywords, contentId]); console.log(response) // Render nothing as the main purpose is API interactions and data processing return (null); }; // Render the main component within a ContentAction ForgeReconciler.render( <React.StrictMode> <App /> </React.StrictMode> );
This is the main part of the app, which contains the top-level logic to call other functions and render the UI.
manifest.yml
by defining index.handler
.App()
is then triggered. This is where all the magic happens:
useProductContext
.getPageData()
resolver function,
which is defined later in this tutorial.callOpenAI()
method.addKeywordsToLabels()
method.index.js
to call a Confluence API to get the content of a pageNavigate to src/resolvers/index.js
. This contains the backend functions, which will handle the asynchronous events for the app.
index.js
1 2import Resolver from '@forge/resolver'; import { route, asUser } from '@forge/api'; import { OpenAI } from 'openai'; const resolver = new Resolver(); // Resolver function to fetch page data from Confluence resolver.define('getContent', async ({ payload }) => { const response = await asUser().requestConfluence(route`/wiki/api/v2/pages/${payload.contentId}?body-format=storage`, { headers: { 'Accept': 'application/json' } }); if (!response.ok) { const err = `Error while getContent with contentId ${payload.contentId}: ${response.status} ${response.statusText}`; console.error(err); throw new Error(err); } const responseData = await response.json(); const returnedData = responseData.body.storage.value; return returnedData; }); resolver.define('callOpenAI', async (prompt) => { // Resolver function to interact with the OpenAI API using a given prompt }); resolver.define('addKeywordsToLabels', async ({ payload }) => { // Resolver function to add keywords as labels to the current page }); export const handler = resolver.getDefinitions();
This function calls the get page API
using the asUser()
method.
created at
and author
.prompt
variable it sends to ChatGPT.Now that the app can retrieve all the content of a Confluence page via an API, the next step is to pass it to the OpenAI API to get the keywords.
index.js
to call the ChatGPT API to retrieve the keywordsIn the previous step, you added the variable prompt
, then constructed a prompt using the page content
and a command that tells OpenAI what to do with that data - in this case, extract keywords.
The code passes the prompt
variable to the callOpenAI
function, which calls the OpenAI API
and returns the results.
Here is the code for the callOpenAI
function:
index.js
1 2// Resolver function to interact with the OpenAI API using a given prompt resolver.define('callOpenAI', async (prompt) => { // Create an instance of the OpenAIApi with the provided configuration const openai = new OpenAI({ apiKey: 'API-KEY', organisation: 'ORG-ID' }); // Create a chat completion request using the OpenAI API const chatCompletion = await openai.chat.completions.create({ messages: [{ role: "user", content: prompt }], model: "gpt-3.5-turbo" }) // Extract the response content from the API response const response = chatCompletion.choices[0].message.content; // Return the generated response from the OpenAI API return response; });
Here, the app makes a basic API call to OpenAI. You can learn more about this through their documentation. The steps involved include:
The API_KEY
environment variable is where you set the OpenAI API key that is needed to interact
with their APIs. You might also need to pass an organisation ID, here referred to as ORG_ID
.
To create an environment variable in Forge, enter the following command in your terminal:
1 2forge variables set --encrypt API_KEY your-api-key forge variables set --encrypt ORG_ID your-org-id
The --encrypt
flag instructs Forge to store the variable in encrypted form.
The last step is to add the keywords in the form of a JavaScript array as labels to the page. We’re going to do that using the add labels to content API.
index.js
1 2// Resolver function to add keywords as labels to the current page resolver.define('addKeywordsToLabels', async ({ payload }) => { // Parse the keywords and prepare them for adding as labels const bodyData = JSON.parse(payload.keywords).map(label => ({ prefix: "global", name: label.split(" ").join("-") })); // Make a request to the Confluence API to add labels to the page const response = await asUser().requestConfluence(route`/wiki/rest/api/content/${payload.contentId}/label`, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(bodyData) }); // Parse and return the response JSON const responseJson = await response.json(); return responseJson; });
Now it’s time to:
forge deploy
in the terminal again as the manifest.yml
file was updated.forge install --upgrade
and select the installation to upgrade. If you have followed along
with this tutorial, it should list the development environment for your Confluence instance.You've shown incredible dedication and skill by finishing the Forge app tutorial. Well done! If you need help, reach out to our developer community. Keep up the excellent work and continue to explore new opportunities for your apps using Forge and OpenAI technology.
Rate this page: