Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in
It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

Forge app development. Part 2. Creating and deleting notes

Creating new note

We must store our notes somewhere, so lets enchance our index.jsx file. There are 2 main possibilities to store data in Jira Forge app: Storage API and useIssuePropertyHook, we'll use second. First of all, install new dependency from your app directory: npm i @forge/ui-jira

And then update file in this way:

import ForgeUI, {render, IssueActivity} from '@forge/ui'
import NotesList from './components/NotesList'
import {useIssueProperty} from '@forge/ui-jira'

const App = () => {    
    
const [notes, setNotes] = useIssueProperty('issueNotes', []])     
    
return (<NotesList notes={notes}/>)
}

export const run = render(
    <IssueActivity>        
  <App/>    
  </IssueActivity>
);

 

Our app will be built successfully (if you run forge tunnel previously), but warning appeared in console:

/app/src/index.jsx3:8     warning  Jira UI hook: "useIssueProperty" requires the "read:jira-work" scope  permission-scope-required 3:8     warning  Jira UI hook: "useIssueProperty" requires the "write:jira-work" scope  permission-scope-required ⚠ 2 problems (0 errors, 2 warnings)  Run forge lint --fix to automatically fix 0 errors and 2 warnings.

 Let's run what Forge suggests. After this check your manifest changes. Read more about scopes here. If you run your app now, you'll set that no notes present for now. Okay, we need a form for creating new notes.

NewNoteForm

Go to our components folder and create new file NewNoteForm.js:

import ForgeUI, {ModalDialog, Form, TextArea} from'@forge/ui'

const NewNoteForm = ({show, submit}) => (
  <ModalDialog header='New Note'onClose={() => show(false)}>
  <Form onSubmit={submit}>
  <TextArea name='text'label=''placeholder='Type your note...'/>
  </Form>
  </ModalDialog>
)

exportdefaultNewNoteForm

 

Pay attention to two functions we must provide to form. Next, create them in index.jsx and add storage facilities with control button:

import ForgeUI, {render, IssueActivity, useState, useProductContext, Fragment, Button, ButtonSet} from '@forge/ui' 
import {useIssueProperty} from '@forge/ui-jira'
import NotesList from './components/NotesList'
import NewNoteForm from "./components/NewNoteForm"

const App = () => {
const {accountId} = useProductContext()
const [notes, setNotes] = useIssueProperty('issueNotes', [])
const [showCreate, setShowCreate] = useState(false)
const createNote = async formData => {
const text = formData.text
const updatedNotes = [...notes, {accountId, text}] s
setShowCreate(false)
await setNotes(updatedNotes)
}

return (
<Fragment>
<NotesList notes={notes}/>
<ButtonSet>
<Button text="Create" onClick={() => setShowCreate(true)}/>
</ButtonSet> {showCreate && <NewNoteForm show={setShowCreate} submit={createNote}/>}
</Fragment>
)
}

export const run = render(
<IssueActivity>
<App/>
</IssueActivity>
);

 

Our createNote function is async and we use await before setNotes function. This guarantees that we can see out new note right after its creation.

Time to test!

Go to your development instance, open issue and click Notes tab. You'll see Create button! Click it, and enter value in form:

4.pngClick Submit button and voila:

5.png

Deleting notes

Great, we can add notes. Now let's delete them.Unfortunately, we have no ability to pick events on Forge components as we use to do in browser environment, because Forge is fully rendered in server. Solution is to create a form where we can choose note to delete.

DeleteNoteForm

Go to our components folder and create new file DeleteNoteForm.js:

import ForgeUI, {Form, Fragment, ModalDialog, Select, Option} from '@forge/ui'

const DeleteNoteForm = ({show, submit, notes}) => {
const options = notes.map(note => <Option label={note.text} value={note.text}/>)

return (
<Fragment>
<ModalDialog header='Delete Note' onClose={() => show(false)}> <Form onSubmit={submit}>
<Select label='' name='text'> {options} </Select>
</Form>
</ModalDialog>
</Fragment>
)
}

export default DeleteNoteForm

 

This form is similar to creation form with little difference - we use Select. 

Let's edit our index.jsx in order to use this shiny form:

import ForgeUI, {render, IssueActivity, useState, useProductContext, Fragment, Button, ButtonSet} from '@forge/ui' 
import {useIssueProperty} from '@forge/ui-jira'
import NotesList from './components/NotesList'
import NewNoteForm from "./components/NewNoteForm"
import DeleteNoteForm from "./components/DeleteNoteForm"

const App = () => {
const {accountId} = useProductContext()
const [notes, setNotes] = useIssueProperty('issueNotes', [])
const [showCreate, setShowCreate] = useState(false)
const [showDelete, setShowDelete] = useState(false)

const createNote = async formData => {
const text = formData.text
const updatedNotes = [...notes, {accountId, text}]
setShowCreate(false)
await setNotes(updatedNotes)
}

const deleteNote = async formData => {
const text = formData.text
const updatedNotes = notes.filter(note => note.text !== text)
setShowDelete(false)
await setNotes(updatedNotes)
}

return (
<Fragment>
<NotesList notes={notes}/>
<ButtonSet>
<Button text="Create" onClick={() => setShowCreate(true)}/>
<Button text="Delete" onClick={() => setShowDelete(true)}/>
</ButtonSet>
{showCreate && <NewNoteForm show={setShowCreate} submit= {createNote}/>}
{showDelete && <DeleteNoteForm show={setShowDelete} submit={deleteNote} notes={notes}/>}
</Fragment>
)
}

export const run = render(
<IssueActivity>
<App/>
</IssueActivity>
);

Ready for testing! 

We'got new button - Delete. Click it:

6.png

Choose our note and press Submit. It's gone forever.

Improving app (it's up to you)

App implementation is rather simplistic. The main drawback is that we can't uniquely identify our notes. And if you have notes with identical text - all of them will be gone on deletion of one.

Also, we store data as entity properties and they are available to all apps. What we can do is to use Storage API.

The last but not the least is code duplication. Compare deleteNote and createNote functions, do you see they are similar? It is possible to abstract with updateNote function with additional function as a param.

There is a room for improvement, i leave it to you as an exercise.

Next

In next part of this tutorial we'll dig in deployment of our app with BitBucket Pipelines.

Comment

Log in or Sign up to comment
TAGS

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you