diff --git a/packages/browser-tests/cypress/commands.js b/packages/browser-tests/cypress/commands.js index 8dc8dcfed..4eaa67983 100644 --- a/packages/browser-tests/cypress/commands.js +++ b/packages/browser-tests/cypress/commands.js @@ -263,8 +263,9 @@ Cypress.Commands.add("loadConsoleWithAuth", (clearWarnings) => { }); Cypress.Commands.add("refreshSchema", () => { - cy.getByDataHook("schema-settings-button").click(); - cy.getByDataHook("schema-refresh").click(); + // toggle between auto-refresh modes to trigger a schema refresh + cy.getByDataHook("schema-auto-refresh-button").click(); + cy.getByDataHook("schema-auto-refresh-button").click(); }); Cypress.Commands.add("getEditorTabs", () => { diff --git a/packages/browser-tests/cypress/integration/console/schema.spec.js b/packages/browser-tests/cypress/integration/console/schema.spec.js index aa5a9a046..b6ca64699 100644 --- a/packages/browser-tests/cypress/integration/console/schema.spec.js +++ b/packages/browser-tests/cypress/integration/console/schema.spec.js @@ -132,6 +132,54 @@ describe("questdb schema with suspended tables with Linux OS error codes", () => }); }); +describe("table select UI", () => { + before(() => { + cy.loadConsoleWithAuth(); + + tables.forEach((table) => { + cy.createTable(table); + }); + cy.refreshSchema(); + }); + beforeEach(() => { + cy.loadConsoleWithAuth(); + }); + + it("should show select ui on click", () => { + cy.getByDataHook("schema-select-button").click(); + cy.getByDataHook("schema-copy-to-clipboard-button").should("be.visible"); + cy.getByDataHook("schema-copy-to-clipboard-button").should("be.disabled"); + cy.getByDataHook("schema-select-all-button").should("be.visible"); + }); + + it("should select and deselect tables", () => { + cy.getByDataHook("schema-select-button").click(); + cy.getByDataHook("schema-table-title").contains("btc_trades").click(); + cy.getByDataHook("schema-table-title") + .contains("chicago_weather_stations") + .click(); + cy.getByDataHook("schema-copy-to-clipboard-button") + .should("not.be.disabled") + .click(); + // Electron only! + if (Cypress.isBrowser("electron")) { + ["btc_trades", "chicago_weather_stations"].forEach((table) => { + cy.window() + .its("navigator.clipboard") + .then((clip) => clip.readText()) + .should("contain", `CREATE TABLE '${table}'`); + }); + } + }); + + after(() => { + cy.loadConsoleWithAuth(); + tables.forEach((table) => { + cy.dropTable(table); + }); + }); +}); + describe("questdb schema in read-only mode", () => { before(() => { cy.intercept( diff --git a/packages/browser-tests/questdb b/packages/browser-tests/questdb index 9a5edd150..3ae8efbe0 160000 --- a/packages/browser-tests/questdb +++ b/packages/browser-tests/questdb @@ -1 +1 @@ -Subproject commit 9a5edd1507fb0224f2e58e1dc5a9120d5a733033 +Subproject commit 3ae8efbe0772c568859f372a2c120055278f3c7b diff --git a/packages/react-components/src/components/Button/skin.ts b/packages/react-components/src/components/Button/skin.ts index 7e64229d0..a3a8d9478 100644 --- a/packages/react-components/src/components/Button/skin.ts +++ b/packages/react-components/src/components/Button/skin.ts @@ -24,6 +24,7 @@ type ColorShape = { white: string; inherit: string; tooltipBackground: string; + offWhite: string; }; type Color = keyof ColorShape; @@ -42,7 +43,7 @@ export const skins = [ "transparent", ] as const; -export type Skin = typeof skins[number]; +export type Skin = (typeof skins)[number]; const themes: { [key in Skin]: { @@ -125,16 +126,16 @@ const themes: { normal: { background: "transparent", border: "transparent", - color: "foreground", + color: "offWhite", }, hover: { background: "comment", border: "transparent", - color: "foreground", + color: "offWhite", }, disabled: { background: "transparent", - border: "gray1", + border: "transparent", color: "gray1", }, }, @@ -165,10 +166,6 @@ export const makeSkin = (skin: Skin) => { color: ${getColor(theme.normal.color)}; border-color: ${getColor(theme.normal.border)}; - &:focus { - box-shadow: inset 0 0 0 1px ${getColor("foreground")}; - } - &:hover:not([disabled]) { background: ${getColor(theme.hover.background)}; color: ${getColor(theme.hover.color)}; diff --git a/packages/react-components/src/theme/color.ts b/packages/react-components/src/theme/color.ts index bab4dc9d4..136e9ddbe 100644 --- a/packages/react-components/src/theme/color.ts +++ b/packages/react-components/src/theme/color.ts @@ -22,6 +22,7 @@ export type Color = { white: string; inherit: string; tooltipBackground: string; + offWhite: string; }; export const color: Color = { @@ -48,4 +49,5 @@ export const color: Color = { white: "#fafafa", inherit: "inherit", tooltipBackground: "#6272a4", + offWhite: "#bdbdbd", }; diff --git a/packages/web-console/src/components/ToggleButton/index.tsx b/packages/web-console/src/components/ToggleButton/index.tsx index 0b301fc9a..9b4c8ffe3 100644 --- a/packages/web-console/src/components/ToggleButton/index.tsx +++ b/packages/web-console/src/components/ToggleButton/index.tsx @@ -82,7 +82,7 @@ const baseStyles = css` ${bezierTransition}; ${({ disabled }) => disabled && "cursor: default; pointer-events: none;"}; color: ${({ selected, theme }) => - theme.color[selected ? "foreground" : "offWhite"]}; + theme.color[selected ? "green" : "offWhite"]}; svg + span, img + span { diff --git a/packages/web-console/src/modules/EventBus/index.ts b/packages/web-console/src/modules/EventBus/index.ts index 047192b61..6454b7293 100644 --- a/packages/web-console/src/modules/EventBus/index.ts +++ b/packages/web-console/src/modules/EventBus/index.ts @@ -21,7 +21,7 @@ class EventBus { unsubscribe( eventType: EventType, - handler: (eventPayload?: T) => void, + handler?: (eventPayload?: T) => void, ): void { this.emitter.off(eventType, handler) } diff --git a/packages/web-console/src/providers/LocalStorageProvider/index.tsx b/packages/web-console/src/providers/LocalStorageProvider/index.tsx index 82351cd80..43eff2c46 100644 --- a/packages/web-console/src/providers/LocalStorageProvider/index.tsx +++ b/packages/web-console/src/providers/LocalStorageProvider/index.tsx @@ -43,6 +43,7 @@ const defaultConfig: LocalConfig = { editorSplitterBasis: 350, resultsSplitterBasis: 350, exampleQueriesVisited: false, + autoRefreshTables: true, } type ContextProps = { @@ -53,6 +54,7 @@ type ContextProps = { resultsSplitterBasis: number updateSettings: (key: StoreKey, value: SettingsType) => void exampleQueriesVisited: boolean + autoRefreshTables: boolean } const defaultValues: ContextProps = { @@ -63,6 +65,7 @@ const defaultValues: ContextProps = { resultsSplitterBasis: 350, updateSettings: (key: StoreKey, value: SettingsType) => undefined, exampleQueriesVisited: false, + autoRefreshTables: true, } export const LocalStorageContext = createContext(defaultValues) @@ -96,6 +99,12 @@ export const LocalStorageProvider = ({ getValue(StoreKey.EXAMPLE_QUERIES_VISITED) === "true", ) + const [autoRefreshTables, setAutoRefreshTables] = useState( + getValue(StoreKey.AUTO_REFRESH_TABLES) + ? getValue(StoreKey.AUTO_REFRESH_TABLES) === "true" + : defaultConfig.autoRefreshTables, + ) + const updateSettings = (key: StoreKey, value: SettingsType) => { setValue(key, value.toString()) refreshSettings(key) @@ -126,6 +135,9 @@ export const LocalStorageProvider = ({ parseInteger(value, defaultConfig.resultsSplitterBasis), ) break + case StoreKey.AUTO_REFRESH_TABLES: + setAutoRefreshTables(value === "true") + break } } @@ -139,6 +151,7 @@ export const LocalStorageProvider = ({ resultsSplitterBasis, updateSettings, exampleQueriesVisited, + autoRefreshTables, }} > {children} diff --git a/packages/web-console/src/providers/LocalStorageProvider/types.ts b/packages/web-console/src/providers/LocalStorageProvider/types.ts index bfb600706..4bf06a02e 100644 --- a/packages/web-console/src/providers/LocalStorageProvider/types.ts +++ b/packages/web-console/src/providers/LocalStorageProvider/types.ts @@ -31,4 +31,5 @@ export type LocalConfig = { editorSplitterBasis: number resultsSplitterBasis: number exampleQueriesVisited: boolean + autoRefreshTables: boolean } diff --git a/packages/web-console/src/scenes/Console/index.tsx b/packages/web-console/src/scenes/Console/index.tsx index 0b2210f31..96845fd76 100644 --- a/packages/web-console/src/scenes/Console/index.tsx +++ b/packages/web-console/src/scenes/Console/index.tsx @@ -142,7 +142,7 @@ const Console = () => { : 0, ) }} - selected={resultsSplitterBasis !== 0} + selected={editorSplitterBasis !== 0} > @@ -167,6 +167,7 @@ const Console = () => { diff --git a/packages/web-console/src/scenes/Editor/Menu/index.tsx b/packages/web-console/src/scenes/Editor/Menu/index.tsx index cddfcb7b2..8e7c4cb2a 100644 --- a/packages/web-console/src/scenes/Editor/Menu/index.tsx +++ b/packages/web-console/src/scenes/Editor/Menu/index.tsx @@ -31,11 +31,8 @@ import { Menu as _MenuIcon } from "@styled-icons/remix-fill" import { CornerDownLeft } from "@styled-icons/evaicons-solid" import { - ErrorButton, PaneMenu, PopperToggle, - SecondaryButton, - SuccessButton, TransitionDuration, TransparentButton, useKeyPress, @@ -49,6 +46,7 @@ import { useLocalStorage } from "../../../providers/LocalStorageProvider" import { StoreKey } from "../../../utils/localStorage/types" import { DocSearch } from "@docsearch/react" import { useSettings } from "../../../providers" +import { Button } from "@questdb/react-components" import "@docsearch/css" @@ -68,15 +66,15 @@ const Separator = styled.div` flex: 1; ` -const QueryPickerButton = styled(SecondaryButton)<{ - firstTimeVisitor: boolean +const QueryPickerButton = styled(Button)<{ + $firstTimeVisitor: boolean }>` position: relative; margin: 0 1rem; flex: 0 0 auto; - ${({ firstTimeVisitor }) => - firstTimeVisitor && + ${({ $firstTimeVisitor }) => + $firstTimeVisitor && `&:after { border-radius: 50%; content: ""; @@ -187,7 +185,10 @@ const Menu = () => { active={queriesPopperActive} onToggle={handleQueriesToggle} trigger={ - + Example queries @@ -203,23 +204,30 @@ const Menu = () => { {running.value && ( - - - Cancel - + )} {!running.value && ( - - - Run + )} diff --git a/packages/web-console/src/scenes/Editor/Monaco/index.tsx b/packages/web-console/src/scenes/Editor/Monaco/index.tsx index 49fc61e6d..72f1d95a1 100644 --- a/packages/web-console/src/scenes/Editor/Monaco/index.tsx +++ b/packages/web-console/src/scenes/Editor/Monaco/index.tsx @@ -381,7 +381,7 @@ const MonacoEditor = () => { }), ) } - }, 250) + }, 1000) void quest .queryRaw(request.query, { limit: "0,1000", explain: true }) diff --git a/packages/web-console/src/scenes/Result/index.tsx b/packages/web-console/src/scenes/Result/index.tsx index 01ee8c3c7..843fecb3d 100644 --- a/packages/web-console/src/scenes/Result/index.tsx +++ b/packages/web-console/src/scenes/Result/index.tsx @@ -36,6 +36,7 @@ import { PaneContent, PaneWrapper, PopperHover, + PrimaryToggleButton, Text, Tooltip, } from "../../components" @@ -73,11 +74,10 @@ const Actions = styled.div` display: grid; grid-auto-flow: column; grid-auto-columns: max-content; - gap: 1rem; + gap: 0; align-items: center; justify-content: flex-end; padding: 0 1rem; - width: 100%; height: 4.5rem; background: ${({ theme }) => theme.color.backgroundDarker}; @@ -167,22 +167,22 @@ const Result = ({ viewMode }: { viewMode: ResultViewMode }) => { { tooltipText: "Freeze left column", trigger: ( - + ), }, { tooltipText: "Move selected column to the front", trigger: ( ), @@ -201,7 +204,7 @@ const Result = ({ viewMode }: { viewMode: ResultViewMode }) => { tooltipText: "Refresh", trigger: ( - } + + {selectOpen && ( + - Settings - - - - - - - {tables.length > 0 && ( - - - - Copy schemas to clipboard - - - )} - + + } + > + Copy schemas to clipboard + + )} + + {selectOpen && ( + { + selectedTables.length === tables?.length + ? setSelectedTables([]) + : setSelectedTables( + tables?.map((t) => t.table_name) ?? [], + ) + }} + > + + + } + > + + {selectedTables.length === tables?.length + ? "Deselect" + : "Select"}{" "} + all + + + )} + + {selectOpen && ( + { + setSelectedTables([]) + setSelectOpen(false) + }} + > + + + } + > + Cancel + + )} + + {!selectOpen && ( + { + if (selectOpen) { + setSelectedTables([]) + } + setSelectOpen(!selectOpen) + }} + {...(selectOpen ? { className: "selected" } : {})} + selected={selectOpen} + disabled={tables?.length === 0} + > + + + } + > + Select + + )} + + {!selectOpen && ( + { + updateSettings( + StoreKey.AUTO_REFRESH_TABLES, + !autoRefreshTables, + ) + void fetchTables() + void fetchColumns() + }} + selected={autoRefreshTables} > - Refresh tables - - - - + + } + > + + Auto refresh{" "} + {autoRefreshTables ? "enabled" : "disabled"} + + + )} )} } shadow={!scrollAtTop} /> +