Added support for the 'Add to macros' feature and fixed various usability issues. #4735
parent
36a71dc7fa
commit
4e3ec91d23
Binary file not shown.
After Width: | Height: | Size: 288 KiB |
|
@ -485,6 +485,12 @@ To create a macro, select the *Manage Macros* option from the *Macros* menu on t
|
|||
:alt: Query Tool Manage Macros dialogue
|
||||
:align: center
|
||||
|
||||
To add a query to macros, write and select your query, then go to the *Macros* menu in the Query Tool and click *Add to macros*. Your query will be automatically saved to macros.
|
||||
|
||||
.. image:: images/query_tool_add_to_macro.png
|
||||
:alt: Query Tool Add To Macros
|
||||
:align: center
|
||||
|
||||
To delete a macro, select the macro on the *Manage Macros* dialogue, and then click the *Delete* button.
|
||||
The server will prompt you for confirmation to delete the macro.
|
||||
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
|
||||
"""empty message
|
||||
|
||||
Revision ID: ac2c2e27dc2d
|
||||
Revises: ec0f11f9a4e6
|
||||
Create Date: 2024-05-17 19:35:03.700104
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ac2c2e27dc2d'
|
||||
down_revision = 'ec0f11f9a4e6'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
meta = sa.MetaData()
|
||||
meta.reflect(op.get_bind(), only=('user_macros',))
|
||||
user_macros_table = sa.Table('user_macros', meta)
|
||||
|
||||
# Create a select statement
|
||||
stmt = sa.select(
|
||||
user_macros_table.columns.mid,
|
||||
user_macros_table.columns.uid, user_macros_table.columns.name,
|
||||
user_macros_table.columns.sql
|
||||
)
|
||||
# Fetch the data from the user_macros table
|
||||
results = op.get_bind().execute(stmt).fetchall()
|
||||
|
||||
# Drop and re-create user macro table.
|
||||
op.drop_table('user_macros')
|
||||
op.create_table(
|
||||
'user_macros',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True),
|
||||
sa.Column('mid', sa.Integer(), nullable=True),
|
||||
sa.Column('uid', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=1024), nullable=False),
|
||||
sa.Column('sql', sa.String()),
|
||||
sa.ForeignKeyConstraint(['mid'], ['macros.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['uid'], ['user.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id',))
|
||||
|
||||
# Reflect the new table structure
|
||||
meta.reflect(op.get_bind(), only=('user_macros',))
|
||||
new_user_macros_table = sa.Table('user_macros', meta)
|
||||
|
||||
# Bulk insert the fetched data into the new user_macros table
|
||||
op.bulk_insert(
|
||||
new_user_macros_table,
|
||||
[
|
||||
{'mid': row[0], 'uid': row[1], 'name': row[2], 'sql': row[3]}
|
||||
for row in results
|
||||
]
|
||||
)
|
||||
|
||||
def downgrade():
|
||||
# pgAdmin only upgrades, downgrade not implemented.
|
||||
pass
|
|
@ -33,7 +33,7 @@ import config
|
|||
#
|
||||
##########################################################################
|
||||
|
||||
SCHEMA_VERSION = 39
|
||||
SCHEMA_VERSION = 40
|
||||
|
||||
##########################################################################
|
||||
#
|
||||
|
@ -433,11 +433,12 @@ class Macros(db.Model):
|
|||
class UserMacros(db.Model):
|
||||
"""Define the macro for a particular user."""
|
||||
__tablename__ = 'user_macros'
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
mid = db.Column(
|
||||
db.Integer, db.ForeignKey('macros.id'), primary_key=True
|
||||
db.Integer, db.ForeignKey('macros.id'), nullable=True
|
||||
)
|
||||
uid = db.Column(
|
||||
db.Integer, db.ForeignKey(USER_ID), primary_key=True
|
||||
db.Integer, db.ForeignKey(USER_ID)
|
||||
)
|
||||
name = db.Column(db.String(1024), nullable=False)
|
||||
sql = db.Column(db.Text(), nullable=False)
|
||||
|
|
|
@ -518,8 +518,13 @@ export default function DataGridView({
|
|||
if(!props.canAddRow) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newRow = schemaRef.current.getNewData();
|
||||
|
||||
const current_macros = schemaRef.current?._top?._sessData?.macro || null;
|
||||
if (current_macros){
|
||||
newRow = schemaRef.current.getNewData(current_macros);
|
||||
}
|
||||
|
||||
if(props.expandEditOnAdd && props.canEdit) {
|
||||
newRowIndex.current = rows.length;
|
||||
}
|
||||
|
|
|
@ -107,11 +107,15 @@ export const PgMenuItem = applyStatics(MenuItem)(({hasCheck=false, checked=false
|
|||
props.onClick(e);
|
||||
};
|
||||
}
|
||||
const keyVal = shortcutToString(shortcut, accesskey);
|
||||
|
||||
const dataLabel = typeof(children) == 'string' ? children : props.datalabel;
|
||||
return <MenuItem {...props} onClick={onClick} data-label={dataLabel} data-checked={checked}>
|
||||
{hasCheck && <CheckIcon className={classes.checkIcon} style={checked ? {} : {visibility: 'hidden'}} data-label="CheckIcon"/>}
|
||||
{children}
|
||||
{(shortcut || accesskey) && <div className={classes.shortcut}>({shortcutToString(shortcut, accesskey)})</div>}
|
||||
<div className={classes.shortcut}>
|
||||
{keyVal ? `(${keyVal})` : ''}
|
||||
</div>
|
||||
</MenuItem>;
|
||||
});
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react';
|
||||
import { makeStyles } from '@mui/styles';
|
||||
import FileCopyRoundedIcon from '@mui/icons-material/FileCopyRounded';
|
||||
import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
|
||||
|
@ -62,7 +62,7 @@ CopyButton.propTypes = {
|
|||
};
|
||||
|
||||
|
||||
export default function CodeMirror({className, currEditor, showCopyBtn=false, customKeyMap=[], ...props}) {
|
||||
export default function CodeMirror({className, currEditor, showCopyBtn=false, customKeyMap=[], onTextSelect, ...props}) {
|
||||
const classes = useStyles();
|
||||
const editor = useRef();
|
||||
const [[showFind, isReplace], setShowFind] = useState([false, false]);
|
||||
|
@ -111,8 +111,36 @@ export default function CodeMirror({className, currEditor, showCopyBtn=false, cu
|
|||
const onMouseEnter = useCallback(()=>{showCopyBtn && setShowCopy(true);}, []);
|
||||
const onMouseLeave = useCallback(()=>{showCopyBtn && setShowCopy(false);}, []);
|
||||
|
||||
// Use to handle text selection events.
|
||||
useEffect(() => {
|
||||
if (!onTextSelect) return;
|
||||
|
||||
const handleSelection = () => {
|
||||
const selectedText = window.getSelection().toString();
|
||||
if (selectedText) {
|
||||
onTextSelect(selectedText);
|
||||
} else {
|
||||
onTextSelect(''); // Reset if no text is selected
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyUp = () => {
|
||||
handleSelection();
|
||||
};
|
||||
// Add event listeners for mouseup and keyup events to detect text selection.
|
||||
document.addEventListener('mouseup', handleSelection);
|
||||
document.addEventListener('keyup', handleKeyUp);
|
||||
|
||||
// Cleanup function to remove event listeners when component unmounts or onTextSelect changes.
|
||||
return () => {
|
||||
document.removeEventListener('mouseup', handleSelection);
|
||||
document.removeEventListener('keyup', handleKeyUp);
|
||||
};
|
||||
}, [onTextSelect]);
|
||||
|
||||
|
||||
return (
|
||||
<div className={clsx(className, classes.root)} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} >
|
||||
<div className={clsx(className, classes.root)} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||
<Editor currEditor={currEditorWrap} customKeyMap={finalCustomKeyMap} {...props} />
|
||||
{showCopy && <CopyButton editor={editor.current} />}
|
||||
<FindDialog editor={editor.current} show={showFind} replace={isReplace} onClose={closeFind} />
|
||||
|
@ -126,4 +154,5 @@ CodeMirror.propTypes = {
|
|||
className: CustomPropTypes.className,
|
||||
showCopyBtn: PropTypes.bool,
|
||||
customKeyMap: PropTypes.array,
|
||||
onTextSelect:PropTypes.func
|
||||
};
|
||||
|
|
|
@ -49,7 +49,7 @@ from pgadmin.tools.sqleditor.utils.query_tool_fs_utils import \
|
|||
read_file_generator
|
||||
from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog
|
||||
from pgadmin.tools.sqleditor.utils.query_history import QueryHistory
|
||||
from pgadmin.tools.sqleditor.utils.macros import get_macros,\
|
||||
from pgadmin.tools.sqleditor.utils.macros import get_macros, \
|
||||
get_user_macros, set_macros
|
||||
from pgadmin.utils.constants import MIMETYPE_APP_JS, \
|
||||
SERVER_CONNECTION_CLOSED, ERROR_MSG_TRANS_ID_NOT_FOUND, \
|
||||
|
@ -130,6 +130,7 @@ class SqlEditorModule(PgAdminModule):
|
|||
'sqleditor.clear_query_history',
|
||||
'sqleditor.get_macro',
|
||||
'sqleditor.get_macros',
|
||||
'sqleditor.get_user_macros',
|
||||
'sqleditor.set_macros',
|
||||
'sqleditor.get_new_connection_data',
|
||||
'sqleditor.get_new_connection_servers',
|
||||
|
@ -2692,3 +2693,15 @@ def update_macros(trans_id):
|
|||
_, _, _, _, _ = check_transaction_status(trans_id)
|
||||
|
||||
return set_macros()
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/get_user_macros',
|
||||
methods=["GET"], endpoint='get_user_macros'
|
||||
)
|
||||
@pga_login_required
|
||||
def user_macros(json_resp=True):
|
||||
"""
|
||||
This method is used to fetch all user macros.
|
||||
"""
|
||||
return get_user_macros()
|
||||
|
|
|
@ -50,6 +50,21 @@ function initConnection(api, params, passdata) {
|
|||
return api.post(url_for('NODE-server.connect_id', params), passdata);
|
||||
}
|
||||
|
||||
export function getRandomName(existingNames) {
|
||||
const maxNumber = existingNames.reduce((max, name) => {
|
||||
const match = name.match(/\d+$/); // Extract the number from the name
|
||||
if (match) {
|
||||
const number = parseInt(match[0], 10);
|
||||
return number > max ? number : max;
|
||||
}
|
||||
return max;
|
||||
}, 0);
|
||||
|
||||
// Generate the new name
|
||||
const newName = `Macro ${maxNumber + 1}`;
|
||||
return newName;
|
||||
}
|
||||
|
||||
function setPanelTitle(docker, panelId, title, qtState, dirty=false) {
|
||||
if(qtState.current_file) {
|
||||
title = qtState.current_file.split('\\').pop().split('/').pop();
|
||||
|
@ -194,6 +209,8 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
|
|||
}],
|
||||
});
|
||||
|
||||
const [selectedText, setSelectedText] = useState('');
|
||||
|
||||
const setQtState = (state)=>{
|
||||
_setQtState((prev)=>({...prev,...evalFunc(null, state, prev)}));
|
||||
};
|
||||
|
@ -250,7 +267,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
|
|||
{
|
||||
maximizable: true,
|
||||
tabs: [
|
||||
LayoutDocker.getPanel({id: PANELS.QUERY, title: gettext('Query'), content: <Query />}),
|
||||
LayoutDocker.getPanel({id: PANELS.QUERY, title: gettext('Query'), content: <Query onTextSelect={(text) => setSelectedText(text)}/>}),
|
||||
LayoutDocker.getPanel({id: PANELS.HISTORY, title: gettext('Query History'), content: <QueryHistory />,
|
||||
cached: undefined}),
|
||||
],
|
||||
|
@ -796,7 +813,38 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
|
|||
}}
|
||||
onClose={onClose}/>
|
||||
}, 850, 500);
|
||||
}, [qtState.preferences.browser]);
|
||||
}, [qtState.preferences.browser]);
|
||||
|
||||
const onAddToMacros = () => {
|
||||
if (selectedText){
|
||||
let currMacros = qtState.params.macros;
|
||||
const existingNames = currMacros.map(macro => macro.name);
|
||||
const newName = getRandomName(existingNames);
|
||||
let changed = [{ 'name': newName, 'sql': selectedText }];
|
||||
|
||||
api.put(
|
||||
url_for('sqleditor.set_macros', {
|
||||
'trans_id': qtState.params.trans_id,
|
||||
}),
|
||||
{ changed: changed }
|
||||
)
|
||||
.then(({ data: respData }) => {
|
||||
const filteredData = respData.filter(m => Boolean(m.name));
|
||||
setQtState(prev => ({
|
||||
...prev,
|
||||
params: {
|
||||
...prev.params,
|
||||
macros: filteredData,
|
||||
},
|
||||
}));
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
setSelectedText('');
|
||||
};
|
||||
|
||||
|
||||
const onFilterClick = useCallback(()=>{
|
||||
const onClose = ()=>docker.current.close('filter-dialog');
|
||||
|
@ -884,8 +932,9 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
|
|||
<MainToolBar
|
||||
containerRef={containerRef}
|
||||
onManageMacros={onManageMacros}
|
||||
onAddToMacros={onAddToMacros}
|
||||
onFilterClick={onFilterClick}
|
||||
/>), [containerRef.current, onManageMacros, onFilterClick])}
|
||||
/>), [containerRef.current, onManageMacros, onFilterClick, onAddToMacros])}
|
||||
<Layout
|
||||
getLayoutInstance={(obj)=>docker.current=obj}
|
||||
defaultLayout={defaultLayout}
|
||||
|
|
|
@ -11,7 +11,7 @@ import React from 'react';
|
|||
import SchemaView from '../../../../../../static/js/SchemaView';
|
||||
import BaseUISchema from '../../../../../../static/js/SchemaView/base_schema.ui';
|
||||
import gettext from 'sources/gettext';
|
||||
import { QueryToolContext } from '../QueryToolComponent';
|
||||
import { QueryToolContext, getRandomName } from '../QueryToolComponent';
|
||||
import url_for from 'sources/url_for';
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -23,17 +23,36 @@ class MacrosCollection extends BaseUISchema {
|
|||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'mid';
|
||||
return 'id';
|
||||
}
|
||||
|
||||
/* Returns the new data row for the schema based on defaults and input */
|
||||
getNewData(current_macros, data={}) {
|
||||
let newRow = {};
|
||||
this.fields.forEach((field)=>{
|
||||
newRow[field.id] = this.defaults[field.id];
|
||||
});
|
||||
newRow = {
|
||||
...newRow,
|
||||
...data,
|
||||
};
|
||||
if (current_macros){
|
||||
// Extract an array of existing names from the 'macro' collection
|
||||
const existingNames = current_macros.map(macro => macro.name);
|
||||
const newName = getRandomName(existingNames);
|
||||
newRow.name = newName;
|
||||
}
|
||||
return newRow;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
return [
|
||||
{
|
||||
id: 'id', label: gettext('Key'), cell: 'select', noEmpty: true,
|
||||
id: 'mid', label: gettext('Key'), cell: 'select', noEmpty: false,
|
||||
width: 100, options: obj.keyOptions, optionsReloadBasis: obj.keyOptions.length,
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
allowClear: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -73,10 +92,14 @@ class MacrosSchema extends BaseUISchema {
|
|||
}
|
||||
|
||||
validate(state, setError) {
|
||||
let allKeys = state.macro.map((m)=>m.id.toString());
|
||||
let allKeys = state.macro.map((m) => m.mid ? m.mid.toString() : null).filter(key => key !== null);
|
||||
let allNames = state.macro.map((m) => m.name ? m.name.toLowerCase() : null);
|
||||
if(allKeys.length != new Set(allKeys).size) {
|
||||
setError('macro', gettext('Key must be unique.'));
|
||||
return true;
|
||||
} else if(allNames.length != new Set(allNames).size) {
|
||||
setError('macro', gettext('Name must be unique.'));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -89,26 +112,28 @@ const useStyles = makeStyles((theme)=>({
|
|||
},
|
||||
}));
|
||||
|
||||
function getChangedMacros(macrosData, changeData) {
|
||||
function getChangedMacros(userMacrosData, changeData) {
|
||||
/* For backend, added, removed is changed. Convert all added, removed to changed. */
|
||||
let changed = [];
|
||||
|
||||
for (const m of (changeData.macro.changed || [])) {
|
||||
let newM = {...m};
|
||||
if('id' in m) {
|
||||
/* if key changed, clear prev and add new */
|
||||
changed.push({id: m.mid, name: null, sql: null});
|
||||
let em = _.find(macrosData, (d)=>d.mid==m.mid);
|
||||
newM = {name: em.name, sql: em.sql, ...m};
|
||||
let em = _.find(userMacrosData, (d)=>d.id==m.id);
|
||||
newM = {name: m.name ? (m.name) : em.name , sql: m.sql ? m.sql : em.sql, mid: m.mid ? m.mid : em.mid, ...m};
|
||||
} else {
|
||||
newM.id = m.mid;
|
||||
}
|
||||
delete newM.mid;
|
||||
changed.push(newM);
|
||||
}
|
||||
for (const m of (changeData.macro.deleted || [])) {
|
||||
changed.push({id: m.id, name: null, sql: null});
|
||||
}
|
||||
for (const m of (changeData.macro.added || [])) {
|
||||
if (m.id && m.id !== 0){
|
||||
m.mid = m.id;
|
||||
delete m.id;
|
||||
}
|
||||
changed.push(m);
|
||||
}
|
||||
return changed;
|
||||
|
@ -118,30 +143,46 @@ export default function MacrosDialog({onClose, onSave}) {
|
|||
const classes = useStyles();
|
||||
const queryToolCtx = React.useContext(QueryToolContext);
|
||||
const [macrosData, setMacrosData] = React.useState([]);
|
||||
const [userMacrosData, setUserMacrosData] = React.useState([]);
|
||||
const [macrosErr, setMacrosErr] = React.useState(null);
|
||||
|
||||
React.useEffect(async ()=>{
|
||||
try {
|
||||
let {data: respData} = await queryToolCtx.api.get(url_for('sqleditor.get_macros', {
|
||||
'trans_id': queryToolCtx.params.trans_id,
|
||||
}));
|
||||
/* Copying id to mid to track key id changes */
|
||||
setMacrosData(respData.macro.map((m)=>({...m, mid: m.id})));
|
||||
// Fetch user macros data
|
||||
let { data: userMacroRespData } = await queryToolCtx.api.get(url_for('sqleditor.get_user_macros'));
|
||||
setUserMacrosData(userMacroRespData);
|
||||
|
||||
} catch (error) {
|
||||
setMacrosErr(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
React.useEffect(async ()=>{
|
||||
try {
|
||||
// Fetch macros data
|
||||
let {data: respData} = await queryToolCtx.api.get(url_for('sqleditor.get_macros', {
|
||||
'trans_id': queryToolCtx.params.trans_id,
|
||||
}));
|
||||
/* Copying id to mid to track key id changes */
|
||||
setMacrosData(respData.macro.map((m)=>({...m, mid: m.id})));
|
||||
|
||||
} catch (error) {
|
||||
setMacrosErr(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
const onSaveClick = (_isNew, changeData)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
const setMacros = async ()=>{
|
||||
try {
|
||||
let changed = getChangedMacros(macrosData, changeData);
|
||||
let changed = getChangedMacros(userMacrosData, changeData);
|
||||
let {data: respData} = await queryToolCtx.api.put(url_for('sqleditor.set_macros', {
|
||||
'trans_id': queryToolCtx.params.trans_id,
|
||||
}), {changed: changed});
|
||||
resolve();
|
||||
onSave(respData.macro?.filter((m)=>Boolean(m.name)));
|
||||
onSave(respData.filter((m) => Boolean(m.name)));
|
||||
onClose();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
|
@ -159,6 +200,7 @@ export default function MacrosDialog({onClose, onSave}) {
|
|||
if(keyOptions.length <= 0) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<SchemaView
|
||||
formType={'dialog'}
|
||||
|
@ -166,7 +208,7 @@ export default function MacrosDialog({onClose, onSave}) {
|
|||
if(macrosErr) {
|
||||
return Promise.reject(macrosErr);
|
||||
}
|
||||
return Promise.resolve({macro: macrosData.filter((m)=>Boolean(m.name))});
|
||||
return Promise.resolve({macro: userMacrosData.filter((m)=>Boolean(m.name))});
|
||||
}}
|
||||
schema={new MacrosSchema(keyOptions)}
|
||||
viewHelperProps={{
|
||||
|
|
|
@ -54,7 +54,7 @@ function autoCommitRollback(type, api, transId, value) {
|
|||
return api.post(url, JSON.stringify(value));
|
||||
}
|
||||
|
||||
export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
|
||||
export function MainToolBar({containerRef, onFilterClick, onManageMacros, onAddToMacros}) {
|
||||
const classes = useStyles();
|
||||
const eventBus = useContext(QueryToolEventsContext);
|
||||
const queryToolCtx = useContext(QueryToolContext);
|
||||
|
@ -633,6 +633,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
|
|||
label={gettext('Macros Menu')}
|
||||
>
|
||||
<PgMenuItem onClick={onManageMacros}>{gettext('Manage macros')}</PgMenuItem>
|
||||
<PgMenuItem onClick={onAddToMacros}>{gettext('Add to macros')}</PgMenuItem>
|
||||
<PgMenuDivider />
|
||||
{queryToolCtx.params?.macros?.map((m)=>{
|
||||
return (
|
||||
|
@ -656,4 +657,5 @@ MainToolBar.propTypes = {
|
|||
containerRef: CustomPropTypes.ref,
|
||||
onFilterClick: PropTypes.func,
|
||||
onManageMacros: PropTypes.func,
|
||||
onAddToMacros: PropTypes.func
|
||||
};
|
||||
|
|
|
@ -23,6 +23,7 @@ import { usePgAdmin } from '../../../../../../static/js/BrowserComponent';
|
|||
import ConfirmPromotionContent from '../dialogs/ConfirmPromotionContent';
|
||||
import usePreferences from '../../../../../../preferences/static/js/store';
|
||||
import { getTitle } from '../../sqleditor_title';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
||||
const useStyles = makeStyles(()=>({
|
||||
|
@ -61,7 +62,7 @@ async function registerAutocomplete(editor, api, transId) {
|
|||
});
|
||||
}
|
||||
|
||||
export default function Query() {
|
||||
export default function Query({onTextSelect}) {
|
||||
const classes = useStyles();
|
||||
const editor = React.useRef();
|
||||
const eventBus = useContext(QueryToolEventsContext);
|
||||
|
@ -390,7 +391,6 @@ export default function Query() {
|
|||
|
||||
const change = useCallback(()=>{
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.QUERY_CHANGED, editor.current.isDirty());
|
||||
|
||||
if(!queryToolCtx.params.is_query_tool && editor.current.isDirty()){
|
||||
if(queryToolCtx.preferences.sqleditor.view_edit_promotion_warning){
|
||||
checkViewEditDataPromotion();
|
||||
|
@ -480,5 +480,11 @@ export default function Query() {
|
|||
onChange={change}
|
||||
autocomplete={true}
|
||||
customKeyMap={shortcutOverrideKeys}
|
||||
onTextSelect={onTextSelect}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
||||
Query.propTypes = {
|
||||
onTextSelect: PropTypes.func,
|
||||
};
|
|
@ -32,15 +32,15 @@ class TestMacros(BaseTestGenerator):
|
|||
operation='set',
|
||||
data={
|
||||
'changed': [
|
||||
{'id': 1,
|
||||
{'mid': 1,
|
||||
'name': 'Test Macro 1',
|
||||
'sql': 'SELECT 1;'
|
||||
},
|
||||
{'id': 2,
|
||||
{'mid': 2,
|
||||
'name': 'Test Macro 2',
|
||||
'sql': 'SELECT 2;'
|
||||
},
|
||||
{'id': 3,
|
||||
{'mid': 3,
|
||||
'name': 'Test Macro 3',
|
||||
'sql': 'SELECT 3;'
|
||||
},
|
||||
|
@ -129,10 +129,11 @@ class TestMacros(BaseTestGenerator):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
for m in self.data['changed']:
|
||||
if self.operation == 'set':
|
||||
m['id'] = m['mid']
|
||||
url = '/sqleditor/get_macros/{0}/{1}'.format(m['id'],
|
||||
self.trans_id)
|
||||
response = self.tester.get(url)
|
||||
|
||||
if self.operation == 'clear':
|
||||
self.assertEqual(response.status_code, 410)
|
||||
elif self.operation == 'set':
|
||||
|
|
|
@ -27,7 +27,7 @@ def get_macros(macro_id, json_resp):
|
|||
:param json_resp: Set True to return json response
|
||||
"""
|
||||
if macro_id:
|
||||
macro = UserMacros.query.filter_by(mid=macro_id,
|
||||
macro = UserMacros.query.filter_by(id=macro_id,
|
||||
uid=current_user.id).first()
|
||||
if macro is None:
|
||||
return make_json_response(
|
||||
|
@ -37,7 +37,8 @@ def get_macros(macro_id, json_resp):
|
|||
)
|
||||
else:
|
||||
return ajax_response(
|
||||
response={'id': macro.mid,
|
||||
response={'id': macro.id,
|
||||
'mid':macro.mid,
|
||||
'name': macro.name,
|
||||
'sql': macro.sql},
|
||||
status=200
|
||||
|
@ -45,13 +46,12 @@ def get_macros(macro_id, json_resp):
|
|||
else:
|
||||
macros = db.session.query(Macros.id, Macros.alt, Macros.control,
|
||||
Macros.key, Macros.key_code,
|
||||
UserMacros.name, UserMacros.sql
|
||||
).outerjoin(
|
||||
UserMacros.name, UserMacros.sql,
|
||||
UserMacros.id).outerjoin(
|
||||
UserMacros, and_(Macros.id == UserMacros.mid,
|
||||
UserMacros.uid == current_user.id)).all()
|
||||
|
||||
data = []
|
||||
|
||||
for m in macros:
|
||||
key_label = 'Ctrl + ' + m[3] if m[2] is True else 'Alt + ' + m[3]
|
||||
data.append({'id': m[0], 'alt': m[1],
|
||||
|
@ -74,7 +74,8 @@ def get_user_macros():
|
|||
This method is used to get all the user macros.
|
||||
"""
|
||||
|
||||
macros = db.session.query(UserMacros.name,
|
||||
macros = db.session.query(UserMacros.id,
|
||||
UserMacros.name,
|
||||
Macros.id,
|
||||
Macros.alt, Macros.control,
|
||||
Macros.key, Macros.key_code,
|
||||
|
@ -86,11 +87,17 @@ def get_user_macros():
|
|||
data = []
|
||||
|
||||
for m in macros:
|
||||
key_label = 'Ctrl + ' + m[4] if m[3] is True else 'Alt + ' + m[4]
|
||||
data.append({'name': m[0], 'id': m[1], 'key': m[4],
|
||||
'key_label': key_label, 'alt': 1 if m[2] else 0,
|
||||
'control': 1 if m[3] else 0, 'key_code': m[5],
|
||||
'sql': m[6]})
|
||||
key_label = (
|
||||
'Ctrl + ' + str(m[5])
|
||||
if m[4] is True
|
||||
else 'Alt + ' + str(m[5])
|
||||
if m[5] is not None
|
||||
else ''
|
||||
)
|
||||
data.append({'id': m[0], 'name': m[1], 'mid': m[2], 'key': m[5],
|
||||
'key_label': key_label, 'alt': 1 if m[3] else 0,
|
||||
'control': 1 if m[4] else 0, 'key_code': m[6],
|
||||
'sql': m[7]})
|
||||
|
||||
return data
|
||||
|
||||
|
@ -111,21 +118,21 @@ def set_macros():
|
|||
)
|
||||
|
||||
for m in data['changed']:
|
||||
if m['id']:
|
||||
if m.get('id'):
|
||||
macro = UserMacros.query.filter_by(
|
||||
uid=current_user.id,
|
||||
mid=m['id']).first()
|
||||
id=m['id']).first()
|
||||
if macro:
|
||||
status, msg = update_macro(m, macro)
|
||||
else:
|
||||
status, msg = create_macro(m)
|
||||
else:
|
||||
status, msg = create_macro(m)
|
||||
|
||||
if not status:
|
||||
return make_json_response(
|
||||
status=410, success=0, errormsg=msg
|
||||
)
|
||||
|
||||
return get_macros(None, True)
|
||||
return get_user_macros()
|
||||
|
||||
|
||||
def create_macro(macro):
|
||||
|
@ -146,7 +153,7 @@ def create_macro(macro):
|
|||
try:
|
||||
new_macro = UserMacros(
|
||||
uid=current_user.id,
|
||||
mid=macro['id'],
|
||||
mid=macro['mid'] if macro.get('mid') else None,
|
||||
name=macro['name'],
|
||||
sql=macro['sql']
|
||||
)
|
||||
|
@ -168,6 +175,7 @@ def update_macro(data, macro):
|
|||
|
||||
name = data.get('name', None)
|
||||
sql = data.get('sql', None)
|
||||
mid = data.get('mid', None)
|
||||
|
||||
if (name or sql) and macro.sql and 'name' in data and name is None:
|
||||
return False, gettext(
|
||||
|
@ -177,11 +185,12 @@ def update_macro(data, macro):
|
|||
"Could not find the required parameter (sql).")
|
||||
|
||||
try:
|
||||
if name or sql:
|
||||
if name or sql or mid:
|
||||
if name:
|
||||
macro.name = name
|
||||
if sql:
|
||||
macro.sql = sql
|
||||
macro.mid = mid if mid != 0 else None
|
||||
else:
|
||||
db.session.delete(macro)
|
||||
|
||||
|
|
Loading…
Reference in New Issue