public inbox for [email protected]  
help / color / mirror / Atom feed
From: Murtuza Zabuawala <[email protected]>
To: Dave Page <[email protected]>
Cc: Robert Eckhardt <[email protected]>
Cc: Joao De Almeida Pereira <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: [pgAdmin4][RM#3055] Allow user to sort the data in View data mode
Date: Thu, 5 Apr 2018 16:59:46 +0530
Message-ID: <CAKKotZRdfOXX_8osxWHxD=G0mUtmwvdyYa-=RHCkpbOhyOid9g@mail.gmail.com> (raw)
In-Reply-To: <CA+OCxow0Ov99inLnLWZjbAHh+YToa2t4jQdcW--gOppow-QN9g@mail.gmail.com>
References: <CAKKotZTzsN7Duyb5KpVuVHBK9pGHXLNvrp7JRVJ1uetAEYhuKg@mail.gmail.com>
	<CA+OCxoxj2Jp0iaJw4TmRWE4Yz3ZwpF47n7agoeq8rR5MaNhyig@mail.gmail.com>
	<CAKKotZSf=7iBb3db3aYvmYpA081tjcsxcSyApVe_y8b4d+7q+Q@mail.gmail.com>
	<CAE+jjakMz=G-gj_u9pQ4wUcbaF5=kdBTz1R25Cb3iavLyEuJMg@mail.gmail.com>
	<CAAtBm9V+y-xTCQvTzQ6tnGoB77fKRsVKdqeq+b+9GzvQ_Cmfsw@mail.gmail.com>
	<CA+OCxoxZmCwSHg_bbP6WjJQhgu01eEqP=Y4ZyXAcWhtb+Q91wQ@mail.gmail.com>
	<CAKKotZSG=4Z2PFs_sE7kgON=BEtot3R599iWYK3_ZCKiEkTLjg@mail.gmail.com>
	<CAAtBm9U=KZmKY--nA3o98T4Xg9+T8Ha0_hmcQzM8k36Dpu0-OQ@mail.gmail.com>
	<CAKKotZS+k_C3YGz3C=SUG9TRnr30zoS614BGJpPgzxjmCARoXQ@mail.gmail.com>
	<CAAtBm9WOWap9=CVyKSN6097RKpmF7U+GWpT-Ep0ftGd-Fz4R6w@mail.gmail.com>
	<CAKKotZQWS=JUb0N0-Cy1KVsWhBjk1TxwhMcZXfrZzQNKvrSVsg@mail.gmail.com>
	<CA+OCxow0Ov99inLnLWZjbAHh+YToa2t4jQdcW--gOppow-QN9g@mail.gmail.com>

Hi Dave,

Please find rebased patch.

--
Regards,
Murtuza Zabuawala
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


On Thu, Apr 5, 2018 at 4:15 PM, Dave Page <[email protected]> wrote:

> Can you rebase this please?
>
> On Wed, Mar 28, 2018 at 8:19 AM, Murtuza Zabuawala <murtuza.zabuawala@
> enterprisedb.com> wrote:
>
>> Hi Dave,
>>
>> Please find updated patch with following changes,
>> - Combined Filter and Data sorting together same as pgAdmin3.
>> - Extracted model into separate file
>> - Change the colour of filter button from orange to blue.
>> - Updated docs and screenshot.
>>
>> @Joao,
>> Could you please provide any reference for learning more about jasmine
>> test framework?
>> ​
>>
>> --
>> Regards,
>> Murtuza Zabuawala
>> EnterpriseDB: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>>
>> On Wed, Mar 28, 2018 at 6:07 AM, Robert Eckhardt <[email protected]>
>> wrote:
>>
>>>
>>>
>>> On Tue, Mar 27, 2018 at 9:54 AM, Murtuza Zabuawala <
>>> [email protected]> wrote:
>>>
>>>>
>>>>
>>>> On Tue, Mar 27, 2018 at 7:06 PM, Robert Eckhardt <[email protected]>
>>>> wrote:
>>>>
>>>>>
>>>>>
>>>>> On Tue, Mar 27, 2018 at 6:25 AM, Murtuza Zabuawala <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> On Tue, Mar 27, 2018 at 3:13 PM, Dave Page <[email protected]> wrote:
>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> On Mon, Mar 26, 2018 at 9:26 PM, Robert Eckhardt <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> On Mon, Mar 26, 2018 at 2:07 PM, Joao De Almeida Pereira <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi Hackers,
>>>>>>>>>
>>>>>>>>> @Murtuza: The patch codewise looks good. Nice to see that we are
>>>>>>>>> using axios instead of jquery ajax calls and that there is some coverage
>>>>>>>>> for the change.
>>>>>>>>> Nevertheless the Javascript testing looks a bit slim and could be
>>>>>>>>> improved. Also the DataSorting class could have some other member functions
>>>>>>>>> like the model validation could be extracted out so that it is easily
>>>>>>>>> tested.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> @Hackers: This was how we tried to test this feature:
>>>>>>>>> 1 - Started pgAdmin
>>>>>>>>> 2 - Opened the query tool for a specific server
>>>>>>>>> 3 - Executed a SQL statment
>>>>>>>>> 4 - Pressed the column header to try to order, nothing happened
>>>>>>>>> 5 - Right clicked the column header to see if it was there the
>>>>>>>>> option, nothing
>>>>>>>>>
>>>>>>>>> This is the behavior that we were expecting, not to have to open
>>>>>>>>> Data View and then press the icon that is not even near the grid in order
>>>>>>>>> to sort the column. Is this really the way we want people to use the grid
>>>>>>>>> in pgAdmin? Should it be more intuitive?
>>>>>>>>>
>>>>>>>>
>>>>>>>> Have we considered making the grid behave more like excel or other
>>>>>>>> grids? I think that having the ascending and descending inside the column
>>>>>>>> header, we could similarly provide filtering. Something that would give
>>>>>>>> users a more intuitive place to look.
>>>>>>>>
>>>>>>>
>>>>>>> Doing the sorting via header clicks is convenient but very
>>>>>>> restrictive. How do you specify multiple columns to sort by for example?
>>>>>>> The current design allows you to select columns and the sort order as you
>>>>>>> see fit.
>>>>>>>
>>>>>>
>>>>> Honestly I'm not sold on my idea, I was just proposing an alternative
>>>>> in an effort to start a discussion about the user experience. Ideally what
>>>>> I'd like to see, maybe this happened, is some user research. When we
>>>>> initial worked on refactoring the results grid we made a bunch of changes.
>>>>> One of the things we intended to do was to follow up to see how people were
>>>>> using the grid now so that we could better understand how it was now being
>>>>> used in order to design and implement features just like this. Clearly we
>>>>> haven't gotten there yet.
>>>>>
>>>>>
>>>>>>
>>>>>> Another reason we can't use that because w
>>>>>> e have already occupied that behaviour for selecting entire column
>>>>>> ​ when user clicks on header.
>>>>>> As Dave suggested, I will be merging it with filter dialog meaning it
>>>>>> will be accessible via direct button on toolbar & keyboard shortcut.​
>>>>>> ​
>>>>>>
>>>>>
>>>>> How are users currently interacting with that filter dialog?
>>>>>
>>>>
>>>> ​By clicking on the toolbar button as well as keyboard shortcut.
>>>> ​
>>>>
>>>>
>>>>
>>>>
>>>
>>> Sorry I wasn't clear. My question was more along the lines of, do we
>>> know if people are using the filter functionality?  What kind of
>>> filters are people using?  What do they like about it? What do they wish
>>> they could do above and beyond sorting, etc.
>>>
>> ​I have not done any data gathering from users so I can't comment on your
>> queries.
>> ​ but a​
>> ​s far as I understood from the feature requests that most of the users
>> expect to have functionality which will allow then to sort columns as it
>> was in pgAdmin3.​
>>
>>
>>
>>> -- Rob
>>>
>>>
>>>>
>>>>> What I'm suggesting is that we understand how users want to interact
>>>>> with their results, be those the results of a query or a table view, then
>>>>> we can design something that meets those needs. I agree that changing the
>>>>> column selection behavior isn't desirable, however, I also feel like
>>>>> providing the best user experience is better than holding onto a particular
>>>>> feature implementation.
>>>>> ​
>>>>>
>>>>>
>>>>
>>>>> -- Rob
>>>>>
>>>>>
>>>>>>
>>>>>>
>>>>>>> --
>>>>>>> Dave Page
>>>>>>> Blog: http://pgsnake.blogspot.com
>>>>>>> Twitter: @pgsnake
>>>>>>>
>>>>>>> EnterpriseDB UK: http://www.enterprisedb.com
>>>>>>> The Enterprise PostgreSQL Company
>>>>>>>
>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>>
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


Attachments:

  [image/png] image.png (87.7K, 3-image.png)
  download | view image

  [application/octet-stream] RM_3055_v2.diff (50.0K, 4-RM_3055_v2.diff)
  download | inline diff:
diff --git a/docs/en_US/editgrid.rst b/docs/en_US/editgrid.rst
index 1cb23cf..b1a9551 100644
--- a/docs/en_US/editgrid.rst
+++ b/docs/en_US/editgrid.rst
@@ -95,6 +95,24 @@ To delete a row, press the *Delete* toolbar button.  A popup will open, asking y
 
 To commit the changes to the server, select the *Save* toolbar button.  Modifications to a row are written to the server automatically when you select a different row.
 
+**Sort/Filter options dialog**
 
+You can access *Sort/Filter options dialog* by clicking on Filter button, This dialog provides information about the sql filter and data sorting in the edit grid window:
 
+.. image:: images/editgrid_filter_dialog.png
+    :alt: Edit grid filter dialog window
+
+* Use *SQL filter* to provide where clause conditions
+* Use *Data sorting* to sort the data in the output grid
+
+To add new column(s) in data sorting grid, click on the [+] icon.
+
+* Use the drop-down *Column* to select the column you want to sort.
+* Use the drop-down *Order* to select the sort order for the column.
+
+To discard a data sorting, and delete the row from the grid, click the trash icon.
+
+* Click the *Help* button (?) to access online help.
+* Click the *Ok* button to save work.
+* Click the *Close* button to discard current changes and close the dialog.
 
diff --git a/docs/en_US/images/editgrid_filter_dialog.png b/docs/en_US/images/editgrid_filter_dialog.png
new file mode 100644
index 0000000..046d9a1
Binary files /dev/null and b/docs/en_US/images/editgrid_filter_dialog.png differ
diff --git a/web/pgadmin/static/js/sqleditor/filter_dialog.js b/web/pgadmin/static/js/sqleditor/filter_dialog.js
new file mode 100644
index 0000000..0ba9e35
--- /dev/null
+++ b/web/pgadmin/static/js/sqleditor/filter_dialog.js
@@ -0,0 +1,243 @@
+define([
+  'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'underscore.string',
+  'pgadmin.alertifyjs', 'sources/pgadmin', 'backbone',
+  'pgadmin.backgrid', 'pgadmin.backform', 'axios',
+  'sources/sqleditor/query_tool_actions',
+  'sources/sqleditor/filter_dialog_model',
+  //'pgadmin.browser.node.ui',
+], function(
+  gettext, url_for, $, _, S, Alertify, pgAdmin, Backbone,
+  Backgrid, Backform, axios, queryToolActions, filterDialogModel
+) {
+
+  let FilterDialog = {
+    'dialog': function(handler) {
+      let title = gettext('Sort/Filter options');
+      axios.get(
+        url_for('sqleditor.get_filter_data', {
+          'trans_id': handler.transId,
+        }),
+        { headers: {'Cache-Control' : 'no-cache'} }
+      ).then(function (res) {
+        let response = res.data.data.result;
+
+        // Check the alertify dialog already loaded then delete it to clear
+        // the cache
+        if (Alertify.filterDialog) {
+          delete Alertify.filterDialog;
+        }
+
+        // Create Dialog
+        Alertify.dialog('filterDialog', function factory() {
+          let $container = $('<div class=\'data_sorting_dialog\'></div>');
+          return {
+            main: function() {
+              this.set('title', gettext('Sort/Filter options'));
+            },
+            build: function() {
+              this.elements.content.appendChild($container.get(0));
+              Alertify.pgDialogBuild.apply(this);
+            },
+            setup: function() {
+              return {
+                buttons: [{
+                  text: '',
+                  key: 112,
+                  className: 'btn btn-default pull-left fa fa-lg fa-question',
+                  attrs: {
+                    name: 'dialog_help',
+                    type: 'button',
+                    label: gettext('Help'),
+                    url: url_for('help.static', {
+                      'filename': 'editgrid.html',
+                    }),
+                  },
+                }, {
+                  text: gettext('Ok'),
+                  className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button',
+                  'data-btn-name': 'ok',
+                }, {
+                  text: gettext('Cancel'),
+                  key: 27,
+                  className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button',
+                  'data-btn-name': 'cancel',
+                }],
+                // Set options for dialog
+                options: {
+                  title: title,
+                  //disable both padding and overflow control.
+                  padding: !1,
+                  overflow: !1,
+                  model: 0,
+                  resizable: true,
+                  maximizable: true,
+                  pinnable: false,
+                  closableByDimmer: false,
+                  modal: false,
+                  autoReset: false,
+                },
+              };
+            },
+            hooks: {
+              // triggered when the dialog is closed
+              onclose: function() {
+                if (this.view) {
+                  this.filterCollectionModel.stopSession();
+                  this.view.model.stopSession();
+                  this.view.remove({
+                    data: true,
+                    internal: true,
+                    silent: true,
+                  });
+                }
+              },
+            },
+            prepare: function() {
+              let self = this;
+              $container.html('');
+              // Disable Ok button
+              this.__internal.buttons[1].element.disabled = true;
+
+              // Status bar
+              this.statusBar = $('<div class=\'pg-prop-status-bar pg-el-xs-12 hide\'>' +
+                '  <div class=\'media error-in-footer bg-red-1 border-red-2 font-red-3 text-14\'>' +
+                '    <div class=\'media-body media-middle\'>' +
+                '      <div class=\'alert-icon error-icon\'>' +
+                '        <i class=\'fa fa-exclamation-triangle\' aria-hidden=\'true\'></i>' +
+                '      </div>' +
+                '      <div class=\'alert-text\'>' +
+                '      </div>' +
+                '    </div>' +
+                '  </div>' +
+                '</div>', {
+                  text: '',
+                }).appendTo($container);
+
+              // To show progress on filter Saving/Updating on AJAX
+              this.showFilterProgress = $(
+                '<div id="show_filter_progress" class="wcLoadingIconContainer busy-fetching hidden">' +
+                '<div class="wcLoadingBackground"></div>' +
+                '<span class="wcLoadingIcon fa fa-spinner fa-pulse"></span>' +
+                '<span class="busy-text wcLoadingLabel">' + gettext('Loading data...') + '</span>' +
+                '</div>').appendTo($container);
+
+              $(
+                self.showFilterProgress[0]
+              ).removeClass('hidden');
+
+              self.filterCollectionModel = filterDialogModel(response);
+
+              let fields = Backform.generateViewSchema(
+                  null, self.filterCollectionModel, 'create', null, null, true
+              );
+
+              let view = this.view = new Backform.Dialog({
+                el: '<div></div>',
+                model: self.filterCollectionModel,
+                schema: fields,
+              });
+
+              $(this.elements.body.childNodes[0]).addClass(
+                'alertify_tools_dialog_properties obj_properties'
+              );
+
+              $container.append(view.render().$el);
+
+              // Enable/disable save button and show/hide statusbar based on session
+              view.listenTo(view.model, 'pgadmin-session:start', function() {
+                view.listenTo(view.model, 'pgadmin-session:invalid', function(msg) {
+                  self.statusBar.removeClass('hide');
+                  $(self.statusBar.find('.alert-text')).html(msg);
+                  // Disable Okay button
+                  self.__internal.buttons[1].element.disabled = true;
+                });
+
+                view.listenTo(view.model, 'pgadmin-session:valid', function() {
+                  self.statusBar.addClass('hide');
+                  $(self.statusBar.find('.alert-text')).html('');
+                  // Enable Okay button
+                  self.__internal.buttons[1].element.disabled = false;
+                });
+              });
+
+              view.listenTo(view.model, 'pgadmin-session:stop', function() {
+                view.stopListening(view.model, 'pgadmin-session:invalid');
+                view.stopListening(view.model, 'pgadmin-session:valid');
+              });
+
+              // Starts monitoring changes to model
+              view.model.startNewSession();
+
+              // Set data in collection
+              let viewDataSortingModel = view.model.get('data_sorting');
+              viewDataSortingModel.add(response['data_sorting']);
+
+              // Hide Progress ...
+              $(
+                self.showFilterProgress[0]
+              ).addClass('hidden');
+
+            },
+            // Callback functions when click on the buttons of the Alertify dialogs
+            callback: function(e) {
+              let self = this;
+
+              if (e.button.element.name == 'dialog_help') {
+                e.cancel = true;
+                pgAdmin.Browser.showHelp(e.button.element.name, e.button.element.getAttribute('url'),
+                  null, null, e.button.element.getAttribute('label'));
+                return;
+              } else if (e.button['data-btn-name'] === 'ok') {
+                e.cancel = true; // Do not close dialog
+
+                let filterCollectionModel = this.filterCollectionModel.toJSON();
+
+                // Show Progress ...
+                $(
+                  self.showFilterProgress[0]
+                ).removeClass('hidden');
+
+                axios.put(
+                  url_for('sqleditor.set_filter_data', {
+                    'trans_id': handler.transId,
+                  }),
+                  filterCollectionModel
+                ).then(function () {
+                  // Hide Progress ...
+                  $(
+                    self.showFilterProgress[0]
+                  ).addClass('hidden');
+                  setTimeout(
+                    function() {
+                      self.close(); // Close the dialog now
+                      Alertify.success(gettext('Filter updated successfully'));
+                      queryToolActions.executeQuery(handler);
+                    }, 10
+                  );
+
+                }).catch(function (error) {
+                  // Hide Progress ...
+                  $(
+                    self.showFilterProgress[0]
+                  ).addClass('hidden');
+                  handler.onExecuteHTTPError(error);
+
+                  setTimeout(
+                    function() {
+                      Alertify.error(error);
+                    }, 10
+                  );
+                });
+              } else {
+                self.close();
+              }
+            },
+          };
+        });
+
+        Alertify.filterDialog(title).resizeTo('65%', '60%');
+      });
+    },
+  };
+  return FilterDialog;
+});
diff --git a/web/pgadmin/static/js/sqleditor/filter_dialog_model.js b/web/pgadmin/static/js/sqleditor/filter_dialog_model.js
new file mode 100644
index 0000000..c3146a4
--- /dev/null
+++ b/web/pgadmin/static/js/sqleditor/filter_dialog_model.js
@@ -0,0 +1,133 @@
+define([
+  'sources/gettext', 'underscore', 'sources/pgadmin',
+  'pgadmin.backform', 'pgadmin.backgrid',
+], function(
+  gettext, _, pgAdmin, Backform, Backgrid
+) {
+
+  let initModel = function(response) {
+
+    let order_mapping = {
+      'asc': gettext('ASC'),
+      'desc': gettext('DESC'),
+    };
+
+    let DataSortingModel = pgAdmin.Browser.DataModel.extend({
+      idAttribute: 'name',
+      defaults: {
+        name: undefined,
+        order: 'asc',
+      },
+      schema: [{
+        id: 'name',
+        name: 'name',
+        label: gettext('Column'),
+        cell: 'select2',
+        editable: true,
+        cellHeaderClasses: 'width_percent_60',
+        headerCell: Backgrid.Extension.CustomHeaderCell,
+        disabled: false,
+        control: 'select2',
+        select2: {
+          allowClear: false,
+        },
+        options: function() {
+          return _.map(response.column_list, (obj) => {
+            return {
+              value: obj,
+              label: obj,
+            };
+          });
+        },
+      },
+      {
+        id: 'order',
+        name: 'order',
+        label: gettext('Order'),
+        control: 'select2',
+        cell: 'select2',
+        cellHeaderClasses: 'width_percent_40',
+        headerCell: Backgrid.Extension.CustomHeaderCell,
+        editable: true,
+        deps: ['type'],
+        select2: {
+          allowClear: false,
+        },
+        options: function() {
+          return _.map(order_mapping, (val, key) => {
+            return {
+              value: key,
+              label: val,
+            };
+          });
+        },
+      },
+      ],
+      validate: function() {
+        let msg = null;
+        this.errorModel.clear();
+        if (_.isUndefined(this.get('name')) ||
+          _.isNull(this.get('name')) ||
+          String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
+          msg = gettext('Please select a column.');
+          this.errorModel.set('name', msg);
+          return msg;
+        } else if (_.isUndefined(this.get('order')) ||
+          _.isNull(this.get('order')) ||
+          String(this.get('order')).replace(/^\s+|\s+$/g, '') == '') {
+          msg = gettext('Please select the order.');
+          this.errorModel.set('order', msg);
+          return msg;
+        }
+        return null;
+      },
+    });
+
+    let FilterCollectionModel = pgAdmin.Browser.DataModel.extend({
+      idAttribute: 'sql',
+      defaults: {
+        sql: response.sql || null,
+      },
+      schema: [{
+        id: 'sql',
+        label: gettext('SQL Filter'),
+        cell: 'string',
+        type: 'text', mode: ['create'],
+        control: Backform.SqlFieldControl.extend({
+          render: function() {
+            let obj = Backform.SqlFieldControl.prototype.render.apply(this, arguments);
+            // We need to set focus on editor after the dialog renders
+            setTimeout(() => {
+              obj.sqlCtrl.focus();
+            }, 1000);
+            return obj;
+          },
+        }),
+        extraClasses:['custom_height_css_class'],
+      },{
+        id: 'data_sorting',
+        name: 'data_sorting',
+        label: gettext('Data Sorting'),
+        model: DataSortingModel,
+        editable: true,
+        type: 'collection',
+        mode: ['create'],
+        control: 'unique-col-collection',
+        uniqueCol: ['name'],
+        canAdd: true,
+        canEdit: false,
+        canDelete: true,
+        visible: true,
+        version_compatible: true,
+      }],
+      validate: function() {
+        return null;
+      },
+    });
+
+    let model = new FilterCollectionModel();
+    return model;
+  };
+
+  return initModel;
+});
diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html
index 2f3bb05..29e6b81 100644
--- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html
+++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html
@@ -195,9 +195,9 @@
                 <ul class="dropdown-menu dropdown-menu-right">
                     <li>
                         <a id="btn-filter-menu" href="#" tabindex="0">{{ _('Filter') }}</a>
-                        <a id="btn-remove-filter" href="#" tabindex="0">{{ _('Remove Filter') }}</a>
                         <a id="btn-include-filter" href="#" tabindex="0">{{ _('By Selection') }}</a>
                         <a id="btn-exclude-filter" href="#" tabindex="0">{{ _('Exclude Selection') }}</a>
+                        <a id="btn-remove-filter" href="#" tabindex="0">{{ _('Remove Filter') }}</a>
                     </li>
                 </ul>
             </div>
@@ -341,23 +341,6 @@
             <div class="editor-title"
                  style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% else %}{{ bgcolor or '#2C76B4' }}{% endif %}; color: {{ fgcolor or 'white' }};"></div>
         </div>
-
-        <div id="filter" class="filter-container hidden">
-            <div class="filter-title">Filter</div>
-            <div class="sql-textarea">
-                <textarea id="sql_filter" rows="5"></textarea>
-            </div>
-            <div class="btn-group">
-                <button id="btn-cancel" type="button" class="btn btn-danger" title="{{ _('Cancel') }}" tabindex="0">
-                    <i class="fa fa-times" aria-hidden="true"></i> {{ _('Cancel') }}
-                </button>
-            </div>
-            <div class="btn-group">
-                <button id="btn-apply" type="button" class="btn btn-primary" title="{{ _('Apply') }}" tabindex="0">
-                    <i class="fa fa-check" aria-hidden="true"></i> {{ _('Apply') }}
-                </button>
-            </div>
-        </div>
         <div id="editor-panel" tabindex="0"></div>
         <iframe id="download-csv" style="display:none"></iframe>
     </div>
diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py
index f8d7d97..622b7c3 100644
--- a/web/pgadmin/tools/sqleditor/__init__.py
+++ b/web/pgadmin/tools/sqleditor/__init__.py
@@ -40,6 +40,7 @@ from pgadmin.tools.sqleditor.utils.query_tool_preferences import \
     RegisterQueryToolPreferences
 from pgadmin.tools.sqleditor.utils.query_tool_fs_utils import \
     read_file_generator
+from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog
 
 MODULE_NAME = 'sqleditor'
 
@@ -92,8 +93,6 @@ class SqlEditorModule(PgAdminModule):
             'sqleditor.fetch',
             'sqleditor.fetch_all',
             'sqleditor.save',
-            'sqleditor.get_filter',
-            'sqleditor.apply_filter',
             'sqleditor.inclusive_filter',
             'sqleditor.exclusive_filter',
             'sqleditor.remove_filter',
@@ -106,7 +105,9 @@ class SqlEditorModule(PgAdminModule):
             'sqleditor.load_file',
             'sqleditor.save_file',
             'sqleditor.query_tool_download',
-            'sqleditor.connection_status'
+            'sqleditor.connection_status',
+            'sqleditor.get_filter_data',
+            'sqleditor.set_filter_data'
         ]
 
     def register_preferences(self):
@@ -782,81 +783,6 @@ def save(trans_id):
         }
     )
 
-
[email protected](
-    '/filter/get/<int:trans_id>',
-    methods=["GET"], endpoint='get_filter'
-)
-@login_required
-def get_filter(trans_id):
-    """
-    This method is used to get the existing filter.
-
-    Args:
-        trans_id: unique transaction id
-    """
-
-    # Check the transaction and connection status
-    status, error_msg, conn, trans_obj, session_obj = \
-        check_transaction_status(trans_id)
-
-    if error_msg == gettext('Transaction ID not found in the session.'):
-        return make_json_response(success=0, errormsg=error_msg,
-                                  info='DATAGRID_TRANSACTION_REQUIRED',
-                                  status=404)
-    if status and conn is not None and \
-       trans_obj is not None and session_obj is not None:
-
-        res = trans_obj.get_filter()
-    else:
-        status = False
-        res = error_msg
-
-    return make_json_response(data={'status': status, 'result': res})
-
-
[email protected](
-    '/filter/apply/<int:trans_id>',
-    methods=["PUT", "POST"], endpoint='apply_filter'
-)
-@login_required
-def apply_filter(trans_id):
-    """
-    This method is used to apply the filter.
-
-    Args:
-        trans_id: unique transaction id
-    """
-    if request.data:
-        filter_sql = json.loads(request.data, encoding='utf-8')
-    else:
-        filter_sql = request.args or request.form
-
-    # Check the transaction and connection status
-    status, error_msg, conn, trans_obj, session_obj = \
-        check_transaction_status(trans_id)
-
-    if error_msg == gettext('Transaction ID not found in the session.'):
-        return make_json_response(success=0, errormsg=error_msg,
-                                  info='DATAGRID_TRANSACTION_REQUIRED',
-                                  status=404)
-
-    if status and conn is not None and \
-       trans_obj is not None and session_obj is not None:
-
-        status, res = trans_obj.set_filter(filter_sql)
-
-        # As we changed the transaction object we need to
-        # restore it and update the session variable.
-        session_obj['command_obj'] = pickle.dumps(trans_obj, -1)
-        update_session_grid_transaction(trans_id, session_obj)
-    else:
-        status = False
-        res = error_msg
-
-    return make_json_response(data={'status': status, 'result': res})
-
-
 @blueprint.route(
     '/filter/inclusive/<int:trans_id>',
     methods=["PUT", "POST"], endpoint='inclusive_filter'
@@ -1561,3 +1487,37 @@ def query_tool_status(trans_id):
         return internal_server_error(
             errormsg=gettext("Transaction status check failed.")
         )
+
+
[email protected](
+    '/filter_dialog/<int:trans_id>',
+    methods=["GET"], endpoint='get_filter_data'
+)
+@login_required
+def get_filter_data(trans_id):
+    """
+    This method is used to get all the columns for data sorting dialog.
+
+    Args:
+        trans_id: unique transaction id
+    """
+    return FilterDialog.get(*check_transaction_status(trans_id))
+
+
[email protected](
+    '/filter_dialog/<int:trans_id>',
+    methods=["PUT"], endpoint='set_filter_data'
+)
+@login_required
+def set_filter_data(trans_id):
+    """
+    This method is used to update the columns for data sorting dialog.
+
+    Args:
+        trans_id: unique transaction id
+    """
+    return FilterDialog.save(
+        *check_transaction_status(trans_id),
+        request=request,
+        trans_id=trans_id
+    )
diff --git a/web/pgadmin/tools/sqleditor/command.py b/web/pgadmin/tools/sqleditor/command.py
index 7ec03c5..fbe37df 100644
--- a/web/pgadmin/tools/sqleditor/command.py
+++ b/web/pgadmin/tools/sqleditor/command.py
@@ -141,6 +141,10 @@ class SQLFilter(object):
       - This method removes the filter applied.
     * validate_filter(row_filter)
       - This method validates the given filter.
+    * get_data_sorting()
+      - This method returns columns for data sorting
+    * set_data_sorting()
+      - This method saves columns for data sorting
     """
 
     def __init__(self, **kwargs):
@@ -160,8 +164,8 @@ class SQLFilter(object):
         self.sid = kwargs['sid']
         self.did = kwargs['did']
         self.obj_id = kwargs['obj_id']
-        self.__row_filter = kwargs['sql_filter'] if 'sql_filter' in kwargs \
-            else None
+        self.__row_filter = kwargs.get('sql_filter', None)
+        self.__dara_sorting = kwargs.get('data_sorting', None)
 
         manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(self.sid)
         conn = manager.connection(did=self.did)
@@ -210,20 +214,41 @@ class SQLFilter(object):
 
         return status, msg
 
+    def get_data_sorting(self):
+        """
+        This function returns the filter.
+        """
+        if self.__dara_sorting and len(self.__dara_sorting) > 0:
+            return self.__dara_sorting
+        return None
+
+    def set_data_sorting(self, data_filter):
+        """
+        This function validates the filter and set the
+        given filter to member variable.
+        """
+        self.__dara_sorting = data_filter['data_sorting']
+
     def is_filter_applied(self):
         """
         This function returns True if filter is applied else False.
         """
+        is_filter_applied = True
         if self.__row_filter is None or self.__row_filter == '':
-            return False
+            is_filter_applied = False
 
-        return True
+        if not is_filter_applied:
+            if self.__dara_sorting and len(self.__dara_sorting) > 0:
+                is_filter_applied = True
+
+        return is_filter_applied
 
     def remove_filter(self):
         """
         This function remove the filter by setting value to None.
         """
         self.__row_filter = None
+        self.__dara_sorting = None
 
     def append_filter(self, row_filter):
         """
@@ -325,13 +350,58 @@ class GridCommand(BaseCommand, SQLFilter, FetchedRowTracker):
         self.cmd_type = kwargs['cmd_type'] if 'cmd_type' in kwargs else None
         self.limit = -1
 
-        if self.cmd_type == VIEW_FIRST_100_ROWS or \
-                self.cmd_type == VIEW_LAST_100_ROWS:
+        if self.cmd_type in (VIEW_FIRST_100_ROWS, VIEW_LAST_100_ROWS):
             self.limit = 100
 
     def get_primary_keys(self, *args, **kwargs):
         return None, None
 
+    def get_all_columns_with_order(self, default_conn):
+        """
+        Responsible for fetching columns from given object
+
+        Args:
+            default_conn: Connection object
+
+        Returns:
+            all_sorted_columns: Columns which are already sorted which will
+                         be used to populate the Grid in the dialog
+            all_columns: List of all the column for given object which will
+                         be used to fill columns options
+        """
+        driver = get_driver(PG_DEFAULT_DRIVER)
+        if default_conn is None:
+            manager = driver.connection_manager(self.sid)
+            conn = manager.connection(did=self.did, conn_id=self.conn_id)
+        else:
+            conn = default_conn
+
+        all_sorted_columns = []
+        data_sorting = self.get_data_sorting()
+        all_columns = []
+        if conn.connected():
+            # Fetch the rest of the column names
+            query = render_template(
+                "/".join([self.sql_path, 'get_columns.sql']),
+                obj_id=self.obj_id
+            )
+            status, result = conn.execute_dict(query)
+            if not status:
+                raise Exception(result)
+
+            for row in result['rows']:
+                all_columns.append(row['attname'])
+        else:
+            raise Exception(
+                gettext('Not connected to server or connection with the '
+                        'server has been closed.')
+            )
+        # If user has custom data sorting then pass as it as it is
+        if data_sorting and len(data_sorting) > 0:
+            all_sorted_columns = data_sorting
+
+        return all_sorted_columns, all_columns
+
     def save(self, changed_data, default_conn=None):
         return forbidden(
             errmsg=gettext("Data cannot be saved for the current object.")
@@ -351,6 +421,17 @@ class GridCommand(BaseCommand, SQLFilter, FetchedRowTracker):
         """
         self.limit = limit
 
+    def get_pk_order(self):
+        """
+        This function gets the order required for primary keys
+        """
+        if self.cmd_type in (VIEW_FIRST_100_ROWS, VIEW_ALL_ROWS):
+            return 'asc'
+        elif self.cmd_type == VIEW_LAST_100_ROWS:
+            return 'desc'
+        else:
+            return None
+
 
 class TableCommand(GridCommand):
     """
@@ -385,6 +466,7 @@ class TableCommand(GridCommand):
         has_oids = self.has_oids(default_conn)
 
         sql_filter = self.get_filter()
+        data_sorting = self.get_data_sorting()
 
         if sql_filter is None:
             sql = render_template(
@@ -392,7 +474,8 @@ class TableCommand(GridCommand):
                 object_name=self.object_name,
                 nsp_name=self.nsp_name, pk_names=pk_names,
                 cmd_type=self.cmd_type, limit=self.limit,
-                primary_keys=primary_keys, has_oids=has_oids
+                primary_keys=primary_keys, has_oids=has_oids,
+                data_sorting=data_sorting
             )
         else:
             sql = render_template(
@@ -401,7 +484,7 @@ class TableCommand(GridCommand):
                 nsp_name=self.nsp_name, pk_names=pk_names,
                 cmd_type=self.cmd_type, sql_filter=sql_filter,
                 limit=self.limit, primary_keys=primary_keys,
-                has_oids=has_oids
+                has_oids=has_oids, data_sorting=data_sorting
             )
 
         return sql
@@ -447,6 +530,73 @@ class TableCommand(GridCommand):
 
         return pk_names, primary_keys
 
+    def get_all_columns_with_order(self, default_conn=None):
+        """
+        It is overridden method specially for Table because we all have to
+        fetch primary keys and rest of the columns both.
+
+        Args:
+            default_conn: Connection object
+
+        Returns:
+            all_sorted_columns: Sorted columns for the Grid
+            all_columns: List of columns for the select2 options
+        """
+        driver = get_driver(PG_DEFAULT_DRIVER)
+        if default_conn is None:
+            manager = driver.connection_manager(self.sid)
+            conn = manager.connection(did=self.did, conn_id=self.conn_id)
+        else:
+            conn = default_conn
+
+        all_sorted_columns = []
+        data_sorting = self.get_data_sorting()
+        all_columns = []
+        if conn.connected():
+
+            # Fetch the primary key column names
+            query = render_template(
+                "/".join([self.sql_path, 'primary_keys.sql']),
+                obj_id=self.obj_id
+            )
+
+            status, result = conn.execute_dict(query)
+            if not status:
+                raise Exception(result)
+
+            for row in result['rows']:
+                all_columns.append(row['attname'])
+                all_sorted_columns.append(
+                    {
+                        'name': row['attname'],
+                        'order': self.get_pk_order()
+                    }
+                )
+
+            # Fetch the rest of the column names
+            query = render_template(
+                "/".join([self.sql_path, 'get_columns.sql']),
+                obj_id=self.obj_id
+            )
+            status, result = conn.execute_dict(query)
+            if not status:
+                raise Exception(result)
+
+            for row in result['rows']:
+                # Only append if not already present in the list
+                if row['attname'] not in all_columns:
+                    all_columns.append(row['attname'])
+        else:
+            raise Exception(
+                gettext('Not connected to server or connection with the '
+                        'server has been closed.')
+            )
+        # If user has custom data sorting then pass as it as it is
+        if data_sorting and len(data_sorting) > 0:
+            all_sorted_columns = data_sorting
+
+        return all_sorted_columns, all_columns
+
     def can_edit(self):
         return True
 
@@ -771,20 +921,22 @@ class ViewCommand(GridCommand):
         to fetch the data for the specified view
         """
         sql_filter = self.get_filter()
+        data_sorting = self.get_data_sorting()
 
         if sql_filter is None:
             sql = render_template(
                 "/".join([self.sql_path, 'objectquery.sql']),
                 object_name=self.object_name,
                 nsp_name=self.nsp_name, cmd_type=self.cmd_type,
-                limit=self.limit
+                limit=self.limit, data_sorting=data_sorting
             )
         else:
             sql = render_template(
                 "/".join([self.sql_path, 'objectquery.sql']),
                 object_name=self.object_name,
                 nsp_name=self.nsp_name, cmd_type=self.cmd_type,
-                sql_filter=sql_filter, limit=self.limit
+                sql_filter=sql_filter, limit=self.limit,
+                data_sorting=data_sorting
             )
 
         return sql
@@ -832,20 +984,22 @@ class ForeignTableCommand(GridCommand):
         to fetch the data for the specified foreign table
         """
         sql_filter = self.get_filter()
+        data_sorting = self.get_data_sorting()
 
         if sql_filter is None:
             sql = render_template(
                 "/".join([self.sql_path, 'objectquery.sql']),
                 object_name=self.object_name,
                 nsp_name=self.nsp_name, cmd_type=self.cmd_type,
-                limit=self.limit
+                limit=self.limit, data_sorting=data_sorting
             )
         else:
             sql = render_template(
                 "/".join([self.sql_path, 'objectquery.sql']),
                 object_name=self.object_name,
                 nsp_name=self.nsp_name, cmd_type=self.cmd_type,
-                sql_filter=sql_filter, limit=self.limit
+                sql_filter=sql_filter, limit=self.limit,
+                data_sorting=data_sorting
             )
 
         return sql
@@ -883,20 +1037,22 @@ class CatalogCommand(GridCommand):
         to fetch the data for the specified catalog object
         """
         sql_filter = self.get_filter()
+        data_sorting = self.get_data_sorting()
 
         if sql_filter is None:
             sql = render_template(
                 "/".join([self.sql_path, 'objectquery.sql']),
                 object_name=self.object_name,
                 nsp_name=self.nsp_name, cmd_type=self.cmd_type,
-                limit=self.limit
+                limit=self.limit, data_sorting=data_sorting
             )
         else:
             sql = render_template(
                 "/".join([self.sql_path, 'objectquery.sql']),
                 object_name=self.object_name,
                 nsp_name=self.nsp_name, cmd_type=self.cmd_type,
-                sql_filter=sql_filter, limit=self.limit
+                sql_filter=sql_filter, limit=self.limit,
+                data_sorting=data_sorting
             )
 
         return sql
@@ -929,6 +1085,9 @@ class QueryToolCommand(BaseCommand, FetchedRowTracker):
     def get_sql(self, default_conn=None):
         return None
 
+    def get_all_columns_with_order(self, default_conn=None):
+        return None
+
     def can_edit(self):
         return False
 
diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
index 46588dc..c54590d 100644
--- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
+++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
@@ -602,3 +602,26 @@ input.editor-checkbox:focus {
   font-size: 13px;
   line-height: 3em;
 }
+
+/* For Filter status bar */
+.data_sorting_dialog .pg-prop-status-bar {
+  position: absolute;
+  bottom: 37px;
+  z-index: 5;
+}
+
+.data_sorting_dialog .CodeMirror-gutter-wrapper {
+  left: -30px !important;
+}
+
+.data_sorting_dialog .CodeMirror-gutters {
+  left: 0px !important;
+}
+
+.data_sorting_dialog .custom_height_css_class {
+  height: 100px;
+}
+
+.data_sorting_dialog .data_sorting {
+  padding: 10px 0px;
+}
diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
index 60dacbb..f8cb05a 100644
--- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
@@ -14,6 +14,7 @@ define('tools.querytool', [
   'sources/sqleditor_utils',
   'sources/sqleditor/execute_query',
   'sources/sqleditor/query_tool_http_error_handler',
+  'sources/sqleditor/filter_dialog',
   'sources/history/index.js',
   'sources/../jsx/history/query_history',
   'react', 'react-dom',
@@ -33,7 +34,7 @@ define('tools.querytool', [
 ], function(
   babelPollyfill, gettext, url_for, $, _, S, alertify, pgAdmin, Backbone, codemirror,
   pgExplain, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent,
-  XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler,
+  XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler,
   HistoryBundle, queryHistory, React, ReactDOM,
   keyboardShortcuts, queryToolActions, Datagrid, modifyAnimation,
   calculateQueryRunTime, callRenderAfterPoll) {
@@ -112,8 +113,7 @@ define('tools.querytool', [
 
     // This function is used to render the template.
     render: function() {
-      var self = this,
-        filter = self.$el.find('#sql_filter');
+      var self = this;
 
       $('.editor-title').text(_.unescape(self.editor_title));
       self.checkConnectionStatus();
@@ -121,31 +121,6 @@ define('tools.querytool', [
       // Fetch and assign the shortcuts to current instance
       self.keyboardShortcutConfig = queryToolActions.getKeyboardShortcuts(self);
 
-      self.filter_obj = CodeMirror.fromTextArea(filter.get(0), {
-        tabindex: '0',
-        lineNumbers: true,
-        mode: self.handler.server_type === 'gpdb' ? 'text/x-gpsql' : 'text/x-pgsql',
-        foldOptions: {
-          widget: '\u2026',
-        },
-        foldGutter: {
-          rangeFinder: CodeMirror.fold.combine(
-            CodeMirror.pgadminBeginRangeFinder,
-            CodeMirror.pgadminIfRangeFinder,
-            CodeMirror.pgadminLoopRangeFinder,
-            CodeMirror.pgadminCaseRangeFinder
-          ),
-        },
-        gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
-        extraKeys: pgBrowser.editor_shortcut_keys,
-        indentWithTabs: pgAdmin.Browser.editor_options.indent_with_tabs,
-        indentUnit: pgAdmin.Browser.editor_options.tabSize,
-        tabSize: pgAdmin.Browser.editor_options.tabSize,
-        lineWrapping: pgAdmin.Browser.editor_options.wrapCode,
-        autoCloseBrackets: pgAdmin.Browser.editor_options.insert_pair_brackets,
-        matchBrackets: pgAdmin.Browser.editor_options.brace_matching,
-      });
-
       // Updates connection status flag
       self.gain_focus = function() {
         setTimeout(function() {
@@ -2141,11 +2116,11 @@ define('tools.querytool', [
               if (self.can_filter && res.data.filter_applied) {
                 $('#btn-filter').removeClass('btn-default');
                 $('#btn-filter-dropdown').removeClass('btn-default');
-                $('#btn-filter').addClass('btn-warning');
-                $('#btn-filter-dropdown').addClass('btn-warning');
+                $('#btn-filter').addClass('btn-primary');
+                $('#btn-filter-dropdown').addClass('btn-primary');
               } else {
-                $('#btn-filter').removeClass('btn-warning');
-                $('#btn-filter-dropdown').removeClass('btn-warning');
+                $('#btn-filter').removeClass('btn-primary');
+                $('#btn-filter-dropdown').removeClass('btn-primary');
                 $('#btn-filter').addClass('btn-default');
                 $('#btn-filter-dropdown').addClass('btn-default');
               }
@@ -3044,50 +3019,8 @@ define('tools.querytool', [
 
       // This function will show the filter in the text area.
       _show_filter: function() {
-        var self = this;
-
-        self.trigger(
-          'pgadmin-sqleditor:loading-icon:show',
-          gettext('Loading the existing filter options...')
-        );
-        $.ajax({
-          url: url_for('sqleditor.get_filter', {
-            'trans_id': self.transId,
-          }),
-          method: 'GET',
-          success: function(res) {
-            self.trigger('pgadmin-sqleditor:loading-icon:hide');
-            if (res.data.status) {
-              $('#filter').removeClass('hidden');
-              $('#editor-panel').addClass('sql-editor-busy-fetching');
-              self.gridView.filter_obj.refresh();
-
-              if (res.data.result == null)
-                self.gridView.filter_obj.setValue('');
-              else
-                self.gridView.filter_obj.setValue(res.data.result);
-              // Set focus on filter area
-              self.gridView.filter_obj.focus();
-            } else {
-              setTimeout(
-                function() {
-                  alertify.alert(gettext('Get Filter Error'), res.data.result);
-                }, 10
-              );
-            }
-          },
-          error: function(e) {
-            self.trigger('pgadmin-sqleditor:loading-icon:hide');
-            let msg = httpErrorHandler.handleQueryToolAjaxError(
-              pgAdmin, self, e, '_show_filter', [], true
-            );
-            setTimeout(
-              function() {
-                alertify.alert(gettext('Get Filter Error'), msg);
-              }, 10
-            );
-          },
-        });
+        let self = this;
+        FilterHandler.dialog(self);
       },
 
       // This function will include the filter by selection.
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/get_columns.sql b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/get_columns.sql
new file mode 100644
index 0000000..610747d
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/get_columns.sql
@@ -0,0 +1,9 @@
+{# ============= Fetch the columns ============= #}
+{% if obj_id %}
+SELECT at.attname, ty.typname
+    FROM pg_attribute at
+    LEFT JOIN pg_type ty ON (ty.oid = at.atttypid)
+WHERE attrelid={{obj_id}}::oid
+    AND at.attnum > 0
+    AND at.attisdropped = FALSE
+{% endif %}
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/objectquery.sql b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/objectquery.sql
index 1cb60d9..add1658 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/objectquery.sql
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/objectquery.sql
@@ -3,7 +3,11 @@ SELECT {% if has_oids %}oid, {% endif %}* FROM {{ conn|qtIdent(nsp_name, object_
 {% if sql_filter %}
 WHERE {{ sql_filter }}
 {% endif %}
-{% if primary_keys %}
+{% if data_sorting and data_sorting|length > 0 %}
+ORDER BY {% for obj in data_sorting %}
+{{ conn|qtIdent(obj.name) }} {{ obj.order|upper }}{% if not loop.last %}, {% else %} {% endif %}
+{% endfor %}
+{% elif primary_keys %}
 ORDER BY {% for p in primary_keys %}{{conn|qtIdent(p)}}{% if cmd_type == 1 or cmd_type == 3 %} ASC{% elif cmd_type == 2 %} DESC{% endif %}
 {% if not loop.last %}, {% else %} {% endif %}{% endfor %}
 {% endif %}
diff --git a/web/pgadmin/tools/sqleditor/utils/filter_dialog.py b/web/pgadmin/tools/sqleditor/utils/filter_dialog.py
new file mode 100644
index 0000000..368dbb4
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/utils/filter_dialog.py
@@ -0,0 +1,96 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Code to handle data sorting in view data mode."""
+import pickle
+import simplejson as json
+from flask_babelex import gettext
+from pgadmin.utils.ajax import make_json_response, internal_server_error
+from pgadmin.tools.sqleditor.utils.update_session_grid_transaction import \
+    update_session_grid_transaction
+
+
+class FilterDialog(object):
+    @staticmethod
+    def get(*args):
+        """To fetch the current sorted columns"""
+        status, error_msg, conn, trans_obj, session_obj = args
+        if error_msg == gettext('Transaction ID not found in the session.'):
+            return make_json_response(
+                success=0,
+                errormsg=error_msg,
+                info='DATAGRID_TRANSACTION_REQUIRED',
+                status=404
+            )
+        column_list = []
+        if status and conn is not None and \
+                trans_obj is not None and session_obj is not None:
+            msg = gettext('Success')
+            columns, column_list = trans_obj.get_all_columns_with_order(conn)
+            sql = trans_obj.get_filter()
+        else:
+            status = False
+            msg = error_msg
+            columns = None
+            sql = None
+
+
+        return make_json_response(
+            data={
+                'status': status,
+                'msg': msg,
+                'result': {
+                    'data_sorting': columns,
+                    'column_list': column_list,
+                    'sql': sql
+                }
+            }
+        )
+
+    @staticmethod
+    def save(*args, **kwargs):
+        """To save the sorted columns"""
+        # Check the transaction and connection status
+        status, error_msg, conn, trans_obj, session_obj = args
+        trans_id = kwargs['trans_id']
+        request = kwargs['request']
+
+        if request.data:
+            data = json.loads(request.data, encoding='utf-8')
+        else:
+            data = request.args or request.form
+
+        if error_msg == gettext('Transaction ID not found in the session.'):
+            return make_json_response(
+                success=0,
+                errormsg=error_msg,
+                info='DATAGRID_TRANSACTION_REQUIRED',
+                status=404
+            )
+
+        if status and conn is not None and \
+           trans_obj is not None and session_obj is not None:
+            trans_obj.set_data_sorting(data)
+            trans_obj.set_filter(data.get('sql'))
+            # As we changed the transaction object we need to
+            # restore it and update the session variable.
+            session_obj['command_obj'] = pickle.dumps(trans_obj, -1)
+            update_session_grid_transaction(trans_id, session_obj)
+            res = gettext('Data sorting object updated successfully')
+        else:
+            return internal_server_error(
+                errormsg=gettext('Failed to update the data on server.')
+            )
+
+        return make_json_response(
+            data={
+                'status': status,
+                'result': res
+            }
+        )
diff --git a/web/pgadmin/tools/sqleditor/utils/tests/test_filter_dialog_callbacks.py b/web/pgadmin/tools/sqleditor/utils/tests/test_filter_dialog_callbacks.py
new file mode 100644
index 0000000..9747978
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/utils/tests/test_filter_dialog_callbacks.py
@@ -0,0 +1,103 @@
+#######################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Apply Explain plan wrapper to sql object."""
+from pgadmin.utils.ajax import make_json_response, internal_server_error
+from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog
+from pgadmin.utils.route import BaseTestGenerator
+
+TX_ID_ERROR_MSG = 'Transaction ID not found in the session.'
+FAILED_TX_MSG = 'Failed to update the data on server.'
+
+
+class MockRequest(object):
+    "To mock request object"
+    def __init__(self):
+        self.data = None
+        self.args = "Test data",
+
+
+class StartRunningDataSortingTest(BaseTestGenerator):
+    """
+    Check that the DataSorting methods works as
+    intended
+    """
+    scenarios = [
+        ('When we do not find Transaction ID in session in get', dict(
+            input_parameters=(None, TX_ID_ERROR_MSG, None, None, None),
+            expected_return_response={
+                'success': 0,
+                'errormsg': TX_ID_ERROR_MSG,
+                'info': 'DATAGRID_TRANSACTION_REQUIRED',
+                'status': 404
+            },
+            type='get'
+        )),
+        ('When we pass all the values as None in get', dict(
+            input_parameters=(None, None, None, None, None),
+            expected_return_response={
+                'data': {
+                    'status': False,
+                    'msg': None,
+                    'result': {
+                        'data_sorting': None,
+                        'column_list': []
+                    }
+                }
+            },
+            type='get'
+        )),
+
+        ('When we do not find Transaction ID in session in save', dict(
+            input_arg_parameters=(None, TX_ID_ERROR_MSG, None, None, None),
+            input_kwarg_parameters={
+                'trans_id': None,
+                'request': MockRequest()
+            },
+            expected_return_response={
+                'success': 0,
+                'errormsg': TX_ID_ERROR_MSG,
+                'info': 'DATAGRID_TRANSACTION_REQUIRED',
+                'status': 404
+            },
+            type='save'
+        )),
+
+        ('When we pass all the values as None in save', dict(
+            input_arg_parameters=(None, None, None, None, None),
+            input_kwarg_parameters={
+                'trans_id': None,
+                'request': MockRequest()
+            },
+            expected_return_response={
+                'status': 500,
+                'success': 0,
+                'errormsg': FAILED_TX_MSG
+
+            },
+            type='save'
+        ))
+    ]
+
+    def runTest(self):
+        expected_response = make_json_response(
+            **self.expected_return_response
+        )
+        if self.type == 'get':
+            result = FilterDialog.get(*self.input_parameters)
+            self.assertEquals(
+                result.status_code, expected_response.status_code
+            )
+        else:
+            result = FilterDialog.save(
+                *self.input_arg_parameters, **self.input_kwarg_parameters
+            )
+            self.assertEquals(
+                result.status_code, expected_response.status_code
+            )
diff --git a/web/regression/javascript/sqleditor/filter_dialog_specs.js b/web/regression/javascript/sqleditor/filter_dialog_specs.js
new file mode 100644
index 0000000..e13fa09
--- /dev/null
+++ b/web/regression/javascript/sqleditor/filter_dialog_specs.js
@@ -0,0 +1,31 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////////////////
+import filterDialog from 'sources/sqleditor/filter_dialog';
+import filterDialogModel from 'sources/sqleditor/filter_dialog_model';
+
+describe('filterDialog', () => {
+  let sqlEditorController;
+  sqlEditorController = jasmine.createSpy('sqlEditorController')
+  describe('filterDialog', () => {
+    describe('when using filter dialog', () => {
+      beforeEach(() => {
+        spyOn(filterDialog, 'dialog');
+      });
+
+      it("it should be defined as function", function() {
+        expect(filterDialog.dialog).toBeDefined();
+      });
+
+      it('it should call without proper handler', () => {
+        expect(filterDialog.dialog).not.toHaveBeenCalledWith({});
+      });
+
+    });
+  });
+});


view thread (25+ 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], [email protected], [email protected]
  Subject: Re: [pgAdmin4][RM#3055] Allow user to sort the data in View data mode
  In-Reply-To: <CAKKotZRdfOXX_8osxWHxD=G0mUtmwvdyYa-=RHCkpbOhyOid9g@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