Created
September 22, 2025 18:34
-
-
Save wjkennedy/4ba89c4cada158776d2629f00e67cc1d to your computer and use it in GitHub Desktop.
Process a list of DC appKeys with the Atlassian Marketplace API to find Cloud equivalents, pricing, etc.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function onOpen() { | |
| SpreadsheetApp.getUi() | |
| .createMenu('Atlassian Tools') | |
| .addItem('Stage 1: Create Cloud/DC sheets from selection', 'createCloudDcSheetsFromSelection') | |
| .addItem('Stage 2: Build consolidated comparison', 'buildComparisonFromCloudDcSheets') | |
| .addToUi(); | |
| } | |
| /** | |
| * Stage 1: For each selected addon key, create two sheets: | |
| * {key}_Cloud and {key}_DC, each with ImportJSON formulas. | |
| */ | |
| function createCloudDcSheetsFromSelection() { | |
| const ss = SpreadsheetApp.getActiveSpreadsheet(); | |
| const sel = ss.getActiveRange(); | |
| const addonKeys = sel.getValues().flat().filter(k => typeof k === 'string' && k.trim() !== ''); | |
| if (addonKeys.length === 0) { | |
| SpreadsheetApp.getUi().alert('No addon keys in selection.'); | |
| return; | |
| } | |
| const base = 'https://marketplace.atlassian.com/rest/2/addons/'; | |
| addonKeys.forEach((rawKey) => { | |
| const key = String(rawKey).trim(); | |
| if (!key) return; | |
| const cloudSheetName = sanitizeSheetName(key + "_Cloud"); | |
| const dcSheetName = sanitizeSheetName(key + "_DC"); | |
| let cloudSh = ss.getSheetByName(cloudSheetName); | |
| if (!cloudSh) cloudSh = ss.insertSheet(cloudSheetName); else cloudSh.clear(); | |
| let dcSh = ss.getSheetByName(dcSheetName); | |
| if (!dcSh) dcSh = ss.insertSheet(dcSheetName); else dcSh.clear(); | |
| const cloudUrl = `${base}${encodeURIComponent(key)}/pricing/cloud/live`; | |
| const dcUrl = `${base}${encodeURIComponent(key)}/pricing/server/live`; | |
| cloudSh.getRange('A1').setFormula(`=ImportJSON("${cloudUrl}")`); | |
| dcSh.getRange('A1').setFormula(`=ImportJSON("${dcUrl}")`); | |
| cloudSh.setFrozenRows(1); | |
| dcSh.setFrozenRows(1); | |
| }); | |
| } | |
| /** | |
| * Stage 2: Merge Cloud/DC sheets into AddonComparison sheet. | |
| */ | |
| function buildComparisonFromCloudDcSheets() { | |
| const ss = SpreadsheetApp.getActiveSpreadsheet(); | |
| const outName = "AddonComparison"; | |
| let out = ss.getSheetByName(outName); | |
| if (!out) out = ss.insertSheet(outName); else out.clear(); | |
| out.appendRow([ | |
| "Addon Key", | |
| "Edition Description", | |
| "User Count", | |
| "License Type", | |
| "Cloud Amount (USD)", | |
| "Cloud Renewal (USD)", | |
| "Cloud Local Currency", | |
| "DC Amount (USD)", | |
| "DC Renewal (USD)", | |
| "DC Local Currency", | |
| "Can Change Prices" | |
| ]); | |
| const sheets = ss.getSheets(); | |
| sheets.forEach(sh => { | |
| const name = sh.getName(); | |
| if (!name.endsWith("_Cloud")) return; | |
| const key = name.replace(/_Cloud$/, ""); | |
| const dcSheet = ss.getSheetByName(key + "_DC"); | |
| if (!dcSheet) return; | |
| const cloudData = sheetToRecords(sh); | |
| const dcData = sheetToRecords(dcSheet); | |
| const allEditions = new Set([...Object.keys(cloudData), ...Object.keys(dcData)]); | |
| allEditions.forEach(edition => { | |
| const c = cloudData[edition] || {}; | |
| const d = dcData[edition] || {}; | |
| out.appendRow([ | |
| key, | |
| edition, | |
| c.userCount || d.userCount || "", | |
| c.licenseType || d.licenseType || "", | |
| c.amount || "", | |
| c.renewal || "", | |
| c.localCurrency || "", | |
| d.amount || "", | |
| d.renewal || "", | |
| d.localCurrency || "", | |
| c.canChange || d.canChange || "" | |
| ]); | |
| }); | |
| }); | |
| autoResizeAll(out); | |
| } | |
| /* --- Helpers --- */ | |
| function sheetToRecords(sheet) { | |
| const values = sheet.getDataRange().getValues(); | |
| if (values.length < 2) return {}; | |
| const headers = values[0].map(h => String(h).toLowerCase()); | |
| const editionIdx = headers.findIndex(h => h.includes("editiondescription")); | |
| const userIdx = headers.findIndex(h => h.includes("unitcount")); | |
| const licenseIdx = headers.findIndex(h => h.includes("licensetype")); | |
| const amtIdx = headers.findIndex(h => h.endsWith("amount") && !h.includes("renewal")); | |
| const renIdx = headers.findIndex(h => h.includes("renewalamount") && !h.includes("local")); | |
| const localCurIdx = headers.findIndex(h => h.includes("localcurrency") && !h.includes("renewal")); | |
| const localAmtIdx = headers.findIndex(h => h.includes("localcurrencyamount") && !h.includes("renewal")); | |
| const localRenIdx = headers.findIndex(h => h.includes("localcurrencyrenewalamount")); | |
| const canChangeIdx = headers.findIndex(h => h.includes("canchangeprices")); | |
| const records = {}; | |
| for (let r = 1; r < values.length; r++) { | |
| const row = values[r]; | |
| const edition = editionIdx >= 0 ? row[editionIdx] : ""; | |
| if (!edition) continue; | |
| records[edition] = { | |
| userCount: userIdx >= 0 ? row[userIdx] : "", | |
| licenseType: licenseIdx >= 0 ? row[licenseIdx] : "", | |
| amount: amtIdx >= 0 ? row[amtIdx] : "", | |
| renewal: renIdx >= 0 ? row[renIdx] : "", | |
| localCurrency: localCurIdx >= 0 ? row[localCurIdx] : "", | |
| localAmt: localAmtIdx >= 0 ? row[localAmtIdx] : "", | |
| localRenewal: localRenIdx >= 0 ? row[localRenIdx] : "", | |
| canChange: canChangeIdx >= 0 ? row[canChangeIdx] : "" | |
| }; | |
| } | |
| return records; | |
| } | |
| function sanitizeSheetName(name) { | |
| return name.replace(/[:\\/?*\[\]]/g, '_').substring(0, 99) || 'addon'; | |
| } | |
| function autoResizeAll(sheet) { | |
| const lastCol = sheet.getLastColumn(); | |
| for (let c = 1; c <= lastCol; c++) { | |
| sheet.autoResizeColumn(c); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment