Update:
I figured out how to use Frame to display an iframe, but then I get a CPS error "frame-ancestors" because when I use forge tunnel, the “Referer” is
http://localhost:8002/
and the accepted values to embed a Jira page are: https://*.atlassian.net https://*.jira.com https://*.atl-paas.net https://*.atlassian.com https://trello.com https://bitbucket.org https://*.jiraalign.com »
If you have any ideas for displaying a Jira page in a Confluence macro, I’m interested.
To help other user, my code :
├── src/
│ ├── index.js
│ ├── fronted/
│ │ ├── config.jsx
│ │ └── index.jsx
│ └── resolvers/
│ └── index.jsx
├── static/
│ └── hello-world/
│ └── build/
│ └── index.html
├── manifest.yml
└── {other files}
manifest.yml
modules:
macro:
- key: test-confluence-ui-kit-custom-config-macro
resource: main
render: native
resolver:
function: resolver
title: test
config:
resource: macro-config
viewportSize: small
render: native
title: Config
function:
- key: resolver
handler: index.handler
resources:
- key: main
path: src/frontend/index.jsx
- key: macro-config
path: src/frontend/config.jsx
- key: example-resource
path: static/hello-world/build
app:
runtime:
name: nodejs22.x
id: ari:cloud:ecosystem::app/xxxxxxxx
permissions:
content:
styles:
- "unsafe-inline"
scripts:
- "unsafe-hashes"
- "unsafe-eval"
- "unsafe-inline"
external:
fetch:
backend:
- '*.atlassian.net'
frames:
- '*.atlassian.net'
src/fronted/config.jsx
import React, { useState, useEffect } from 'react';
import ForgeReconciler, { useConfig, Button, Label, SectionMessage, Stack, Textfield } from '@forge/react';
import { view } from '@forge/bridge';
import { Frame } from '@forge/react';
const useSubmit = () => {
const [error, setError] = useState();
const [message, setMessage] = useState('');
const submit = async (fields) => {
const payload = { config: fields };
try {
await view.submit(payload);
setError(false);
setMessage(`Submitted successfully.`);
} catch (error) {
setError(true);
setMessage(`${error.code}: ${error.message}`);
}
};
return {
error,
message,
submit
};
};
const Config = () => {
const [value, setValue] = useState('');
const { error, message, submit } = useSubmit();
const config = useConfig();
useEffect(() => {
setValue(config?.myField);
}, [config?.myField]);
return (
<Stack space="space.200">
<Frame resource="example-resource" />
<Label labelFor="myField">Config field:</Label>
<Textfield id="myField" value={value} onChange={(e) => setValue(e.target.value)} />
<Button appearance="subtle" onClick={view.close}>
Close
</Button>
<Button appearance="primary" onClick={() => submit({ myField: value })}>
Submit
</Button>
{typeof error !== 'undefined' && (
<SectionMessage appearance={error ? 'error' : 'success'}>{message}</SectionMessage>
)}
</Stack>
);
};
ForgeReconciler.render(
<React.StrictMode>
<Config />
</React.StrictMode>
);
static/hello-world/build
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
<script type="content" src="src/index.jsx"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">Hello embed JIRA XSP1-3 </div>
<iframe
src="https://myatlassiantest.atlassian.net/browse/XSP1-3/embed?originUrl=https%3A%2F%2Fmyatlassiantest.atlassian.net&parentProduct=smartlink&themeState=dark%3Adark+light%3Alight+spacing%3Aspacing+typography%3Atypography-refreshed+colorMode%3Alight"
width="100%"
height="100%"
style="border: none;">
</iframe>
</body>
</html>