Skip to content

Commit

Permalink
Added support for the 'Add to macros' feature and fixed various usabi…
Browse files Browse the repository at this point in the history
…lity issues. #4735
  • Loading branch information
RohitBhati8269 committed May 24, 2024
1 parent 36a71dc commit 4e3ec91
Show file tree
Hide file tree
Showing 14 changed files with 279 additions and 51 deletions.
Binary file added docs/en_US/images/query_tool_add_to_macro.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions docs/en_US/query_tool.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
61 changes: 61 additions & 0 deletions web/migrations/versions/ac2c2e27dc2d_.py
Original file line number Diff line number Diff line change
@@ -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
7 changes: 4 additions & 3 deletions web/pgadmin/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
#
##########################################################################

SCHEMA_VERSION = 39
SCHEMA_VERSION = 40

##########################################################################
#
Expand Down Expand Up @@ -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)
Expand Down
7 changes: 6 additions & 1 deletion web/pgadmin/static/js/SchemaView/DataGridView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
6 changes: 5 additions & 1 deletion web/pgadmin/static/js/components/Menu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
});

Expand Down
35 changes: 32 additions & 3 deletions web/pgadmin/static/js/components/ReactCodeMirror/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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} />
Expand All @@ -126,4 +154,5 @@ CodeMirror.propTypes = {
className: CustomPropTypes.className,
showCopyBtn: PropTypes.bool,
customKeyMap: PropTypes.array,
onTextSelect:PropTypes.func
};
15 changes: 14 additions & 1 deletion web/pgadmin/tools/sqleditor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
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, \
Expand Down Expand Up @@ -130,6 +130,7 @@ def get_exposed_url_endpoints(self):
'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',
Expand Down Expand Up @@ -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()
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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)}));
};
Expand Down Expand Up @@ -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}),
],
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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}
Expand Down
Loading

0 comments on commit 4e3ec91

Please sign in to comment.