Sync redux metadata with react-query and vice versa

[DSF-429]: https://hasurahq.atlassian.net/browse/DSF-429?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9565
GitOrigin-RevId: 6d53d39ebd445a17186233aa056ebe4d957c386b
This commit is contained in:
Luca Restagno 2023-06-16 18:58:24 +02:00 committed by hasura-bot
parent d711392fb1
commit b309fc9211
8 changed files with 70 additions and 19 deletions

View File

@ -175,4 +175,6 @@ export {
removeLSItem,
} from './lib/utils/localStorage';
export { listenForStoreMetadataChanges } from './lib/store.utils';
export { default as App } from './lib/components/App/App';

View File

@ -9,6 +9,7 @@ import { stripTrailingSlash } from './components/Common/utils/urlUtils';
import { SERVER_CONSOLE_MODE } from './constants';
import { parseConsoleType, ConsoleType } from './utils/envUtils';
import { QueryClient } from 'react-query';
export type LuxFeature =
| 'DatadogIntegration'
@ -179,6 +180,7 @@ export type EnvVars = {
declare global {
interface Window extends GlobalWindowHeap {
__env: EnvVars;
reactQueryClient: QueryClient;
}
const CONSOLE_ASSET_VERSION: string;
}

View File

@ -4,6 +4,7 @@ import { useHttpClient } from '../Network';
import { useCallback } from 'react';
import { useQuery, useQueryClient } from 'react-query';
import { APIError } from '../../hooks/error';
import { useAppDispatch } from '../../storeHooks';
export const DEFAULT_STALE_TIME = 5 * 60000; // 5 minutes as default stale time
@ -39,11 +40,18 @@ export const useMetadata = <FinalResult = Metadata>(
) => {
const httpClient = useHttpClient();
const invalidateMetadata = useInvalidateMetadata();
const dispatch = useAppDispatch();
const queryReturn = useQuery<Metadata, APIError, FinalResult>({
queryKey: [METADATA_QUERY_KEY],
queryFn: async () => {
const result = await exportMetadata({ httpClient });
dispatch({
type: 'Metadata/EXPORT_METADATA_SUCCESS',
data: result,
});
return result;
},
staleTime: options.staleTime,

View File

@ -4,6 +4,12 @@ import { ReactQueryDevtools } from 'react-query/devtools';
export const reactQueryClient = new QueryClient();
/**
* This is needed by the redux store to trigger
* invalidate queries when the metadata is updated in the store.
*/
window.reactQueryClient = reactQueryClient;
export const ReactQueryProvider: React.FC = ({ children }) => (
<QueryClientProvider client={reactQueryClient}>
{children}

View File

@ -1,4 +1,4 @@
import { MetadataActions } from './actions';
import { ExportMetadataSuccess, MetadataActions } from './actions';
import {
HasuraMetadataV3,
CollectionName,
@ -99,30 +99,37 @@ const renameSourceAttributes = (sources: HasuraMetadataV3['sources']) =>
return { ...s, tables };
});
const updateMetadata = (
action: ExportMetadataSuccess,
state: MetadataState
) => {
const metadata =
'metadata' in action.data ? action.data.metadata : action.data;
return {
...state,
metadataObject: {
...metadata,
sources: renameSourceAttributes(metadata.sources),
},
resourceVersion:
'resource_version' in action.data ? action.data.resource_version : 1,
allowedQueries: setAllowedQueries(
metadata?.query_collections,
metadata?.allowlist
),
inheritedRoles: metadata?.inherited_roles,
loading: false,
error: null,
};
};
export const metadataReducer = (
state = defaultState,
action: MetadataActions
): MetadataState => {
switch (action.type) {
case 'Metadata/EXPORT_METADATA_SUCCESS':
const metadata =
'metadata' in action.data ? action.data.metadata : action.data;
return {
...state,
metadataObject: {
...metadata,
sources: renameSourceAttributes(metadata.sources),
},
resourceVersion:
'resource_version' in action.data ? action.data.resource_version : 1,
allowedQueries: setAllowedQueries(
metadata?.query_collections,
metadata?.allowlist
),
inheritedRoles: metadata?.inherited_roles,
loading: false,
error: null,
};
return updateMetadata(action, state);
case 'Metadata/EXPORT_METADATA_REQUEST':
return {
...state,

View File

@ -1,5 +1,6 @@
import { routerMiddleware } from 'react-router-redux';
import { browserHistory } from 'react-router';
import { listenForStoreMetadataChanges } from './store.utils';
// Since we only use it in dev, this warning doesn't make sense.
// eslint-disable-next-line import/no-extraneous-dependencies
@ -24,6 +25,8 @@ export const store = configureStore({
devTools: __DEVELOPMENT__,
});
listenForStoreMetadataChanges(store);
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

View File

@ -0,0 +1,20 @@
import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore';
let previousStore: any = null;
export const listenForStoreMetadataChanges = (store: ToolkitStore) => {
store.subscribe(() => {
if (!previousStore) {
previousStore = store.getState();
return;
}
const currentStore = store.getState();
if (
currentStore?.metadata?.resourceVersion >
previousStore?.metadata?.resourceVersion
) {
window.reactQueryClient.invalidateQueries('export_metadata');
}
previousStore = store.getState();
});
};

View File

@ -2,6 +2,7 @@ import { compose, createStore, applyMiddleware } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import { browserHistory } from 'react-router';
import thunk from 'redux-thunk';
import { listenForStoreMetadataChanges } from '@hasura/console-legacy-ce';
import reducer from './reducer';
@ -26,4 +27,6 @@ if (__DEVELOPMENT__) {
const store = _finalCreateStore(reducer);
listenForStoreMetadataChanges(store);
export default store;