public inbox for [email protected]
help / color / mirror / Atom feedFrom: Aditya Toshniwal <[email protected]>
To: Dave Page <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: [pgAdmin][RM4139] Drag and drop object names in Query Editor from Browser Tree
Date: Thu, 27 Jun 2019 16:01:51 +0530
Message-ID: <CAM9w-_=XpoE+GYxgtVvDtkLNNG5QCZse3vnMh74Sg9umSFgYRg@mail.gmail.com> (raw)
In-Reply-To: <CA+OCxozZ38LofqpoAXDwVDoF8HoSRocmtPAk=cbDv2G5q8n7AA@mail.gmail.com>
References: <CAM9w-_mxRdG6peLx3VdF1z-+yY11qQEPNTQ0ekT5TNaEWT05QQ@mail.gmail.com>
<CA+OCxowrfff5pU-sKTpk8G4oDpxpYRSmJ-mZbrhWWTYPB7fubQ@mail.gmail.com>
<CAM9w-_nD6z_DtLWyKbU9nh34_jQVbj9UcHesznRiqeAoQ0LCKw@mail.gmail.com>
<CA+OCxoyABTHRkBS++mFDc6cD6SswOZ-p8r=gpctLHXkxnyF5Pg@mail.gmail.com>
<CAM9w-_kPPak3LyuYnYiCauUAwRrddSVZQmpP4s7MCydz2Z9RSA@mail.gmail.com>
<CA+OCxozZ38LofqpoAXDwVDoF8HoSRocmtPAk=cbDv2G5q8n7AA@mail.gmail.com>
Hi Hackers,
Attached is the updated patch based on the review comments in trailing mail.
Kindly review.
On Wed, Jun 26, 2019 at 5:09 PM Dave Page <[email protected]> wrote:
>
>
> On Wed, Jun 26, 2019 at 7:38 AM Aditya Toshniwal <
> [email protected]> wrote:
>
>> Hi,
>>
>> On Wed, Jun 26, 2019 at 5:06 PM Dave Page <[email protected]> wrote:
>>
>>>
>>>
>>> On Wed, Jun 26, 2019 at 7:28 AM Aditya Toshniwal <
>>> [email protected]> wrote:
>>>
>>>> Hi,
>>>>
>>>> On Wed, Jun 26, 2019 at 4:51 PM Dave Page <[email protected]> wrote:
>>>>
>>>>> Hi
>>>>>
>>>>> On Wed, Jun 26, 2019 at 2:24 AM Aditya Toshniwal <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Hackers,
>>>>>>
>>>>>> Attached is the patch to allow tree nodes to be dragged and dropped
>>>>>> in query editor wherever you take the drop cursor. The drop text will be
>>>>>> fully qualified and double quoted if required.
>>>>>> For functions/procedures it will drop the the label stripping away
>>>>>> the parameter names, with empty pair of round brackets. It will also set
>>>>>> the focus cursor inside the brackets if there were params otherwiser to the
>>>>>> end of text.
>>>>>> For adding a node type, you need to register it in browser.js along
>>>>>> with a callback function which will return drop text along with cursor
>>>>>> positioning and selection. Currently, I have registered for "table
>>>>>> partition type sequence package view mview foreign_table edbvar schema
>>>>>> column edbfunc function edbproc procedure".
>>>>>>
>>>>>> Please note, the drag design customisation is not supported in IE.
>>>>>>
>>>>>
>>>>> I think for functions/procedures, we should include placeholders for
>>>>> the parameters, so the user has a hint about what they need to fill in to
>>>>> complete the call.
>>>>>
>>>> I can fill in the parameter names same as tree node, with cursor
>>>> selection on all the params ?
>>>>
>>>
>>> Can you select the first one, and put the cursor in front of it?
>>>
>> Yes. Will do that. So that, just after drop if the user types anything it
>> will replace the first param name.
>>
>
> Right.
>
>
>>
>>>
>>>>
>>>>> Also; shouldn't there be a doc update?
>>>>>
>>>> Yeah :/
>>>> Should there be any screenshots ?
>>>>
>>>
>>> I don't see much need.
>>>
>> Cool.
>>
>
> :-)
>
>
>>
>>> Thanks.
>>>
>>>
>>>>
>>>>> Thanks.
>>>>>
>>>>> --
>>>>> Dave Page
>>>>> Blog: http://pgsnake.blogspot.com
>>>>> Twitter: @pgsnake
>>>>>
>>>>> EnterpriseDB UK: http://www.enterprisedb.com
>>>>> The Enterprise PostgreSQL Company
>>>>>
>>>>
>>>>
>>>> --
>>>> Thanks and Regards,
>>>> Aditya Toshniwal
>>>> Software Engineer | EnterpriseDB India | Pune
>>>> "Don't Complain about Heat, Plant a TREE"
>>>>
>>>
>>>
>>> --
>>> Dave Page
>>> Blog: http://pgsnake.blogspot.com
>>> Twitter: @pgsnake
>>>
>>> EnterpriseDB UK: http://www.enterprisedb.com
>>> The Enterprise PostgreSQL Company
>>>
>>
>>
>> --
>> Thanks and Regards,
>> Aditya Toshniwal
>> Software Engineer | EnterpriseDB India | Pune
>> "Don't Complain about Heat, Plant a TREE"
>>
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>
--
Thanks and Regards,
Aditya Toshniwal
Software Engineer | EnterpriseDB India | Pune
"Don't Complain about Heat, Plant a TREE"
Attachments:
[application/octet-stream] RM4139_v2.patch (15.4K, 3-RM4139_v2.patch)
download | inline diff:
diff --git a/docs/en_US/tree_control.rst b/docs/en_US/tree_control.rst
index 73eb114f..03d4f1ca 100644
--- a/docs/en_US/tree_control.rst
+++ b/docs/en_US/tree_control.rst
@@ -19,6 +19,12 @@ view:
control.
* Click the minus sign (-) to the left of a node to close that node.
+You can also **drag and drop** certain object names to the query tool which
+is very helpful to save time in typing long object names. The text on drop will be
+fully qualified with schema name. Double quotes will be added to the text if required.
+For functions and procedures, the function name along with parameter names will
+be pasted in the query tool.
+
Access context-sensitive menus by right-clicking on a node of the tree control
to perform common tasks. Menus display options that include one or more of the
following selections (options appear in alphabetical order):
diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js
index 29c11c2e..38252887 100644
--- a/web/pgadmin/browser/static/js/browser.js
+++ b/web/pgadmin/browser/static/js/browser.js
@@ -12,7 +12,7 @@ define('pgadmin.browser', [
'sources/gettext', 'sources/url_for', 'require', 'jquery', 'underscore', 'underscore.string',
'bootstrap', 'sources/pgadmin', 'pgadmin.alertifyjs', 'bundled_codemirror',
'sources/check_node_visibility', './toolbar', 'pgadmin.help',
- 'sources/csrf', 'pgadmin.browser.utils',
+ 'sources/csrf', 'sources/utils', 'pgadmin.browser.utils',
'wcdocker', 'jquery.contextmenu', 'jquery.aciplugin', 'jquery.acitree',
'pgadmin.browser.preferences', 'pgadmin.browser.messages',
'pgadmin.browser.menu', 'pgadmin.browser.panel', 'pgadmin.browser.layout',
@@ -24,7 +24,7 @@ define('pgadmin.browser', [
tree,
gettext, url_for, require, $, _, S,
Bootstrap, pgAdmin, Alertify, codemirror,
- checkNodeVisibility, toolBar, help, csrfToken
+ checkNodeVisibility, toolBar, help, csrfToken, pgadminUtils,
) {
window.jQuery = window.$ = $;
// Some scripts do export their object in the window only.
@@ -102,6 +102,46 @@ define('pgadmin.browser', [
b.tree = $('#tree').aciTree('api');
b.treeMenu.register($('#tree'));
+
+ b.treeMenu.registerDraggableType({
+ 'table partition type sequence package view mview foreign_table edbvar' : (data, item)=>{
+ return pgadminUtils.fully_qualify(b, data, item);
+ },
+ 'schema column' : (data)=>{
+ return pgadminUtils.quote_ident(data._label);
+ },
+ 'edbfunc function edbproc procedure' : (data, item)=>{
+ let newData = {...data},
+ parsedFunc = null,
+ dropVal = '',
+ curPos = {from: 0, to: 0};
+
+ parsedFunc = pgadminUtils.parseFuncParams(newData._label);
+ newData._label = parsedFunc.func_name;
+ dropVal = pgadminUtils.fully_qualify(b, newData, item);
+
+ if(parsedFunc.params.length > 0) {
+ dropVal = dropVal + '(';
+ curPos.from = dropVal.length;
+ dropVal = dropVal + parsedFunc.params[0][0];
+ curPos.to = dropVal.length;
+
+ for(let i=1; i<parsedFunc.params.length; i++) {
+ dropVal = dropVal + ', ' + parsedFunc.params[i][0];
+ }
+
+ dropVal = dropVal + ')';
+ } else {
+ dropVal = dropVal + '()';
+ curPos.from = curPos.to = dropVal.length + 1;
+ }
+
+ return {
+ text: dropVal,
+ cur: curPos,
+ };
+ },
+ });
};
// Extend the browser class attributes
diff --git a/web/pgadmin/static/js/tree/tree.js b/web/pgadmin/static/js/tree/tree.js
index 782f4d59..99397ef4 100644
--- a/web/pgadmin/static/js/tree/tree.js
+++ b/web/pgadmin/static/js/tree/tree.js
@@ -8,6 +8,7 @@
//////////////////////////////////////////////////////////////////////////
import {isValidData} from 'sources/utils';
+import $ from 'jquery';
export class TreeNode {
constructor(id, data, domNode, parent) {
@@ -97,6 +98,87 @@ export class Tree {
constructor() {
this.rootNode = new TreeNode(undefined, {});
this.aciTreeApi = undefined;
+ this.draggableTypes = {};
+ }
+
+ /*
+ *
+ * The dropDetailsFunc should return an object of sample
+ * {text: 'xyz', cur: {from:0, to:0} where text is the drop text and
+ * cur is selection range of text after dropping. If returned as
+ * string, by default cursor will be set to the end of text
+ */
+ registerDraggableType(typeOrTypeDict, dropDetailsFunc=null) {
+ if(typeof typeOrTypeDict == 'object') {
+ Object.keys(typeOrTypeDict).forEach((type)=>{
+ this.registerDraggableType(type, typeOrTypeDict[type]);
+ });
+ } else {
+ if(dropDetailsFunc != null) {
+ typeOrTypeDict.replace(/ +/, ' ').split(' ').forEach((type)=>{
+ this.draggableTypes[type] = dropDetailsFunc;
+ });
+ }
+ }
+ }
+
+ getDraggable(type) {
+ if(this.draggableTypes[type]) {
+ return this.draggableTypes[type];
+ } else {
+ return null;
+ }
+ }
+
+ prepareDraggable(data, item) {
+ let dropDetailsFunc = this.getDraggable(data._type);
+
+ if(dropDetailsFunc != null) {
+ item.find('.aciTreeItem')
+ .attr('draggable', true)
+ .on('dragstart', (e)=> {
+ let dropDetails = dropDetailsFunc(data, item);
+ let origEvent = e.originalEvent;
+
+ if(typeof dropDetails == 'string') {
+ dropDetails = {
+ text:dropDetails,
+ cur:{
+ from:dropDetails.length,
+ to: dropDetails.length,
+ },
+ };
+ } else {
+ if(!dropDetails.cur) {
+ dropDetails = {
+ ...dropDetails,
+ cur:{
+ from:dropDetails.text.length,
+ to: dropDetails.text.length,
+ },
+ };
+ }
+ }
+
+ origEvent.dataTransfer.setData('text', JSON.stringify(dropDetails));
+
+ /* setDragImage is not supported in IE. We leave it to
+ * its default look and feel
+ */
+ if(origEvent.dataTransfer.setDragImage) {
+ let dragItem = $(`
+ <div class="drag-tree-node">
+ <span>${dropDetails.text}</span>
+ </div>`
+ );
+
+ $('body .drag-tree-node').remove();
+ $('body').append(dragItem);
+
+ origEvent.dataTransfer.setDragImage(dragItem[0], 0, 0);
+ }
+ });
+ }
}
addNewNode(id, data, domNode, parentPath) {
@@ -163,6 +245,9 @@ export class Tree {
if (eventName === 'added') {
const id = api.getId(item);
const data = api.itemData(item);
+
+ this.prepareDraggable(data, item);
+
const parentId = this.translateTreeNodeIdFromACITree(api.parent(item));
this.addNewNode(id, data, item, parentId);
}
diff --git a/web/pgadmin/static/js/utils.js b/web/pgadmin/static/js/utils.js
index 1c58a9eb..fe9fd8ed 100644
--- a/web/pgadmin/static/js/utils.js
+++ b/web/pgadmin/static/js/utils.js
@@ -8,6 +8,7 @@
//////////////////////////////////////////////////////////////////////////
import _ from 'underscore';
+import { getTreeNodeHierarchyFromIdentifier } from 'sources/tree/pgadmin_tree_node';
export function parseShortcutValue(obj) {
var shortcut = '';
@@ -83,3 +84,118 @@ export function getGCD(inp_arr) {
export function getMod(no, divisor) {
return ((no % divisor) + divisor) % divisor;
}
+
+export function parseFuncParams(label) {
+ let paramArr = [],
+ funcName = '',
+ paramStr = '';
+
+ if(label.endsWith('()')) {
+ funcName = label.substring(0, label.length-2);
+ } else if(!label.endsWith(')')) {
+ funcName = label;
+ } else if(!label.endsWith('()') && label.endsWith(')')) {
+ let i = 0,
+ startBracketPos = label.length;
+
+ /* Parse through the characters in reverse to find the param start bracket */
+ i = label.length-2;
+ while(i >= 0) {
+ if(label[i] == '(') {
+ startBracketPos = i;
+ break;
+ } else if(label[i] == '"') {
+ /* If quotes, skip all the chars till next quote */
+ i--;
+ while(label[i] != '"') i--;
+ }
+ i--;
+ }
+
+ funcName = label.substring(0, startBracketPos);
+ paramStr = label.substring(startBracketPos+1, label.length-1);
+
+ let paramStart = 0,
+ paramName = '',
+ paramModes = ['IN', 'OUT', 'INOUT', 'VARIADIC'];
+
+ paramStart = i = 0;
+ while(i < paramStr.length) {
+ if(paramStr[i] == '"') {
+ /* If quotes, skip all the chars till next quote */
+ i++;
+ while(paramStr[i] != '"') i++;
+ } else if (paramStr[i] == ' ') {
+ /* if paramName is already set, ignore till comma
+ * Or if paramName is parsed as one of the modes, reset.
+ */
+ if(paramName == '' || paramModes.indexOf(paramName) > -1 ) {
+ paramName = paramStr.substring(paramStart, i);
+ paramStart = i+1;
+ }
+ }
+ else if (paramStr[i] == ',') {
+ paramArr.push([paramName, paramStr.substring(paramStart, i)]);
+ paramName = '';
+ paramStart = i+1;
+ }
+ i++;
+ }
+ paramArr.push([paramName, paramStr.substring(paramStart)]);
+ }
+
+ return {
+ 'func_name': funcName,
+ 'param_string': paramStr,
+ 'params': paramArr,
+ };
+}
+
+export function quote_ident(value) {
+ /* check if the string is number or not */
+ let quoteIt = false;
+ if (!isNaN(parseInt(value))){
+ quoteIt = true;
+ }
+
+ if(value.search(/[^a-z0-9_]/g) > -1) {
+ /* escape double quotes */
+ value = value.replace(/"/g, '""');
+ quoteIt = true;
+ }
+
+ if(quoteIt) {
+ return `"${value}"`;
+ } else {
+ return value;
+ }
+}
+
+export function fully_qualify(pgBrowser, data, item) {
+ const parentData = getTreeNodeHierarchyFromIdentifier.call(pgBrowser, item);
+ let namespace = '';
+
+ if (parentData.schema !== undefined) {
+ namespace = quote_ident(parentData.schema._label);
+ }
+ else if (parentData.view !== undefined) {
+ namespace = quote_ident(parentData.view._label);
+ }
+ else if (parentData.catalog !== undefined) {
+ namespace = quote_ident(parentData.catalog._label);
+ }
+
+ if (parentData.package !== undefined && data._type != 'package') {
+ if(namespace == '') {
+ namespace = quote_ident(parentData.package._label);
+ } else {
+ namespace += '.' + quote_ident(parentData.package._label);
+ }
+ }
+
+ if(namespace != '') {
+ return namespace + '.' + quote_ident(data._label);
+ } else {
+ return quote_ident(data._label);
+ }
+}
diff --git a/web/pgadmin/static/scss/_pgadmin.style.scss b/web/pgadmin/static/scss/_pgadmin.style.scss
index ed426545..f5a8877c 100644
--- a/web/pgadmin/static/scss/_pgadmin.style.scss
+++ b/web/pgadmin/static/scss/_pgadmin.style.scss
@@ -983,3 +983,15 @@ table.table-empty-rows{
padding: 0px !important;
position: absolute;
}
+
+.drag-tree-node {
+ position: absolute;
+ top:-100px;
+ left:0;
+ z-index: 99999;
+ color: $input-focus-color;
+ background: $input-bg;
+ border: $input-border-width solid $input-focus-border-color;
+ border-radius: $input-border-radius;
+ padding: $input-btn-padding-y $input-btn-padding-x;
+}
diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
index a8560574..ef468f63 100644
--- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
@@ -341,8 +341,31 @@ define('tools.querytool', [
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
extraKeys: pgBrowser.editor_shortcut_keys,
scrollbarStyle: 'simple',
+ dragDrop: false,
});
+ if(self.handler.is_query_tool) {
+ self.query_tool_obj.setOption('dragDrop', true);
+ self.query_tool_obj.on('drop', (editor, e) => {
+ var cursor = editor.coordsChar({
+ left: e.x,
+ top: e.y,
+ });
+ var dropDetails = JSON.parse(e.dataTransfer.getData('text'));
+ e.codemirrorIgnore = true;
+ e.dataTransfer.clearData('text');
+ editor.replaceRange(dropDetails.text, cursor);
+ editor.focus();
+ editor.setSelection({
+ ...cursor,
+ ch: cursor.ch + dropDetails.cur.from,
+ },{
+ ...cursor,
+ ch: cursor.ch +dropDetails.cur.to,
+ });
+ });
+ }
+
pgBrowser.Events.on('pgadmin:query_tool:sql_panel:focus', ()=>{
self.query_tool_obj.focus();
});
diff --git a/web/regression/javascript/pgadmin_utils_spec.js b/web/regression/javascript/pgadmin_utils_spec.js
index 02bd5478..bc1959a4 100644
--- a/web/regression/javascript/pgadmin_utils_spec.js
+++ b/web/regression/javascript/pgadmin_utils_spec.js
@@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
-import { getEpoch, getGCD, getMod } from 'sources/utils';
+import { getEpoch, getGCD, getMod, quote_ident, parseFuncParams } from 'sources/utils';
describe('getEpoch', function () {
it('should return non zero', function () {
@@ -51,3 +51,87 @@ describe('getMod', function () {
expect(getMod(-7,5)).toEqual(3);
});
});
+
+describe('quote_ident', function () {
+ it('normal string', function () {
+ expect(quote_ident('abcd')).toEqual('abcd');
+ });
+
+ it('contains certain characters string', function () {
+ expect(quote_ident('Abcd')).toEqual('"Abcd"');
+ expect(quote_ident('abc$d')).toEqual('"abc$d"');
+ expect(quote_ident('ab cd')).toEqual('"ab cd"');
+ });
+
+ it('starts with number', function () {
+ expect(quote_ident('1a')).toEqual('"1a"');
+ expect(quote_ident('a1')).toEqual('a1');
+ });
+});
+
+describe('parseFuncParams', function () {
+ let funcLabel = '',
+ expectedObj = {};
+
+ it('function with params', function () {
+ funcLabel = 'func1(a integer, b text)';
+ expectedObj = {
+ 'func_name': 'func1',
+ 'param_string': 'a integer, b text',
+ 'params': [
+ ['a', 'integer'],
+ ['b', 'text'],
+ ],
+ };
+ expect(parseFuncParams(funcLabel)).toEqual(expectedObj);
+ });
+
+ it('function without params', function () {
+ funcLabel = 'func1()';
+ expectedObj = {
+ 'func_name': 'func1',
+ 'param_string': '',
+ 'params': [],
+ };
+ expect(parseFuncParams(funcLabel)).toEqual(expectedObj);
+ });
+
+ it('function name special chars', function () {
+ funcLabel = 'fun(c1(a integer, b text)';
+ expectedObj = {
+ 'func_name': 'fun(c1',
+ 'param_string': 'a integer, b text',
+ 'params': [
+ ['a', 'integer'],
+ ['b', 'text'],
+ ],
+ };
+ expect(parseFuncParams(funcLabel)).toEqual(expectedObj);
+ });
+
+ it('function params special chars', function () {
+ funcLabel = 'func1("a(b" integer, "a b" text)';
+ expectedObj = {
+ 'func_name': 'func1',
+ 'param_string': '"a(b" integer, "a b" text',
+ 'params': [
+ ['"a(b"', 'integer'],
+ ['"a b"', 'text'],
+ ],
+ };
+ expect(parseFuncParams(funcLabel)).toEqual(expectedObj);
+ });
+
+ it('function params with modes', function () {
+ funcLabel = 'func1(IN a integer, OUT b text)';
+ expectedObj = {
+ 'func_name': 'func1',
+ 'param_string': 'IN a integer, OUT b text',
+ 'params': [
+ ['a', 'integer'],
+ ['b', 'text'],
+ ],
+ };
+ expect(parseFuncParams(funcLabel)).toEqual(expectedObj);
+ });
+});
view thread (8+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected]
Subject: Re: [pgAdmin][RM4139] Drag and drop object names in Query Editor from Browser Tree
In-Reply-To: <CAM9w-_=XpoE+GYxgtVvDtkLNNG5QCZse3vnMh74Sg9umSFgYRg@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox