From 237f57cf361707c687e8076526333d3b4496515e Mon Sep 17 00:00:00 2001 From: Ben Miller Date: Sun, 19 Oct 2025 23:11:22 -0600 Subject: [PATCH] drastically reduce time to create photo folders --- src/Product.ts | 93 +++++++++++++++++++------------- src/createMissingPhotoFolders.ts | 19 +++++-- src/newSku.ts | 11 +++- src/onEditQueue.ts | 10 ++++ src/sheetUtils.ts | 10 ++++ 5 files changed, 99 insertions(+), 44 deletions(-) diff --git a/src/Product.ts b/src/Product.ts index 0ccdece..c8caf87 100644 --- a/src/Product.ts +++ b/src/Product.ts @@ -330,44 +330,8 @@ export class Product { } CreatePhotoFolder() { - console.log("CreatePhotoFolder()"); - const config = new Config(); - if (!config.productPhotosFolderId) { - console.log( - "productPhotoFolderId not set in config. Skipping folder creation." - ); - return; - } - - const productInventorySheet = - SpreadsheetApp.getActiveSpreadsheet().getSheetByName("product_inventory"); - const row = getRowByColumnValue("product_inventory", "sku", this.sku); - const photosCell = getCellRangeByColumnName( - productInventorySheet, - "photos", - row - ); - const folderUrl = photosCell.getValue(); - - if (folderUrl && folderUrl.includes("drive.google.com")) { - console.log(`Photo folder already exists: ${folderUrl}`); - return; - } - - const parentFolder = DriveApp.getFolderById(config.productPhotosFolderId); - const folderName = this.sku - - // DriveApp.createFolder is idempotent if a folder with the same name exists in the parent - const newFolder = parentFolder.createFolder(folderName); - console.log(`Created or found photo folder: '${folderName}'`); - let url = newFolder.getUrl(); - console.log(`Folder URL: ${url}`); - - let linkValue = SpreadsheetApp.newRichTextValue() - .setText(folderName) - .setLinkUrl(url) - .build() - photosCell.setRichTextValue(linkValue) + console.log("Product.CreatePhotoFolder()"); + createPhotoFolderForSku(new(Config), this.sku); } PublishToShopifyOnlineStore(shop: Shop) { @@ -414,3 +378,56 @@ export class Product { // TODO: shopify_status } } + +export function createPhotoFolderForSku(config: Config, sku: string) { + console.log(`createPhotoFolderForSku('${sku}')`) + if (!config.productPhotosFolderId) { + console.log( + "productPhotoFolderId not set in config. Skipping folder creation." + ) + return + } + + const productInventorySheet = + SpreadsheetApp.getActiveSpreadsheet().getSheetByName("product_inventory") + const row = getRowByColumnValue("product_inventory", "sku", sku) + if (!row) { + console.log(`SKU '${sku}' not found in sheet. Cannot create folder.`) + return + } + const photosCell = getCellRangeByColumnName( + productInventorySheet, + "photos", + row + ) + const folderUrl = photosCell.getRichTextValue().getLinkUrl() + console.log(`Folder URL from cell: ${folderUrl}`) + + if (folderUrl && folderUrl.includes("drive.google.com")) { + console.log(`Photo folder already exists: ${folderUrl}`) + return + } else { + console.log(`Creating photo folder for SKU: ${sku}`) + } + + const parentFolder = DriveApp.getFolderById(config.productPhotosFolderId) + const folderName = sku + let newFolder: GoogleAppsScript.Drive.Folder + + const existingFolders = parentFolder.getFoldersByName(folderName) + if (existingFolders.hasNext()) { + newFolder = existingFolders.next() + console.log(`Found existing photo folder: '${folderName}'`) + } else { + newFolder = parentFolder.createFolder(folderName) + console.log(`Created new photo folder: '${folderName}'`) + } + let url = newFolder.getUrl() + console.log(`Folder URL: ${url}`) + + let linkValue = SpreadsheetApp.newRichTextValue() + .setText(folderName) + .setLinkUrl(url) + .build() + photosCell.setRichTextValue(linkValue) +} diff --git a/src/createMissingPhotoFolders.ts b/src/createMissingPhotoFolders.ts index bcf1c14..0a1fc62 100644 --- a/src/createMissingPhotoFolders.ts +++ b/src/createMissingPhotoFolders.ts @@ -1,5 +1,6 @@ -import { Product } from "./Product" -import { getColumnValuesByName, toastAndLog } from "./sheetUtils" +import { createPhotoFolderForSku } from "./Product" +import { getColumnRichTextByName, getColumnValuesByName, toastAndLog } from "./sheetUtils" +import { Config } from "./config" export function createMissingPhotoFolders() { const ss = SpreadsheetApp.getActive() @@ -10,14 +11,22 @@ export function createMissingPhotoFolders() { } let skus = getColumnValuesByName(s, "sku") + let photos = getColumnRichTextByName(s, "photos") + let config = new Config() - for (let i = 0; i < skus.length; i++) { + // Process rows backward, as that is where the missing folders are most likely to occur + for (let i = skus.length - 1; i >= 0; i--) { const sku = String(skus[i][0]) if (!sku) { continue } - const product = new Product(sku) - product.CreatePhotoFolder() + let folderUrl = photos[i][0].getLinkUrl() + if (folderUrl && folderUrl.includes("drive.google.com")) { + console.log(`Photo folder already exists for SKU: ${sku}`) + continue + } + + createPhotoFolderForSku(config, sku) } toastAndLog("Finished creating missing photo folders.") } diff --git a/src/newSku.ts b/src/newSku.ts index 2171b36..a8a44eb 100644 --- a/src/newSku.ts +++ b/src/newSku.ts @@ -1,3 +1,5 @@ +import { createPhotoFolderForSku } from "./Product" +import { Config } from "./config" import { getColumnByName, getCellRangeByColumnName, @@ -24,8 +26,13 @@ export function newSkuHandler(e: GoogleAppsScript.Events.SheetsOnEdit) { // Acquire a user lock to prevent multiple onEdit calls from clashing const documentLock = LockService.getDocumentLock() try { + const config = new (Config); documentLock.waitLock(LOCK_TIMEOUT_MS) - newSku(row) + const sku = newSku(row) + console.log("new sku: " + sku) + createPhotoFolderForSku(config, String(sku)) + } catch (error) { + console.log("Error in newSkuHandler: " + error.message) } finally { documentLock.releaseLock() } @@ -70,4 +77,6 @@ export function newSku(row: number) { let newId = maxId + 1 console.log("newId: " + newId) idCell.setValue(newId) + + return `${skuPrefixCellValue}-${newId.toString().padStart(4, "0")}` } diff --git a/src/onEditQueue.ts b/src/onEditQueue.ts index f1f705b..3e06730 100644 --- a/src/onEditQueue.ts +++ b/src/onEditQueue.ts @@ -22,6 +22,11 @@ export function onEditQueue(e) { console.log("No SKU found for row " + row) return } + // Make sure SKU conforms to expected patterns + if (sku.match(`\\?`) || sku.match(`n$`)) { + console.log("SKU is a placeholder ('?' or 'n...'), skipping batching.") + return + } // Acquire a user lock to prevent multiple onEdit calls from clashing const documentLock = LockService.getDocumentLock() try { @@ -85,6 +90,11 @@ export function processBatchedEdits() { console.log( `Processing SKU ${edit.sku}, Timestamp: ${new Date(edit.timestamp)}` ) + // Make sure SKU conforms to expected patterns + if (!edit.sku.match(/^\w+-\d{4}$/)) { + console.log(`SKU ${edit.sku} is not valid, skipping processing.`) + return + } let p = new Product(edit.sku) p.UpdateShopifyProduct(shop) }) diff --git a/src/sheetUtils.ts b/src/sheetUtils.ts index 069a72a..225d88e 100644 --- a/src/sheetUtils.ts +++ b/src/sheetUtils.ts @@ -50,6 +50,16 @@ export function getColumnValuesByName( } } +export function getColumnRichTextByName( + sheet: GoogleAppsScript.Spreadsheet.Sheet, + columnName: string +) { + let column = getColumnRangeByName(sheet, columnName) + if (column != null) { + return column.getRichTextValues() + } +} + export function vlookupByColumns( sheetName: string, searchColumn: string,