Skip to content
Snippets Groups Projects
Unverified Commit 8e463220 authored by Oisin Coveney's avatar Oisin Coveney Committed by GitHub
Browse files

Convert EmbeddingContext to Sdk Store slice (#41585)

parent 5b9522cf
No related branches found
No related tags found
No related merge requests found
Showing
with 135 additions and 88 deletions
...@@ -5,8 +5,9 @@ import { ...@@ -5,8 +5,9 @@ import {
DEFAULT_FONT, DEFAULT_FONT,
EMBEDDING_SDK_ROOT_ELEMENT_ID, EMBEDDING_SDK_ROOT_ELEMENT_ID,
} from "embedding-sdk/config"; } from "embedding-sdk/config";
import { EmbeddingContext } from "embedding-sdk/context";
import { useInitData } from "embedding-sdk/hooks"; import { useInitData } from "embedding-sdk/hooks";
import { useSdkSelector } from "embedding-sdk/store";
import { getIsInitialized } from "embedding-sdk/store/selectors";
import type { SDKConfigType } from "embedding-sdk/types"; import type { SDKConfigType } from "embedding-sdk/types";
import { SdkContentWrapper } from "./SdkContentWrapper"; import { SdkContentWrapper } from "./SdkContentWrapper";
...@@ -20,23 +21,18 @@ export const AppInitializeController = ({ ...@@ -20,23 +21,18 @@ export const AppInitializeController = ({
config, config,
children, children,
}: AppInitializeControllerProps) => { }: AppInitializeControllerProps) => {
const { isLoggedIn, isInitialized } = useInitData({ useInitData({
config, config,
}); });
const isInitialized = useSdkSelector(getIsInitialized);
return ( return (
<EmbeddingContext.Provider <SdkContentWrapper
value={{ id={EMBEDDING_SDK_ROOT_ELEMENT_ID}
isInitialized, font={config.font ?? DEFAULT_FONT}
isLoggedIn,
}}
> >
<SdkContentWrapper {!isInitialized ? <div>{t`Loading…`}</div> : children}
id={EMBEDDING_SDK_ROOT_ELEMENT_ID} </SdkContentWrapper>
font={config.font ?? DEFAULT_FONT}
>
{!isInitialized ? <div>{t`Loading…`}</div> : children}
</SdkContentWrapper>
</EmbeddingContext.Provider>
); );
}; };
import cx from "classnames"; import cx from "classnames";
import { useEffect } from "react"; import { useEffect } from "react";
import { useSdkSelector } from "embedding-sdk/store";
import { getIsInitialized, getIsLoggedIn } from "embedding-sdk/store/selectors";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper"; import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import CS from "metabase/css/core/index.css"; import CS from "metabase/css/core/index.css";
import { useDispatch, useSelector } from "metabase/lib/redux"; import { useDispatch, useSelector } from "metabase/lib/redux";
...@@ -21,8 +23,6 @@ import { Group, Stack } from "metabase/ui"; ...@@ -21,8 +23,6 @@ import { Group, Stack } from "metabase/ui";
import { getEmbeddingMode } from "metabase/visualizations/click-actions/lib/modes"; import { getEmbeddingMode } from "metabase/visualizations/click-actions/lib/modes";
import type { CardId } from "metabase-types/api"; import type { CardId } from "metabase-types/api";
import { useEmbeddingContext } from "../../context";
interface InteractiveQuestionProps { interface InteractiveQuestionProps {
questionId: CardId; questionId: CardId;
} }
...@@ -30,7 +30,9 @@ interface InteractiveQuestionProps { ...@@ -30,7 +30,9 @@ interface InteractiveQuestionProps {
export const InteractiveQuestion = ({ export const InteractiveQuestion = ({
questionId, questionId,
}: InteractiveQuestionProps): JSX.Element | null => { }: InteractiveQuestionProps): JSX.Element | null => {
const { isInitialized, isLoggedIn } = useEmbeddingContext(); const isInitialized = useSdkSelector(getIsInitialized);
const isLoggedIn = useSdkSelector(getIsLoggedIn);
const dispatch = useDispatch(); const dispatch = useDispatch();
const question = useSelector(getQuestion); const question = useSelector(getQuestion);
const mode = question && getEmbeddingMode(question); const mode = question && getEmbeddingMode(question);
......
import cx from "classnames"; import cx from "classnames";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useSdkSelector } from "embedding-sdk/store";
import { getIsInitialized, getIsLoggedIn } from "embedding-sdk/store/selectors";
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper"; import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
import CS from "metabase/css/core/index.css"; import CS from "metabase/css/core/index.css";
import { useSelector } from "metabase/lib/redux"; import { useSelector } from "metabase/lib/redux";
...@@ -18,8 +20,6 @@ import { PublicMode } from "metabase/visualizations/click-actions/modes/PublicMo ...@@ -18,8 +20,6 @@ import { PublicMode } from "metabase/visualizations/click-actions/modes/PublicMo
import Question from "metabase-lib/v1/Question"; import Question from "metabase-lib/v1/Question";
import type { Card, CardId, Dataset } from "metabase-types/api"; import type { Card, CardId, Dataset } from "metabase-types/api";
import { useEmbeddingContext } from "../../context";
interface QueryVisualizationProps { interface QueryVisualizationProps {
questionId: CardId; questionId: CardId;
showVisualizationSelector?: boolean; showVisualizationSelector?: boolean;
...@@ -37,7 +37,9 @@ export const StaticQuestion = ({ ...@@ -37,7 +37,9 @@ export const StaticQuestion = ({
questionId, questionId,
showVisualizationSelector, showVisualizationSelector,
}: QueryVisualizationProps): JSX.Element | null => { }: QueryVisualizationProps): JSX.Element | null => {
const { isInitialized, isLoggedIn } = useEmbeddingContext(); const isInitialized = useSdkSelector(getIsInitialized);
const isLoggedIn = useSdkSelector(getIsLoggedIn);
const metadata = useSelector(getMetadata); const metadata = useSelector(getMetadata);
const [{ loading, card, result, cardError, resultError }, setState] = const [{ loading, card, result, cardError, resultError }, setState] =
......
import { createContext, useContext } from "react";
interface EmbeddingSdkContextData {
isInitialized: boolean;
isLoggedIn: boolean;
}
export const EmbeddingContext = createContext<EmbeddingSdkContextData>({
isInitialized: false,
isLoggedIn: false,
});
export const useEmbeddingContext = () => {
return useContext(EmbeddingContext);
};
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import _ from "underscore"; import _ from "underscore";
import { store } from "embedding-sdk/store"; import { store, useSdkDispatch, useSdkSelector } from "embedding-sdk/store";
import { import {
getOrRefreshSession, getOrRefreshSession,
getSessionTokenState, setIsInitialized,
setIsLoggedIn,
} from "embedding-sdk/store/reducer"; } from "embedding-sdk/store/reducer";
import {
getIsInitialized,
getIsLoggedIn,
getSessionTokenState,
} from "embedding-sdk/store/selectors";
import type { EmbeddingSessionTokenState } from "embedding-sdk/store/types"; import type { EmbeddingSessionTokenState } from "embedding-sdk/store/types";
import type { SDKConfigType } from "embedding-sdk/types"; import type { SDKConfigType } from "embedding-sdk/types";
import { reloadSettings } from "metabase/admin/settings/settings"; import { reloadSettings } from "metabase/admin/settings/settings";
import api from "metabase/lib/api"; import api from "metabase/lib/api";
import { useDispatch } from "metabase/lib/redux";
import { refreshCurrentUser } from "metabase/redux/user"; import { refreshCurrentUser } from "metabase/redux/user";
import registerVisualizations from "metabase/visualizations/register"; import registerVisualizations from "metabase/visualizations/register";
...@@ -26,10 +31,11 @@ export const useInitData = ({ ...@@ -26,10 +31,11 @@ export const useInitData = ({
isLoggedIn: boolean; isLoggedIn: boolean;
isInitialized: boolean; isInitialized: boolean;
} => { } => {
const dispatch = useDispatch(); const dispatch = useSdkDispatch();
const isInitialized = useSdkSelector(getIsInitialized);
const isLoggedIn = useSdkSelector(getIsLoggedIn);
const [isInitialized, setIsInitialized] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [sessionTokenState, setSessionTokenState] = const [sessionTokenState, setSessionTokenState] =
useState<EmbeddingSessionTokenState | null>(null); useState<EmbeddingSessionTokenState | null>(null);
...@@ -68,7 +74,7 @@ export const useInitData = ({ ...@@ -68,7 +74,7 @@ export const useInitData = ({
} else if (config.authType === "apiKey" && config.apiKey) { } else if (config.authType === "apiKey" && config.apiKey) {
api.apiKey = config.apiKey; api.apiKey = config.apiKey;
} else { } else {
setIsLoggedIn(false); dispatch(setIsLoggedIn(false));
return; return;
} }
...@@ -76,8 +82,8 @@ export const useInitData = ({ ...@@ -76,8 +82,8 @@ export const useInitData = ({
dispatch(refreshCurrentUser()), dispatch(refreshCurrentUser()),
dispatch(reloadSettings()), dispatch(reloadSettings()),
]).then(() => { ]).then(() => {
setIsInitialized(true); dispatch(setIsInitialized(true));
setIsLoggedIn(true); dispatch(setIsLoggedIn(true));
}); });
}, [config, dispatch, sessionTokenState]); }, [config, dispatch, sessionTokenState]);
......
import { useMemo } from "react"; import { useMemo } from "react";
import { useEmbeddingContext } from "embedding-sdk/context"; import { useSdkSelector } from "embedding-sdk/store";
import { getIsInitialized, getIsLoggedIn } from "embedding-sdk/store/selectors";
import { useSearchListQuery } from "metabase/common/hooks"; import { useSearchListQuery } from "metabase/common/hooks";
export const useQuestionSearch = (searchQuery?: string) => { export const useQuestionSearch = (searchQuery?: string) => {
const { isInitialized, isLoggedIn } = useEmbeddingContext(); const isInitialized = useSdkSelector(getIsInitialized);
const isLoggedIn = useSdkSelector(getIsLoggedIn);
const query = useMemo(() => { const query = useMemo(() => {
return searchQuery return searchQuery
......
import type { AnyAction, Store } from "@reduxjs/toolkit"; import type { AnyAction, Store, ThunkDispatch } from "@reduxjs/toolkit";
import type { TypedUseSelectorHook } from "react-redux";
import { useSelector, useDispatch } from "react-redux";
import { tokenReducer } from "embedding-sdk/store/reducer"; import type { SdkStoreState } from "embedding-sdk/store/types";
import type { SdkState } from "embedding-sdk/store/types";
import reducers from "metabase/reducers-main"; import reducers from "metabase/reducers-main";
import { getStore } from "metabase/store"; import { getStore } from "metabase/store";
import { sdk } from "./reducer";
const SDK_REDUCERS = { const SDK_REDUCERS = {
...reducers, ...reducers,
embeddingSessionToken: tokenReducer, sdk,
}; };
export const store = getStore(SDK_REDUCERS, null, { export const store = getStore(SDK_REDUCERS, null, {
embed: { embed: {
isEmbeddingSdk: true, isEmbeddingSdk: true,
}, },
}) as unknown as Store<SdkState, AnyAction>; }) as unknown as Store<SdkStoreState, AnyAction>;
export const useSdkSelector: TypedUseSelectorHook<SdkStoreState> = useSelector;
export const useSdkDispatch: () => ThunkDispatch<
SdkStoreState,
void,
AnyAction
> = useDispatch;
import type { PayloadAction } from "@reduxjs/toolkit";
import { createReducer } from "@reduxjs/toolkit"; import { createReducer } from "@reduxjs/toolkit";
import { createAction } from "redux-actions";
import type { import type { SdkState, SdkStoreState } from "embedding-sdk/store/types";
EmbeddingSessionTokenState,
SdkState,
} from "embedding-sdk/store/types";
import { createAsyncThunk } from "metabase/lib/redux"; import { createAsyncThunk } from "metabase/lib/redux";
const initialState: EmbeddingSessionTokenState = { import { getSessionTokenState } from "./selectors";
token: null,
loading: false, const SET_IS_LOGGED_IN = "sdk/SET_IS_LOGGED_IN";
error: null, const SET_IS_INITIALIZED = "sdk/SET_IS_INITIALIZED";
};
export const getSessionTokenState = (state: SdkState) => export const setIsLoggedIn = createAction<boolean>(SET_IS_LOGGED_IN);
state.embeddingSessionToken; export const setIsInitialized = createAction<boolean>(SET_IS_INITIALIZED);
const GET_OR_REFRESH_SESSION = "embeddingSessionToken/GET_OR_REFRESH_SESSION"; const GET_OR_REFRESH_SESSION = "sdk/token/GET_OR_REFRESH_SESSION";
const REFRESH_TOKEN = "sdk/token/REFRESH_TOKEN";
export const getOrRefreshSession = createAsyncThunk( export const getOrRefreshSession = createAsyncThunk(
GET_OR_REFRESH_SESSION, GET_OR_REFRESH_SESSION,
async (url: string, { dispatch, getState }) => { async (url: string, { dispatch, getState }) => {
const state = getSessionTokenState(getState() as SdkState); const state = getSessionTokenState(getState() as SdkStoreState);
const token = state?.token; const token = state?.token;
const isTokenValid = token && token.exp * 1000 >= Date.now(); const isTokenValid = token && token.exp * 1000 >= Date.now();
...@@ -32,8 +31,6 @@ export const getOrRefreshSession = createAsyncThunk( ...@@ -32,8 +31,6 @@ export const getOrRefreshSession = createAsyncThunk(
}, },
); );
const REFRESH_TOKEN = "embeddingSessionToken/REFRESH_TOKEN";
export const refreshTokenAsync = createAsyncThunk( export const refreshTokenAsync = createAsyncThunk(
REFRESH_TOKEN, REFRESH_TOKEN,
async (url: string) => { async (url: string) => {
...@@ -45,24 +42,59 @@ export const refreshTokenAsync = createAsyncThunk( ...@@ -45,24 +42,59 @@ export const refreshTokenAsync = createAsyncThunk(
}, },
); );
const tokenReducer = createReducer(initialState, builder => const initialState: SdkState = {
builder token: {
.addCase(refreshTokenAsync.pending, state => { token: null,
state.loading = true; loading: false,
return state; error: null,
}) },
.addCase(refreshTokenAsync.fulfilled, (state, action) => { isLoggedIn: false,
state.token = action.payload; isInitialized: false,
state.error = null; };
state.loading = false;
return state;
})
.addCase(refreshTokenAsync.rejected, (state, action) => {
state.token = null;
state.error = action.error;
state.loading = false;
return state;
}),
);
export { tokenReducer }; export const sdk = createReducer(initialState, {
[refreshTokenAsync.pending.type]: state => {
return {
...state,
token: {
...state.token,
loading: true,
},
};
},
[refreshTokenAsync.fulfilled.type]: (state, action) => {
return {
...state,
token: {
...state.token,
token: action.payload,
error: null,
loading: false,
},
};
},
[refreshTokenAsync.rejected.type]: (state, action) => {
return {
...state,
isLoggedIn: false,
token: {
...state.token,
token: null,
error: action.error,
loading: false,
},
};
},
[SET_IS_LOGGED_IN]: (state, action: PayloadAction<boolean>) => {
return {
...state,
isLoggedIn: action.payload,
};
},
[SET_IS_INITIALIZED]: (state, action: PayloadAction<boolean>) => {
return {
...state,
isInitialized: action.payload,
};
},
});
import type { SdkStoreState } from "embedding-sdk/store/types";
export const getIsLoggedIn = (state: SdkStoreState) => state.sdk.isLoggedIn;
export const getIsInitialized = (state: SdkStoreState) =>
state.sdk.isInitialized;
export const getSessionTokenState = (state: SdkStoreState) => state.sdk.token;
...@@ -11,6 +11,12 @@ export type EmbeddingSessionTokenState = { ...@@ -11,6 +11,12 @@ export type EmbeddingSessionTokenState = {
error: SerializedError | null; error: SerializedError | null;
}; };
export interface SdkState extends State { export type SdkState = {
embeddingSessionToken: EmbeddingSessionTokenState; token: EmbeddingSessionTokenState;
isLoggedIn: boolean;
isInitialized: boolean;
};
export interface SdkStoreState extends State {
sdk: SdkState;
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment