In this tutorial, we’re going through the steps of migrating a Jira issue translator app built with UI Kit 1 to the latest version of UI Kit. We’ll be creating a new UI Kit app and migrate the code from the existing UI Kit 1 app to the new version. We'll also be using this sample UI Kit 1 app.
Make sure to do the following before you start migrating your app:
Update the Forge CLI to the latest version by running the following command:
npm install -g @forge/cli@latest
Create a new UI Kit app by running the following command:
forge create
For our app, the following options are used:
1 2Name: forge-ui-kit-translate Category: UI Kit Template: jira-issue-panel
This is the latest version of UI Kit. The UI Kit 1 option has been removed from the latest version of the CLI.
Update the manifest to include the same permissions as the existing UI Kit 1 app by running the following command:
1 2permissions: scopes: - "read:jira-work" external: fetch: backend: - "https://api.cognitive.microsofttranslator.com"
Go into src/frontend/index.jsx
. The initial template looks like this:
1 2import React, { useEffect, useState } from 'react'; import ForgeReconciler, { Text } from '@forge/react'; import { invoke } from '@forge/bridge'; const App = () => { const [data, setData] = useState(null); useEffect(() => { invoke('getText', { example: 'my-invoke-variable' }).then(setData); }, []); return ( <> <Text>Hello world!</Text> <Text>{data ? data : 'Loading...'}</Text> </> ); }; ForgeReconciler.render( <React.StrictMode> <App /> </React.StrictMode> );
This file holds all of the app’s frontend logic. We’ll be adding our UI Kit components here.
Copy over the existing app’s UI code and update UI Kit 1 components to the latest UI Kit components. The migrated code should now look like this:
1 2import React, { Fragment, useState } from "react"; import ForgeReconciler, { ButtonGroup, Button, Text } from "@forge/react"; import { invoke } from "@forge/bridge"; const LANGUAGES = [ ["🇯🇵 日本語", "ja"], ["🇰🇷 한국어", "ko"], ["🇬🇧 English", "en"], ]; const App = () => { const [translation, setTranslation] = useState(null); const setLanguage = async (countryCode) => { // will add in the next steps }; 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.ButtonSet
with ButtonGroup
Button
and Text
as children of the component.You’ll notice the setLanguage
logic is missing in this file. 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,
such as @forge/api
. These resolvers can then be invoked from the frontend.
To add this, go into src/resolvers/index.js
, and 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 we will use to invoke this resolver.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); };
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: