This tutorial will walk you through creating a more complex Forge app for Jira using UI Kit.
The tutorial will cover the following Forge concepts:
jira-issue-panel
Key-Value Store
invoke
The Jira expense tracker app is designed to show you how to create an app that allows a user to create, edit and delete items in a to do list, the to do list items are stored and retreived using the Forge Storage API, and displayed using a Dynamic Table.
Approximate time needed: 30-40 minutes
Suggested Skills: Basic understanding of Javascript, React is recommended.
In part one you will create your Forge UI kit app using the Jira issue panel template. You will then customised the app to display a list of expenses, and their total using test data.
Then in part two, you will make changes to your app to store and retrieve data created by the user within Forge storage.
Finally, in part three, you will modify the app to update and display (in addition to storing and retrieving) data created by the user within Forge storage.
Along the way, you'll find extra tips, links and resources that will allow you to learn more about what was covered in each section, and become familiar with how resources are laid out in the Forge documentation.
We recommend completing Forge Quest in order as each section is designed to build on the previous one.
If you haven't completed the Forge Novices section, it is recommended you complete that before starting this tutorial.
Create an app based on the Jira Issue Panel template.
Create your app by running:
1 2forge create
Enter a name for your app (up to 50 characters). For example jira-issue-expenses.
Select the UI Kit category.
Select Jira from the list of products.
Select the jira-issue-panel template.
Change to the app subdirectory to see the app files:
1 2cd jira-issue-expenses
The app we'll create will create a Jira Issue Panel, which can be added to the new Jira Issue View in Jira Work Management, Jira Software and Jira Service Management.
1 2forge deploy
1 2forge install
jira
using the arrow keys and press the enter key.Once the install complete
message appears, your app is installed and ready to use on the site specified. You can delete your app from the site by running the forge uninstall
command.
Now that your app is installed, it's time to see it in action in your Jira site.
In this section, you'll make some changes to the src/frontend/index.jsx
to display some test data.
This data will be displayed using a Dynamic table.
1 2forge tunnel
src/frontend
directory.data.jsx
with the following content (this will be our temporary display data until the storage is up and running):
1 2export const conferenceExpenses = [ { id: 1, description: "Hotel", amount: 310, }, { id: 2, description: "Lunch", amount: 19, }, { id: 3, description: "Uber", amount: 15.90, }, { id: 4, description: "Dinner", amount: 35.00, } ];
index.jsx
file. The default content of the file is shown below:
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> );
import ForgeReconciler, { Text } from '@forge/react'
line to include all of the components listed above:
import ForgeReconciler, {Button, DynamicTable, Inline, Lozenge, Text, Textfield} from '@forge/react';
import { conferenceExpenses } from './data';
1 2return ( <> <DynamicTable caption="Expenses" rows={fillTable(conferenceExpenses)} /> </> );
fillTable
function:
1 2const fillTable = ( expenses ) => { console.log(expenses) if (expenses.length > 0) { const rows = expenses.map((item) => ({ cells: [ { content: <Text>{item.description}</Text>, }, { content: <Text>{item.amount}</Text>, }, ], })) return rows; } else return null; }
index.jsx
.In this section you will improve the table, making it look more like a to do list, using the following elements:
Under the table Text and a Button will be displayed.
You will also use the Inline component to control the way the elements are displayed.
These elements will not be wired up to change any data yet, but it will show how the final app will look.
index.jsx
if you closed itfillTable
function:
content: <Text>{item.description}</Text>,
with a Textfield so that later the user will be able to edit the to do items:
1 2content: <Textfield appearance="subtle" spacing="compact" id="expense-description" defaultValue={item.description}/>,
content: <Text>{item.amount}</Text>,
with a Textfield so that later the user will be able to edit the to do items:
1 2content: <Textfield appearance="subtle" spacing="compact" id="expense-amount" defaultValue={item.amount}/>,
1 2{ content: <Button appearance="subtle" iconBefore="trash" spacing="compact"/> },
fillTable
function, create a new function called getTotal
to determine the total of the expenses in the table:
1 2const getTotal = (expenses) => { let total = 0; expenses.forEach(expense => { total += expense.amount; }); return total; }
1 2return ( <> <DynamicTable caption="Expenses" rows={fillTable(conferenceExpenses)} /> <Inline spread='space-between'> <Text>Total: ${getTotal(conferenceExpenses)}</Text> <Button>Delete All</Button> </Inline> </> );
1 2forge deploy
Your src/frontend/index.jsx
should look like this:
1 2import React, { useEffect, useState } from 'react'; import ForgeReconciler, {Button, DynamicTable, Inline, Text, Textfield} from '@forge/react'; import { invoke } from '@forge/bridge'; import { conferenceExpenses } from './data'; const App = () => { const [data, setData] = useState(null); useEffect(() => { invoke('getText', { example: 'my-invoke-variable' }).then(setData); }, []); const fillTable = ( expenses ) => { console.log(expenses) if (expenses.length > 0) { const rows = expenses.map((item) => ({ cells: [ { content: <Textfield appearance="subtle" spacing="compact" id="expense-description" defaultValue={item.description}/>, }, { content: <Textfield appearance="subtle" spacing="compact" id="expense-amount" defaultValue={item.amount}/>, }, { content: <Button appearance="subtle" iconBefore="trash" spacing="compact"/> }, ], })) return rows; } else return null; } const getTotal = (expenses) => { let total = 0; expenses.forEach(expense => { total += expense.amount; }); return total; } return ( <> <DynamicTable caption="Expenses" rows={fillTable(conferenceExpenses)} /> <Inline spread='space-between'> <Text>Total: ${getTotal(conferenceExpenses)}</Text> <Button>Delete All</Button> </Inline> </> ); }; ForgeReconciler.render( <React.StrictMode> <App /> </React.StrictMode> );
In the next step you will modify the app to store and retrieve data using the Forge Storage API.
If you found this part of the tutorial helpful, please consider providing feedback using rate this page below!
Rate this page: