Slide 1

Slide 1 text

RALPH WHITBECK | SENIOR DEVELOPER ADVOCATE Customizing Confluence 
 with Forge Developer Day ’23: The Meetup

Slide 2

Slide 2 text

• Atlassian Cloud Development Instance 
 http://go.atlassian.com/cloud-dev • Forge CLI & Docker CLI PREREQUISITES

Slide 3

Slide 3 text

http://go.atlassian.com/ devday23slack SLACK INSTANCE

Slide 4

Slide 4 text

• Reference Hands-On Code Repo 
 https://bitbucket.org/atlassian/dd23-forge-confluence • Slides (also on Readme in above repo) 
 https://speakerdeck.com/rwhitbeck/customizing- confluence-with-forge REPO & SLIDES

Slide 5

Slide 5 text

Agenda What is Forge? Let’s Build an App API Calls Forge Storage Extending Confluence - Macro

Slide 6

Slide 6 text

A little history

Slide 7

Slide 7 text

In 2001 - Open[ish] Source Downloadable Source Modifying Directly

Slide 8

Slide 8 text

2004 - Atlassian Plugins Plugin Architecture written from scratch Service Provider Interface

Slide 9

Slide 9 text

2008 - Plugins 2 OSGi based Dependency Isolation Dynamic Loading

Slide 10

Slide 10 text

2014 - Atlassian Connect Language Agnostic Web APIs Cloud Integration

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

We host, you build

Slide 13

Slide 13 text

Secure by design

Slide 14

Slide 14 text

UI your way

Slide 15

Slide 15 text

Reasons to build a Forge App Marketplace Opportunity Monetize your idea by selling it on the Atlassian Marketplace Customized User Experience Tailor the user experience to meet specific needs and preferences Process Automation Automate repetitive tasks, streamline workflows, and eliminate manual processes Integrate External Tools Enabling seamless data exchange and collaboration between different tools Solve Business Needs Address specific pain points or requirements within your organization

Slide 16

Slide 16 text

Agenda What is Forge? Let’s Build an App API Calls Forge Storage Extending Confluence - Macro

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

Macro / UI Kit Confluence REST API Forge Storage Forge CLI

Slide 24

Slide 24 text

Forge CLI Setting up your App

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

Hands-on

Slide 29

Slide 29 text

• Run forge create in a terminal window • Create a new app called confluence-page-ring • Select the template category UI kit > confluence-macro • cd into the new directory that forge create created • Run forge deploy • Run forge install Select Confluence and give it your developer instance • Create a page on the developer instance and type /confluence page ring 
 and make sure you can add the macro onto your page • Download / Clone code repo: https://bitbucket.org/atlassian/dd23-forge- confluence HANDS-ON - SET-UP OUR PROJECT

Slide 30

Slide 30 text

Agenda What is Forge? Let’s Build an App API Calls Forge Storage Extending Confluence - Macro

Slide 31

Slide 31 text

Call an Confluence API from Forge 1. Request info on the current page 2. Get the title of the current page 3. Display the title on the page

Slide 32

Slide 32 text

Things we’ll need • Product Context and Update State • @forge/ui • Installed with UI Kit in the template 
 • Product Authentication Fetch Package • @forge/api • Needs to be installed
 • Confluence API v2 - GET Page by ID /pages/{id}

Slide 33

Slide 33 text

UI Kit hooks Functions that let you manage the data you need to render your app

Slide 34

Slide 34 text

useState • adds and updates local state to a component import ForgeUI, { useState, Button, Macro, render } from '@forge/ui'; const App = () => { const [count, setCount] = useState(0); return ( { setCount(count + 1); }} /> ); }; export const run = render(} />); Docs: https://developer.atlassian.com/platform/forge/ui-kit-hooks-reference/#usestate

Slide 35

Slide 35 text

useState • adds and updates local state to a component import ForgeUI, { useState, Button, Macro, render } from '@forge/ui'; const App = () => { const [count, setCount] = useState(0); return ( { setCount(count + 1); }} /> ); }; export const run = render(} />); Docs: https://developer.atlassian.com/platform/forge/ui-kit-hooks-reference/#usestate

Slide 36

Slide 36 text

useProductContext • reads the context in which the component is currently running const context = useProductContext(); Docs: https://developer.atlassian.com/platform/forge/ui-kit-hooks-reference/#usestate

Slide 37

Slide 37 text

import ForgeUI, { render, Fragment, Macro, Text, useProductContext, useState } from "@forge/ui"; Docs: https://developer.atlassian.com/platform/forge/ui-kit-hooks-reference/ 
 https://developer.atlassian.com/platform/forge/ui-kit/#hooks

Slide 38

Slide 38 text

// Get info of the current page, including Content ID const context = useProductContext(); console.log("context: " + JSON.stringify(context)); console.log("context.contentId: " + context.contentId); Docs: https://developer.atlassian.com/platform/forge/ui-kit-hooks-reference/

Slide 39

Slide 39 text

const context = useProductContext(); console.log("context: " + JSON.stringify(context)); { "accountId": {accountId}, "accountType": "licensed", "cloudId": {cloudId}, "contentId": "393217", "localId": {localId}, "spaceKey": "~557057729b34f7b3a34dda9883b1b9ed7dc794", "installContext": “{installContext}”, "isConfig": false, "extensionContext": { "type": "macro", "isEditing": false, "references": [] }, "moduleKey": "confluence-page-ring-macro-hello-world" } console.log("context.contentId: " + context.contentId); 393217 Docs: https://developer.atlassian.com/platform/forge/ui-kit-hooks-reference/

Slide 40

Slide 40 text

const context = useProductContext(); console.log("context: " + JSON.stringify(context)); { "accountId": {accountId}, "accountType": "licensed", "cloudId": {cloudId}, "contentId": "393217", "localId": {localId}, "spaceKey": "~557057729b34f7b3a34dda9883b1b9ed7dc794", "installContext": “{installContext}”, "isConfig": false, "extensionContext": { "type": "macro", "isEditing": false, "references": [] }, "moduleKey": "confluence-page-ring-macro-hello-world" } console.log("context.contentId: " + context.contentId); 393217 Docs: https://developer.atlassian.com/platform/forge/ui-kit-hooks-reference/

Slide 41

Slide 41 text

Fetch API Make requests to Atlassian product APIs from within your Forge app

Slide 42

Slide 42 text

@forge/api A partial implementation of node-fetch, which fetches data from an HTTP server. $ npm install @forge/api Handles Product Authentication for us

Slide 43

Slide 43 text

import api, { route } from "@forge/api"; const response = await api.asUser() .requestConfluence(route`/wiki/api/v2/pages/{id}`, { headers: { 'Accept': 'application/json' } }); console.log(`Response: ${response.status}`); console.log(await response.json()); Docs: https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-pages-id-get

Slide 44

Slide 44 text

import api, { route } from "@forge/api"; const response = await api.asUser() .requestConfluence(route`/wiki/api/v2/pages/{id}`, { headers: { 'Accept': 'application/json' } }); console.log(`Response: ${response.status}`); console.log(await response.json()); Docs: https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-pages-id-get

Slide 45

Slide 45 text

import api, { route } from "@forge/api"; const response = await api.asUser() .requestConfluence(route`/wiki/api/v2/pages/{id}`, { headers: { 'Accept': 'application/json' } }); console.log(`Response: ${response.status}`); console.log(await response.json()); Docs: https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-pages-id-get

Slide 46

Slide 46 text

const response = await api.asUser() .requestConfluence(route`/wiki/api/v2/pages/{id}`, { headers: { 'Accept': 'application/json' } }); Docs: https://developer.atlassian.com/platform/forge/runtime-reference/product-fetch-api/#contextual-methods Contextual Method • api.asUser() - call an Atlassian API as the user of the app • api.asApp() - Call an Atlassian API as the app, using the app user. The call 
 will work regardless of who used the app.

Slide 47

Slide 47 text

const response = await api.asUser() .requestConfluence(route`/wiki/api/v2/pages/{id}`, { headers: { 'Accept': 'application/json' } }); Docs: https://developer.atlassian.com/platform/forge/runtime-reference/product-fetch-api/#requestconfluence https://www.npmjs.com/package/node-fetch#options requestConfluence • Makes a request to the Confluence REST API • Takes a path built using the route tagged template function • Second argument is the options based on node-fetch library

Slide 48

Slide 48 text

const response = await api.asUser() .requestConfluence(route`/wiki/api/v2/pages/${contentId}`, { headers: { 'Accept': 'application/json' } }); Docs: https://developer.atlassian.com/platform/forge/runtime-reference/product-fetch-api/#route 
 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent • Tagged Template Function • Constructs the path that's passed to the product fetch APIs • Runs encodeURIComponent on each interpolated parameter in the template string • protection against security vulnerabilities, such as path traversal and query string injection route

Slide 49

Slide 49 text

Hands-on Calling a Confluence REST API

Slide 50

Slide 50 text

COLLABORATIVE HANDS-ON • Run npm install @forge/api in the newly created project • Get the product context and save the contentID to a variable • Create a function called fetchPage that takes in the context.contentID 
 and returns the page title of the current page. • Call the new fetchPage function using useState • Update the manifest.yml and add the scope needed

Slide 51

Slide 51 text

Agenda What is Forge? Let’s Build an App API Calls Forge Storage Extending Confluence - Macro

Slide 52

Slide 52 text

FORGE STORAGE • Stores JSON value with a specified key • Data stored isn’t shared between Forge apps on the same or different sites • Subject to quotas and limits https://developer.atlassian.com/platform/forge/platform- quotas-and-limits/ • Your app needs the storage:app scope • Import the storage function from the @forge/api package
 import { storage } from “@forge/api"; • Types of values to be saved: arrays, boolean, numbers, objects, strings

Slide 53

Slide 53 text

FORGE STORAGE - SETTING A VALUE import { storage } from "@forge/api"; storage.set(key, value); var arrPageRing = []; arrPageRing.push(3569276); storage.set(“page-ring", arrPageRing); Docs: https://developer.atlassian.com/platform/forge/runtime-reference/storage-api/

Slide 54

Slide 54 text

FORGE STORAGE - GETTING A VALUE import { storage } from "@forge/api"; // storage.get returns a promise var fsData = await storage.get(key); var fsData = await storage.get("page-ring"); console.log(fsData); // [3569276] Docs: https://developer.atlassian.com/platform/forge/runtime-reference/storage-api/

Slide 55

Slide 55 text

FORGE STORAGE - DELETING A VALUE import { storage } from "@forge/api"; storage.delete(key); storage.delete("page-ring"); Docs: https://developer.atlassian.com/platform/forge/runtime-reference/storage-api/

Slide 56

Slide 56 text

COLABORATIVE HANDS-ON 1. Test if the page is already stored. 2. Save the contentID into an array and into Forge Storage 3. Get the Adjacent page in the direction desired (next or previous)

Slide 57

Slide 57 text

Agenda What is Forge? Let’s Build an App API Calls Forge Storage Extending Confluence - Macro

Slide 58

Slide 58 text

UI Kit Building the user interface of your app

Slide 59

Slide 59 text

CONFLUENCE MODULES • Content action • Content Byline item • Context menu • Custom Content • Global Page • Global Settings • Homepage Feed • Space page • Space settings • Macro Docs: https://developer.atlassian.com/platform/forge/manifest-reference/modules/index-confluence/

Slide 60

Slide 60 text

Content Action

Slide 61

Slide 61 text

Content Menu

Slide 62

Slide 62 text

Macro Insert dynamic content into the UI via the Editor

Slide 63

Slide 63 text

import ForgeUI, { render, Fragment, Macro, Text } from "@forge/ui"; const App = () => { return ( Hello World! ); }; export const run = render( } /> ); Docs: https://developer.atlassian.com/platform/forge/ui-kit-components/confluence/macro/

Slide 64

Slide 64 text

COMMON UI KIT COMPONENTS - FRAGMENT return ( ); Docs: https://developer.atlassian.com/platform/forge/ui-kit-components/fragment/

Slide 65

Slide 65 text

COMMON UI KIT COMPONENT - TABLE return ( Hello World! ); Docs: https://developer.atlassian.com/platform/forge/ui-kit-components/table/

Slide 66

Slide 66 text

COMMON UI KIT COMPONENT - TEXT return ( {Page.title} ); Docs: https://developer.atlassian.com/platform/forge/ui-kit-components/text/

Slide 67

Slide 67 text

COMMON UI KIT COMPONENTS - LINK return ( {Page.title} ); Docs: https://developer.atlassian.com/platform/forge/ui-kit-components/text/#link

Slide 68

Slide 68 text

COMMON UI KIT COMPONENTS import ForgeUI, { render, Fragment, Macro, Table, Row, Cell, Text, Link} from "@forge/ui";

Slide 69

Slide 69 text

COLLABORATIVE HANDS-ON • Using UI Kit Components, create a Table with Links for Previous and Next

Slide 70

Slide 70 text

RALPH WHITBECK | SENIOR DEVELOPER ADVOCATE Customizing Confluence 
 with Forge Developer Day ’23: The Meetup