diff options
-rw-r--r-- | polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js | 68 | ||||
-rw-r--r-- | polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html | 50 |
2 files changed, 80 insertions, 38 deletions
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js index fd764405b3..e0854f0844 100644 --- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js +++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js @@ -135,6 +135,42 @@ const ANONYMIZED_REVISION_BASE_URL = ANONYMIZED_CHANGE_BASE_URL + '/revisions/*'; + /** + * Wrapper around Map for caching server responses. Site-based so that + * changes to CANONICAL_PATH will result in a different cache going into + * effect. + */ + class SiteBasedCache { + constructor() { + // Container of per-canonical-path caches. + this._data = new Map(); + } + + // Returns the cache for the current canonical path. + _cache() { + if (!this._data.has(window.CANONICAL_PATH)) { + this._data.set(window.CANONICAL_PATH, new Map()); + } + return this._data.get(window.CANONICAL_PATH); + } + + has(key) { + return this._cache().has(key); + } + + get(key) { + return this._cache().get(key); + } + + set(key, value) { + this._cache().set(key, value); + } + + delete(key) { + this._cache().delete(key); + } + } + Polymer({ is: 'gr-rest-api-interface', @@ -171,7 +207,7 @@ properties: { _cache: { type: Object, - value: {}, // Intentional to share the object across instances. + value: new SiteBasedCache(), // Shared across instances. }, _credentialCheck: { type: Object, @@ -268,7 +304,7 @@ } return res; }).catch(err => { - const isLoggedIn = !!this._cache['/accounts/self/detail']; + const isLoggedIn = !!this._cache.get('/accounts/self/detail'); if (isLoggedIn && err && err.message === FAILED_TO_FETCH_ERROR) { this.checkCredentials(); return; @@ -784,7 +820,7 @@ */ saveDiffPreferences(prefs, opt_errFn) { // Invalidate the cache. - this._cache['/accounts/self/preferences.diff'] = undefined; + this._cache.delete('/accounts/self/preferences.diff'); return this._send({ method: 'PUT', url: '/accounts/self/preferences.diff', @@ -800,7 +836,7 @@ */ saveEditPreferences(prefs, opt_errFn) { // Invalidate the cache. - this._cache['/accounts/self/preferences.edit'] = undefined; + this._cache.delete('/accounts/self/preferences.edit'); return this._send({ method: 'PUT', url: '/accounts/self/preferences.edit', @@ -816,7 +852,7 @@ reportUrlAsIs: true, errFn: resp => { if (!resp || resp.status === 403) { - this._cache['/accounts/self/detail'] = null; + this._cache.delete('/accounts/self/detail'); } }, }); @@ -828,7 +864,7 @@ reportUrlAsIs: true, errFn: resp => { if (!resp || resp.status === 403) { - this._cache['/accounts/self/avatar.change.url'] = null; + this._cache.delete('/accounts/self/avatar.change.url'); } }, }); @@ -910,7 +946,7 @@ return this._send(req).then(() => { // If result of getAccountEmails is in cache, update it in the cache // so we don't have to invalidate it. - const cachedEmails = this._cache['/accounts/self/emails']; + const cachedEmails = this._cache.get('/accounts/self/emails'); if (cachedEmails) { const emails = cachedEmails.map(entry => { if (entry.email === email) { @@ -919,7 +955,7 @@ return {email}; } }); - this._cache['/accounts/self/emails'] = emails; + this._cache.set('/accounts/self/emails', emails); } }); }, @@ -930,11 +966,11 @@ _updateCachedAccount(obj) { // If result of getAccount is in cache, update it in the cache // so we don't have to invalidate it. - const cachedAccount = this._cache['/accounts/self/detail']; + const cachedAccount = this._cache.get('/accounts/self/detail'); if (cachedAccount) { // Replace object in cache with new object to force UI updates. - this._cache['/accounts/self/detail'] = - Object.assign({}, cachedAccount, obj); + this._cache.set('/accounts/self/detail', + Object.assign({}, cachedAccount, obj)); } }, @@ -1064,14 +1100,14 @@ if (!res) { return; } if (res.status === 403) { this.fire('auth-error'); - this._cache['/accounts/self/detail'] = null; + this._cache.delete('/accounts/self/detail'); } else if (res.ok) { return this.getResponseObject(res); } }).then(res => { this._credentialCheck.checking = false; if (res) { - this._cache['/accounts/self/detail'] = res; + this._cache.delete('/accounts/self/detail'); } return res; }).catch(err => { @@ -1154,13 +1190,13 @@ return this._sharedFetchPromises[req.url]; } // TODO(andybons): Periodic cache invalidation. - if (this._cache[req.url] !== undefined) { - return Promise.resolve(this._cache[req.url]); + if (this._cache.has(req.url)) { + return Promise.resolve(this._cache.get(req.url)); } this._sharedFetchPromises[req.url] = this._fetchJSON(req) .then(response => { if (response !== undefined) { - this._cache[req.url] = response; + this._cache.set(req.url, response); } this._sharedFetchPromises[req.url] = undefined; return response; diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html index d9656e4758..8161c35163 100644 --- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html +++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html @@ -38,11 +38,15 @@ limitations under the License. suite('gr-rest-api-interface tests', () => { let element; let sandbox; + let ctr = 0; setup(() => { + // Modify CANONICAL_PATH to effectively reset cache. + ctr += 1; + window.CANONICAL_PATH = `test${ctr}`; + sandbox = sinon.sandbox.create(); element = fixture('basic'); - element._cache = {}; element._projectLookup = {}; const testJSON = ')]}\'\n{"hello": "bonjour"}'; sandbox.stub(window, 'fetch').returns(Promise.resolve({ @@ -85,7 +89,7 @@ limitations under the License. test('cached promise', done => { const promise = Promise.reject('foo'); - element._cache['/foo'] = promise; + element._cache.set('/foo', promise); element._fetchSharedCacheURL({url: '/foo'}).catch(p => { assert.equal(p, 'foo'); done(); @@ -98,19 +102,20 @@ limitations under the License. gr: 'guten tag', noval: null, }); - assert.equal(url, '/path/?sp=hola&gr=guten%20tag&noval'); + assert.equal(url, + window.CANONICAL_PATH + '/path/?sp=hola&gr=guten%20tag&noval'); url = element._urlWithParams('/path/', { sp: 'hola', en: ['hey', 'hi'], }); - assert.equal(url, '/path/?sp=hola&en=hey&en=hi'); + assert.equal(url, window.CANONICAL_PATH + '/path/?sp=hola&en=hey&en=hi'); // Order must be maintained with array params. url = element._urlWithParams('/path/', { l: ['c', 'b', 'a'], }); - assert.equal(url, '/path/?l=c&l=b&l=a'); + assert.equal(url, window.CANONICAL_PATH + '/path/?l=c&l=b&l=a'); }); test('request callbacks can be canceled', done => { @@ -441,7 +446,7 @@ limitations under the License. Promise.reject({message: 'Failed to fetch'})); window.fetch.onSecondCall().returns(Promise.resolve(fakeAuthResponse)); // Emulate logged in. - element._cache['/accounts/self/detail'] = {}; + element._cache.set('/accounts/self/detail', {}); const serverErrorStub = sandbox.stub(); element.addEventListener('server-error', serverErrorStub); const authErrorStub = sandbox.stub(); @@ -450,7 +455,7 @@ limitations under the License. flush(() => { assert.isTrue(authErrorStub.called); assert.isFalse(serverErrorStub.called); - assert.isNull(element._cache['/accounts/self/detail']); + assert.isFalse(element._cache.has('/accounts/self/detail')); done(); }); }); @@ -471,7 +476,7 @@ limitations under the License. ]; window.fetch.restore(); sandbox.stub(window, 'fetch', url => { - if (url === '/accounts/self/detail') { + if (url === window.CANONICAL_PATH + '/accounts/self/detail') { return Promise.resolve(responses.shift()); } }); @@ -487,7 +492,7 @@ limitations under the License. test('checkCredentials promise rejection', () => { window.fetch.restore(); - element._cache['/accounts/self/detail'] = true; + element._cache.set('/accounts/self/detail', true); sandbox.spy(element, 'checkCredentials'); sandbox.stub(window, 'fetch', url => { return Promise.reject({message: 'Failed to fetch'}); @@ -515,10 +520,10 @@ limitations under the License. test('saveDiffPreferences invalidates cache line', () => { const cacheKey = '/accounts/self/preferences.diff'; sandbox.stub(element, '_send'); - element._cache[cacheKey] = {tab_size: 4}; + element._cache.set(cacheKey, {tab_size: 4}); element.saveDiffPreferences({tab_size: 8}); assert.isTrue(element._send.called); - assert.notOk(element._cache[cacheKey]); + assert.isFalse(element._cache.has(cacheKey)); }); test('getAccount when resp is null does not add anything to the cache', @@ -529,11 +534,11 @@ limitations under the License. element.getAccount().then(() => { assert.isTrue(element._fetchSharedCacheURL.called); - assert.isNull(element._cache[cacheKey]); + assert.isFalse(element._cache.has(cacheKey)); done(); }); - element._cache[cacheKey] = 'fake cache'; + element._cache.set(cacheKey, 'fake cache'); stub.lastCall.args[0].errFn(); }); @@ -545,10 +550,10 @@ limitations under the License. element.getAccount().then(() => { assert.isTrue(element._fetchSharedCacheURL.called); - assert.isNull(element._cache[cacheKey]); + assert.isFalse(element._cache.has(cacheKey)); done(); }); - element._cache[cacheKey] = 'fake cache'; + element._cache.set(cacheKey, 'fake cache'); stub.lastCall.args[0].errFn({status: 403}); }); @@ -559,10 +564,10 @@ limitations under the License. element.getAccount().then(response => { assert.isTrue(element._fetchSharedCacheURL.called); - assert.equal(element._cache[cacheKey], 'fake cache'); + assert.equal(element._cache.get(cacheKey), 'fake cache'); done(); }); - element._cache[cacheKey] = 'fake cache'; + element._cache.set(cacheKey, 'fake cache'); stub.lastCall.args[0].errFn({}); }); @@ -728,7 +733,7 @@ limitations under the License. test('setAccountStatus', () => { sandbox.stub(element, '_send').returns(Promise.resolve('OOO')); - element._cache['/accounts/self/detail'] = {}; + element._cache.set('/accounts/self/detail', {}); return element.setAccountStatus('OOO').then(() => { assert.isTrue(element._send.calledOnce); assert.equal(element._send.lastCall.args[0].method, 'PUT'); @@ -736,7 +741,7 @@ limitations under the License. '/accounts/self/status'); assert.deepEqual(element._send.lastCall.args[0].body, {status: 'OOO'}); - assert.deepEqual(element._cache['/accounts/self/detail'], + assert.deepEqual(element._cache.get('/accounts/self/detail'), {status: 'OOO'}); }); }); @@ -830,7 +835,7 @@ limitations under the License. Promise.resolve([change_num, file_name, file_contents])); sandbox.stub(element, 'getResponseObject') .returns(Promise.resolve([change_num, file_name, file_contents])); - element._cache['/changes/' + change_num + '/edit/' + file_name] = {}; + element._cache.set('/changes/' + change_num + '/edit/' + file_name, {}); return element.saveChangeEdit(change_num, file_name, file_contents) .then(() => { assert.isTrue(element._send.calledOnce); @@ -849,7 +854,7 @@ limitations under the License. Promise.resolve([change_num, message])); sandbox.stub(element, 'getResponseObject') .returns(Promise.resolve([change_num, message])); - element._cache['/changes/' + change_num + '/message'] = {}; + element._cache.set('/changes/' + change_num + '/message', {}); return element.putChangeCommitMessage(change_num, message).then(() => { assert.isTrue(element._send.calledOnce); assert.equal(element._send.lastCall.args[0].method, 'PUT'); @@ -1031,7 +1036,8 @@ limitations under the License. const changeNum = 4321; element._projectLookup[changeNum] = 'test'; const params = {foo: 'bar'}; - const expectedUrl = '/changes/test~4321/detail?foo=bar'; + const expectedUrl = + window.CANONICAL_PATH + '/changes/test~4321/detail?foo=bar'; sandbox.stub(element._etags, 'getOptions'); sandbox.stub(element._etags, 'collect'); return element._getChangeDetail(changeNum, params).then(() => { |