From a38f03c95f17e3f0048aa5aea448308f1f3555a3 Mon Sep 17 00:00:00 2001 From: Maciej Bodek Date: Fri, 1 Dec 2023 16:58:01 +0100 Subject: [PATCH 1/5] Add URL deep-linking for UI panels --- .../web-console/src/scenes/Console/index.tsx | 11 +++-- .../web-console/src/store/Console/actions.ts | 8 +-- .../web-console/src/store/Console/reducers.ts | 49 ++++++++++++++++--- .../src/store/Console/selectors.ts | 12 +++-- .../web-console/src/store/Console/types.ts | 16 +++--- packages/web-console/src/store/reducers.ts | 6 +-- 6 files changed, 71 insertions(+), 31 deletions(-) diff --git a/packages/web-console/src/scenes/Console/index.tsx b/packages/web-console/src/scenes/Console/index.tsx index 43488f228..6e264b0de 100644 --- a/packages/web-console/src/scenes/Console/index.tsx +++ b/packages/web-console/src/scenes/Console/index.tsx @@ -84,13 +84,16 @@ const Console = () => { 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 === "result" && result ? "flex" : "none" } if (zeroStateRef.current) { zeroStateRef.current.style.display = - panel === "zeroState" ? "flex" : "none" + panel === "zeroState" || (panel === "result" && !result) + ? "flex" + : "none" } if (importRef.current) { importRef.current.style.display = panel === "import" ? "flex" : "none" @@ -100,7 +103,7 @@ const Console = () => { useEffect(() => { if (resultRef.current && result) { dispatch(actions.console.setActiveBottomPanel("result")) - } else if (zeroStateRef.current) { + } else if (activeBottomPanel === "zeroState") { dispatch(actions.console.setActiveBottomPanel("zeroState")) } }, [result]) 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..3bac5904f 100644 --- a/packages/web-console/src/store/Console/reducers.ts +++ b/packages/web-console/src/store/Console/reducers.ts @@ -27,13 +27,44 @@ 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 && ["create", "news"].includes(sidebar) ? sidebar : undefined + +const getValidTopPanel = (topPanel?: TopPanel) => + topPanel && ["tables"].includes(topPanel) ? topPanel : undefined + +const getValidBottomPanel = (bottomPanel?: BottomPanel): BottomPanel => + bottomPanel && ["result", "zeroState", "import"].includes(bottomPanel) + ? bottomPanel + : "zeroState" + +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 +74,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 +97,7 @@ const _console = ( } case ConsoleAT.SET_ACTIVE_TOP_PANEL: { + setUrlParam("topPanel", getValidTopPanel(action.payload)) return { ...state, activeTopPanel: action.payload, @@ -72,6 +105,7 @@ const _console = ( } case ConsoleAT.SET_ACTIVE_SIDEBAR: { + setUrlParam("sidebar", getValidSidebar(action.payload)) return { ...state, activeSidebar: action.payload, @@ -79,6 +113,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..92559390c 100644 --- a/packages/web-console/src/store/Console/types.ts +++ b/packages/web-console/src/store/Console/types.ts @@ -33,9 +33,9 @@ export type QueryGroup = { queries: Query[] } -export type TopPanel = "tables" | undefined +export type TopPanel = "tables" -export type Sidebar = "news" | "create" | undefined +export type Sidebar = "news" | "create" export type BottomPanel = "result" | "zeroState" | "import" @@ -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, } From e886a0f061ae7f5c6db704b8c75c978d5e7c4b4d Mon Sep 17 00:00:00 2001 From: Maciej Bodek Date: Fri, 1 Dec 2023 17:20:17 +0100 Subject: [PATCH 2/5] Add Cypress tests --- .../cypress/integration/console/panel.spec.js | 27 +++++++++++++++++++ .../components/TableSchemaDialog/dialog.tsx | 5 +++- .../web-console/src/scenes/News/index.tsx | 2 +- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 packages/browser-tests/cypress/integration/console/panel.spec.js 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..6e2237b4c --- /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=import`); + cy.get('[data-hook="import-dropbox"]').should("be.visible"); + cy.matchImageSnapshot(); + }); + + it("should show news panel", () => { + cy.visit(`${baseUrl}/?sidebar=news`); + 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=create`); + 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/TableSchemaDialog/dialog.tsx b/packages/web-console/src/components/TableSchemaDialog/dialog.tsx index f61fb51eb..07a44ce9a 100644 --- a/packages/web-console/src/components/TableSchemaDialog/dialog.tsx +++ b/packages/web-console/src/components/TableSchemaDialog/dialog.tsx @@ -199,7 +199,10 @@ export const Dialog = ({ } }} > - + name="table-schema" defaultValues={defaults} diff --git a/packages/web-console/src/scenes/News/index.tsx b/packages/web-console/src/scenes/News/index.tsx index 74abdab26..e92b77850 100644 --- a/packages/web-console/src/scenes/News/index.tsx +++ b/packages/web-console/src/scenes/News/index.tsx @@ -197,7 +197,7 @@ const News = () => { /> } > - + {isLoading && !enterpriseNews && ( From cc5e73ff2ee3dec84b8e539ac371f2e11d8e19b7 Mon Sep 17 00:00:00 2001 From: Maciej Bodek Date: Mon, 18 Dec 2023 14:29:57 +0100 Subject: [PATCH 3/5] fix: result panel invisible for new users --- packages/web-console/src/scenes/Console/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/web-console/src/scenes/Console/index.tsx b/packages/web-console/src/scenes/Console/index.tsx index 6e264b0de..39f977da4 100644 --- a/packages/web-console/src/scenes/Console/index.tsx +++ b/packages/web-console/src/scenes/Console/index.tsx @@ -102,8 +102,10 @@ const Console = () => { useEffect(() => { if (resultRef.current && result) { + showPanel("result") dispatch(actions.console.setActiveBottomPanel("result")) } else if (activeBottomPanel === "zeroState") { + showPanel("zeroState") dispatch(actions.console.setActiveBottomPanel("zeroState")) } }, [result]) From 20268f71cd33ba5ecc47a5a901c65fee391d64fb Mon Sep 17 00:00:00 2001 From: Maciej Bodek Date: Mon, 18 Dec 2023 16:13:23 +0100 Subject: [PATCH 4/5] Refactor panel naming across the app --- .../components/CreateTableDialog/index.tsx | 6 ++-- .../components/TableSchemaDialog/dialog.tsx | 4 +-- .../src/modules/ZeroState/start.tsx | 6 ++-- .../web-console/src/scenes/Console/index.tsx | 31 ++++++++----------- .../web-console/src/scenes/News/index.tsx | 4 +-- .../web-console/src/store/Console/reducers.ts | 8 ++--- .../web-console/src/store/Console/types.ts | 6 ++-- 7 files changed, 27 insertions(+), 38 deletions(-) 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 07a44ce9a..09ba480ac 100644 --- a/packages/web-console/src/components/TableSchemaDialog/dialog.tsx +++ b/packages/web-console/src/components/TableSchemaDialog/dialog.tsx @@ -193,9 +193,7 @@ export const Dialog = ({ }} onOpenChange={(isOpen) => { if (isOpen && action === "add") { - dispatch( - actions.console.setActiveSidebar(isOpen ? "create" : undefined), - ) + dispatch(actions.console.setActiveSidebar(isOpen ? "p2" : undefined)) } }} > 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 showPanel = (panel: BottomPanel | undefined) => { if (resultRef.current) { resultRef.current.style.display = - panel === "result" && result ? "flex" : "none" + panel === "p1" && result ? "flex" : "none" } if (zeroStateRef.current) { zeroStateRef.current.style.display = - panel === "zeroState" || (panel === "result" && !result) - ? "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) { - showPanel("result") - dispatch(actions.console.setActiveBottomPanel("result")) - } else if (activeBottomPanel === "zeroState") { - showPanel("zeroState") - dispatch(actions.console.setActiveBottomPanel("zeroState")) + showPanel("p1") + dispatch(actions.console.setActiveBottomPanel("p1")) + } else if (activeBottomPanel === "p0") { + showPanel("p0") + dispatch(actions.console.setActiveBottomPanel("p0")) } }, [result]) @@ -135,7 +133,7 @@ const Console = () => { onClick={() => { dispatch( actions.console.setActiveTopPanel( - resultsSplitterBasis === 0 ? "tables" : undefined, + resultsSplitterBasis === 0 ? "p1" : undefined, ), ) updateSettings( @@ -193,14 +191,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} @@ -217,10 +212,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 e92b77850..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={ diff --git a/packages/web-console/src/store/Console/reducers.ts b/packages/web-console/src/store/Console/reducers.ts index 3bac5904f..8fbef0a48 100644 --- a/packages/web-console/src/store/Console/reducers.ts +++ b/packages/web-console/src/store/Console/reducers.ts @@ -33,15 +33,13 @@ import { } from "./types" const getValidSidebar = (sidebar?: Sidebar) => - sidebar && ["create", "news"].includes(sidebar) ? sidebar : undefined + sidebar && ["p1", "p2"].includes(sidebar) ? sidebar : undefined const getValidTopPanel = (topPanel?: TopPanel) => - topPanel && ["tables"].includes(topPanel) ? topPanel : undefined + topPanel && ["p1"].includes(topPanel) ? topPanel : undefined const getValidBottomPanel = (bottomPanel?: BottomPanel): BottomPanel => - bottomPanel && ["result", "zeroState", "import"].includes(bottomPanel) - ? bottomPanel - : "zeroState" + bottomPanel && ["p0", "p1", "p2"].includes(bottomPanel) ? bottomPanel : "p0" export const getInitialState = (): ConsoleStateShape => { const url = new URL(window.location.href) diff --git a/packages/web-console/src/store/Console/types.ts b/packages/web-console/src/store/Console/types.ts index 92559390c..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" +export type TopPanel = "p1" -export type Sidebar = "news" | "create" +export type Sidebar = "p1" | "p2" -export type BottomPanel = "result" | "zeroState" | "import" +export type BottomPanel = "p0" | "p1" | "p2" export type ConsoleConfigShape = Readonly<{ githubBanner: boolean From 662201211a31ddd0031bf68fff8e38ac611aacbb Mon Sep 17 00:00:00 2001 From: Maciej Bodek Date: Mon, 18 Dec 2023 16:15:18 +0100 Subject: [PATCH 5/5] Update tests --- .../browser-tests/cypress/integration/console/panel.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/browser-tests/cypress/integration/console/panel.spec.js b/packages/browser-tests/cypress/integration/console/panel.spec.js index 6e2237b4c..512fe8db9 100644 --- a/packages/browser-tests/cypress/integration/console/panel.spec.js +++ b/packages/browser-tests/cypress/integration/console/panel.spec.js @@ -4,13 +4,13 @@ const baseUrl = "http://localhost:9999"; describe("URL deep linking", () => { it("should show import panel", () => { - cy.visit(`${baseUrl}/?bottomPanel=import`); + 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=news`); + 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"); @@ -18,7 +18,7 @@ describe("URL deep linking", () => { }); it("should show create table panel", () => { - cy.visit(`${baseUrl}/?sidebar=create`); + 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");