-
Notifications
You must be signed in to change notification settings - Fork 271
feat: add support for conditional requests #119
Conversation
Codecov Report
@@ Coverage Diff @@
## master #119 +/- ##
==========================================
- Coverage 100% 98.02% -1.98%
==========================================
Files 74 73 -1
Lines 914 912 -2
Branches 220 212 -8
==========================================
- Hits 914 894 -20
- Misses 0 17 +17
- Partials 0 1 +1
Continue to review full report at Codecov.
|
Codecov Report
@@ Coverage Diff @@
## master #119 +/- ##
=====================================
Coverage 100% 100%
=====================================
Files 74 77 +3
Lines 914 978 +64
Branches 220 234 +14
=====================================
+ Hits 914 978 +64
Continue to review full report at Codecov.
|
@williaster, @kristw any thoughts on the unit tests? I know we're aiming for 100% coverage. |
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.
Left a couple comments.
Re testing, this comment in the jest issue you linked to suggests that jsdom does have this now? would that work?
@@ -1,6 +1,10 @@ | |||
import 'whatwg-fetch'; | |||
import { CallApi } from '../types'; | |||
|
|||
const cacheAvailable = 'caches' in self; | |||
const NOT_MODIFIED = 304; |
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.
response numbers should be in a separate constants
file
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.
Thanks, will do!
@@ -1,6 +1,10 @@ | |||
import 'whatwg-fetch'; | |||
import { CallApi } from '../types'; | |||
|
|||
const cacheAvailable = 'caches' in self; |
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'd UPPER_CASE
this since it's used as a constant in this file
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.
+1
@@ -26,6 +30,41 @@ export default function callApi({ | |||
signal, | |||
}; | |||
|
|||
if (method === 'GET' && cacheAvailable) { | |||
return caches.open('superset').then(supersetCache => |
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 superset
key should also be a constant in a separate file, and maybe more unique, matching the package name seems safer (even though it sounds like Cache
s are scoped to by origin) e.g., @SUPERSET-UI/CONNECTION
?
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.
Good point.
} | ||
} | ||
|
||
return fetch(url, request); // eslint-disable-line compat/compat |
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.
since we have multiple occurrences of this now we should just move this to the top of the file /* eslint compat/compat: 'off' */
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.
+1
// `If-None-Match` header in a conditional request | ||
const etag = cachedResponse.headers.get('Etag'); | ||
if (etag) { | ||
request.headers = request.headers || {}; |
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.
could simplify to request.headers = { ...request.headers, ['If-None-Match']: etag }
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, I didn't know the spread operator worked with null
s. Will do.
@@ -23,6 +23,7 @@ a high-level it supports: | |||
- supports `GET` and `POST` requests (no `PUT` or `DELETE`) | |||
- timeouts | |||
- query aborts through the `AbortController` API | |||
- conditional `GET` requests using `If-None-Match` and `ETag` headers |
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'd maybe mention Cache
here? conditional sounds like something else to me.
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.
"Conditional request" is the actual name of a GET
request with a If-None-Match
header containing the ETag
of the resource: https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests#Conditional_headers. The fact that we're using the Cache API here is an implementation detail, we could use local storage, session storage or cookies.
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.
To expand on my comment: we're not simply caching, we're storing the resources and reusing them if and only if the hash hasn't changed.
@williaster I'll try to use one of those new packages to write the tests. |
@williaster I added unit tests and got the coverage up to 100%. |
Could you add another test case when |
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.
thanks for adding tests and getting them to one hundo 💯
@kristw and I had a couple more small comments but looks good overall.
"caches": true | ||
}, | ||
"setupFiles": [ | ||
"<rootDir>/test/setupTests.ts" |
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 think ideally we'd fix build-config
to resolve .ts
setup files but this is okay for now.
import 'whatwg-fetch'; | ||
import { CallApi } from '../types'; | ||
import { CACHE_KEY, NOT_MODIFIED, OK } from '../constants'; |
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.
sorry only nit with the status constants is that there's no context if you read them in isolation e.g., OK
= okay what? could we prefix with STATUS_*
or HTTP_*
?
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.
Fixed this.
@@ -26,6 +30,38 @@ export default function callApi({ | |||
signal, | |||
}; | |||
|
|||
if (method === 'GET' && CACHE_AVAILABLE) { |
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.
@kristw and I were discussing whether this should be opt-in or not. currently there's no way to opt-out, though I guess if if depends on the server/backend implementing Etag
s then maybe that's a non-issue.
any thoughts on this?
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 could add a new option to callApi
, to turn this on/off. In the initial tests I did with dashboards and ETag, some of the dashboards load in half the time, so I think we might want to default this to be enabled.
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 have merged and added this new issue for turning conditional request on/off.
#128
Ping @kristw @williaster |
Hey, I'm coming here to debug an issue I'm seeing where changes to the superset code aren't reflected in changed behaviour/UI when accessing superset, and wondered whether that might be because some sort of cache is enabled during development. Deleting entire folders of JS assets (the dist folder) has no impact on the functionality of superset (even when the HTTP cache is disabled) which leads me to believe there's a backend cache being used. Any guidance/direction appreciated. |
🏆 Enhancements
This PR adds support for conditional
GET
requests, since they're not supported by the Fetch API. This is how it works:GET
request is successful (status200 OK
) and it has anETag
header, a copy of the response is stored using the Cache API.ETag
is retrieved.If-None-Matches
with the storedETag
.304 Not Modified
, the cached response is used instead.200 OK
the previous cache is deleted and the new response is stored.This will be used with apache/superset#7032 to allow for conditional requests in Superset.
I started writing unit tests, but the Cache API is not supported neither in Jest (jestjs/jest#7365) nor in jsdom (jsdom/jsdom#2422). I did full end-to-end tests with Superset, verifying that resources are properly cached and retrieved.