The Batch Create Jira Issues example shows how you can use JavaScript Button for Confluence to traverse a table on a Confluence page and create a hierarchy of Jira issues.
Setup
To set up the example, create a new empty page and add a JavaScript Button at the very top with the following configuration:
- Button Caption: Setup
- Function Name: setup
- Function Parameter: $page
- Result Location: $page
- JavaScript Code:
function setup(page) {
const body = JSON.parse(page.body.atlas_doc_format.value);
const extensionKey = body.content[0].attrs.extensionKey;
return {
id: page.id,
title: page.title,
status: page.status,
spaceId: page.spaceId,
parentId: page.parentId,
version: {
number: page.version.number + 1,
message: "Setup Batch Jira Example"
},
body: {
representation: "atlas_doc_format",
value: "{\"type\":\"doc\",\"content\":[{\"type\":\"table\",\"attrs\":{\"layout\":\"default\",\"width\":1084.0,\"localId\":\"81f37798-ebcd-4096-a38c-0255d694fe85\"},\"content\":[{\"type\":\"tableRow\",\"content\":[{\"type\":\"tableHeader\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[118.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"#\",\"type\":\"text\",\"marks\":[{\"type\":\"strong\"}]}]}]},{\"type\":\"tableHeader\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[195.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Type\",\"type\":\"text\",\"marks\":[{\"type\":\"strong\"}]}]}]},{\"type\":\"tableHeader\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[348.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Summary\",\"type\":\"text\",\"marks\":[{\"type\":\"strong\"}]}]}]},{\"type\":\"tableHeader\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[423.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Description\",\"type\":\"text\",\"marks\":[{\"type\":\"strong\"}]}]}]}]},{\"type\":\"tableRow\",\"content\":[{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[118.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"1\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[195.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Epic\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[348.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Frontend\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[423.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Provides the frontend for our Webshop\",\"type\":\"text\"}]}]}]},{\"type\":\"tableRow\",\"content\":[{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[118.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"1.1\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[195.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"New Feature\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[348.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Add Item to cart\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[423.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"As a user I want to be able to add the item I currently looking at to the cart\",\"type\":\"text\"}]}]}]},{\"type\":\"tableRow\",\"content\":[{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[118.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"1.1.1\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[195.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Sub-task\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[348.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Add “Add to cart button” to item\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[423.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"See title\",\"type\":\"text\"}]}]}]},{\"type\":\"tableRow\",\"content\":[{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[118.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"1.1.2\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[195.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Sub-task\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[348.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Add \\\"Show cart button” to top of page\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[423.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"See title\",\"type\":\"text\"}]}]}]},{\"type\":\"tableRow\",\"content\":[{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[118.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"1.2\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[195.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"New Feature\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[348.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Remove Item from cart\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[423.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"As a user I want to be able to remove an item from my current cart\",\"type\":\"text\"}]}]}]},{\"type\":\"tableRow\",\"content\":[{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[118.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"2\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[195.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Epic\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[348.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Backend\",\"type\":\"text\"}]}]},{\"type\":\"tableCell\",\"attrs\":{\"colspan\":1,\"rowspan\":1,\"colwidth\":[423.0]},\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Provides the backend for our Webshop\",\"type\":\"text\"}]}]}]}]},{\"type\":\"paragraph\",\"content\":[{\"text\":\"Jira Project Key: YOUR_PROJECT_KEY_HERE\",\"type\":\"text\"}]},{\"type\":\"paragraph\",\"content\":[{\"text\":\"Jira URL: https://YOUR_INSTANCE.atlassian.net\",\"type\":\"text\"}]},{\"type\":\"extension\",\"attrs\":{\"layout\":\"default\",\"extensionType\":\"com.atlassian.ecosystem\",\"extensionKey\":\"" + extensionKey + "\",\"text\":\"JavaScript Button (Staging)\",\"parameters\":{\"guestParams\":{\"resultLocation\":\"$page\",\"code\":\"$(\'Code\')\",\"functionName\":\"createIssues\",\"cqlQuery\":\"\",\"caption\":\"Create All Issues\",\"parameters\":\"$page, $(\'Jira Project Key\'), $(\'Jira URL\')\"},\"localId\":\"59740357-5be4-46b5-a153-fdbc9f89859f\",\"extensionId\":\"ari:cloud:ecosystem::extension/" + extensionKey + "\",\"extensionTitle\":\"JavaScript Button (Staging)\"},\"localId\":\"f3068f57-818c-4641-91d8-5f611f0c179b\"}},{\"type\":\"paragraph\",\"content\":[{\"text\":\"Code:\",\"type\":\"text\"}]},{\"type\":\"codeBlock\",\"attrs\":{\"language\":\"javascript\"},\"marks\":[{\"type\":\"breakout\",\"attrs\":{\"mode\":\"wide\"}}],\"content\":[{\"text\":\"async function createIssues(page, projectKey, jiraUrl) {\\n const projectId = await getProjectId(projectKey);\\n if (projectId === null) {\\n throw new Error(`Project with Key ${projectKey} not found in Jira`);\\n }\\n \\n const pageBody = JSON.parse(page.body.atlas_doc_format.value);\\n const table = pageBody.content[0];\\n const rows = table.content;\\n \\n //map to retrieve issue type id given a type name\\n const issueTypeIdsByType = new Map();\\n \\n //stack of parents\\n const parents = []; \\n parents.push(null);\\n \\n //iterate over all rows, except the header row\\n for (let i = 1; i < rows.length; i++) {\\n if (rows[i].content[2].content[0].content[0].type === \\\"inlineCard\\\") {\\n //already created an issue for this row\\n continue;\\n }\\n \\n const number = rows[i].content[0].content[0].content[0].text;\\n const type = rows[i].content[1].content[0].content[0].text;\\n const summary = rows[i].content[2].content[0].content[0].text;\\n const description = rows[i].content[3].content[0].content[0].text;\\n \\n //fix the stack if we move back in the hierarchy\\n const numberOfDots = number.split(\\\".\\\").length - 1;\\n while (parents.length > numberOfDots + 1) {\\n parents.pop();\\n }\\n const parentKey = parents[parents.length -1];\\n \\n //find the correct issue type id\\n let issueTypeId = issueTypeIdsByType.get(type);\\n if (issueTypeId === undefined) {\\n issueTypeId = await getTypeId(projectId, type);\\n if (issueTypeId === null) {\\n throw new Error(`Project with Key ${projectKey} does not have issue type ${type}.`);\\n }\\n issueTypeIdsByType.set(type, issueTypeId);\\n }\\n \\n //now lets create the issue\\n const newIssue = await createIssue(\\n projectId, \\n issueTypeId, \\n number + \\\" - \\\" + summary, \\n description, \\n parentKey\\n );\\n console.log(`Created issue ${JSON.stringify(newIssue)}`);\\n parents.push(newIssue.key);\\n \\n rows[i].content[2].content[0].content[0] = {\\n type: \\\"inlineCard\\\",\\n attrs: {\\n url: `${jiraUrl}/browse/${newIssue.key}`\\n }\\n }\\n }\\n \\n page.body.atlas_doc_format.value = JSON.stringify(pageBody);\\n page.version.number = page.version.number + 1;\\n page.version.message = \\\"Creating Issues with JavaScript Button for Confluence\\\";\\n \\n return page;\\n}\\n\\nasync function createIssue(projectId, issueTypeId, summary, description, parentKey) {\\n var bodyData = {\\n fields: {\\n description: {\\n content: [\\n {\\n content: [\\n {\\n text: description,\\n type: \\\"text\\\"\\n }\\n ],\\n type: \\\"paragraph\\\"\\n }\\n ],\\n type: \\\"doc\\\",\\n version: 1\\n },\\n issuetype: {\\n id: issueTypeId\\n },\\n parent: {\\n key: parentKey\\n },\\n project: {\\n id: projectId\\n },\\n summary: summary\\n }\\n };\\n \\n const response = await api.asUser().requestJira(route`/rest/api/3/issue`, {\\n method: \'POST\',\\n headers: {\\n \'Accept\': \'application/json\',\\n \'Content-Type\': \'application/json\'\\n },\\n body: JSON.stringify(bodyData)\\n });\\n \\n console.log(`Response: ${response.status} ${response.statusText}`);\\n \\n return await response.json();\\n}\\n\\nasync function getTypeId(projectKey, typeName) {\\n const response = await api.asUser().requestJira(route`/rest/api/3/issuetype/project?projectId=10000`, {\\n headers: {\\n \'Accept\': \'application/json\'\\n }\\n });\\n console.log(`Response: ${response.status} ${response.statusText}`);\\n \\n const issueTypes = await response.json();\\n \\n const result = issueTypes.find(element => element[\'name\'] == typeName);\\n if (result === undefined) {\\n return null;\\n } else {\\n return result[\'id\'];\\n }\\n}\\n\\nasync function getProjectId(projectKey) {\\n const response = await api.asUser().requestJira(route`/rest/api/3/project`, {\\n headers: {\\n \'Accept\': \'application/json\'\\n }\\n });\\n console.log(`Response: ${response.status} ${response.statusText}`);\\n\\n const projects = await response.json();\\n\\n const result = projects.find(element => element[\'key\'] == projectKey);\\n if (result === undefined) {\\n return null;\\n } else {\\n return result[\'id\'];\\n }\\n}\",\"type\":\"text\"}]}],\"version\":1}"
}
}
}
Invoke the button and after your page reloads the Page should look like this:
Edit the page and replace YOUR_PROJECT_KEY_HERE with a Jira project key that you want to use to create the issues shown in the table.
When you invoke the button an issue should be created for each row with the correct issue type and issue parent: