diff --git a/src/components/EditorWindow.tsx b/src/components/EditorWindow.tsx index 3415d97..75671ce 100644 --- a/src/components/EditorWindow.tsx +++ b/src/components/EditorWindow.tsx @@ -19,6 +19,7 @@ import useResizeObserver from "@react-hook/resize-observer"; import syntax from "../editor/syntax"; import theme from "../editor/theme"; import { editorWindow, editorWrapper } from "../style"; +import QueryHistory from "./QueryHistory"; type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor; @@ -53,6 +54,8 @@ const EditorWindow: FunctionComponent = ({ const uiTheme = useTheme(); const [currentScript, setCurrentScript] = useState(""); + const [showHistory, setShowHistory] = useState(false); + const [queryHistory, setQueryHistory] = useState([]); // Find out if system is in dark mode so we can use the appropriate editor theme const [isDarkMode, setIsDarkMode] = useState(false); @@ -216,8 +219,12 @@ const EditorWindow: FunctionComponent = ({ // If selected text use that, otherwise send full script script = selected && selected != "" ? selected : currentScript; + // Remove trailing ; if (script) script = script.replace(/;$/, ""); + // Story query in query history + setQueryHistory((h) => [...h, script]); + // Load actual results onExecuteQuery(script); } @@ -273,6 +280,15 @@ const EditorWindow: FunctionComponent = ({ saveScript(); }, }, + { + key: "history", + title: "Query History", + iconProps: { iconName: "FullHistory" }, + className: "history-button", + onClick: () => { + setShowHistory(true); + }, + }, ]; const overflowItems: ICommandBarItemProps[] = [ @@ -376,6 +392,11 @@ const EditorWindow: FunctionComponent = ({ onChange={updateScripts} /> + setShowHistory(false)} + /> ); }; diff --git a/src/components/QueryHistory.tsx b/src/components/QueryHistory.tsx new file mode 100644 index 0000000..9efba85 --- /dev/null +++ b/src/components/QueryHistory.tsx @@ -0,0 +1,109 @@ +import { + DetailsList, + IColumn, + IconButton, + Panel, + SelectionMode, + Stack, + Text, +} from "@fluentui/react"; +import React, { FC, ReactNode } from "react"; + +interface QueryHistoryProps { + history: string[]; + show: boolean; + onDismiss: () => void; +} + +const QueryHistory: FC = ({ + history = [], + show = false, + onDismiss, +}: QueryHistoryProps) => { + const items = history.map((q) => { + return { + query: q, //.substr(0, 100), + actions: ["copy"], + }; + }); + + const columns = [ + { + key: "query", + name: "Query", + fieldName: "query", + minWidth: 100, + maxWidth: 200, + isResizable: false, + }, + { + key: "actions", + name: "", + fieldName: "actions", + minWidth: 10, + maxWidth: 100, + isResizable: false, + }, + ]; + + const copyQuery = (query: string) => { + navigator.clipboard.writeText(query); + onDismiss(); + }; + + const historyActions = { + copy: (query: string) => ( + copyQuery(query)} + /> + ), + }; + + const renderHistoryEntry = ( + item?: any, + index?: number, + column?: IColumn + ): ReactNode => { + if (!column || !item) return <>; + + return ( + <> + {column.key === "query" ? ( + <> + + + {item.query} + + + + ) : ( + item.actions.map((a: keyof typeof historyActions) => { + return historyActions[a](item.query); + }) + )} + + ); + }; + + return ( + + + + ); +}; + +export default QueryHistory; diff --git a/tests/query-history.js b/tests/query-history.js new file mode 100644 index 0000000..bd9a61c --- /dev/null +++ b/tests/query-history.js @@ -0,0 +1,84 @@ +const assert = require("assert"); +const cleanupDB = require("./cleanup"); + +const isMac = process.platform === "darwin"; + +describe("Query History", function () { + before(async function () { + this.timeout(5000); + this.appWindow = await this.app.firstWindow(); + await this.appWindow.waitForLoadState("domcontentloaded"); + this.modal = await this.appWindow.$(".server-management-modal"); + await this.modal.waitForSelector(".server-list li"); + const server = await this.modal.$(":nth-match(.server-list li, 1)"); + await server.click(); + await this.appWindow.waitForTimeout(50); + + const connectButton = await this.modal.$('button:has-text("Connect")'); + + await connectButton.click(); + await this.modal.waitForElementState("hidden"); + + const editor = await this.appWindow.$(".monaco-editor textarea"); + const goButton = await this.appWindow.$(".go-button"); + + const query = "t:flip `name`iq!(`Dent`Beeblebrox`Prefect;98 42 126)"; + await editor.press(`${isMac ? "Meta" : "Control"}+a`); + await editor.type(query); + await goButton.click(); + + const query2 = "tables[]"; + await editor.press(`${isMac ? "Meta" : "Control"}+a`); + await editor.type(query2); + await goButton.click(); + + const query3 = "t"; + await editor.press(`${isMac ? "Meta" : "Control"}+a`); + await editor.type(query3); + await goButton.click(); + + await this.appWindow.waitForSelector( + ":nth-match(.table-list .ms-GroupedList-group, 1)" + ); + }); + + it("should display the Query History", async function () { + const historyButton = await this.appWindow.$(".history-button"); + await historyButton.click(); + + const panel = await this.appWindow.waitForSelector( + ".ms-Panel.is-open.history-panel" + ); + assert.notStrictEqual(panel, null); + + const entries = await panel.$$(".ms-DetailsRow"); + + assert.strictEqual(entries.length, 3); + }); + + it("should let me select copy a previous query", async function () { + const panel = await this.appWindow.waitForSelector( + ".ms-Panel.is-open.history-panel" + ); + const row = await panel.$(":nth-match(.ms-DetailsRow, 2)"); + + const copyButton = await row.$(".ms-Button--icon"); + await copyButton.click(); + + await this.appWindow.waitForSelector(".ms-Panel.is-open.history-panel", { + state: "hidden", + }); + + const editor = await this.appWindow.$(".monaco-editor textarea"); + + await editor.press(`${isMac ? "Meta" : "Control"}+a`); + await editor.press(`${isMac ? "Meta" : "Control"}+v`); + + assert.strictEqual(await editor.inputValue(), "tables[]"); + }); + + after(async function () { + await cleanupDB("delete t from `.; delete t2 from `."); + await this.appWindow.reload(); + }); +});