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 {
DEFAULT_FONT,
EMBEDDING_SDK_ROOT_ELEMENT_ID,
} from "embedding-sdk/config";
import { EmbeddingContext } from "embedding-sdk/context";
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 { SdkContentWrapper } from "./SdkContentWrapper";
......@@ -20,23 +21,18 @@ export const AppInitializeController = ({
config,
children,
}: AppInitializeControllerProps) => {
const { isLoggedIn, isInitialized } = useInitData({
useInitData({
config,
});
const isInitialized = useSdkSelector(getIsInitialized);
return (
<EmbeddingContext.Provider
value={{
isInitialized,
isLoggedIn,
}}
<SdkContentWrapper
id={EMBEDDING_SDK_ROOT_ELEMENT_ID}
font={config.font ?? DEFAULT_FONT}
>
<SdkContentWrapper
id={EMBEDDING_SDK_ROOT_ELEMENT_ID}
font={config.font ?? DEFAULT_FONT}
>
{!isInitialized ? <div>{t`Loading…`}</div> : children}
</SdkContentWrapper>
</EmbeddingContext.Provider>
{!isInitialized ? <div>{t`Loading…`}</div> : children}
</SdkContentWrapper>
);
};
import cx from "classnames";
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 CS from "metabase/css/core/index.css";
import { useDispatch, useSelector } from "metabase/lib/redux";
......@@ -21,8 +23,6 @@ import { Group, Stack } from "metabase/ui";
import { getEmbeddingMode } from "metabase/visualizations/click-actions/lib/modes";
import type { CardId } from "metabase-types/api";
import { useEmbeddingContext } from "../../context";
interface InteractiveQuestionProps {
questionId: CardId;
}
......@@ -30,7 +30,9 @@ interface InteractiveQuestionProps {
export const InteractiveQuestion = ({
questionId,
}: InteractiveQuestionProps): JSX.Element | null => {
const { isInitialized, isLoggedIn } = useEmbeddingContext();
const isInitialized = useSdkSelector(getIsInitialized);
const isLoggedIn = useSdkSelector(getIsLoggedIn);
const dispatch = useDispatch();
const question = useSelector(getQuestion);
const mode = question && getEmbeddingMode(question);
......
import cx from "classnames";
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 CS from "metabase/css/core/index.css";
import { useSelector } from "metabase/lib/redux";
......@@ -18,8 +20,6 @@ import { PublicMode } from "metabase/visualizations/click-actions/modes/PublicMo
import Question from "metabase-lib/v1/Question";
import type { Card, CardId, Dataset } from "metabase-types/api";
import { useEmbeddingContext } from "../../context";
interface QueryVisualizationProps {
questionId: CardId;
showVisualizationSelector?: boolean;
......@@ -37,7 +37,9 @@ export const StaticQuestion = ({
questionId,
showVisualizationSelector,
}: QueryVisualizationProps): JSX.Element | null => {
const { isInitialized, isLoggedIn } = useEmbeddingContext();
const isInitialized = useSdkSelector(getIsInitialized);
const isLoggedIn = useSdkSelector(getIsLoggedIn);
const metadata = useSelector(getMetadata);
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 _ from "underscore";
import { store } from "embedding-sdk/store";
import { store, useSdkDispatch, useSdkSelector } from "embedding-sdk/store";
import {
getOrRefreshSession,
getSessionTokenState,
setIsInitialized,
setIsLoggedIn,
} from "embedding-sdk/store/reducer";
import {
getIsInitialized,
getIsLoggedIn,
getSessionTokenState,
} from "embedding-sdk/store/selectors";
import type { EmbeddingSessionTokenState } from "embedding-sdk/store/types";
import type { SDKConfigType } from "embedding-sdk/types";
import { reloadSettings } from "metabase/admin/settings/settings";
import api from "metabase/lib/api";
import { useDispatch } from "metabase/lib/redux";
import { refreshCurrentUser } from "metabase/redux/user";
import registerVisualizations from "metabase/visualizations/register";
......@@ -26,10 +31,11 @@ export const useInitData = ({
isLoggedIn: 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] =
useState<EmbeddingSessionTokenState | null>(null);
......@@ -68,7 +74,7 @@ export const useInitData = ({
} else if (config.authType === "apiKey" && config.apiKey) {
api.apiKey = config.apiKey;
} else {
setIsLoggedIn(false);
dispatch(setIsLoggedIn(false));
return;
}
......@@ -76,8 +82,8 @@ export const useInitData = ({
dispatch(refreshCurrentUser()),
dispatch(reloadSettings()),
]).then(() => {
setIsInitialized(true);
setIsLoggedIn(true);
dispatch(setIsInitialized(true));
dispatch(setIsLoggedIn(true));
});
}, [config, dispatch, sessionTokenState]);
......
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";
export const useQuestionSearch = (searchQuery?: string) => {
const { isInitialized, isLoggedIn } = useEmbeddingContext();
const isInitialized = useSdkSelector(getIsInitialized);
const isLoggedIn = useSdkSelector(getIsLoggedIn);
const query = useMemo(() => {
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 { SdkState } from "embedding-sdk/store/types";
import type { SdkStoreState } from "embedding-sdk/store/types";
import reducers from "metabase/reducers-main";
import { getStore } from "metabase/store";
import { sdk } from "./reducer";
const SDK_REDUCERS = {
...reducers,
embeddingSessionToken: tokenReducer,
sdk,
};
export const store = getStore(SDK_REDUCERS, null, {
embed: {
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 { createAction } from "redux-actions";
import type {
EmbeddingSessionTokenState,
SdkState,
} from "embedding-sdk/store/types";
import type { SdkState, SdkStoreState } from "embedding-sdk/store/types";
import { createAsyncThunk } from "metabase/lib/redux";
const initialState: EmbeddingSessionTokenState = {
token: null,
loading: false,
error: null,
};
import { getSessionTokenState } from "./selectors";
const SET_IS_LOGGED_IN = "sdk/SET_IS_LOGGED_IN";
const SET_IS_INITIALIZED = "sdk/SET_IS_INITIALIZED";
export const getSessionTokenState = (state: SdkState) =>
state.embeddingSessionToken;
export const setIsLoggedIn = createAction<boolean>(SET_IS_LOGGED_IN);
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(
GET_OR_REFRESH_SESSION,
async (url: string, { dispatch, getState }) => {
const state = getSessionTokenState(getState() as SdkState);
const state = getSessionTokenState(getState() as SdkStoreState);
const token = state?.token;
const isTokenValid = token && token.exp * 1000 >= Date.now();
......@@ -32,8 +31,6 @@ export const getOrRefreshSession = createAsyncThunk(
},
);
const REFRESH_TOKEN = "embeddingSessionToken/REFRESH_TOKEN";
export const refreshTokenAsync = createAsyncThunk(
REFRESH_TOKEN,
async (url: string) => {
......@@ -45,24 +42,59 @@ export const refreshTokenAsync = createAsyncThunk(
},
);
const tokenReducer = createReducer(initialState, builder =>
builder
.addCase(refreshTokenAsync.pending, state => {
state.loading = true;
return state;
})
.addCase(refreshTokenAsync.fulfilled, (state, action) => {
state.token = action.payload;
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;
}),
);
const initialState: SdkState = {
token: {
token: null,
loading: false,
error: null,
},
isLoggedIn: false,
isInitialized: false,
};
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 = {
error: SerializedError | null;
};
export interface SdkState extends State {
embeddingSessionToken: EmbeddingSessionTokenState;
export type SdkState = {
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