-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
feat(bootstrap): Cache projects in IndexedDB #89139
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
Changes from all commits
0cbd4f3
91906ae
88b18d0
af50e41
829cb29
2d3f9ef
7893bed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'; | ||
import {persistQueryClient} from '@tanstack/react-query-persist-client'; | ||
import {del as removeItem, get as getItem, set as setItem} from 'idb-keyval'; | ||
|
||
import {SENTRY_RELEASE_VERSION} from 'sentry/constants'; | ||
import {DEFAULT_QUERY_CLIENT_CONFIG, QueryClient} from 'sentry/utils/queryClient'; | ||
|
||
/** | ||
* Named it appQueryClient because we already have a queryClient in sentry/utils/queryClient | ||
* sentry/utils/queryClient is a small wrapper around react-query's functionality for our API. | ||
* | ||
* appQueryClient below is the app's react-query cache and should not be imported directly. | ||
* Instead, use `const queryClient = useQueryClient()`. | ||
* @link https://tanstack.com/query/v5/docs/reference/QueryClient | ||
*/ | ||
export const appQueryClient = new QueryClient(DEFAULT_QUERY_CLIENT_CONFIG); | ||
const cacheKey = 'sentry-react-query-cache'; | ||
|
||
const localStoragePersister = createAsyncStoragePersister({ | ||
// We're using indexedDB as our storage provider because projects cache can be large | ||
storage: {getItem, setItem, removeItem}, | ||
// Reduce the frequency of writes to indexedDB | ||
throttleTime: 10_000, | ||
// The cache is stored entirely on one key | ||
key: cacheKey, | ||
}); | ||
|
||
const isProjectsCacheEnabled = | ||
window.indexedDB && | ||
(window.__initialData?.features as unknown as string[])?.includes( | ||
'organizations:cache-projects-ui' | ||
); | ||
|
||
/** | ||
* Attach the persister to the query client | ||
* @link https://tanstack.com/query/latest/docs/framework/react/plugins/persistQueryClient | ||
*/ | ||
if (isProjectsCacheEnabled) { | ||
persistQueryClient({ | ||
queryClient: appQueryClient, | ||
persister: localStoragePersister, | ||
/** | ||
* Clear cache on release version change | ||
* Locally this does nothing, if you need to clear cache locally you can clear indexdb | ||
*/ | ||
buster: SENTRY_RELEASE_VERSION ?? 'local', | ||
dehydrateOptions: { | ||
// Persist a subset of queries to local storage | ||
shouldDehydrateQuery(query) { | ||
// This could be extended later to persist other queries | ||
return ( | ||
// Query is not pending or failed | ||
query.state.status === 'success' && | ||
!query.isStale() && | ||
Array.isArray(query.queryKey) && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this check is unnecessary - the |
||
// Currently only bootstrap-projects is persisted | ||
query.queryKey[0] === 'bootstrap-projects' | ||
); | ||
}, | ||
}, | ||
}); | ||
} | ||
|
||
export function restoreQueryCache() { | ||
if (isProjectsCacheEnabled) { | ||
localStoragePersister.restoreClient(); | ||
} | ||
} | ||
|
||
export async function clearQueryCache() { | ||
if (isProjectsCacheEnabled) { | ||
// Mark queries as stale so they won't be recached | ||
appQueryClient.invalidateQueries({queryKey: ['bootstrap-projects']}); | ||
Comment on lines
+72
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this not only marks them as stale, but also refetches them if they are still active! to avoid this, we need:
|
||
await removeItem(cacheKey); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,21 +3,16 @@ import {createBrowserRouter, RouterProvider} from 'react-router-dom'; | |
import {wrapCreateBrowserRouterV6} from '@sentry/react'; | ||
import {ReactQueryDevtools} from '@tanstack/react-query-devtools'; | ||
|
||
import {appQueryClient} from 'sentry/appQueryClient'; | ||
import {OnboardingContextProvider} from 'sentry/components/onboarding/onboardingContext'; | ||
import {ThemeAndStyleProvider} from 'sentry/components/themeAndStyleProvider'; | ||
import {USE_REACT_QUERY_DEVTOOL} from 'sentry/constants'; | ||
import {routes} from 'sentry/routes'; | ||
import {DANGEROUS_SET_REACT_ROUTER_6_HISTORY} from 'sentry/utils/browserHistory'; | ||
import { | ||
DEFAULT_QUERY_CLIENT_CONFIG, | ||
QueryClient, | ||
QueryClientProvider, | ||
} from 'sentry/utils/queryClient'; | ||
import {QueryClientProvider} from 'sentry/utils/queryClient'; | ||
|
||
import {buildReactRouter6Routes} from './utils/reactRouter6Compat/router'; | ||
|
||
const queryClient = new QueryClient(DEFAULT_QUERY_CLIENT_CONFIG); | ||
|
||
function buildRouter() { | ||
const sentryCreateBrowserRouter = wrapCreateBrowserRouterV6(createBrowserRouter); | ||
const router = sentryCreateBrowserRouter(buildReactRouter6Routes(routes())); | ||
|
@@ -30,7 +25,7 @@ function Main() { | |
const [router] = useState(buildRouter); | ||
|
||
return ( | ||
<QueryClientProvider client={queryClient}> | ||
<QueryClientProvider client={appQueryClient}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we really want to use the |
||
<ThemeAndStyleProvider> | ||
<OnboardingContextProvider> | ||
<RouterProvider router={router} /> | ||
|
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.
Can we have this comment explain the difference to try and help avoid people using this one if they don't need it?