// Scoutworks → badges to order. https://developers.scoutworks.app/recipes/badges-to-order
const BASE = 'https://api-demo.scoutworks.app/api/v1';
function badgesToOrder() {
const records = readAll('/badges?status=completed');
// One row for each badge (and stage), with everyone who has earned it.
const byBadge = {};
records.forEach(function (r) {
const key = r.badge.id + '|' + (r.level || '');
if (!byBadge[key]) {
byBadge[key] = { name: r.badge.name, level: r.level, category: r.badge.category, who: [] };
}
byBadge[key].who.push(r.member.firstName + ' ' + r.member.lastName);
});
const rows = Object.keys(byBadge)
.map(function (key) { return byBadge[key]; })
.sort(function (a, b) { return a.name.localeCompare(b.name) || (a.level || 0) - (b.level || 0); })
.map(function (b) {
return [
safe(b.name),
b.level ? 'Stage ' + b.level : '',
CATEGORIES[b.category] || b.category,
b.who.length,
safe(b.who.sort().join(', ')),
];
});
writeTab('Badges to order', [['Badge', 'Stage', 'Kind', 'How many', 'For']].concat(rows));
SpreadsheetApp.getActive().toast(records.length + ' badges to order.');
}
const CATEGORIES = {
core_badges: 'Core badge',
challenge_awards: 'Challenge award',
activity_badges: 'Activity badge',
staged_activity_badges: 'Staged activity badge',
};
// Replaces a tab's contents with these rows.
function writeTab(name, rows) {
const book = SpreadsheetApp.getActive();
const sheet = book.getSheetByName(name) || book.insertSheet(name);
sheet.clearContents();
sheet.getRange(1, 1, rows.length, rows[0].length).setValues(rows);
sheet.setFrozenRows(1);
}
// Every item in a list, following nextCursor to the last page.
function readAll(path) {
const items = [];
let cursor = null;
do {
let url = BASE + path + (path.indexOf('?') === -1 ? '?' : '&') + 'limit=200';
if (cursor) url += '&cursor=' + encodeURIComponent(cursor);
const body = fetchScoutworks(url);
items.push.apply(items, body.data);
cursor = body.nextCursor;
} while (cursor);
return items;
}
// Calls the API. Waits and tries again if the rate limit is reached.
function fetchScoutworks(url) {
const apiKey = PropertiesService.getScriptProperties().getProperty('SCOUTWORKS_API_KEY');
if (!apiKey) throw new Error('Add SCOUTWORKS_API_KEY in Project Settings → Script Properties.');
for (let attempt = 0; attempt < 3; attempt++) {
const res = UrlFetchApp.fetch(url, {
headers: { Authorization: 'Bearer ' + apiKey },
muteHttpExceptions: true,
});
const body = JSON.parse(res.getContentText());
if (res.getResponseCode() === 200) return body;
if (res.getResponseCode() === 429) {
Utilities.sleep((body.retryAfter || 30) * 1000);
continue;
}
throw new Error(body.error.code + ': ' + body.error.message);
}
throw new Error('Scoutworks is still busy. Try again in a minute.');
}
// Stops a name that starts with = + - or @ from running as a formula.
function safe(value) {
const text = String(value);
return /^\s*[=+\-@]/.test(text) ? "'" + text : text;
}