I am trying to create an admin page that has a form that stores two input fields in Jira key/value storage. I had this working with UI Kit 1 but have to switch to the new UI Kit and have been running into this error message.
adminForm.jsx:45 Uncaught (in promise) Error: There was an error invoking the function - window is not defined
at invoke (https://jira-frontend-bifrost.prod-east.frontend.public.atl-paas.net/assets/async-forge-ui-full-page-extension.e927791e.js:39:78872)
at async https://jira-frontend-bifrost.prod-east.frontend.public.atl-paas.net/assets/async-forge-ui-full-page-extension.e927791e.js:39:109927
Error: There was an error invoking the function - window is not defined
at fe.error (https://forge.cdn.prod.atlassian-dev.net/global-bridge.js:2:74679)
at Object.<anonymous> (https://forge.cdn.prod.atlassian-dev.net/global-bridge.js:2:82452)
at JSON.parse (<anonymous>)
at Ee.o (https://forge.cdn.prod.atlassian-dev.net/global-bridge.js:2:82311)
at Ee (https://forge.cdn.prod.atlassian-dev.net/global-bridge.js:2:82464)
at We.i.on (https://forge.cdn.prod.atlassian-dev.net/global-bridge.js:2:87124)
at We (https://forge.cdn.prod.atlassian-dev.net/global-bridge.js:2:87261)
at https://forge.cdn.prod.atlassian-dev.net/global-bridge.js:2:93247
at e.try (https://forge.cdn.prod.atlassian-dev.net/global-bridge.js:2:65731)
at https://forge.cdn.prod.atlassian-dev.net/global-bridge.js:2:93044
Here is my code, starting with the manifest.yml
permissions:
scopes:
- storage:app
modules:
jira:adminPage:
- key: bigeye-jira-admin-page
resource: admin-page
title: Bigeye
render: native
icon: resource:icons;bigeye_icon.png
resolver:
function: resolver
function:
- key: resolver
handler: index.handler
resources:
- key: admin-page
path: src/frontend/adminForm.jsx
app:
id: ari:cloud:ecosystem::app/xxxxxxxxx
runtime:
name: nodejs20.x
Here is my adminForm.jsx file
import ForgeReconciler, {
Form,
Textfield,
Text,
SectionMessage,
FormHeader,
FormSection,
Stack,
useForm,
Label,
ErrorMessage,
Box,
FormFooter,
Button
} from "@forge/react";
import React, { Fragment, useState, useEffect } from "react";
import { invoke } from "@forge/bridge";
const WEBHOOK_STORAGE_KEY = 'bigeye-webhook-url'
const PROJECT_LIST_KEY = 'bigeye-project-keys'
function SavedMessage({isSaved}) {
if (isSaved) {
return <SectionMessage appearance="confirmation">
<Text>Bigeye configuration has been updated!</Text>
</SectionMessage>
}
else {
return null
}
}
const AdminForm = () => {
const [webhookUrl, setWebhookUrl] = useState(null);
const [projectKeys, setProjectKeys] = useState(null);
const [isUpdated, setIsUpdated] = useState(false);
const { handleSubmit, register, getFieldId, formState } = useForm();
const { errors, touchedFields } = formState;
useEffect(async() => {
const currWebhook = await invoke("getStorageValue", { key: `${WEBHOOK_STORAGE_KEY}` });
setWebhookUrl(currWebhook);
const currProjectKeys = await invoke("getStorageValue", { key: `${PROJECT_LIST_KEY}` });
setProjectKeys(currProjectKeys);
}, [invoke, setWebhookUrl, setProjectKeys]
);
const onSubmit = (formData) => {
console.log(formData);
// Update the webhook url
const wh = formData['webhook']
console.log(`Setting the ${WEBHOOK_STORAGE_KEY} property to ${wh}`)
invoke("setStorageValue", { key: `${WEBHOOK_STORAGE_KEY}`, value: wh }).then(setWebhookUrl);
// Update the list of project keys
const pks = formData['projectKeys']
console.log(`Setting the ${PROJECT_LIST_KEY} property to ${pks}`)
invoke("setStorageValue", { key: `${PROJECT_LIST_KEY}`, value: pks }).then(setProjectKeys);
// Set to true so that success message shows
setIsUpdated(true)
};
return (
<Fragment>
<SavedMessage isSaved={isUpdated} />
<Form onSubmit={handleSubmit(onSubmit)}>
<FormHeader title="Bigeye Integration Settings">
All fields are required.
</FormHeader>
<FormSection>
<Stack space="space.75">
<Box>
<Label labelFor={getFieldId("webhook")}>
Webhook Url
</Label>
<Textfield
{...register("webhook", {
required: true,
defaultValue: webhookUrl
})}
/>
{errors["webhook"] && (
<ErrorMessage>Please enter a valid webhook URL</ErrorMessage>
)}
</Box>
<Box>
<Label labelFor={getFieldId("projectKeys")}>
Project Keys (Separate each key with a comma. i.e. BI,DO,DQ)
</Label>
<Textfield
{...register("projectKeys", {
required: true,
defaultValue: projectKeys
})}
/>
{errors["projectKeys"] && (
<ErrorMessage>Please enter your desired Project keys</ErrorMessage>
)}
</Box>
</Stack>
</FormSection>
<FormFooter>
<Button appearance="primary" type="submit">
Submit
</Button>
</FormFooter>
</Form>
</Fragment>
);
};
ForgeReconciler.render(
<React.StrictMode>
<AdminForm/>
</React.StrictMode>
)
Here is my resolvers/index.jsx file, inspired by this repo
import Resolver from "@forge/resolver";
import { storage } from "@forge/api";
const resolver = new Resolver();
async function getDefinition(key) {
const value = await storage.get(key);
return value ? value.definition : "";
}
resolver.define('getDefinition', async(req) => {
const value = await getDefinition(req.payload.key);
return await Promise.all(value);
})
resolver.define('setDefinition', async(req) => {
const { key, value } = req.payload;
await storage.set(key, value);
})
export const handler = resolver.getDefinitions();
Finally, here are my versions from package.json
"dependencies": {
"@forge/api": "^3.4.0",
"@forge/bridge": "3.3.0",
"@forge/react": "10.1.0",
"@forge/resolver": "1.5.31",
"react": "^18.2.0"
}