In this tutorial, we will be going through the steps of migrating a Jira issue translator app built with UI Kit 1 to the latest version of UI Kit. We will be migrating this sample UI Kit 1 app.
For a full list of changes between UI Kit 1 and UI Kit, see the UI Kit 1 to UI Kit upgrade page.
Make sure to do the following before you start migrating your app:
Have a UI Kit 1 app you would like to migrate ready or use our sample UI Kit 1 app to follow along.
Update the Forge CLI to the latest version by running the following command:
npm install -g @forge/cli@latest
UI Kit requires a few new packages to be installed. Install the following packages from the root of the project by running the following command:
npm install @forge/react @forge/resolver @forge/bridge --save
@forge/react
is a package that contains all UI Kit components.@forge/resolver
is a package that allows you to define backend functions.@forge/bridge
is a package that allows you to invoke product specific capabilities and resolver functions from the frontend.@forge/ui
can be uninstalled as it is no longer required.In UI Kit, frontend code will be invoked in a sandboxed environment on the client. Frontend code is any code that renders our UI or JavaScript code that can be executed in the browser. Sandboxing ensures that the frontend code runs securely and independently within the browser.
Backend code will be invoked from resolvers. This approach is used to manage data requests and responses, ensuring that backend logic is executed in response to specific queries or mutations. Resolvers are configured to trigger the appropriate backend functions when needed.
We will first create a separate directory for frontend code and migrate our frontend logic to there.
Create the following directory and file: src/frontend/index.js
.
Move all frontend code into this file. Any code using @forge/api
is backend code and will need to be moved into a resolver in the next step.
From our sample app, this is what the initial src/frontend/index.jsx
file will look like:
1 2import React, { Fragment, useState } from "react"; import ForgeReconciler, { ButtonGroup, Button, Text } from "@forge/react"; import { invoke } from "@forge/bridge"; // will be used in `setLanguage` in the next step const LANGUAGES = [ ["🇯🇵 日本語", "ja"], ["🇰🇷 í•œêµì–´", "ko"], ["🇬🇧 English", "en"], ]; const App = () => { const [translation, setTranslation] = useState(null); const setLanguage = async (countryCode) => { // will be implemented in the next step }; return ( <Fragment> <ButtonGroup> {LANGUAGES.map(([label, code]) => ( <Button key={code} onClick={async () => { await setLanguage(code); }} > {label} </Button> ))} </ButtonGroup> {translation && ( <Fragment> <Text>**{translation.summary}**</Text> <Text>{translation.description}</Text> </Fragment> )} </Fragment> ); }; ForgeReconciler.render( <React.StrictMode> <App /> </React.StrictMode> );
Note the differences from the original UI Kit 1 code:
@forge/react
is the new library holding all UI Kit components.Fragment
component should be from the react
library. The shorthand syntax <></>
can also be used instead and doesn’t require an additional import.ForgeReconciler.render
ButtonSet
with ButtonGroup
Button
and Text
as children of the component.From the previous step, you’ll notice the setLanguage
logic is missing. This is due to the usage of the
@forge/api package. @forge/api
can only be used in a
Forge resolver function, as it is a Node package and is incompatible with the frontend.
Forge resolvers are a series of backend functions for your app that can use backend packages. These resolvers can then be invoked from the frontend using invoke
from @forge/bridge
.
Create a new directory and file with the following folder structure src/resolvers/index.js
.
Copy over the setLanguage
function into the resolver.
1 2import Resolver from "@forge/resolver"; import api, { route } from "@forge/api"; async function checkResponse(apiName, response) { if (!response.ok) { const message = `Error from ${apiName}: ${ response.status } ${await response.text()}`; console.error(message); throw new Error(message); } else if (process.env.DEBUG_LOGGING) { console.debug(`Response from ${apiName}: ${await response.text()}`); } } const TRANSLATE_API = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0"; const resolver = new Resolver(); resolver.define("setLanguage", async ({ context, payload }) => { const countryCode = payload.countryCode; const issueKey = context.extension.issue.key; // Fetch issue fields to translate from Jira const issueResponse = await api .asApp() .requestJira( route`/rest/api/2/issue/${issueKey}?fields=summary,description` ); await checkResponse("Jira API", issueResponse); const { summary, description } = (await issueResponse.json()).fields; // Translate the fields using the Azure Cognitive Services Translatioon API const translateResponse = await api.fetch( `${TRANSLATE_API}&to=${countryCode}`, { method: "POST", headers: { "Content-Type": "application/json; charset=UTF-8", // See README.md for details on generating a Translation API key "Ocp-Apim-Subscription-Key": process.env.TRANSLATE_API_KEY, "Ocp-Apim-Subscription-Region": process.env.TRANSLATE_API_LOCATION, }, body: JSON.stringify([ { Text: summary }, { Text: description || "No description" }, ]), } ); await checkResponse("Translate API", translateResponse); const [summaryTranslation, descriptionTranslation] = await translateResponse.json(); // Update the UI with the translations return { to: countryCode, summary: summaryTranslation.translations[0].text, description: descriptionTranslation.translations[0].text, }; }); export const handler = resolver.getDefinitions();
Important things to note:
resolver.define
is a string that will be used as a key to invoke this resolver.We'll also need to export this resolver so that it can be imported into our manifest file in the next section.
Add a new src/index.js
file with the following contents:
1 2export { handler } from "./resolvers";
Back in src/frontend/index.jsx
, add the following to invoke your new resolver:
1 2const setLanguage = async (countryCode) => { const resp = await invoke("setLanguage", { countryCode }); setTranslation(resp); };
We now need to wire up the frontend and backend and also update our app to use UI Kit. We can do this by updating our manifest.yml
file.
render: native
property to your app so that it will now render using UI Kit instead of UI Kit 1.1 2modules: jira:issuePanel: - key: translate render: native ...
Remove the function
property in your app as this is no longer required for UI Kit.
Replace the existing UI Kit 1 function
's key
and handler
properties with a new key
and handler
that points to our new resolver.
1 2function: - key: resolver handler: index.handler
resource
property and set a unique key
and path
to our frontend file.1 2resource: - key: main path: src/frontend/index.jsx
1 2modules: jira:issuePanel: - key: translate resource: main resolver: function: resolver render: native title: Translate icon: https://developer.atlassian.com/platform/forge/images/icons/issue-translation-icon.svg description: Translate issue fields into other languages function: - key: resolver handler: index.handler resource: - key: main path: src/frontend/index.jsx app: id: "ari:cloud:ecosystem::app/764f9c2d-fac3-493f-b9fe-aabda3c639a2" runtime: name: sandbox permissions: scopes: - "read:jira-work" external: fetch: backend: - "https://api.cognitive.microsofttranslator.com"
That's it! To test, set up Forge variables, then deploy and install the app in to your instance.
You can find the migrated sample app here.
Rate this page: