Skip to content

Commit ad58069

Browse files
authored
Merge pull request #96 from ScottLogic/feature/query-history
feat: add query history Closes #62
2 parents 46fd753 + 3512c65 commit ad58069

File tree

3 files changed

+214
-0
lines changed

3 files changed

+214
-0
lines changed

src/components/EditorWindow.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import useResizeObserver from "@react-hook/resize-observer";
1919
import syntax from "../editor/syntax";
2020
import theme from "../editor/theme";
2121
import { editorWindow, editorWrapper } from "../style";
22+
import QueryHistory from "./QueryHistory";
2223

2324
type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;
2425

@@ -53,6 +54,8 @@ const EditorWindow: FunctionComponent<EditorWindowProps> = ({
5354
const uiTheme = useTheme();
5455

5556
const [currentScript, setCurrentScript] = useState("");
57+
const [showHistory, setShowHistory] = useState(false);
58+
const [queryHistory, setQueryHistory] = useState<string[]>([]);
5659

5760
// Find out if system is in dark mode so we can use the appropriate editor theme
5861
const [isDarkMode, setIsDarkMode] = useState(false);
@@ -216,8 +219,12 @@ const EditorWindow: FunctionComponent<EditorWindowProps> = ({
216219
// If selected text use that, otherwise send full script
217220
script = selected && selected != "" ? selected : currentScript;
218221

222+
// Remove trailing ;
219223
if (script) script = script.replace(/;$/, "");
220224

225+
// Story query in query history
226+
setQueryHistory((h) => [...h, script]);
227+
221228
// Load actual results
222229
onExecuteQuery(script);
223230
}
@@ -273,6 +280,15 @@ const EditorWindow: FunctionComponent<EditorWindowProps> = ({
273280
saveScript();
274281
},
275282
},
283+
{
284+
key: "history",
285+
title: "Query History",
286+
iconProps: { iconName: "FullHistory" },
287+
className: "history-button",
288+
onClick: () => {
289+
setShowHistory(true);
290+
},
291+
},
276292
];
277293

278294
const overflowItems: ICommandBarItemProps[] = [
@@ -376,6 +392,11 @@ const EditorWindow: FunctionComponent<EditorWindowProps> = ({
376392
onChange={updateScripts}
377393
/>
378394
</div>
395+
<QueryHistory
396+
show={showHistory}
397+
history={queryHistory}
398+
onDismiss={() => setShowHistory(false)}
399+
/>
379400
</Stack>
380401
);
381402
};

src/components/QueryHistory.tsx

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import {
2+
DetailsList,
3+
IColumn,
4+
IconButton,
5+
Panel,
6+
SelectionMode,
7+
Stack,
8+
Text,
9+
} from "@fluentui/react";
10+
import React, { FC, ReactNode } from "react";
11+
12+
interface QueryHistoryProps {
13+
history: string[];
14+
show: boolean;
15+
onDismiss: () => void;
16+
}
17+
18+
const QueryHistory: FC<QueryHistoryProps> = ({
19+
history = [],
20+
show = false,
21+
onDismiss,
22+
}: QueryHistoryProps) => {
23+
const items = history.map((q) => {
24+
return {
25+
query: q, //.substr(0, 100),
26+
actions: ["copy"],
27+
};
28+
});
29+
30+
const columns = [
31+
{
32+
key: "query",
33+
name: "Query",
34+
fieldName: "query",
35+
minWidth: 100,
36+
maxWidth: 200,
37+
isResizable: false,
38+
},
39+
{
40+
key: "actions",
41+
name: "",
42+
fieldName: "actions",
43+
minWidth: 10,
44+
maxWidth: 100,
45+
isResizable: false,
46+
},
47+
];
48+
49+
const copyQuery = (query: string) => {
50+
navigator.clipboard.writeText(query);
51+
onDismiss();
52+
};
53+
54+
const historyActions = {
55+
copy: (query: string) => (
56+
<IconButton
57+
iconProps={{ iconName: "copy" }}
58+
key="copy"
59+
onClick={() => copyQuery(query)}
60+
/>
61+
),
62+
};
63+
64+
const renderHistoryEntry = (
65+
item?: any,
66+
index?: number,
67+
column?: IColumn
68+
): ReactNode => {
69+
if (!column || !item) return <></>;
70+
71+
return (
72+
<>
73+
{column.key === "query" ? (
74+
<>
75+
<Stack verticalAlign="center" style={{ height: "100%" }}>
76+
<Text block={true} nowrap={true}>
77+
{item.query}
78+
</Text>
79+
</Stack>
80+
</>
81+
) : (
82+
item.actions.map((a: keyof typeof historyActions) => {
83+
return historyActions[a](item.query);
84+
})
85+
)}
86+
</>
87+
);
88+
};
89+
90+
return (
91+
<Panel
92+
headerText="History"
93+
isOpen={show}
94+
onDismiss={onDismiss}
95+
className="history-panel"
96+
// You MUST provide this prop! Otherwise screen readers will just say "button" with no label.
97+
closeButtonAriaLabel="Close"
98+
>
99+
<DetailsList
100+
items={items}
101+
columns={columns}
102+
selectionMode={SelectionMode.none}
103+
onRenderItemColumn={renderHistoryEntry}
104+
/>
105+
</Panel>
106+
);
107+
};
108+
109+
export default QueryHistory;

tests/query-history.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
const assert = require("assert");
2+
const cleanupDB = require("./cleanup");
3+
4+
const isMac = process.platform === "darwin";
5+
6+
describe("Query History", function () {
7+
before(async function () {
8+
this.timeout(5000);
9+
this.appWindow = await this.app.firstWindow();
10+
await this.appWindow.waitForLoadState("domcontentloaded");
11+
this.modal = await this.appWindow.$(".server-management-modal");
12+
await this.modal.waitForSelector(".server-list li");
13+
const server = await this.modal.$(":nth-match(.server-list li, 1)");
14+
await server.click();
15+
await this.appWindow.waitForTimeout(50);
16+
17+
const connectButton = await this.modal.$('button:has-text("Connect")');
18+
19+
await connectButton.click();
20+
await this.modal.waitForElementState("hidden");
21+
22+
const editor = await this.appWindow.$(".monaco-editor textarea");
23+
const goButton = await this.appWindow.$(".go-button");
24+
25+
const query = "t:flip `name`iq!(`Dent`Beeblebrox`Prefect;98 42 126)";
26+
await editor.press(`${isMac ? "Meta" : "Control"}+a`);
27+
await editor.type(query);
28+
await goButton.click();
29+
30+
const query2 = "tables[]";
31+
await editor.press(`${isMac ? "Meta" : "Control"}+a`);
32+
await editor.type(query2);
33+
await goButton.click();
34+
35+
const query3 = "t";
36+
await editor.press(`${isMac ? "Meta" : "Control"}+a`);
37+
await editor.type(query3);
38+
await goButton.click();
39+
40+
await this.appWindow.waitForSelector(
41+
":nth-match(.table-list .ms-GroupedList-group, 1)"
42+
);
43+
});
44+
45+
it("should display the Query History", async function () {
46+
const historyButton = await this.appWindow.$(".history-button");
47+
await historyButton.click();
48+
49+
const panel = await this.appWindow.waitForSelector(
50+
".ms-Panel.is-open.history-panel"
51+
);
52+
assert.notStrictEqual(panel, null);
53+
54+
const entries = await panel.$$(".ms-DetailsRow");
55+
56+
assert.strictEqual(entries.length, 3);
57+
});
58+
59+
it("should let me select copy a previous query", async function () {
60+
const panel = await this.appWindow.waitForSelector(
61+
".ms-Panel.is-open.history-panel"
62+
);
63+
const row = await panel.$(":nth-match(.ms-DetailsRow, 2)");
64+
65+
const copyButton = await row.$(".ms-Button--icon");
66+
await copyButton.click();
67+
68+
await this.appWindow.waitForSelector(".ms-Panel.is-open.history-panel", {
69+
state: "hidden",
70+
});
71+
72+
const editor = await this.appWindow.$(".monaco-editor textarea");
73+
74+
await editor.press(`${isMac ? "Meta" : "Control"}+a`);
75+
await editor.press(`${isMac ? "Meta" : "Control"}+v`);
76+
77+
assert.strictEqual(await editor.inputValue(), "tables[]");
78+
});
79+
80+
after(async function () {
81+
await cleanupDB("delete t from `.; delete t2 from `.");
82+
await this.appWindow.reload();
83+
});
84+
});

0 commit comments

Comments
 (0)