Skip to content

Commit

Permalink
feat: store skus and last previewed in files storage
Browse files Browse the repository at this point in the history
  • Loading branch information
duynguyen committed Jan 24, 2025
1 parent e442dfb commit 203aa0c
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 38 deletions.
13 changes: 7 additions & 6 deletions actions/check-product-changes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}

Expand Down
33 changes: 21 additions & 12 deletions actions/check-product-changes/poller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -32,7 +40,7 @@ async function loadState(storeCode, stateLib) {
// <timestamp>,<sku1>,<timestamp>,<sku2>,<timestamp>,<sku3>,...,<timestamp>
// 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)),
Expand All @@ -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);
}

/**
Expand All @@ -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<Object>} The result of the polling action.
*/
function checkParams(params) {
Expand Down Expand Up @@ -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' });
Expand Down Expand Up @@ -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 };

Expand Down Expand Up @@ -218,7 +227,7 @@ async function poll(params, stateLib) {
counts.failed++;
}
}
await saveState(state, stateLib);
await saveState(state, filesLib);
}

timings.sample('publishedPaths');
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -294,4 +303,4 @@ return {
};
}

module.exports = { poll, loadState, saveState };
module.exports = { poll, loadState, saveState, getFileLocation };
21 changes: 21 additions & 0 deletions test/__mocks__/files.js
Original file line number Diff line number Diff line change
@@ -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;
40 changes: 20 additions & 20 deletions test/check-product-changes.test.js
Original file line number Diff line number Diff line change
@@ -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,
{
Expand All @@ -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,
{
Expand All @@ -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');
});
});

0 comments on commit 203aa0c

Please sign in to comment.