Skip to content

Commit

Permalink
feat(translation): introduce cache busting logic (#4240)
Browse files Browse the repository at this point in the history
### Related Ticket(s)
#3959

### Description

Added a new `timestamp` property within the response json to compare with the current time. If the timestamp is older than two hours, a new response will be fetched with a new, current timestamp. 

Also implemented tests for the new methods.

### Changelog

**New**

- `timestamp` property added to the response json for cache busting purposes

<!-- React and Web Component deploy previews are enabled by default. -->
<!-- To enable additional available deploy previews, apply the following -->
<!-- labels for the corresponding package: -->
<!-- *** "package: services": Services -->
<!-- *** "package: utilities": Utilities -->
<!-- *** "package: styles": Carbon Expressive -->
<!-- *** "RTL": React / Web Components (RTL) -->
<!-- *** "feature flag": React / Web Components (experimental) -->
  • Loading branch information
IgnacioBecerra authored Oct 27, 2020
1 parent 194756b commit 3aa6800
Show file tree
Hide file tree
Showing 3 changed files with 1,599 additions and 15 deletions.
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
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

0 comments on commit 3aa6800

Please sign in to comment.