-
Notifications
You must be signed in to change notification settings - Fork 159
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
feat(translation): introduce cache busting logic #4240
Conversation
Deploy preview created for package Built with commit: f8128e76a2a42533c1edfb2959e24b50056ba639 |
Deploy preview created for package Built with commit: f8128e76a2a42533c1edfb2959e24b50056ba639 |
Interesting change @IgnacioBecerra - Seems that the code adds timestamp for each cache data item so you wanted to manage cache TTL per-item? Note that |
That's the idea, add a timestamp to the cached item, and before using it again, check if it's been longer than two hours. If so, it expires and we retrieve the newest response + update the timestamp. I'll keep that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great to see this is ready for review @IgnacioBecerra!
); | ||
|
||
// reinitializing import | ||
const TranslationAPI = (await import('../Translation')).default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I won't block proceeding this PR with my comment here, but I'd rather see resetting things done via mocks. The behavior of dynamic import tends to be dependent on build toolchain and we may have high chance to get an undefined behavior if it's used as a mean for "resetting".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, I'll be applying the same logic for Locale API, so I'll use mocks there and also add it here. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any update here...? Thanks!
const timeDiff = response.timestamp - previousSession.timestamp, | ||
_twoHours = 60 * 60 * 2000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you explain what makes the cache elapsed time here greater than two hours?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! As an example, the cached time in previousSession is 1337
(a very old timestamp), while response
would have the current time as 1603206344
. By doing 1603206344 - 1337
we receive 160319297
, which is greater than 7200000
(two hours). This is to ensure that the cache busting code runs for this test, as it is required for the time difference to be greater than two hours.
If the previous session timestamp was much closer in time to the current one, the difference would be less than two hours, and the response
received would remain unchanged from the previous session.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your answer @IgnacioBecerra! I was asking where in the code or in test that makes timeDiff
greater than two hours, given the test says "timeDiff
should be greater than two hours". What code sets response.timestamp
and what code sets previousSession.timestamp
? Did you make the timestamp deterministic by e.g. mocks (rather than relying on system time that is non-deterministic)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see what you mean! I set previousSession
in line 9 using import oldSession from './data/timestamp_response.json';
.
timestamp_response.json
uses the same data in ./data/response.json
, with the only difference is that it includes timestamp: 1337
. This would be the only deterministic timestamp. On the other hand, response
always contains the current timestamp from the latest response received from TranslationAPI
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool thanks @IgnacioBecerra! Given your response, given we know the timestamps come from mock, why do we need to test the two timestamps between the two mocks? Or was it simply for ensuring we are picking up the right (fresh) data?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the latter, I wanted to make extra sure the logic was working. Since we tested the fresh data in sessionStorage
was the same as in the response
, I used the mock to compare times, and had to check if timeDiff
was greater than two hours.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, thank you for clarifying @IgnacioBecerra! I suggest more direct way for testing if we pick up the right data. One approach can be object ref. Another approach may be unique key/ID.
Deploy preview created for package Built with commit: f8128e76a2a42533c1edfb2959e24b50056ba639 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some good improvements @IgnacioBecerra!
const mockDate = new Date('2019'); | ||
const _Date = Date; | ||
global.Date = jest.fn(() => mockDate); | ||
global.Date.now = _Date.now; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried using that and many other variations before using this approach, but unfortunately this one was the only one that actually had any effect on response
. Not sure what is causing it though, the implementations don't differ much from another.
it('timestamp should not change if within two hours', async () => { | ||
// using recent cached session | ||
sessionStorageMock.setItem( | ||
'dds-translation-us-en', | ||
JSON.stringify(responseSuccess) | ||
); | ||
|
||
const previousSession = JSON.parse( | ||
sessionStorageMock.getItem('dds-translation-us-en') | ||
); | ||
|
||
// reinitializing import | ||
const TranslationAPI = (await import('../Translation')).default; | ||
const response = await TranslationAPI.getTranslation({ | ||
lc: 'en', | ||
cc: 'us', | ||
}); | ||
|
||
// timestamp should remain unchanged | ||
expect(response.timestamp).toEqual(previousSession.timestamp); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have some details on what this case tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use responseSuccess
that was just recently refreshed as the cached data. Since it's very recent (just ran), then running the API again shouldn't change the cached data again -- it simply retrieves it. Here we just check if the timestamp remains the same from the previous session.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, given we don't (and won't) change the timestamp of existing cache, probably we wanted to simply see if the unique ID of response
equals to one of previousSession
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your response to my earlier question @IgnacioBecerra!
packages/services/src/services/Translation/__tests__/Translation.test.js
Outdated
Show resolved
Hide resolved
packages/services/src/services/Translation/__tests__/Translation.test.js
Outdated
Show resolved
Hide resolved
it('timestamp should not change if within two hours', async () => { | ||
// using recent cached session | ||
sessionStorageMock.setItem( | ||
'dds-translation-us-en', | ||
JSON.stringify(responseSuccess) | ||
); | ||
|
||
const previousSession = JSON.parse( | ||
sessionStorageMock.getItem('dds-translation-us-en') | ||
); | ||
|
||
// reinitializing import | ||
const TranslationAPI = (await import('../Translation')).default; | ||
const response = await TranslationAPI.getTranslation({ | ||
lc: 'en', | ||
cc: 'us', | ||
}); | ||
|
||
// timestamp should remain unchanged | ||
expect(response.timestamp).toEqual(previousSession.timestamp); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, given we don't (and won't) change the timestamp of existing cache, probably we wanted to simply see if the unique ID of response
equals to one of previousSession
?
@asudoh The unique ID for Since it's the only thing that differs between these two responses, I checked the ID change, deleted the IDs from their jsons, and compared the rest in the latest commit. Also applied your suggestions, thanks! |
@IgnacioBecerra did something happen to this branch? There's now 94 changed files. |
@jeffchew I merged master into this branch to resolved the merge conflict, so the changed files should be from all the recent commits from master. I hadn't noticed the 94 files, but I think there's probably a better way to do this |
It shouldn't be this many files, just the ones you changed so I think the merge might not have happened correctly. |
f840e8d
to
ccb566c
Compare
@jeffchew Resolved the issue! Back to only three files changed. |
Could you fix this? Given we set the unique ID solely for testing purpose and we use it for the cache record identify, the ID being changed won't make sense. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the updates @IgnacioBecerra!
); | ||
|
||
// reinitializing import | ||
const TranslationAPI = (await import('../Translation')).default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any update here...? Thanks!
// timestamps should have at least a two hour difference | ||
const timeDiff = response.timestamp - previousSession.timestamp, | ||
_twoHours = 60 * 60 * 2000; | ||
expect(timeDiff).toBeGreaterThan(_twoHours); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per discussion, these lines are no longer needed:
// timestamps should have at least a two hour difference | |
const timeDiff = response.timestamp - previousSession.timestamp, | |
_twoHours = 60 * 60 * 2000; | |
expect(timeDiff).toBeGreaterThan(_twoHours); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the updates @IgnacioBecerra!
}, | ||
setItem(key, value) { | ||
value = JSON.parse(value); | ||
value['id'] = 'TRANSLATION_FRESH'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ID is better set from test cases:
value['id'] = 'TRANSLATION_FRESH'; |
const previousSession = JSON.parse( | ||
sessionStorageMock.getItem('dds-translation-us-en') | ||
); | ||
|
||
expect(previousSession.id).toEqual('TRANSLATION_FRESH'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given we are not testing sessionStorageMock
here, I don't think this is needed:
const previousSession = JSON.parse( | |
sessionStorageMock.getItem('dds-translation-us-en') | |
); | |
expect(previousSession.id).toEqual('TRANSLATION_FRESH'); |
// using very old cached session | ||
sessionStorageMock.setItem( | ||
'dds-translation-us-en', | ||
JSON.stringify(oldSession) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSON.stringify(oldSession) | |
JSON.stringify(Object.assign(oldSession, { id: uniqueId })) |
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 |
There was a problem hiding this comment.
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()
:
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 |
sessionStorageMock.getItem('dds-translation-us-en') | ||
); | ||
|
||
response['id'] = newSession.id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that we are testing if the data comes from the cache. If so, the cache should have its ID assigned already and thus we don't set one to response
:
response['id'] = newSession.id; |
// newest response and storage data should match | ||
expect(response).toEqual(newSession); | ||
|
||
// should contain timestamp | ||
expect(response).toHaveProperty('timestamp'); | ||
|
||
// should equal mock timestamp | ||
expect(response.timestamp).toEqual(mockDate); | ||
expect(response.id).toEqual('TRANSLATION_FRESH'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that we are testing if the data comes from the cache. If so, ID check is the only assertion we need:
// newest response and storage data should match | |
expect(response).toEqual(newSession); | |
// should contain timestamp | |
expect(response).toHaveProperty('timestamp'); | |
// should equal mock timestamp | |
expect(response.timestamp).toEqual(mockDate); | |
expect(response.id).toEqual('TRANSLATION_FRESH'); | |
expect(response.id).toEqual(uniqueId); |
it('timestamp should not change if within two hours', async () => { | ||
// using recent cached session | ||
sessionStorageMock.setItem( | ||
'dds-translation-us-en', | ||
JSON.stringify(responseSuccess) | ||
); | ||
|
||
const previousSession = JSON.parse( | ||
sessionStorageMock.getItem('dds-translation-us-en') | ||
); | ||
|
||
const response = await TranslationAPI.getTranslation({ | ||
lc: 'en', | ||
cc: 'us', | ||
}); | ||
|
||
// body and timestamp should remain unchanged | ||
expect(response).toEqual(previousSession); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given we don't have any code to change the timestamp of cache, this test case seems not necessary:
it('timestamp should not change if within two hours', async () => { | |
// using recent cached session | |
sessionStorageMock.setItem( | |
'dds-translation-us-en', | |
JSON.stringify(responseSuccess) | |
); | |
const previousSession = JSON.parse( | |
sessionStorageMock.getItem('dds-translation-us-en') | |
); | |
const response = await TranslationAPI.getTranslation({ | |
lc: 'en', | |
cc: 'us', | |
}); | |
// body and timestamp should remain unchanged | |
expect(response).toEqual(previousSession); | |
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍 - Thanks @IgnacioBecerra for all the updates! 🎉
### Related Ticket(s) #3959 ### Description As done in [TranslationAPI](#4240), this PR adds 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) -->
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