From 203aa0c17e0e672dd73474dc31e1e67441cb98b7 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Fri, 24 Jan 2025 16:10:59 +0700 Subject: [PATCH] feat: store skus and last previewed in files storage --- actions/check-product-changes/index.js | 13 ++++---- actions/check-product-changes/poller.js | 33 ++++++++++++-------- test/__mocks__/files.js | 21 +++++++++++++ test/check-product-changes.test.js | 40 ++++++++++++------------- 4 files changed, 69 insertions(+), 38 deletions(-) create mode 100644 test/__mocks__/files.js diff --git a/actions/check-product-changes/index.js b/actions/check-product-changes/index.js index 72f6030..2b04fe6 100644 --- a/actions/check-product-changes/index.js +++ b/actions/check-product-changes/index.js @@ -10,22 +10,23 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const { stateLib } = require('@adobe/aio-lib-state'); +const { State, Files } = require('@adobe/aio-sdk'); const { poll } = require('./poller'); async function main(params) { - const state = await stateLib.init(); - const running = await state.get('running'); + const stateLib = await State.init(); + const filesLib = await Files.init(); + const running = await stateLib.get('running'); if (running?.value === 'true') { return { state: 'skipped' }; } try { - await state.put('running', 'true'); - return await poll(params, state); + await stateLib.put('running', 'true'); + return await poll(params, filesLib); } finally { - await state.put('running', 'false'); + await stateLib.put('running', 'false'); } } diff --git a/actions/check-product-changes/poller.js b/actions/check-product-changes/poller.js index 68c4d3a..a607c7e 100644 --- a/actions/check-product-changes/poller.js +++ b/actions/check-product-changes/poller.js @@ -17,11 +17,19 @@ const { GetAllSkusQuery, GetLastModifiedQuery } = require('../queries'); const { Core } = require('@adobe/aio-sdk'); const BATCH_SIZE = 50; +const FILE_PREFIX = 'check-product-changes'; +const FILE_EXT = 'txt'; -async function loadState(storeCode, stateLib) { +function getFileLocation(stateKey) { + return `${FILE_PREFIX}/${stateKey}.${FILE_EXT}`; +} + +async function loadState(storeCode, filesLib) { const stateKey = storeCode ? `${storeCode}` : 'default'; - const stateData = await stateLib.get(stateKey); - if (!stateData?.value) { + const fileLocation = getFileLocation(stateKey); + const buffer = await filesLib.read(fileLocation); + const stateData = buffer?.toString(); + if (!stateData) { return { storeCode, skusLastQueriedAt: new Date(0), @@ -32,7 +40,7 @@ async function loadState(storeCode, stateLib) { // ,,,,,,..., // the first timestamp is the last time the SKUs were fetched from Adobe Commerce // folloed by a pair of SKUs and timestamps which are the last preview times per SKU - const [catalogQueryTimestamp, ...skus] = stateData && stateData.value ? stateData.value.split(',') : [0]; + const [catalogQueryTimestamp, ...skus] = stateData.split(','); return { storeCode, skusLastQueriedAt: new Date(parseInt(catalogQueryTimestamp)), @@ -42,17 +50,18 @@ async function loadState(storeCode, stateLib) { }; } -async function saveState(state, stateLib) { +async function saveState(state, filesLib) { let { storeCode } = state; if (!storeCode) { storeCode = 'default'; } const stateKey = `${storeCode}`; + const fileLocation = getFileLocation(stateKey); const stateData = [ state.skusLastQueriedAt.getTime(), ...Object.entries(state.skus).flatMap(([sku, lastPreviewedAt]) => [sku, lastPreviewedAt.getTime()]), ].join(','); - await stateLib.put(stateKey, stateData); + await filesLib.write(fileLocation, stateData); } /** @@ -72,7 +81,7 @@ async function saveState(state, stateLib) { * @param {string} [params.storeUrl] - The store's base URL. * @param {string} [params.storeCodes] - Comma-separated list of store codes. * @param {string} [params.LOG_LEVEL] - The log level. - * @param {Object} stateLib - The state provider object. + * @param {Object} filesLib - The files provider object. * @returns {Promise} The result of the polling action. */ function checkParams(params) { @@ -107,7 +116,7 @@ function shouldProcessProduct(product) { return urlKey?.match(/^[a-zA-Z0-9-]+$/) && lastModifiedDate >= lastPreviewDate; } -async function poll(params, stateLib) { +async function poll(params, filesLib) { checkParams(params); const log = Core.Logger('main', { level: params.LOG_LEVEL || 'info' }); @@ -144,7 +153,7 @@ async function poll(params, stateLib) { const results = await Promise.all(storeCodes.map(async (storeCode) => { const timings = new Timings(); // load state - const state = await loadState(storeCode, stateLib); + const state = await loadState(storeCode, filesLib); timings.sample('loadedState'); const context = { ...sharedContext, storeCode }; @@ -218,7 +227,7 @@ async function poll(params, stateLib) { counts.failed++; } } - await saveState(state, stateLib); + await saveState(state, filesLib); } timings.sample('publishedPaths'); @@ -247,7 +256,7 @@ async function poll(params, stateLib) { await deleteBatch({ counts, batch, state, adminApi }); } // save state after deletes - await saveState(state, stateLib); + await saveState(state, filesLib); } } catch (e) { // in case the index doesn't yet exist or any other error @@ -294,4 +303,4 @@ return { }; } -module.exports = { poll, loadState, saveState }; \ No newline at end of file +module.exports = { poll, loadState, saveState, getFileLocation }; \ No newline at end of file diff --git a/test/__mocks__/files.js b/test/__mocks__/files.js new file mode 100644 index 0000000..f9ff52e --- /dev/null +++ b/test/__mocks__/files.js @@ -0,0 +1,21 @@ +class Files { + internalStorage = {}; + + constructor(storageLatency = 500) { + this.storageLatency = storageLatency; + } + + async read(fileLocation) { + return new Promise((resolve) => { + setTimeout(() => resolve(this.internalStorage[fileLocation]), this.storageLatency); + }); + } + + async write(fileLocation, fileData) { + return new Promise((resolve) => { + setTimeout(() => resolve(this.internalStorage[fileLocation] = fileData), this.storageLatency); + }); + } +} + +module.exports = Files; diff --git a/test/check-product-changes.test.js b/test/check-product-changes.test.js index d43ae77..7c7ae7a 100644 --- a/test/check-product-changes.test.js +++ b/test/check-product-changes.test.js @@ -1,11 +1,11 @@ const assert = require('node:assert/strict'); -const { loadState, saveState } = require('../actions/check-product-changes/poller.js'); -const State = require('./__mocks__/state.js'); +const { loadState, saveState, getFileLocation } = require('../actions/check-product-changes/poller.js'); +const Files = require('./__mocks__/files.js'); describe('Poller', () => { it('loadState returns default state', async () => { - const stateLib = new State(0); - const state = await loadState('uk', stateLib); + const filesLib = new Files(0); + const state = await loadState('uk', filesLib); assert.deepEqual( state, { @@ -17,9 +17,9 @@ describe('Poller', () => { }); it('loadState returns parsed state', async () => { - const stateLib = new State(0); - await stateLib.put('uk', '1,sku1,2,sku2,3,sku3,4'); - const state = await loadState('uk', stateLib); + const filesLib = new Files(0); + await filesLib.write(getFileLocation('uk'), '1,sku1,2,sku2,3,sku3,4'); + const state = await loadState('uk', filesLib); assert.deepEqual( state, { @@ -35,31 +35,31 @@ describe('Poller', () => { }); it('loadState after saveState', async () => { - const stateLib = new State(0); - await stateLib.put('uk', '1,sku1,2,sku2,3,sku3,4'); - const state = await loadState('uk', stateLib); + const filesLib = new Files(0); + await filesLib.write(getFileLocation('uk'), '1,sku1,2,sku2,3,sku3,4'); + const state = await loadState('uk', filesLib); state.skusLastQueriedAt = new Date(5); state.skus['sku1'] = new Date(5); state.skus['sku2'] = new Date(6); - await saveState(state, stateLib); + await saveState(state, filesLib); - const serializedState = await stateLib.get('uk'); - assert.equal(serializedState?.value, '5,sku1,5,sku2,6,sku3,4'); + const serializedState = await filesLib.read(getFileLocation('uk')); + assert.equal(serializedState, '5,sku1,5,sku2,6,sku3,4'); - const newState = await loadState('uk', stateLib); + const newState = await loadState('uk', filesLib); assert.deepEqual(newState, state); }); it('loadState after saveState with null storeCode', async () => { - const stateLib = new State(0); - await stateLib.put('default', '1,sku1,2,sku2,3,sku3,4'); - const state = await loadState(null, stateLib); + const filesLib = new Files(0); + await filesLib.write(getFileLocation('default'), '1,sku1,2,sku2,3,sku3,4'); + const state = await loadState(null, filesLib); state.skusLastQueriedAt = new Date(5); state.skus['sku1'] = new Date(5); state.skus['sku2'] = new Date(6); - await saveState(state, stateLib); + await saveState(state, filesLib); - const serializedState = await stateLib.get('default'); - assert.equal(serializedState?.value, '5,sku1,5,sku2,6,sku3,4'); + const serializedState = await filesLib.read(getFileLocation('default')); + assert.equal(serializedState, '5,sku1,5,sku2,6,sku3,4'); }); }); \ No newline at end of file