Compare commits
4 Commits
17e0c1b707
...
a5f9b1542c
| Author | SHA1 | Date | |
|---|---|---|---|
| a5f9b1542c | |||
| 688536d0ac | |||
| 6d75973835 | |||
| 62514fa20e |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
**/node_modules/**
|
**/node_modules/**
|
||||||
dist/**
|
dist/**
|
||||||
desktop.ini
|
desktop.ini
|
||||||
|
.continue/**
|
||||||
@ -43,37 +43,24 @@ export function columnOnEditHandler(e: GoogleAppsScript.Events.SheetsOnEdit) {
|
|||||||
for (let row = e.range.getRow(); row <= e.range.getLastRow(); row++) {
|
for (let row = e.range.getRow(); row <= e.range.getLastRow(); row++) {
|
||||||
console.log("row: " + row)
|
console.log("row: " + row)
|
||||||
let updateString = "updating " + header + " on row " + row
|
let updateString = "updating " + header + " on row " + row
|
||||||
switch (header) {
|
let shopifyUpdateColumns = [
|
||||||
case "shopify_status":
|
"shopify_status",
|
||||||
|
"title",
|
||||||
|
"product_type",
|
||||||
|
"tags",
|
||||||
|
"description",
|
||||||
|
"base_price",
|
||||||
|
"original_price",
|
||||||
|
"weight_grams",
|
||||||
|
"product_height_cm",
|
||||||
|
"product_width_cm",
|
||||||
|
"product_depth_cm"
|
||||||
|
]
|
||||||
|
if (shopifyUpdateColumns.includes(header)) {
|
||||||
|
// Accumulate changes for 30s before updating
|
||||||
toastAndLog(updateString)
|
toastAndLog(updateString)
|
||||||
updateProductToShopify(row)
|
updateProductToShopify(row)
|
||||||
break
|
break
|
||||||
case "title":
|
|
||||||
toastAndLog(updateString)
|
|
||||||
updateProductToShopify(row)
|
|
||||||
break
|
|
||||||
case "product_type":
|
|
||||||
toastAndLog(updateString)
|
|
||||||
updateProductToShopify(row)
|
|
||||||
break
|
|
||||||
case "tags":
|
|
||||||
toastAndLog(updateString)
|
|
||||||
updateProductToShopify(row)
|
|
||||||
break
|
|
||||||
case "description":
|
|
||||||
toastAndLog(updateString)
|
|
||||||
updateProductToShopify(row)
|
|
||||||
break
|
|
||||||
case "price":
|
|
||||||
toastAndLog(updateString)
|
|
||||||
updateProductToShopify(row)
|
|
||||||
break
|
|
||||||
case "compare_at_price":
|
|
||||||
toastAndLog(updateString)
|
|
||||||
updateProductToShopify(row)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
toastAndLog("completed " + updateString)
|
toastAndLog("completed " + updateString)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
formatGqlForJSON,
|
formatGqlForJSON,
|
||||||
} from "./shopifyApi"
|
} from "./shopifyApi"
|
||||||
import * as shopify from 'shopify-admin-api-typings'
|
import * as shopify from 'shopify-admin-api-typings'
|
||||||
import { getCellRangeByColumnName, getRowByColumnValue } from "./sheetUtils"
|
import { getCellRangeByColumnName, getRowByColumnValue, vlookupByColumns } from "./sheetUtils"
|
||||||
import { Config } from "./config"
|
import { Config } from "./config"
|
||||||
|
|
||||||
|
|
||||||
@ -114,10 +114,12 @@ export class Product {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShopifyCategory(): string {
|
||||||
|
return vlookupByColumns("values", "product_type", this.product_type, "shopify_category")
|
||||||
|
}
|
||||||
|
|
||||||
ToShopifyProductSet() {
|
ToShopifyProductSet() {
|
||||||
let sps = new ShopifyProductSetInput()
|
let sps = new ShopifyProductSetInput()
|
||||||
//TODO: map category IDs
|
|
||||||
//sps.category = this.category
|
|
||||||
if (this.shopify_id != "") {
|
if (this.shopify_id != "") {
|
||||||
sps.id = this.shopify_id
|
sps.id = this.shopify_id
|
||||||
}
|
}
|
||||||
@ -125,6 +127,10 @@ export class Product {
|
|||||||
sps.status = this.shopify_status
|
sps.status = this.shopify_status
|
||||||
}
|
}
|
||||||
sps.productType = this.product_type
|
sps.productType = this.product_type
|
||||||
|
let category = this.ShopifyCategory()
|
||||||
|
if (category !== "") {
|
||||||
|
sps.category = this.ShopifyCategory()
|
||||||
|
}
|
||||||
sps.tags = this.tags
|
sps.tags = this.tags
|
||||||
sps.title = this.title
|
sps.title = this.title
|
||||||
sps.descriptionHtml = this.description
|
sps.descriptionHtml = this.description
|
||||||
|
|||||||
@ -14,6 +14,10 @@ import { createMissingPhotoFolders } from "./createMissingPhotoFolders"
|
|||||||
import { reinstallTriggers } from "./triggers"
|
import { reinstallTriggers } from "./triggers"
|
||||||
import { newSkuHandler } from "./newSku"
|
import { newSkuHandler } from "./newSku"
|
||||||
import { columnOnEditHandler } from "./OnEditHandler"
|
import { columnOnEditHandler } from "./OnEditHandler"
|
||||||
|
import {
|
||||||
|
onEditQueue,
|
||||||
|
processBatchedEdits
|
||||||
|
} from "./onEditQueue"
|
||||||
import { fillProductFromTemplate } from "./fillProductFromTemplate"
|
import { fillProductFromTemplate } from "./fillProductFromTemplate"
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
@ -25,6 +29,8 @@ import { fillProductFromTemplate } from "./fillProductFromTemplate"
|
|||||||
;(global as any).matchProductToShopifyOnEditHandler = matchProductToShopifyOnEditHandler
|
;(global as any).matchProductToShopifyOnEditHandler = matchProductToShopifyOnEditHandler
|
||||||
;(global as any).updateShopifyProductHandler = updateShopifyProductHandler
|
;(global as any).updateShopifyProductHandler = updateShopifyProductHandler
|
||||||
;(global as any).columnOnEditHandler = columnOnEditHandler
|
;(global as any).columnOnEditHandler = columnOnEditHandler
|
||||||
|
;(global as any).onEditQueue = onEditQueue
|
||||||
|
;(global as any).processBatchedEdits = processBatchedEdits
|
||||||
;(global as any).reauthorizeScript = reauthorizeScript
|
;(global as any).reauthorizeScript = reauthorizeScript
|
||||||
;(global as any).reinstallTriggers = reinstallTriggers
|
;(global as any).reinstallTriggers = reinstallTriggers
|
||||||
;(global as any).newSkuHandler = newSkuHandler
|
;(global as any).newSkuHandler = newSkuHandler
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
getColumnValuesByName,
|
getColumnValuesByName,
|
||||||
} from "./sheetUtils"
|
} from "./sheetUtils"
|
||||||
|
|
||||||
|
const LOCK_TIMEOUT_MS = 1000 * 10
|
||||||
|
|
||||||
export function newSkuHandler(e: GoogleAppsScript.Events.SheetsOnEdit) {
|
export function newSkuHandler(e: GoogleAppsScript.Events.SheetsOnEdit) {
|
||||||
var sheet = SpreadsheetApp.getActive().getActiveSheet()
|
var sheet = SpreadsheetApp.getActive().getActiveSheet()
|
||||||
@ -20,7 +21,14 @@ export function newSkuHandler(e: GoogleAppsScript.Events.SheetsOnEdit) {
|
|||||||
console.log("new ID was not requested, returning")
|
console.log("new ID was not requested, returning")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Acquire a user lock to prevent multiple onEdit calls from clashing
|
||||||
|
const documentLock = LockService.getDocumentLock()
|
||||||
|
try {
|
||||||
|
documentLock.waitLock(LOCK_TIMEOUT_MS)
|
||||||
newSku(row)
|
newSku(row)
|
||||||
|
} finally {
|
||||||
|
documentLock.releaseLock()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function newSku(row: number) {
|
export function newSku(row: number) {
|
||||||
|
|||||||
109
src/onEditQueue.ts
Normal file
109
src/onEditQueue.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { getCellValueByColumnName } from "./sheetUtils"
|
||||||
|
import { Product } from "./Product"
|
||||||
|
import { Shop } from "./shopifyApi"
|
||||||
|
|
||||||
|
// --- Constants ---
|
||||||
|
const BATCH_INTERVAL_MS = 30 * 1000 // 30 seconds
|
||||||
|
const LOCK_TIMEOUT_MS = 10 * 1000 // 10 seconds for lock acquisition
|
||||||
|
const CACHE_KEY_EDITS = "pendingEdits"
|
||||||
|
const CACHE_KEY_LAST_EDIT_TIME = "lastEditTime"
|
||||||
|
const SCRIPT_PROPERTY_TRIGGER_SCHEDULED = "batchTriggerScheduled"
|
||||||
|
|
||||||
|
export function onEditQueue(e) {
|
||||||
|
const sheet = e.source.getActiveSheet()
|
||||||
|
if (sheet.getName() !== "product_inventory") {
|
||||||
|
console.log("skipping edit on sheet " + sheet.getName())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const range = e.range
|
||||||
|
const row = range.getRow()
|
||||||
|
const sku = getCellValueByColumnName(sheet, "sku", row)
|
||||||
|
if (!sku) {
|
||||||
|
console.log("No SKU found for row " + row)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Acquire a user lock to prevent multiple onEdit calls from clashing
|
||||||
|
const documentLock = LockService.getDocumentLock()
|
||||||
|
try {
|
||||||
|
documentLock.waitLock(LOCK_TIMEOUT_MS)
|
||||||
|
|
||||||
|
const scriptProperties = PropertiesService.getScriptProperties() // Shared cache for all users
|
||||||
|
|
||||||
|
// 1. Accumulate edits in cache
|
||||||
|
let pendingEdits = []
|
||||||
|
try {
|
||||||
|
pendingEdits = JSON.parse(
|
||||||
|
scriptProperties.getProperty(CACHE_KEY_EDITS) || "[]"
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Cache corruption: " + e.message)
|
||||||
|
scriptProperties.setProperty(CACHE_KEY_EDITS, "[]")
|
||||||
|
}
|
||||||
|
const existingIndex = pendingEdits.findIndex((item) => item.sku === sku)
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
console.log("New edit on queued SKU '"+sku+"', resetting timer...")
|
||||||
|
pendingEdits[existingIndex].timestamp = Date.now()
|
||||||
|
} else {
|
||||||
|
console.log("New SKU '"+sku+"' added to queue.")
|
||||||
|
pendingEdits.push({ sku, timestamp: Date.now() })
|
||||||
|
}
|
||||||
|
scriptProperties.setProperty(CACHE_KEY_EDITS, JSON.stringify(pendingEdits))
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
"Error in onEdit (lock acquisition or cache operation): " + error.message
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
documentLock.releaseLock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processBatchedEdits() {
|
||||||
|
const scriptLock = LockService.getScriptLock() // Use script lock for the processing function
|
||||||
|
try {
|
||||||
|
scriptLock.waitLock(LOCK_TIMEOUT_MS)
|
||||||
|
|
||||||
|
const scriptProperties = PropertiesService.getScriptProperties()
|
||||||
|
|
||||||
|
let pendingEdits = []
|
||||||
|
try {
|
||||||
|
const pendingEditsStr = scriptProperties.getProperty(CACHE_KEY_EDITS)
|
||||||
|
pendingEdits = pendingEditsStr ? JSON.parse(pendingEditsStr) : []
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Cache corruption: " + e.message)
|
||||||
|
scriptProperties.setProperty(CACHE_KEY_EDITS, "[]")
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Total SKUs in queue: ${pendingEdits.length}`)
|
||||||
|
const now = Date.now()
|
||||||
|
const toProcess = pendingEdits.filter(
|
||||||
|
(edit) => now - edit.timestamp > BATCH_INTERVAL_MS
|
||||||
|
)
|
||||||
|
if (toProcess.length > 0) {
|
||||||
|
let shop = new Shop()
|
||||||
|
console.log(`Processing ${toProcess.length} SKUs...`)
|
||||||
|
toProcess.forEach((edit) => {
|
||||||
|
console.log(
|
||||||
|
`Processing SKU ${edit.sku}, Timestamp: ${new Date(edit.timestamp)}`
|
||||||
|
)
|
||||||
|
let p = new Product(edit.sku)
|
||||||
|
p.UpdateShopifyProduct(shop)
|
||||||
|
})
|
||||||
|
|
||||||
|
pendingEdits = pendingEdits.filter(
|
||||||
|
edit => !toProcess.some(p => p.sku === edit.sku)
|
||||||
|
);
|
||||||
|
scriptProperties.setProperty(CACHE_KEY_EDITS, JSON.stringify(pendingEdits));
|
||||||
|
|
||||||
|
console.log(`Processed ${toProcess.length} edits.`)
|
||||||
|
} else {
|
||||||
|
console.log("No pending edits to process.")
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
"Error in processBatchedEdits (lock acquisition or processing): " +
|
||||||
|
error.message
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
scriptLock.releaseLock()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,5 +10,11 @@ export function reinstallTriggers() {
|
|||||||
.forSpreadsheet(ss)
|
.forSpreadsheet(ss)
|
||||||
.onEdit()
|
.onEdit()
|
||||||
.create()
|
.create()
|
||||||
ScriptApp.newTrigger("columnOnEditHandler").forSpreadsheet(ss).onEdit().create()
|
// ScriptApp.newTrigger("columnOnEditHandler").forSpreadsheet(ss).onEdit().create()
|
||||||
|
// ScriptApp.newTrigger("onEditQueue").forSpreadsheet(ss).onEdit().create()
|
||||||
|
ScriptApp.newTrigger("onEditQueue").forSpreadsheet(ss).onEdit().create()
|
||||||
|
ScriptApp.newTrigger("processBatchedEdits")
|
||||||
|
.timeBased()
|
||||||
|
.everyMinutes(1)
|
||||||
|
.create()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user