diff --git a/packages/browser-tests/cypress/integration/console/panel.spec.js b/packages/browser-tests/cypress/integration/console/panel.spec.js new file mode 100644 index 000000000..512fe8db9 --- /dev/null +++ b/packages/browser-tests/cypress/integration/console/panel.spec.js @@ -0,0 +1,27 @@ +/// + +const baseUrl = "http://localhost:9999"; + +describe("URL deep linking", () => { + it("should show import panel", () => { + cy.visit(`${baseUrl}/?bottomPanel=p2`); + cy.get('[data-hook="import-dropbox"]').should("be.visible"); + cy.matchImageSnapshot(); + }); + + it("should show news panel", () => { + cy.visit(`${baseUrl}/?sidebar=p1`); + cy.get('[data-hook="news-content"]').should("be.visible"); + cy.get('[data-hook="news-panel-button"]').click(); + cy.url().should("not.contain", "sidebar=news"); + cy.matchImageSnapshot(); + }); + + it("should show create table panel", () => { + cy.visit(`${baseUrl}/?sidebar=p2`); + cy.get('[data-hook="schema-content"]').should("be.visible"); + cy.get('[data-hook="create-table-panel-button"]').click(); + cy.url().should("not.contain", "sidebar=create"); + cy.matchImageSnapshot(); + }); +}); diff --git a/packages/web-console/src/components/CreateTableDialog/index.tsx b/packages/web-console/src/components/CreateTableDialog/index.tsx index 2644c43f8..a2ccb5420 100644 --- a/packages/web-console/src/components/CreateTableDialog/index.tsx +++ b/packages/web-console/src/components/CreateTableDialog/index.tsx @@ -38,12 +38,12 @@ export const CreateTableDialog = () => { } useEffect(() => { - setAddTableDialogOpen(activeSidebar === "create" ? "add" : undefined) + setAddTableDialogOpen(activeSidebar === "p2" ? "add" : undefined) }, [activeSidebar]) useEffect(() => { if (addTableDialogOpen !== undefined) { - dispatch(actions.console.setActiveSidebar("create")) + dispatch(actions.console.setActiveSidebar("p2")) } }, [addTableDialogOpen]) @@ -72,7 +72,7 @@ export const CreateTableDialog = () => { onClick: () => { dispatch( actions.console.setActiveSidebar( - addTableDialogOpen ? undefined : "create", + addTableDialogOpen ? undefined : "p2", ), ) }, diff --git a/packages/web-console/src/components/TableSchemaDialog/dialog.tsx b/packages/web-console/src/components/TableSchemaDialog/dialog.tsx index f61fb51eb..09ba480ac 100644 --- a/packages/web-console/src/components/TableSchemaDialog/dialog.tsx +++ b/packages/web-console/src/components/TableSchemaDialog/dialog.tsx @@ -193,13 +193,14 @@ export const Dialog = ({ }} onOpenChange={(isOpen) => { if (isOpen && action === "add") { - dispatch( - actions.console.setActiveSidebar(isOpen ? "create" : undefined), - ) + dispatch(actions.console.setActiveSidebar(isOpen ? "p2" : undefined)) } }} > - + name="table-schema" defaultValues={defaults} diff --git a/packages/web-console/src/modules/ZeroState/start.tsx b/packages/web-console/src/modules/ZeroState/start.tsx index 666f81d49..d2a35e3c2 100644 --- a/packages/web-console/src/modules/ZeroState/start.tsx +++ b/packages/web-console/src/modules/ZeroState/start.tsx @@ -66,9 +66,7 @@ export const Start = () => { - dispatch(actions.console.setActiveBottomPanel("import")) - } + onClick={() => dispatch(actions.console.setActiveBottomPanel("p2"))} > File upload icon { Import CSV dispatch(actions.console.setActiveSidebar("create"))} + onClick={() => dispatch(actions.console.setActiveSidebar("p2"))} > Create table icon { const importRef = React.useRef(null) const horizontalSplitterRef = React.useRef(null) - const showPanel = (panel: BottomPanel) => { + const showPanel = (panel: BottomPanel | undefined) => { if (resultRef.current) { - resultRef.current.style.display = panel === "result" ? "flex" : "none" + resultRef.current.style.display = + panel === "p1" && result ? "flex" : "none" } if (zeroStateRef.current) { zeroStateRef.current.style.display = - panel === "zeroState" ? "flex" : "none" + panel === "p0" || (panel === "p1" && !result) ? "flex" : "none" } if (importRef.current) { - importRef.current.style.display = panel === "import" ? "flex" : "none" + importRef.current.style.display = panel === "p2" ? "flex" : "none" } } useEffect(() => { if (resultRef.current && result) { - dispatch(actions.console.setActiveBottomPanel("result")) - } else if (zeroStateRef.current) { - dispatch(actions.console.setActiveBottomPanel("zeroState")) + showPanel("p1") + dispatch(actions.console.setActiveBottomPanel("p1")) + } else if (activeBottomPanel === "p0") { + showPanel("p0") + dispatch(actions.console.setActiveBottomPanel("p0")) } }, [result]) @@ -131,7 +134,7 @@ const Console = () => { onClick={() => { dispatch( actions.console.setActiveTopPanel( - resultsSplitterBasis === 0 ? "tables" : undefined, + resultsSplitterBasis === 0 ? "p1" : undefined, ), ) updateSettings( @@ -189,14 +192,11 @@ const Console = () => { data-hook={`${mode}-panel-button`} direction="left" onClick={() => { - dispatch( - actions.console.setActiveBottomPanel("result"), - ) + dispatch(actions.console.setActiveBottomPanel("p1")) setResultViewMode(mode) }} selected={ - activeBottomPanel === "result" && - resultViewMode === mode + activeBottomPanel === "p1" && resultViewMode === mode } > {icon} @@ -213,10 +213,10 @@ const Console = () => { readOnly={readOnly} {...(!readOnly && { onClick: () => { - dispatch(actions.console.setActiveBottomPanel("import")) + dispatch(actions.console.setActiveBottomPanel("p2")) }, })} - selected={activeBottomPanel === "import"} + selected={activeBottomPanel === "p2"} data-hook="import-panel-button" > diff --git a/packages/web-console/src/scenes/News/index.tsx b/packages/web-console/src/scenes/News/index.tsx index 74abdab26..4bb2a5951 100644 --- a/packages/web-console/src/scenes/News/index.tsx +++ b/packages/web-console/src/scenes/News/index.tsx @@ -165,7 +165,7 @@ const News = () => { }, [newsOpened, enterpriseNews]) useEffect(() => { - setNewsOpened(activeSidebar === "news") + setNewsOpened(activeSidebar === "p1") }, [activeSidebar]) return ( @@ -175,7 +175,7 @@ const News = () => { open={newsOpened} onOpenChange={async (newsOpened) => { dispatch( - actions.console.setActiveSidebar(newsOpened ? "news" : undefined), + actions.console.setActiveSidebar(newsOpened ? "p1" : undefined), ) }} trigger={ @@ -197,7 +197,7 @@ const News = () => { /> } > - + {isLoading && !enterpriseNews && ( diff --git a/packages/web-console/src/store/Console/actions.ts b/packages/web-console/src/store/Console/actions.ts index e586cc8ad..46977d964 100644 --- a/packages/web-console/src/store/Console/actions.ts +++ b/packages/web-console/src/store/Console/actions.ts @@ -44,16 +44,18 @@ const setConfig = (payload: ConsoleConfigShape): ConsoleAction => ({ type: ConsoleAT.SET_CONFIG, }) -const setActiveTopPanel = (panel: TopPanel): ConsoleAction => ({ +const setActiveTopPanel = (panel: TopPanel | undefined): ConsoleAction => ({ payload: panel, type: ConsoleAT.SET_ACTIVE_TOP_PANEL, }) -const setActiveSidebar = (panel: Sidebar): ConsoleAction => ({ +const setActiveSidebar = (panel: Sidebar | undefined): ConsoleAction => ({ payload: panel, type: ConsoleAT.SET_ACTIVE_SIDEBAR, }) -const setActiveBottomPanel = (panel: BottomPanel): ConsoleAction => ({ +const setActiveBottomPanel = ( + panel: BottomPanel | undefined, +): ConsoleAction => ({ payload: panel, type: ConsoleAT.SET_ACTIVE_BOTTOM_PANEL, }) diff --git a/packages/web-console/src/store/Console/reducers.ts b/packages/web-console/src/store/Console/reducers.ts index d11b0b5b3..8fbef0a48 100644 --- a/packages/web-console/src/store/Console/reducers.ts +++ b/packages/web-console/src/store/Console/reducers.ts @@ -27,13 +27,42 @@ import { ConsoleAction, ConsoleAT, ConsoleStateShape, -} from "../../types" + TopPanel, + Sidebar, + BottomPanel, +} from "./types" -export const initialState: ConsoleStateShape = { - sideMenuOpened: false, - activeTopPanel: "tables", - activeSidebar: undefined, - activeBottomPanel: "zeroState", +const getValidSidebar = (sidebar?: Sidebar) => + sidebar && ["p1", "p2"].includes(sidebar) ? sidebar : undefined + +const getValidTopPanel = (topPanel?: TopPanel) => + topPanel && ["p1"].includes(topPanel) ? topPanel : undefined + +const getValidBottomPanel = (bottomPanel?: BottomPanel): BottomPanel => + bottomPanel && ["p0", "p1", "p2"].includes(bottomPanel) ? bottomPanel : "p0" + +export const getInitialState = (): ConsoleStateShape => { + const url = new URL(window.location.href) + const bottomPanel = (url.searchParams.get("bottomPanel") ?? "") as BottomPanel + const sidebar = (url.searchParams.get("sidebar") ?? "") as Sidebar + const topPanel = (url.searchParams.get("topPanel") ?? "") as TopPanel + + return { + sideMenuOpened: getValidSidebar(sidebar) !== undefined, + activeTopPanel: getValidTopPanel(topPanel), + activeSidebar: getValidSidebar(sidebar), + activeBottomPanel: getValidBottomPanel(bottomPanel), + } as ConsoleStateShape +} + +const setUrlParam = (key: string, value?: TopPanel | BottomPanel | Sidebar) => { + const url = new URL(window.location.href) + if (value) { + url.searchParams.set(key, value) + } else { + url.searchParams.delete(key) + } + window.history.replaceState({}, "", url.toString()) } export const defaultConfig: ConsoleConfigShape = { @@ -43,9 +72,10 @@ export const defaultConfig: ConsoleConfigShape = { } const _console = ( - state = initialState, + state = getInitialState(), action: ConsoleAction, ): ConsoleStateShape => { + const url = new URL(window.location.href) switch (action.type) { case ConsoleAT.SET_CONFIG: { return { @@ -65,6 +95,7 @@ const _console = ( } case ConsoleAT.SET_ACTIVE_TOP_PANEL: { + setUrlParam("topPanel", getValidTopPanel(action.payload)) return { ...state, activeTopPanel: action.payload, @@ -72,6 +103,7 @@ const _console = ( } case ConsoleAT.SET_ACTIVE_SIDEBAR: { + setUrlParam("sidebar", getValidSidebar(action.payload)) return { ...state, activeSidebar: action.payload, @@ -79,6 +111,7 @@ const _console = ( } case ConsoleAT.SET_ACTIVE_BOTTOM_PANEL: { + setUrlParam("bottomPanel", getValidBottomPanel(action.payload)) return { ...state, activeBottomPanel: action.payload, diff --git a/packages/web-console/src/store/Console/selectors.ts b/packages/web-console/src/store/Console/selectors.ts index 7ee6f5abf..dfc559396 100644 --- a/packages/web-console/src/store/Console/selectors.ts +++ b/packages/web-console/src/store/Console/selectors.ts @@ -37,14 +37,16 @@ const getConfig: (store: StoreShape) => ConsoleConfigShape = (store) => const getSideMenuOpened: (store: StoreShape) => boolean = (store) => store.console.sideMenuOpened -const getActiveTopPanel: (store: StoreShape) => TopPanel = (store) => - store.console.activeTopPanel +const getActiveTopPanel: (store: StoreShape) => TopPanel | undefined = ( + store, +) => store.console.activeTopPanel -const getActiveSidebar: (store: StoreShape) => Sidebar = (store) => +const getActiveSidebar: (store: StoreShape) => Sidebar | undefined = (store) => store.console.activeSidebar -const getActiveBottomPanel: (store: StoreShape) => BottomPanel = (store) => - store.console.activeBottomPanel +const getActiveBottomPanel: (store: StoreShape) => BottomPanel | undefined = ( + store, +) => store.console.activeBottomPanel export default { getConfig, diff --git a/packages/web-console/src/store/Console/types.ts b/packages/web-console/src/store/Console/types.ts index 665cc038a..f53f8a989 100644 --- a/packages/web-console/src/store/Console/types.ts +++ b/packages/web-console/src/store/Console/types.ts @@ -33,11 +33,11 @@ export type QueryGroup = { queries: Query[] } -export type TopPanel = "tables" | undefined +export type TopPanel = "p1" -export type Sidebar = "news" | "create" | undefined +export type Sidebar = "p1" | "p2" -export type BottomPanel = "result" | "zeroState" | "import" +export type BottomPanel = "p0" | "p1" | "p2" export type ConsoleConfigShape = Readonly<{ githubBanner: boolean @@ -48,9 +48,9 @@ export type ConsoleConfigShape = Readonly<{ export type ConsoleStateShape = Readonly<{ config?: ConsoleConfigShape sideMenuOpened: boolean - activeTopPanel: TopPanel - activeSidebar: Sidebar - activeBottomPanel: BottomPanel + activeTopPanel?: TopPanel + activeSidebar?: Sidebar + activeBottomPanel?: BottomPanel }> export enum ConsoleAT { @@ -82,17 +82,17 @@ type ToggleSideMenuAction = Readonly<{ }> type setActiveTopPanelAction = Readonly<{ - payload: TopPanel + payload?: TopPanel type: ConsoleAT.SET_ACTIVE_TOP_PANEL }> type setActiveSidebarAction = Readonly<{ - payload: Sidebar + payload?: Sidebar type: ConsoleAT.SET_ACTIVE_SIDEBAR }> type setActiveBottomPanelAction = Readonly<{ - payload: BottomPanel + payload?: BottomPanel type: ConsoleAT.SET_ACTIVE_BOTTOM_PANEL }> diff --git a/packages/web-console/src/store/reducers.ts b/packages/web-console/src/store/reducers.ts index ef1103f14..e4f0b4b79 100644 --- a/packages/web-console/src/store/reducers.ts +++ b/packages/web-console/src/store/reducers.ts @@ -24,9 +24,7 @@ import { combineReducers } from "redux" -import _console, { - initialState as consoleInitialState, -} from "./Console/reducers" +import _console, { getInitialState } from "./Console/reducers" import query, { initialState as queryInitialState } from "./Query/reducers" import telemetry, { @@ -40,7 +38,7 @@ const rootReducer = combineReducers({ }) export const initialState = { - console: consoleInitialState, + console: getInitialState(), query: queryInitialState, telemetry: telemetryInitialState, }