From 25b89f76242da9f98c3aa099322079de52b6f155 Mon Sep 17 00:00:00 2001 From: Aditya Toshniwal Date: Mon, 25 Apr 2022 18:11:39 +0530 Subject: [PATCH] Fixed the following issues for the new query tool: 1) Failed to fetch query history error sometimes. 2) In copy paste row, if a copied row has [null], then those are pasted as an empty string. 3) When Data output is empty, show an empty grid. 4) Schema diff generates a script button resulting in an empty window. Fixes #7306. 5) Detach the DataOutput panel > Try editing text cell > Text editor is hidden behind the data output panel refs #6131 --- web/pgadmin/static/js/SchemaView/index.jsx | 4 ++-- web/pgadmin/static/js/helpers/Layout.jsx | 2 +- .../static/scss/_codemirror.overrides.scss | 5 +++-- web/pgadmin/tools/erd/__init__.py | 1 + .../js/erd_tool/ui_components/BodyWidget.jsx | 3 +++ .../js/components/QueryToolComponent.jsx | 2 +- .../components/QueryToolDataGrid/CopyData.js | 2 ++ .../components/QueryToolDataGrid/Editors.jsx | 5 ++--- .../js/components/QueryToolDataGrid/index.jsx | 8 ++----- .../static/js/components/sections/Query.jsx | 4 +++- .../js/components/sections/QueryHistory.jsx | 18 +++++++++++---- .../js/components/sections/ResultSet.jsx | 22 +++++++++++++------ .../sqleditor/static/js/show_query_tool.js | 2 ++ .../sqleditor/static/js/sqleditor_title.js | 2 +- 14 files changed, 52 insertions(+), 28 deletions(-) diff --git a/web/pgadmin/static/js/SchemaView/index.jsx b/web/pgadmin/static/js/SchemaView/index.jsx index c50d56e25..71f3285d2 100644 --- a/web/pgadmin/static/js/SchemaView/index.jsx +++ b/web/pgadmin/static/js/SchemaView/index.jsx @@ -734,12 +734,12 @@ function SchemaDialogView({ onClose={onErrClose} /> {showFooter && - {useMemo(()=> + {useMemo(()=>((!props.disableSqlHelp || !props.disableDialogHelp) && props.onHelp(true, isNew)} icon={} disabled={props.disableSqlHelp} className={classes.buttonMargin} title="SQL help for this object type."/> props.onHelp(false, isNew)} icon={} title="Help for this dialog." disabled={props.disableDialogHelp}/> - , [])} + ), [])} } className={classes.buttonMargin}> {gettext('Close')} diff --git a/web/pgadmin/static/js/helpers/Layout.jsx b/web/pgadmin/static/js/helpers/Layout.jsx index 0f18c1b83..8adfa6327 100644 --- a/web/pgadmin/static/js/helpers/Layout.jsx +++ b/web/pgadmin/static/js/helpers/Layout.jsx @@ -40,7 +40,6 @@ const useStyles = makeStyles((theme)=>({ border: 'none', '&.dragging': { opacity: 0.6, - pointerEvents: 'visible', }, '& .dock': { borderRadius: 'inherit', @@ -49,6 +48,7 @@ const useStyles = makeStyles((theme)=>({ borderRadius: theme.shape.borderRadius, '&.dock-panel.dragging': { opacity: 1, + pointerEvents: 'visible', }, '& .dock-ink-bar': { height: '0px', diff --git a/web/pgadmin/static/scss/_codemirror.overrides.scss b/web/pgadmin/static/scss/_codemirror.overrides.scss index 14c08a5f9..f49fe7f52 100644 --- a/web/pgadmin/static/scss/_codemirror.overrides.scss +++ b/web/pgadmin/static/scss/_codemirror.overrides.scss @@ -211,5 +211,6 @@ background-color: $sql-editor-disable-bg !important; } - - +.sql-editor-mark { + border-bottom: 2px dotted red; +} diff --git a/web/pgadmin/tools/erd/__init__.py b/web/pgadmin/tools/erd/__init__.py index 260b5a70e..40ad9a6d8 100644 --- a/web/pgadmin/tools/erd/__init__.py +++ b/web/pgadmin/tools/erd/__init__.py @@ -479,6 +479,7 @@ def initialize_erd(trans_id, sgid, sid, did): return make_json_response( data={ 'connId': str(trans_id), + 'database': conn.db, 'serverVersion': conn.manager.version, } ) diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx index 63e3cae54..6a1586ff8 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx +++ b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx @@ -81,6 +81,7 @@ export default class BodyWidget extends React.Component { table_dialog_open: true, oto_dialog_open: true, otm_dialog_open: true, + database: null, }; this.diagram = new ERDCore(); /* Flag for checking if user has opted for save before close */ @@ -575,6 +576,7 @@ export default class BodyWidget extends React.Component { sid: this.props.params.sid, did: this.props.params.did, stype: this.props.params.server_type, + database: this.state.database, }; let sqlId = `erd${this.props.params.trans_id}`; @@ -764,6 +766,7 @@ export default class BodyWidget extends React.Component { this.setState({ conn_status: CONNECT_STATUS.CONNECTED, server_version: response.data.data.serverVersion, + database: response.data.data.database, }); return true; } catch (error) { diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx index ba566d309..7738dc33f 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx @@ -111,7 +111,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN const modal = useModal(); /* Connection status poller */ - let pollTime = qtState.preferences.sqleditor.connection_status_fetch_time > 0 ? + let pollTime = qtState.preferences.sqleditor.connection_status_fetch_time > 0 && !qtState.obtaining_conn ? qtState.preferences.sqleditor.connection_status_fetch_time*1000 : -1; /* No need to poll when the query is executing. Query poller will the txn status */ if(qtState.connection_status === CONNECTION_STATUS.TRANSACTION_STATUS_ACTIVE && qtState.connected) { diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/CopyData.js b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/CopyData.js index b434c81e2..43f51e3aa 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/CopyData.js +++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/CopyData.js @@ -47,6 +47,8 @@ export default class CopyData { } clipboard.copyToClipboard(csvRows.join('\n')); localStorage.setItem('copied-with-headers', withHeaders); + /* Push actual row to storage, can be used to identify null columns */ + localStorage.setItem('copied-rows', JSON.stringify(rows)); } escape(iStr) { diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/Editors.jsx b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/Editors.jsx index d7815170b..16e62a51d 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/Editors.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/Editors.jsx @@ -22,8 +22,7 @@ import Notifier from '../../../../../../static/js/helpers/Notifier'; const useStyles = makeStyles((theme)=>({ textEditor: { position: 'absolute', - - zIndex: 1050, + zIndex: 1080, backgroundColor: theme.palette.background.default, padding: '0.25rem', fontSize: '12px', @@ -41,7 +40,7 @@ const useStyles = makeStyles((theme)=>({ }, jsonEditor: { position: 'absolute', - zIndex: 1050, + zIndex: 1080, backgroundColor: theme.palette.background.default, ...theme.mixins.panelBorder, padding: '0.25rem', diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/index.jsx b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/index.jsx index f5c5b1c0d..664497451 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/index.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/index.jsx @@ -335,12 +335,8 @@ export default function QueryToolDataGrid({columns, rows, totalRowCount, dataCha }; useEffect(()=>{ - if(columns.length > 0 || rows.length > 0) { - let initCols = initialiseColumns(columns, rows, totalRowCount, columnWidthBy); - setColumns(formatColumns(initCols, dataChangeStore, selectedColumns, onSelectedColumnsChangeWrapped, props.rowKeyGetter, classes)); - } else { - setColumns([], [], 0); - } + let initCols = initialiseColumns(columns, rows, totalRowCount, columnWidthBy); + setColumns(formatColumns(initCols, dataChangeStore, selectedColumns, onSelectedColumnsChangeWrapped, props.rowKeyGetter, classes)); }, [columns, rowsResetKey]); useEffect(()=>{ diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx index 13a81c17d..42dc05a3b 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx @@ -166,9 +166,11 @@ export default function Query() { const layoutEvenBus = useContext(LayoutEventsContext); const lastSavedText = React.useRef(''); const markedLine = React.useRef(0); + const marker = React.useRef(); const removeHighlightError = (cmObj)=>{ // Remove already existing marker + marker.current?.clear(); cmObj.removeLineClass(markedLine.current, 'wrap', 'CodeMirror-activeline-background'); markedLine.current = 0; }; @@ -218,7 +220,7 @@ export default function Query() { endMarker = errorLine.length; // Mark the error text - cmObj.markText({ + marker.current = cmObj.markText({ line: errorLineNo, ch: startMarker, }, { diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/QueryHistory.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/QueryHistory.jsx index 9319bbb3a..2f2246a85 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/QueryHistory.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/QueryHistory.jsx @@ -14,7 +14,7 @@ import _ from 'lodash'; import clsx from 'clsx'; import { Box, Grid, List, ListItem, ListSubheader } from '@material-ui/core'; import url_for from 'sources/url_for'; -import { QueryToolContext, QueryToolEventsContext } from '../QueryToolComponent'; +import { QueryToolConnectionContext, QueryToolContext, QueryToolEventsContext } from '../QueryToolComponent'; import moment from 'moment'; import PlayArrowRoundedIcon from '@material-ui/icons/PlayArrowRounded'; import AssessmentRoundedIcon from '@material-ui/icons/AssessmentRounded'; @@ -86,6 +86,9 @@ const useStyles = makeStyles((theme)=>({ }, removeBtnMargin: { marginLeft: '0.25rem', + }, + queryMargin: { + marginTop: '12px', } })); @@ -337,12 +340,13 @@ function QueryHistoryDetails({entry}) { {gettext('Copy to Query Editor')} @@ -361,6 +365,7 @@ QueryHistoryDetails.propTypes = { export function QueryHistory() { const qhu = React.useRef(new QueryHistoryUtils()); const queryToolCtx = React.useContext(QueryToolContext); + const queryToolConnCtx = React.useContext(QueryToolConnectionContext); const classes = useStyles(); const eventBus = React.useContext(QueryToolEventsContext); const [selectedItemKey, setSelectedItemKey] = React.useState(1); @@ -371,11 +376,16 @@ export function QueryHistory() { const layoutEvenBus = React.useContext(LayoutEventsContext); const listRef = React.useRef(); - React.useEffect(async ()=>{ + React.useEffect(()=>{ layoutEvenBus.registerListener(LAYOUT_EVENTS.ACTIVE, (currentTabId)=>{ currentTabId == PANELS.HISTORY && listRef.current?.focus(); }); + }, []); + React.useEffect(async ()=>{ + if(!queryToolConnCtx.connected) { + return; + } setLoaderText(gettext('Fetching history...')); try { let {data: respData} = await queryToolCtx.api.get(url_for('sqleditor.get_query_history', { @@ -402,7 +412,7 @@ export function QueryHistory() { listRef.current?.focus(); eventBus.registerListener(QUERY_TOOL_EVENTS.PUSH_HISTORY, pushHistory); return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.PUSH_HISTORY, pushHistory); - }, []); + }, [queryToolConnCtx.connected]); const onRemove = async ()=>{ setLoaderText(gettext('Removing history entry...')); diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx index 6bdb45624..bb8b89ee8 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx @@ -547,10 +547,12 @@ export class ResultSetUtils { return columns; } - processClipboardVal(columnVal, col) { + processClipboardVal(columnVal, col, rawCopiedVal) { if(columnVal === '') { if(col.has_default_val) { columnVal = undefined; + } else if(rawCopiedVal === null) { + columnVal = null; } } if(col.cell === 'boolean') { @@ -570,14 +572,19 @@ export class ResultSetUtils { if(!_.isArray(result) || !_.size(result)) { return retVal; } - for(const rec of result) { + let copiedRowsObjects = []; + try { + /* If the raw row objects are available, use to them identify null values */ + copiedRowsObjects = JSON.parse(localStorage.getItem('copied-rows')); + } catch {/* Suppress the error */} + for(const [recIdx, rec] of result?.entries()) { // Convert 2darray to dict. let rowObj = {}; for(const col of columns) { let columnVal = rec[col.pos]; /* If the source is clipboard, then it needs some extra handling */ if(fromClipboard) { - columnVal = this.processClipboardVal(columnVal, col); + columnVal = this.processClipboardVal(columnVal, col, copiedRowsObjects[recIdx]?.[col.key]); } rowObj[col.key] = columnVal; } @@ -605,15 +612,16 @@ export class ResultSetUtils { } queryFinished(httpMessage, onResultsAvailable, onExplain) { - let msg; let endTime = new Date(); this.eventBus.fireEvent(QUERY_TOOL_EVENTS.EXECUTION_END, true); this.eventBus.fireEvent(QUERY_TOOL_EVENTS.SET_CONNECTION_STATUS, httpMessage.data.data.transaction_status); this.eventBus.fireEvent(QUERY_TOOL_EVENTS.TASK_END, gettext('Query complete'), endTime); this.setEndTime(endTime); - msg = gettext('Query returned successfully in %s.', this.queryRunTime()); + let msg = gettext('Query returned successfully in %s.', this.queryRunTime()); if(this.hasResultsToDisplay(httpMessage.data.data)) { + msg = gettext('Successfully run. Total query runtime: %s.', this.queryRunTime()) + + '\n' + gettext('%s rows affected.', httpMessage.data.data?.rows_affected); if(!_.isNull(httpMessage.data.data.additional_messages)){ msg = httpMessage.data.data.additional_messages + '\n' + msg; } @@ -1194,10 +1202,10 @@ export function ResultSet() { - {(columns.length == 0 && rows.length == 0) && + {!queryData && } - {(columns.length != 0 || rows.length != 0) && <> + {queryData && <>