Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(translation): introduce cache busting logic #4240

Merged
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions packages/services/src/services/Translation/Translation.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ const _sessionTranslationKey = 'dds-translation';
*/
const _requestsTranslation = {};

/**
* Two hours in milliseconds to compare session timestamp.
*
* @type {number}
* @private
*/
const _twoHours = 60 * 60 * 2000;

/**
* Translation API class with methods for fetching i18n data for ibm.com
*/
Expand Down Expand Up @@ -106,14 +114,9 @@ class TranslationAPI {
* @param {Function} reject rejects the promise
*/
static fetchTranslation(lang, country, resolve, reject) {
const sessionTranslation =
typeof sessionStorage === 'undefined'
? undefined
: JSON.parse(
sessionStorage.getItem(
`${_sessionTranslationKey}-${country}-${lang}`
)
);
const itemKey = `${_sessionTranslationKey}-${country}-${lang}`;

const sessionTranslation = this.getSessionCache(itemKey);

if (sessionTranslation) {
resolve(sessionTranslation);
Expand All @@ -131,6 +134,7 @@ class TranslationAPI {
})
.then(response => this.transformData(response.data))
.then(data => {
data['timestamp'] = Date.now();
sessionStorage.setItem(
`${_sessionTranslationKey}-${country}-${lang}`,
JSON.stringify(data)
Expand Down Expand Up @@ -167,6 +171,32 @@ class TranslationAPI {
data.footerMenu.push(data.socialFollow);
return data;
}

/**
* Retrieves session cache and checks if cache needs to be refreshed
*
* @param {string} key session storage key
* @returns {object} session storage object
*/
static getSessionCache(key) {
const session =
typeof sessionStorage === 'undefined'
? undefined
: JSON.parse(sessionStorage.getItem(key));

if (!session || !session.timestamp) {
return;
}

const currentTime = Date.now(),
timeDiff = currentTime - session.timestamp;

if (timeDiff > _twoHours) {
sessionStorage.removeItem(key);
return;
}
return session;
}
}

export default TranslationAPI;
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
*/

import mockAxios from 'axios';
import oldSession from './data/timestamp_response.json';
import responseSuccess from './data/response.json';
import root from 'window-or-global';
import TranslationAPI from '../Translation';

jest.mock('../../Locale', () => ({
LocaleAPI: {
Expand All @@ -23,6 +25,29 @@ jest.mock('axios', () => {
};
});

const sessionStorageMock = (() => {
let cache = {};

return {
getItem(key) {
return cache[key] || null;
},
setItem(key, value) {
cache[key] = value;
},
removeItem(key) {
delete cache[key];
},
clear() {
cache = {};
},
};
})();

Object.defineProperty(window, 'sessionStorage', {
value: sessionStorageMock,
});

describe('TranslationAPI', () => {
const { location } = root;

Expand All @@ -38,9 +63,6 @@ describe('TranslationAPI', () => {
href: 'https://www.loremipsum.com',
};

// reinitializing import
const TranslationAPI = (await import('../Translation')).default;

const response = await TranslationAPI.getTranslation({
lc: 'en',
cc: 'us',
Expand All @@ -54,9 +76,6 @@ describe('TranslationAPI', () => {
});

it('should fetch the i18n data', async () => {
// reinitializing import
const TranslationAPI = (await import('../Translation')).default;

// Expected endpoint called
const endpoint = `${process.env.TRANSLATION_HOST}/common/v18/js/data/jsononly`;
const fetchUrl = `${endpoint}/usen.json`;
Expand All @@ -67,7 +86,6 @@ describe('TranslationAPI', () => {
});

const elseResponse = await TranslationAPI.getTranslation({});

expect(elseResponse).toEqual(responseSuccess);

expect(mockAxios.get).toHaveBeenCalledWith(fetchUrl, {
Expand All @@ -79,4 +97,31 @@ describe('TranslationAPI', () => {

expect(response).toEqual(responseSuccess);
});

it('should return a json with a recent timestamp', async () => {
const mockDate = 1546300800000; // Epoch time of January 1, 2019 midnight UTC
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also please refer to how the ID is set via Object.assign():

Suggested change
const mockDate = 1546300800000; // Epoch time of January 1, 2019 midnight UTC
const uniqueId = `__translation_cache__${Math.random().toString(36).slice(2)}`;
const mockDate = 1546300800000; // Epoch time of January 1, 2019 midnight UTC

global.Date.now = jest.fn(() => mockDate);

// using very old cached session
sessionStorageMock.setItem(
'dds-translation-us-en',
JSON.stringify(Object.assign(oldSession, { CACHE: true }))
);

await TranslationAPI.getTranslation({
lc: 'en',
cc: 'us',
});

const newSession = JSON.parse(
sessionStorageMock.getItem('dds-translation-us-en')
);

// fresh data would lack this property
expect(newSession).not.toHaveProperty('CACHE');
});

afterEach(() => {
TranslationAPI.clearCache();
});
});
Loading