From 85dc7e7349fff40751c7352e9486c1d0ce9fb7a7 Mon Sep 17 00:00:00 2001 From: Rick Bergfalk Date: Sun, 14 Mar 2021 22:19:17 -0500 Subject: [PATCH 01/13] Add optional statements and filters to batches list API --- server/models/batches.js | 32 +++++++++++++++++++++++++ server/routes/batches.js | 23 ++++++++++++++++-- server/test/api/batches.js | 48 +++++++++++++++++++++++++++++++++++++- 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/server/models/batches.js b/server/models/batches.js index 9a67dafeb..739d67aa4 100644 --- a/server/models/batches.js +++ b/server/models/batches.js @@ -1,4 +1,5 @@ const sqlLimiter = require('sql-limiter'); +const _ = require('lodash'); const ensureJson = require('./ensure-json'); class Batches { @@ -41,6 +42,37 @@ class Batches { return items; } + /** + * Get all batches and more for user and query id + * @param {object} user + * @param {string} [queryId] + * @param {boolean} [includeStatements] + */ + async findAllForUserQuery(user, queryId = null, includeStatements = false) { + let batches = await this.sequelizeDb.Batches.findAll({ + where: { userId: user.id, queryId }, + }); + batches = batches.map((item) => item.toJSON()); + + if (includeStatements) { + const batchIds = batches.map((batch) => batch.id); + let statements = await this.sequelizeDb.Statements.findAll({ + where: { batchId: batchIds }, + }); + statements = statements.map((statement) => statement.toJSON()); + const statementsByBatchId = _.groupBy(statements, 'batchId'); + batches.forEach((batch) => { + batch.statements = statementsByBatchId[batch.id]; + }); + } + + // TODO: it'd be nice if there was a small set of statement results to return here for query execution history + // Unsure how best to implement at this time. + // Would additional reads work here or should small result preview be stored on statements table? + + return batches; + } + /** * Create a new batch (and statements) * selectedText is parsed out into statements diff --git a/server/routes/batches.js b/server/routes/batches.js index a7c28b717..219e1439c 100644 --- a/server/routes/batches.js +++ b/server/routes/batches.js @@ -66,8 +66,27 @@ router.post( * @param {Res} res */ async function list(req, res) { - const { models, user } = req; - const batches = await models.batches.findAllForUser(user); + const { models, user, query } = req; + const { queryId, includeStatements } = query; + + let batches; + if (queryId) { + const cleanedQueryId = queryId === 'null' ? null : queryId; + let cleanedIncludeStatements = false; + if (includeStatements) { + cleanedIncludeStatements = + includeStatements.toString().toLowerCase().trim() === 'true'; + } + + batches = await models.batches.findAllForUserQuery( + user, + cleanedQueryId, + cleanedIncludeStatements + ); + } else { + batches = await models.batches.findAllForUser(user); + } + return res.utils.data(batches); } diff --git a/server/test/api/batches.js b/server/test/api/batches.js index cbb3c8155..e7b3eafbe 100644 --- a/server/test/api/batches.js +++ b/server/test/api/batches.js @@ -24,6 +24,7 @@ describe('api/batches', function () { let query; let connection; let batch; + let batchWithoutQueryId; let statement1; let statement2; @@ -72,6 +73,51 @@ describe('api/batches', function () { assert.equal(batch.status, 'started'); }); + it('creates batch without query id', async function () { + batchWithoutQueryId = await utils.post('admin', `/api/batches`, { + connectionId: connection.id, + batchText: queryText, + selectedText: queryText, + }); + assert(batchWithoutQueryId.id); + }); + + it('gets list for query id including statements', async function () { + const batches = await utils.get( + 'admin', + `/api/batches?queryId=${query.id}&includeStatements=true` + ); + const foundBatch = batches.find((b) => b.id === batch.id); + assert.strictEqual(foundBatch.statements.length, 2); + }); + + it('gets list for query id without statements', async function () { + const batches = await utils.get( + 'admin', + `/api/batches?queryId=${query.id}&includeStatements=false` + ); + const foundBatch = batches.find((b) => b.id === batch.id); + assert(!foundBatch.statements); + }); + + it('gets list for no queryId including statements', async function () { + const batches = await utils.get( + 'admin', + `/api/batches?queryId=null&includeStatements=true` + ); + const foundBatch = batches.find((b) => b.id === batchWithoutQueryId.id); + assert.strictEqual(foundBatch.statements.length, 2); + }); + + it('gets list for no query id without statements', async function () { + const batches = await utils.get( + 'admin', + `/api/batches?queryId=null&includeStatements=false` + ); + const foundBatch = batches.find((b) => b.id === batchWithoutQueryId.id); + assert(!foundBatch.statements); + }); + it('GETs finished result', async function () { batch = await utils.get('admin', `/api/batches/${batch.id}`); while (batch.status !== 'finished' && batch.status !== 'errored') { @@ -177,7 +223,7 @@ describe('api/batches', function () { it('Only batch creator can view batch', async function () { const adminBatches = await utils.get('admin', `/api/batches`); - assert.equal(adminBatches.length, 1); + assert.equal(adminBatches.length, 2); const editorBatches = await utils.get('editor', `/api/batches`); assert.equal(editorBatches.length, 0); await utils.get('editor', `/api/batches/${batch.id}`, 403); From e42cc29ff16648b237a219f94c12037ce63d989c Mon Sep 17 00:00:00 2001 From: Rick Bergfalk Date: Tue, 16 Mar 2021 10:22:21 -0500 Subject: [PATCH 02/13] Initial button and drawer WIP --- client/src/queryEditor/HistoryDrawer.tsx | 90 +++++++++++++++++++ client/src/queryEditor/Toolbar.tsx | 2 + .../src/queryEditor/ToolbarHistoryButton.tsx | 22 +++++ client/src/utilities/api.ts | 6 ++ 4 files changed, 120 insertions(+) create mode 100644 client/src/queryEditor/HistoryDrawer.tsx create mode 100644 client/src/queryEditor/ToolbarHistoryButton.tsx diff --git a/client/src/queryEditor/HistoryDrawer.tsx b/client/src/queryEditor/HistoryDrawer.tsx new file mode 100644 index 000000000..05ba77efd --- /dev/null +++ b/client/src/queryEditor/HistoryDrawer.tsx @@ -0,0 +1,90 @@ +import React, { useEffect, useRef } from 'react'; +import Drawer from '../common/Drawer'; +import ErrorBlock from '../common/ErrorBlock'; +import InfoBlock from '../common/InfoBlock'; +import SpinKitCube from '../common/SpinKitCube'; +import { api } from '../utilities/api'; + +type Props = { + visible?: boolean; + onClose: (...args: any[]) => any; +}; + +function HistoryDrawer({ onClose, visible }: Props) { + const bottomEl = useRef(null); + const containerEl = useRef(null); + + let { + data: queryBatches, + error: queryBatchHistoryError, + } = api.useQueryBatchHistory('null'); + + const fetching = !queryBatches; + + function handleClose() { + onClose(); + } + + let content = null; + + if (fetching) { + content = ( +
+ +
+ ); + } else if (queryBatchHistoryError) { + content = Error getting execution history; + } else if (queryBatches) { + if (queryBatches.length === 0) { + content = ( +
+ No queries found +
+ ); + } + content = queryBatches + .map((batch) => { + return ( +
+
{batch.createdAt}
+
{batch.status}
+
{batch.batchText}
+ {(batch.statements || []).map((statement) => { + return
{statement.durationMs}
; + })} +
+
+ ); + }) + .concat([
]); + } + + const batchLength = queryBatches ? queryBatches.length : 0; + useEffect(() => { + if (visible && batchLength > 0) { + setTimeout(() => { + bottomEl && bottomEl.current && bottomEl.current.scrollIntoView(false); + }); + } + }, [batchLength, visible]); + + return ( + +
+ {content} +
+
+ ); +} + +export default React.memo(HistoryDrawer); diff --git a/client/src/queryEditor/Toolbar.tsx b/client/src/queryEditor/Toolbar.tsx index e136d5328..e58af8ec9 100644 --- a/client/src/queryEditor/Toolbar.tsx +++ b/client/src/queryEditor/Toolbar.tsx @@ -2,6 +2,7 @@ import React from 'react'; import ConnectionDropDown from './ConnectionDropdown'; import ToolbarChartButton from './ToolbarChartButton'; import ToolbarConnectionClientButton from './ToolbarConnectionClientButton'; +import ToolbarHistoryButton from './ToolbarHistoryButton'; import ToolbarQueryName from './ToolbarQueryName'; import ToolbarRunButton from './ToolbarRunButton'; import ToolbarSpacer from './ToolbarSpacer'; @@ -27,6 +28,7 @@ function Toolbar() { +
diff --git a/client/src/queryEditor/ToolbarHistoryButton.tsx b/client/src/queryEditor/ToolbarHistoryButton.tsx new file mode 100644 index 000000000..45017d66b --- /dev/null +++ b/client/src/queryEditor/ToolbarHistoryButton.tsx @@ -0,0 +1,22 @@ +import HistoryIcon from 'mdi-react/HistoryIcon'; +import React, { useState } from 'react'; +import IconButton from '../common/IconButton'; +import HistoryDrawer from './HistoryDrawer'; + +function ToolbarHistoryButton() { + const [show, setShow] = useState(false); + + return ( + <> + setShow(true)} + > + + + {show && setShow(false)} />} + + ); +} + +export default React.memo(ToolbarHistoryButton); diff --git a/client/src/utilities/api.ts b/client/src/utilities/api.ts index 21969d8fc..0723a2f2b 100644 --- a/client/src/utilities/api.ts +++ b/client/src/utilities/api.ts @@ -129,6 +129,12 @@ export const api = { return useSWR(`/api/batches/${batchId}`); }, + useQueryBatchHistory(queryId: string) { + return useSWR( + `/api/batches?queryId=${queryId}&includeStatements=true` + ); + }, + getStatementResults(statementId: string) { return this.get(`/api/statements/${statementId}/results`); }, From 05ebc76e9c6457a9f1c17b739132bc108661b3b2 Mon Sep 17 00:00:00 2001 From: Rick Bergfalk Date: Tue, 16 Mar 2021 10:32:54 -0500 Subject: [PATCH 03/13] Scope code style --- client/src/common/Code.module.css | 4 ++-- client/src/common/Code.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/common/Code.module.css b/client/src/common/Code.module.css index b3d8d89f7..4d91428fa 100644 --- a/client/src/common/Code.module.css +++ b/client/src/common/Code.module.css @@ -1,4 +1,4 @@ -pre { +.pre { font-family: 'Courier 10 Pitch', Courier, monospace; font-size: 95%; line-height: 140%; @@ -6,7 +6,7 @@ pre { word-wrap: break-word; } -code { +.code { font-family: Monaco, Consolas, 'Andale Mono', 'DejaVu Sans Mono', monospace; font-size: 95%; line-height: 140%; diff --git a/client/src/common/Code.tsx b/client/src/common/Code.tsx index 0fbc96cfe..8b8bb6029 100644 --- a/client/src/common/Code.tsx +++ b/client/src/common/Code.tsx @@ -6,7 +6,7 @@ export interface Props extends React.HTMLAttributes { } const Code = ({ children, className, type, ...rest }: Props) => { - const cs = []; + const cs = [styles.code]; if (className) { cs.push(className); @@ -17,7 +17,7 @@ const Code = ({ children, className, type, ...rest }: Props) => { } return ( -
+    
       
         {children}
       

From d3752886eff3c1b6d6e74fbed90e6a64155edac6 Mon Sep 17 00:00:00 2001
From: Rick Bergfalk 
Date: Tue, 16 Mar 2021 10:37:55 -0500
Subject: [PATCH 04/13] Add prism code rendering

---
 client/package-lock.json                 | 15 +++++++++++++++
 client/package.json                      |  1 +
 client/src/queryEditor/HistoryDrawer.tsx | 21 ++++++++++++++++++++-
 3 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/client/package-lock.json b/client/package-lock.json
index 659c49beb..44e7e0840 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -37,6 +37,7 @@
         "mdi-react": "^7.4.0",
         "mitt": "^2.1.0",
         "parse-link-header": "^1.0.1",
+        "prism-react-renderer": "^1.2.0",
         "prop-types": "^15.7.2",
         "query-string": "^6.13.8",
         "react": "^17.0.1",
@@ -3576,6 +3577,14 @@
         "node": ">= 10"
       }
     },
+    "node_modules/prism-react-renderer": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.2.0.tgz",
+      "integrity": "sha512-GHqzxLYImx1iKN1jJURcuRoA/0ygCcNhfGw1IT8nPIMzarmKQ3Nc+JcG0gi8JXQzuh0C5ShE4npMIoqNin40hg==",
+      "peerDependencies": {
+        "react": ">=0.14.9"
+      }
+    },
     "node_modules/progress": {
       "version": "2.0.3",
       "dev": true,
@@ -7221,6 +7230,12 @@
         "react-is": "^17.0.1"
       }
     },
+    "prism-react-renderer": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.2.0.tgz",
+      "integrity": "sha512-GHqzxLYImx1iKN1jJURcuRoA/0ygCcNhfGw1IT8nPIMzarmKQ3Nc+JcG0gi8JXQzuh0C5ShE4npMIoqNin40hg==",
+      "requires": {}
+    },
     "progress": {
       "version": "2.0.3",
       "dev": true
diff --git a/client/package.json b/client/package.json
index d198f0459..5ee20533b 100644
--- a/client/package.json
+++ b/client/package.json
@@ -32,6 +32,7 @@
     "mdi-react": "^7.4.0",
     "mitt": "^2.1.0",
     "parse-link-header": "^1.0.1",
+    "prism-react-renderer": "^1.2.0",
     "prop-types": "^15.7.2",
     "query-string": "^6.13.8",
     "react": "^17.0.1",
diff --git a/client/src/queryEditor/HistoryDrawer.tsx b/client/src/queryEditor/HistoryDrawer.tsx
index 05ba77efd..4dc38e127 100644
--- a/client/src/queryEditor/HistoryDrawer.tsx
+++ b/client/src/queryEditor/HistoryDrawer.tsx
@@ -1,9 +1,11 @@
 import React, { useEffect, useRef } from 'react';
+import Highlight, { defaultProps } from 'prism-react-renderer';
 import Drawer from '../common/Drawer';
 import ErrorBlock from '../common/ErrorBlock';
 import InfoBlock from '../common/InfoBlock';
 import SpinKitCube from '../common/SpinKitCube';
 import { api } from '../utilities/api';
+import theme from 'prism-react-renderer/themes/vsLight';
 
 type Props = {
   visible?: boolean;
@@ -49,7 +51,24 @@ function HistoryDrawer({ onClose, visible }: Props) {
           
{batch.createdAt}
{batch.status}
-
{batch.batchText}
+ + {({ className, style, tokens, getLineProps, getTokenProps }) => ( +
+                  {tokens.map((line, i) => (
+                    
+ {line.map((token, key) => ( + + ))} +
+ ))} +
+ )} +
{(batch.statements || []).map((statement) => { return
{statement.durationMs}
; })} From 5fdf88f30b13808290feca5001d5bf2a7bbabf84 Mon Sep 17 00:00:00 2001 From: Rick Bergfalk Date: Tue, 16 Mar 2021 11:59:42 -0500 Subject: [PATCH 05/13] Better looking run history --- client/src/css/index.css | 24 ++++++- client/src/queryEditor/HistoryDrawer.tsx | 81 +++++++++++++++++++++--- client/src/types.ts | 6 ++ client/src/utilities/api.ts | 3 +- server/routes/batches.js | 7 ++ 5 files changed, 110 insertions(+), 11 deletions(-) diff --git a/client/src/css/index.css b/client/src/css/index.css index e59799ee4..771b91fa9 100644 --- a/client/src/css/index.css +++ b/client/src/css/index.css @@ -139,6 +139,28 @@ input { text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); } +.sp-error { + display: flex; + justify-content: center; + align-items: center; + overflow: auto; + + font-size: 1.3rem; + padding: 16px; + text-align: center; + color: hsl(323, 100%, 42%); +} + +.sp-info { + display: flex; + justify-content: center; + align-items: center; + + font-size: 1.3rem; + padding: 16px; + text-align: center; +} + /* Might be trying out more of just plain css, less of css.modules */ .sp-error-block { height: 100%; @@ -163,7 +185,7 @@ input { align-items: center; font-size: 1.3rem; - padding: 24px; + padding: 16px; text-align: center; } diff --git a/client/src/queryEditor/HistoryDrawer.tsx b/client/src/queryEditor/HistoryDrawer.tsx index 4dc38e127..e5f0ea737 100644 --- a/client/src/queryEditor/HistoryDrawer.tsx +++ b/client/src/queryEditor/HistoryDrawer.tsx @@ -1,11 +1,15 @@ import React, { useEffect, useRef } from 'react'; import Highlight, { defaultProps } from 'prism-react-renderer'; +import humanizeDuration from 'humanize-duration'; +import capitalize from 'lodash/capitalize'; import Drawer from '../common/Drawer'; import ErrorBlock from '../common/ErrorBlock'; import InfoBlock from '../common/InfoBlock'; import SpinKitCube from '../common/SpinKitCube'; import { api } from '../utilities/api'; import theme from 'prism-react-renderer/themes/vsLight'; +import { useSessionQueryId, useSessionQueryName } from '../stores/editor-store'; +import styles from './StatementsTable.module.css'; type Props = { visible?: boolean; @@ -16,10 +20,13 @@ function HistoryDrawer({ onClose, visible }: Props) { const bottomEl = useRef(null); const containerEl = useRef(null); + const queryId = useSessionQueryId() || 'null'; + const queryName = useSessionQueryName() || 'unsaved queries'; + let { data: queryBatches, error: queryBatchHistoryError, - } = api.useQueryBatchHistory('null'); + } = api.useQueryBatchHistory(queryId); const fetching = !queryBatches; @@ -48,13 +55,23 @@ function HistoryDrawer({ onClose, visible }: Props) { content = queryBatches .map((batch) => { return ( -
-
{batch.createdAt}
-
{batch.status}
+
+

{batch.createdAtCalendar}

+
+ {capitalize(batch.status)} in {humanizeDuration(batch.durationMs)} +
{({ className, style, tokens, getLineProps, getTokenProps }) => ( @@ -69,10 +86,52 @@ function HistoryDrawer({ onClose, visible }: Props) {
)} + {!batch.statements && batch.status === 'finished' ? ( +
+ Query results purged from storage +
+ ) : null} {(batch.statements || []).map((statement) => { - return
{statement.durationMs}
; + if (statement.error) { + return ( +
+ {statement.error.title} +
+ ); + } + + return ( + + + + {(statement?.columns || []).map((column) => ( + + ))} + + + + + +
{column.name}
+ + {statement.rowCount}{' '} + {statement.rowCount === 1 ? 'row' : 'rows'} + {statement.incomplete ? '(incomplete)' : ''} + +
+ ); })} -
); }) @@ -90,7 +149,7 @@ function HistoryDrawer({ onClose, visible }: Props) { return (
{content}
diff --git a/client/src/types.ts b/client/src/types.ts index cacb0e66a..5300060d8 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -76,6 +76,12 @@ export interface Batch { updatedAt: string | Date; } +export interface BatchHistoryItem extends Batch { + startTimeCalendar: string | Date; + stopTimeCalendar: string | Date; + createdAtCalendar: string | Date; +} + export type ConnectionFields = Record; export interface ConnectionDetail extends ConnectionFields { diff --git a/client/src/utilities/api.ts b/client/src/utilities/api.ts index 0723a2f2b..51cbfc7a6 100644 --- a/client/src/utilities/api.ts +++ b/client/src/utilities/api.ts @@ -5,6 +5,7 @@ import message from '../common/message'; import { AppInfo, Batch, + BatchHistoryItem, Connection, ConnectionAccess, ConnectionDetail, @@ -130,7 +131,7 @@ export const api = { }, useQueryBatchHistory(queryId: string) { - return useSWR( + return useSWR( `/api/batches?queryId=${queryId}&includeStatements=true` ); }, diff --git a/server/routes/batches.js b/server/routes/batches.js index 219e1439c..ec7b69a2b 100644 --- a/server/routes/batches.js +++ b/server/routes/batches.js @@ -1,4 +1,5 @@ require('../typedefs'); +const moment = require('moment'); const router = require('express').Router(); const mustBeAuthenticated = require('../middleware/must-be-authenticated.js'); const executeBatch = require('../lib/execute-batch'); @@ -87,6 +88,12 @@ async function list(req, res) { batches = await models.batches.findAllForUser(user); } + batches.forEach((batch) => { + batch.startTimeCalendar = moment(batch.startTime).calendar(); + batch.stopTimeCalendar = moment(batch.stopTime).calendar(); + batch.createdAtCalendar = moment(batch.createdAt).calendar(); + }); + return res.utils.data(batches); } From c0692dcb45bc23c3c088bfd06e4a399a9ab4a5a8 Mon Sep 17 00:00:00 2001 From: Rick Bergfalk Date: Tue, 16 Mar 2021 12:03:56 -0500 Subject: [PATCH 06/13] Fix tooltip --- client/src/queryEditor/ToolbarHistoryButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/queryEditor/ToolbarHistoryButton.tsx b/client/src/queryEditor/ToolbarHistoryButton.tsx index 45017d66b..693ecc6bf 100644 --- a/client/src/queryEditor/ToolbarHistoryButton.tsx +++ b/client/src/queryEditor/ToolbarHistoryButton.tsx @@ -9,7 +9,7 @@ function ToolbarHistoryButton() { return ( <> setShow(true)} > From 182cfd5cb42b708a0712a55a8ea185a3e13d7767 Mon Sep 17 00:00:00 2001 From: Rick Bergfalk Date: Tue, 16 Mar 2021 12:19:58 -0500 Subject: [PATCH 07/13] Show purge message for errors too --- client/src/queryEditor/HistoryDrawer.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/queryEditor/HistoryDrawer.tsx b/client/src/queryEditor/HistoryDrawer.tsx index e5f0ea737..e248ed311 100644 --- a/client/src/queryEditor/HistoryDrawer.tsx +++ b/client/src/queryEditor/HistoryDrawer.tsx @@ -86,7 +86,8 @@ function HistoryDrawer({ onClose, visible }: Props) {
)} - {!batch.statements && batch.status === 'finished' ? ( + {!batch.statements && + (batch.status === 'finished' || batch.status === 'error') ? (
Query results purged from storage
From cd9c7c36099f37bd613c73476acddb38b4a667ad Mon Sep 17 00:00:00 2001 From: Rick Bergfalk Date: Tue, 16 Mar 2021 12:20:21 -0500 Subject: [PATCH 08/13] Limit batch history --- server/models/batches.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/server/models/batches.js b/server/models/batches.js index 739d67aa4..285db8d60 100644 --- a/server/models/batches.js +++ b/server/models/batches.js @@ -47,10 +47,18 @@ class Batches { * @param {object} user * @param {string} [queryId] * @param {boolean} [includeStatements] + * @param {number} [limit] */ - async findAllForUserQuery(user, queryId = null, includeStatements = false) { + async findAllForUserQuery( + user, + queryId = null, + includeStatements = false, + limit = 40 + ) { let batches = await this.sequelizeDb.Batches.findAll({ where: { userId: user.id, queryId }, + limit, + order: [['createdAt', 'DESC']], }); batches = batches.map((item) => item.toJSON()); @@ -66,9 +74,8 @@ class Batches { }); } - // TODO: it'd be nice if there was a small set of statement results to return here for query execution history - // Unsure how best to implement at this time. - // Would additional reads work here or should small result preview be stored on statements table? + // Results are in desc order, but we'll return them in ascending + batches = _.sortBy(batches, ['createdAt']); return batches; } From 620195bbdfa1fdcf8caa523f51dd72663c32b04e Mon Sep 17 00:00:00 2001 From: Rick Bergfalk Date: Tue, 16 Mar 2021 12:33:26 -0500 Subject: [PATCH 09/13] Fix no history found message --- client/src/queryEditor/HistoryDrawer.tsx | 174 ++++++++++++----------- 1 file changed, 93 insertions(+), 81 deletions(-) diff --git a/client/src/queryEditor/HistoryDrawer.tsx b/client/src/queryEditor/HistoryDrawer.tsx index e248ed311..df6bc6c1d 100644 --- a/client/src/queryEditor/HistoryDrawer.tsx +++ b/client/src/queryEditor/HistoryDrawer.tsx @@ -48,95 +48,107 @@ function HistoryDrawer({ onClose, visible }: Props) { if (queryBatches.length === 0) { content = (
- No queries found + No run history found for this query.
); - } - content = queryBatches - .map((batch) => { - return ( -
-

{batch.createdAtCalendar}

-
- {capitalize(batch.status)} in {humanizeDuration(batch.durationMs)} -
- { + return ( +
- {({ className, style, tokens, getLineProps, getTokenProps }) => ( -
-                  {tokens.map((line, i) => (
-                    
- {line.map((token, key) => ( - - ))} -
- ))} -
- )} - - {!batch.statements && - (batch.status === 'finished' || batch.status === 'error') ? ( -
- Query results purged from storage +

{batch.createdAtCalendar}

+
+ {capitalize(batch.status)} in{' '} + {humanizeDuration(batch.durationMs)}
- ) : null} - {(batch.statements || []).map((statement) => { - if (statement.error) { + + {({ + className, + style, + tokens, + getLineProps, + getTokenProps, + }) => ( +
+                    {tokens.map((line, i) => (
+                      
+ {line.map((token, key) => ( + + ))} +
+ ))} +
+ )} +
+ {!batch.statements && + (batch.status === 'finished' || batch.status === 'error') ? ( +
+ Query results purged from storage +
+ ) : null} + {(batch.statements || []).map((statement) => { + if (statement.error) { + return ( +
+ {statement.error.title} +
+ ); + } + + if (statement.status !== 'finished') { + return null; + } + return ( -
- {statement.error.title} -
+ + + {(statement?.columns || []).map((column) => ( + {column.name} + ))} + + + + + {statement.rowCount}{' '} + {statement.rowCount === 1 ? 'row' : 'rows'} + {statement.incomplete ? '(incomplete)' : ''} + + + + + ); - } - - return ( - - - - {(statement?.columns || []).map((column) => ( - - ))} - - - - - -
{column.name}
- - {statement.rowCount}{' '} - {statement.rowCount === 1 ? 'row' : 'rows'} - {statement.incomplete ? '(incomplete)' : ''} - -
- ); - })} -
- ); - }) - .concat([
]); + })} +
+ ); + }) + .concat([
]); + } } const batchLength = queryBatches ? queryBatches.length : 0; From 28f15bdcfc4b8819f26c9921381f5d49fcd7c7da Mon Sep 17 00:00:00 2001 From: Rick Bergfalk Date: Tue, 16 Mar 2021 13:34:24 -0500 Subject: [PATCH 10/13] Allow loading of history item --- client/src/queryEditor/HistoryDrawer.tsx | 14 ++++++++++- client/src/stores/editor-actions.ts | 30 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/client/src/queryEditor/HistoryDrawer.tsx b/client/src/queryEditor/HistoryDrawer.tsx index df6bc6c1d..4342839af 100644 --- a/client/src/queryEditor/HistoryDrawer.tsx +++ b/client/src/queryEditor/HistoryDrawer.tsx @@ -10,6 +10,8 @@ import { api } from '../utilities/api'; import theme from 'prism-react-renderer/themes/vsLight'; import { useSessionQueryId, useSessionQueryName } from '../stores/editor-store'; import styles from './StatementsTable.module.css'; +import Button from '../common/Button'; +import { setEditorBatchHistoryItem } from '../stores/editor-actions'; type Props = { visible?: boolean; @@ -62,6 +64,7 @@ function HistoryDrawer({ onClose, visible }: Props) { padding: 8, marginTop: 16, marginBottom: 16, + position: 'relative', }} >

{batch.createdAtCalendar}

@@ -69,6 +72,15 @@ function HistoryDrawer({ onClose, visible }: Props) { {capitalize(batch.status)} in{' '} {humanizeDuration(batch.durationMs)}
+ {(statement?.columns || []).map((column) => ( - {column.name} + {column.name} ))} diff --git a/client/src/stores/editor-actions.ts b/client/src/stores/editor-actions.ts index bd742bef8..2fd163a23 100644 --- a/client/src/stores/editor-actions.ts +++ b/client/src/stores/editor-actions.ts @@ -5,6 +5,7 @@ import { ACLRecord, AppInfo, Batch, + BatchHistoryItem, ChartFields, Connection, ConnectionClient, @@ -463,6 +464,35 @@ export const runQuery = async () => { }); }; +export const setEditorBatchHistoryItem = async ( + batchHistoryItem: BatchHistoryItem +) => { + const { focusedSessionId } = getState(); + + // Statements might not exist if query result data is purged + // In that case, just restore the SQL and chart config and similar + // clear out batchId/selectedStatementId + const hasStatements = batchHistoryItem.statements; + + setSession(focusedSessionId, { + queryName: batchHistoryItem.name, + queryText: batchHistoryItem.batchText, + chartType: batchHistoryItem.chart?.chartType, + chartFields: batchHistoryItem.chart?.fields, + connectionId: batchHistoryItem.connectionId, + connectionClient: undefined, + selectedText: '', + batchId: hasStatements ? batchHistoryItem.id : undefined, + selectedStatementId: undefined, + isRunning: false, + runQueryStartTime: batchHistoryItem.startTime, + }); + + if (hasStatements) { + setBatch(focusedSessionId, batchHistoryItem.id, batchHistoryItem); + } +}; + export const saveQuery = async (additionalUpdates?: Partial) => { const { focusedSessionId } = getState(); const session = getState().getFocusedSession(); From 9e1561ccc943614e1cd29af74c504a65ddb508c2 Mon Sep 17 00:00:00 2001 From: Rick Bergfalk Date: Tue, 16 Mar 2021 13:56:36 -0500 Subject: [PATCH 11/13] Ensure statements are sorted correctly --- server/models/batches.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/models/batches.js b/server/models/batches.js index 285db8d60..4745b109c 100644 --- a/server/models/batches.js +++ b/server/models/batches.js @@ -71,6 +71,9 @@ class Batches { const statementsByBatchId = _.groupBy(statements, 'batchId'); batches.forEach((batch) => { batch.statements = statementsByBatchId[batch.id]; + if (batch.statements) { + _.sortBy(batch.statements, ['sequence']); + } }); } From 8fb8fa0cf50a242a45b04756d0be1e972535675f Mon Sep 17 00:00:00 2001 From: Rick Bergfalk Date: Tue, 16 Mar 2021 22:47:45 -0500 Subject: [PATCH 12/13] Remove unnecessary setTimeout --- client/src/queryEditor/HistoryDrawer.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/src/queryEditor/HistoryDrawer.tsx b/client/src/queryEditor/HistoryDrawer.tsx index 4342839af..6cd752e7d 100644 --- a/client/src/queryEditor/HistoryDrawer.tsx +++ b/client/src/queryEditor/HistoryDrawer.tsx @@ -166,9 +166,7 @@ function HistoryDrawer({ onClose, visible }: Props) { const batchLength = queryBatches ? queryBatches.length : 0; useEffect(() => { if (visible && batchLength > 0) { - setTimeout(() => { - bottomEl && bottomEl.current && bottomEl.current.scrollIntoView(false); - }); + bottomEl && bottomEl.current && bottomEl.current.scrollIntoView(false); } }, [batchLength, visible]); From 4acba2cbd1c10902e961ae174fbace9df22d2113 Mon Sep 17 00:00:00 2001 From: Rick Bergfalk Date: Tue, 16 Mar 2021 22:59:12 -0500 Subject: [PATCH 13/13] Misc cleanup --- client/src/queryEditor/HistoryDrawer.tsx | 41 ++++++++++++------------ 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/client/src/queryEditor/HistoryDrawer.tsx b/client/src/queryEditor/HistoryDrawer.tsx index 6cd752e7d..4b741a3c6 100644 --- a/client/src/queryEditor/HistoryDrawer.tsx +++ b/client/src/queryEditor/HistoryDrawer.tsx @@ -1,17 +1,17 @@ -import React, { useEffect, useRef } from 'react'; -import Highlight, { defaultProps } from 'prism-react-renderer'; import humanizeDuration from 'humanize-duration'; import capitalize from 'lodash/capitalize'; +import Highlight, { defaultProps } from 'prism-react-renderer'; +import theme from 'prism-react-renderer/themes/vsLight'; +import React, { useEffect, useRef } from 'react'; +import Button from '../common/Button'; import Drawer from '../common/Drawer'; import ErrorBlock from '../common/ErrorBlock'; import InfoBlock from '../common/InfoBlock'; import SpinKitCube from '../common/SpinKitCube'; -import { api } from '../utilities/api'; -import theme from 'prism-react-renderer/themes/vsLight'; +import { setEditorBatchHistoryItem } from '../stores/editor-actions'; import { useSessionQueryId, useSessionQueryName } from '../stores/editor-store'; +import { api } from '../utilities/api'; import styles from './StatementsTable.module.css'; -import Button from '../common/Button'; -import { setEditorBatchHistoryItem } from '../stores/editor-actions'; type Props = { visible?: boolean; @@ -25,16 +25,22 @@ function HistoryDrawer({ onClose, visible }: Props) { const queryId = useSessionQueryId() || 'null'; const queryName = useSessionQueryName() || 'unsaved queries'; - let { + const { data: queryBatches, error: queryBatchHistoryError, } = api.useQueryBatchHistory(queryId); const fetching = !queryBatches; - function handleClose() { - onClose(); - } + const batchLength = queryBatches ? queryBatches.length : 0; + useEffect(() => { + if (visible && batchLength > 0) { + // Without setTimeout this fires too soon? This is sort of hacky and could be fragile + setTimeout(() => { + bottomEl && bottomEl.current && bottomEl.current.scrollIntoView(false); + }, 50); + } + }, [batchLength, visible]); let content = null; @@ -60,7 +66,7 @@ function HistoryDrawer({ onClose, visible }: Props) {
{ setEditorBatchHistoryItem(batch); - handleClose(); + onClose(); }} > Open in editor @@ -163,19 +169,12 @@ function HistoryDrawer({ onClose, visible }: Props) { } } - const batchLength = queryBatches ? queryBatches.length : 0; - useEffect(() => { - if (visible && batchLength > 0) { - bottomEl && bottomEl.current && bottomEl.current.scrollIntoView(false); - } - }, [batchLength, visible]); - return (