194 lines
6.2 KiB
JavaScript
194 lines
6.2 KiB
JavaScript
/***********************
|
|
* PROPFIG (edit these)
|
|
***********************/
|
|
const PROPFIG = {
|
|
spreadsheetId: "17II0V8EsTL-9nHq2HgqhHcay-t-hhOaY5wNEzXHEsAM",
|
|
sheetId: 249063374, // the tab's sheetId (not gid string; usually same number)
|
|
courseCol: 6, // Column C = 3
|
|
headerRow: 1,
|
|
startDataRow: 2,
|
|
northpassBaseUrl: "https://api.northpass.com",
|
|
cacheTtlSeconds: 6 * 60 * 60 // 6 hours
|
|
};
|
|
/**
|
|
* One-time setup:
|
|
* Run setNorthpassApiKey_() to store your API key securely in Script Properties.
|
|
*/
|
|
function setNorthpassApiKey() {
|
|
const apiKey = "N5AQIwjIeeYDBSU9vTST37Nvg";
|
|
PropertiesService.getScriptProperties().setProperty("NORTHPASS_API_KEY", apiKey);
|
|
}
|
|
|
|
/**
|
|
* Main entrypoint: reads course names from col C and writes core_competencies
|
|
* into the first empty column (based on header row).
|
|
*/
|
|
function writeCoreCompetenciesFromCourses() {
|
|
const ss = SpreadsheetApp.openById(PROPFIG.spreadsheetId);
|
|
const sheet = getSheetById_(ss, PROPFIG.sheetId);
|
|
|
|
const apiKey = PropertiesService.getScriptProperties().getProperty("NORTHPASS_API_KEY");
|
|
if (!apiKey) throw new Error("Missing NORTHPASS_API_KEY. Run setNorthpassApiKey_() first.");
|
|
|
|
// Determine output column: first empty cell in header row; if none, append.
|
|
const lastCol = Math.max(sheet.getLastColumn(), 1);
|
|
const headerValues = sheet.getRange(PROPFIG.headerRow, 1, 1, lastCol).getValues()[0];
|
|
let outCol = headerValues.findIndex(v => String(v || "").trim() === "") + 1;
|
|
if (outCol === 0) outCol = lastCol + 1;
|
|
|
|
// Write a header label (optional but useful)
|
|
sheet.getRange(PROPFIG.headerRow, outCol).setValue("core_competencies");
|
|
|
|
// Read course column values
|
|
const lastRow = sheet.getLastRow();
|
|
if (lastRow < PROPFIG.startDataRow) return;
|
|
|
|
const numRows = lastRow - PROPFIG.startDataRow + 1;
|
|
const courseValues = sheet.getRange(PROPFIG.startDataRow, PROPFIG.courseCol, numRows, 1).getValues();
|
|
|
|
// Prep caches: in-run memo + script cache
|
|
const runMemo = {}; // { courseNameLower: { id, coreCompetenciesString } }
|
|
const cache = CacheService.getScriptCache();
|
|
|
|
// Build output column values aligned to rows
|
|
const output = new Array(numRows).fill([""]);
|
|
|
|
for (let i = 0; i < numRows; i++) {
|
|
const rawCourse = String(courseValues[i][0] || "").trim();
|
|
if (!rawCourse) continue;
|
|
|
|
const key = rawCourse.toLowerCase();
|
|
|
|
// 1) in-run memo
|
|
if (runMemo[key]) {
|
|
output[i] = [runMemo[key].coreCompetenciesString];
|
|
continue;
|
|
}
|
|
|
|
// 2) CacheService (persists across runs for TTL)
|
|
const cached = cache.get(cacheKey_(key));
|
|
if (cached) {
|
|
const parsed = JSON.parse(cached);
|
|
runMemo[key] = parsed;
|
|
output[i] = [parsed.coreCompetenciesString];
|
|
continue;
|
|
}
|
|
|
|
// 3) Fetch from Northpass
|
|
const courseId = fetchCourseIdByExactName_(apiKey, rawCourse);
|
|
const coreCompetencies = fetchCoreCompetenciesByCourseId_(apiKey, courseId);
|
|
|
|
const coreStr = normalizeToSingleString_(coreCompetencies);
|
|
|
|
const record = { id: courseId, coreCompetenciesString: coreStr };
|
|
runMemo[key] = record;
|
|
cache.put(cacheKey_(key), JSON.stringify(record), PROPFIG.cacheTtlSeconds);
|
|
|
|
output[i] = [coreStr];
|
|
}
|
|
|
|
// Write results in one batch
|
|
sheet.getRange(PROPFIG.startDataRow, outCol, numRows, 1).setValues(output);
|
|
}
|
|
|
|
/***********************
|
|
* Northpass API helpers
|
|
***********************/
|
|
|
|
/**
|
|
* Gets course id by exact name using:
|
|
* /v2/courses?filter[name][eq]=<courseName>
|
|
* If no match is returned, falls back to:
|
|
* /v2/courses?filter[name]=<courseName>
|
|
*
|
|
* Filtering is documented by Northpass. :contentReference[oaicite:3]{index=3}
|
|
*/
|
|
function fetchCourseIdByExactName_(apiKey, courseName) {
|
|
const encoded = encodeURIComponent(courseName);
|
|
|
|
const urlsToTry = [
|
|
`${PROPFIG.northpassBaseUrl}/v2/courses?filter[name][eq]=${encoded}`,
|
|
`${PROPFIG.northpassBaseUrl}/v2/courses?filter[name]=${encoded}` // fallback
|
|
];
|
|
|
|
for (const url of urlsToTry) {
|
|
const json = northpassGetJson_(apiKey, url);
|
|
|
|
// Typical Northpass JSON: { data: [ { id, type, attributes... }, ... ] } :contentReference[oaicite:4]{index=4}
|
|
const data = json && json.data;
|
|
if (Array.isArray(data) && data.length > 0 && data[0].id) {
|
|
return data[0].id;
|
|
}
|
|
}
|
|
|
|
throw new Error(`No course found for name "${courseName}" (tried eq + fallback).`);
|
|
}
|
|
|
|
/**
|
|
* Fetches course properties:
|
|
* /v2/properties/courses/{id}
|
|
* Returns: data.attributes.customized_properties.core_competencies :contentReference[oaicite:5]{index=5}
|
|
*/
|
|
function fetchCoreCompetenciesByCourseId_(apiKey, courseId) {
|
|
const url = `${PROPFIG.northpassBaseUrl}/v2/properties/courses/${encodeURIComponent(courseId)}`;
|
|
const json = northpassGetJson_(apiKey, url);
|
|
|
|
const core =
|
|
json &&
|
|
json.data &&
|
|
json.data.attributes &&
|
|
json.data.attributes.customized_properties &&
|
|
json.data.attributes.customized_properties.core_competencies;
|
|
|
|
// Could be undefined/null/string/array/object depending on how it's stored
|
|
return core ?? "";
|
|
}
|
|
|
|
/**
|
|
* Makes an authenticated GET request to Northpass using X-Api-Key header.
|
|
* Northpass API key auth is documented. :contentReference[oaicite:6]{index=6}
|
|
*/
|
|
function northpassGetJson_(apiKey, url) {
|
|
const resp = UrlFetchApp.fetch(url, {
|
|
method: "get",
|
|
muteHttpExceptions: true,
|
|
headers: {
|
|
"Accept": "application/json",
|
|
"X-Api-Key": apiKey
|
|
}
|
|
});
|
|
|
|
const code = resp.getResponseCode();
|
|
const text = resp.getContentText();
|
|
|
|
if (code < 200 || code >= 300) {
|
|
throw new Error(`Northpass API error ${code} for ${url}\n${text}`);
|
|
}
|
|
return JSON.parse(text);
|
|
}
|
|
|
|
/***********************
|
|
* Sheet helpers
|
|
***********************/
|
|
function getSheetById_(spreadsheet, sheetId) {
|
|
const sheets = spreadsheet.getSheets();
|
|
for (const s of sheets) {
|
|
if (s.getSheetId() === sheetId) return s;
|
|
}
|
|
throw new Error(`No sheet found with sheetId=${sheetId}`);
|
|
}
|
|
|
|
/***********************
|
|
* Formatting + caching
|
|
***********************/
|
|
function normalizeToSingleString_(value) {
|
|
if (value === null || value === undefined) return "";
|
|
if (Array.isArray(value)) return value.map(v => String(v).trim()).filter(Boolean).join("; ");
|
|
if (typeof value === "object") return JSON.stringify(value);
|
|
return String(value);
|
|
}
|
|
|
|
function cacheKey_(courseNameLower) {
|
|
return `np_course_${courseNameLower}`;
|
|
}
|