Subject: [PATCH] extract function from sqleditor

From: George Gelashvili <[email-masked]>
Date: Mon, 5 Jun 2017 16:30:38 -0400

Hello Hackers!

Re. https://www.postgresql.org/message-id/CAFS4TJb-
7VTQQrnOi0g6MaoxMfEK9LzCds2yG%2BByS-mLHwydQA%40mail.gmail.com

We extracted the function bound to onSelectedRangesChanged from sqleditor!

- we read through the existing implementation and wrapped it in
`web/regression/javascript/selection/set_staged_rows_spec.js`
- we made some changes for clarity

Thanks,
Joao and George
diff --git a/web/pgadmin/static/js/selection/set_staged_rows.js b/web/pgadmin/static/js/selection/set_staged_rows.js
new file mode 100644
index 00000000..cace7282
--- /dev/null
+++ b/web/pgadmin/static/js/selection/set_staged_rows.js
@@ -0,0 +1,114 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2017, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+define(
+  [
+    'jquery',
+    'underscore'
+  ],
+  function ($, _) {
+    function disableButton(selector) {
+      $(selector).prop('disabled', true);
+    }
+
+    function enableButton(selector) {
+      $(selector).prop('disabled', false);
+    }
+
+    function getRowPrimaryKeyValuesToStage(selectedRows, primaryKeyColumnIndices, gridData) {
+      return _.reduce(selectedRows, function (primaryKeyValuesToStage, dataGridRowIndex) {
+        var gridRow = gridData[dataGridRowIndex];
+
+        if (isRowMissingPrimaryKeys(gridRow, primaryKeyColumnIndices)) {
+          return primaryKeyValuesToStage;
+        }
+
+        var tempPK = gridRow.__temp_PK;
+        primaryKeyValuesToStage[tempPK] = getSingleRowPrimaryKeyValueToStage(primaryKeyColumnIndices, gridRow);
+
+        return primaryKeyValuesToStage;
+      }, {});
+    }
+
+    function isRowMissingPrimaryKeys(gridRow, primaryKeyColumnIndices) {
+      if (_.isUndefined(gridRow)) {
+        return true;
+      }
+
+      return !_.isUndefined(
+        _.find(primaryKeyColumnIndices, function (pkIndex) {
+          return _.isUndefined(gridRow[pkIndex]);
+        })
+      );
+    }
+
+    function getSingleRowPrimaryKeyValueToStage(primaryKeyColumnIndices, gridRow) {
+      var rowToStage = {};
+      if (primaryKeyColumnIndices.length) {
+        _.each(_.keys(gridRow), function (columnPos) {
+          if (_.contains(primaryKeyColumnIndices, Number(columnPos)))
+            rowToStage[columnPos] = gridRow[columnPos];
+        })
+      }
+      return rowToStage;
+    }
+
+    function getPrimaryKeysForSelectedRows(self, selectedRows) {
+      var primaryKeyColumnIndices = _.map(_.keys(self.keys), function (columnName) {
+        var columnInfo = _.findWhere(self.columns, {name: columnName});
+        return columnInfo['pos'];
+      });
+
+      var gridData = self.grid.getData();
+      var stagedRows = getRowPrimaryKeyValuesToStage(selectedRows, primaryKeyColumnIndices, gridData);
+
+      return stagedRows;
+    }
+
+    var setStagedRows = function (e, args) {
+      var self = this;
+
+      function setStagedRows(rowsToStage) {
+        self.editor.handler.data_store.staged_rows = rowsToStage;
+      }
+
+      function isEditMode() {
+        return self.editor.handler.can_edit;
+      }
+
+      disableButton('#btn-delete-row');
+      disableButton('#btn-copy-row');
+
+      if (!_.has(this.selection, 'getSelectedRows')) {
+        setStagedRows({});
+        return;
+      }
+
+      var selectedRows = this.selection.getSelectedRows();
+
+      if (selectedRows.length > 0) {
+        var stagedRows = getPrimaryKeysForSelectedRows(self, selectedRows);
+        setStagedRows(stagedRows);
+        if (_.isEmpty(stagedRows)) {
+          this.selection.setSelectedRows([]);
+        }
+
+        enableButton('#btn-copy-row');
+        if (isEditMode()) {
+          enableButton('#btn-delete-row');
+        }
+      } else {
+        setStagedRows({});
+      }
+    };
+    return setStagedRows;
+  }
+);
+
+
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
index 597c4367..c83d1b36 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
@@ -4,6 +4,7 @@ define(
     'backbone', 'backgrid', 'codemirror', 'pgadmin.misc.explain',
     'sources/selection/grid_selector', 'sources/selection/clipboard',
     'sources/selection/copy_data',
+    'sources/selection/set_staged_rows',
 
     'slickgrid', 'bootstrap', 'pgadmin.browser', 'wcdocker',
     'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection',
@@ -27,7 +28,7 @@ define(
     'slickgrid/slick.grid'
   ],
   function(
-    $, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain, GridSelector, clipboard, copyData
+    $, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain, GridSelector, clipboard, copyData, setStagedRows
   ) {
     /* Return back, this has been called more than once */
     if (pgAdmin.SqlEditor)
@@ -693,84 +694,8 @@ define(
 
         // Listener function to watch selected rows from grid
         if (editor_data.selection) {
-           editor_data.selection.onSelectedRangesChanged.subscribe(function(e, args) {
-             var collection = this.grid.getData(),
-               primary_key_list = _.keys(this.keys),
-               _tmp_keys = [],
-               _columns = this.columns,
-               rows_for_stage = {},
-               selected_rows_list = [];
-
-               // Only if entire row(s) are selected via check box
-               if(_.has(this.selection, 'getSelectedRows')) {
-                 selected_rows_list = this.selection.getSelectedRows();
-                 // We will map selected row primary key name with position
-                 // For each Primary key
-                 _.each(primary_key_list, function(p) {
-                   // For each columns search primary key position
-                   _.each(_columns, function(c) {
-                      if(c.name == p) {
-                        _tmp_keys.push(c.pos);
-                      }
-                   });
-                 });
-                 // Now assign mapped temp PK to PK
-                 primary_key_list =  _tmp_keys;
-
-                 // Check if selected is new row ?
-                 // Allow to delete if yes
-                 var count = selected_rows_list.length-1,
-                  cell_el = this.grid.getCellNode(selected_rows_list[count],0),
-                  parent_el = $(cell_el).parent(),
-                  is_new_row = $(parent_el).hasClass('new_row');
-
-                 // Clear selection model if row primary keys is set to default
-                 var row_data = _.clone(collection[selected_rows_list[count]]),
-                   is_primary_key = _.has(row_data, primary_key_list) &&
-                                      row_data[0] != undefined ? true : false;
-
-                 if (primary_key_list.length &&
-                     !is_primary_key && !is_new_row
-                 ) {
-                   this.selection.setSelectedRows([]);
-                   selected_rows_list = [];
-                 }
-               }
-
-              // Clear the object as no rows to delete
-              // and disable delete/copy rows button
-              var clear_staged_rows = function() {
-                rows_for_stage = {};
-                $("#btn-delete-row").prop('disabled', true);
-                $("#btn-copy-row").prop('disabled', true);
-              }
-
-              // If any row(s) selected ?
-              if(selected_rows_list.length) {
-                if(this.editor.handler.can_edit)
-                  // Enable delete rows and copy rows button
-                  $("#btn-delete-row").prop('disabled', false);
-                  $("#btn-copy-row").prop('disabled', false);
-                  // Collect primary key data from collection as needed for stage row
-                  _.each(selected_rows_list, function(row_index) {
-                    var row_data = collection[row_index],
-                      p_keys_list = _.pick(row_data, primary_key_list),
-                      is_primary_key = Object.keys(p_keys_list).length ?
-                                       p_keys_list[0] : undefined;
-
-                    // Store Primary key data for selected rows
-                    if (!_.isUndefined(row_data) && !_.isUndefined(p_keys_list)) {
-                      // check for invalid row
-                      rows_for_stage[row_data.__temp_PK] = p_keys_list;
-                    }
-                  });
-              } else {
-                //clear staged rows
-                clear_staged_rows();
-              }
-             // Update main data store
-             this.editor.handler.data_store.staged_rows = rows_for_stage;
-           }.bind(editor_data));
+           editor_data.selection.onSelectedRangesChanged.subscribe(
+             setStagedRows.bind(editor_data));
         }

diff --git a/web/regression/javascript/selection/set_staged_rows_spec.js b/web/regression/javascript/selection/set_staged_rows_spec.js
new file mode 100644
index 00000000..11e293fd
--- /dev/null
+++ b/web/regression/javascript/selection/set_staged_rows_spec.js
@@ -0,0 +1,238 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2017, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+define([
+  "jquery",
+  "underscore",
+  "sources/selection/set_staged_rows",
+], function ($, _, SetStagedRows) {
+  describe('when no full rows are selected', function () {
+    var sqlEditorObj, deleteButton, copyButton;
+    beforeEach(function () {
+      var gridSpy = jasmine.createSpyObj('gridSpy', ['getData', 'getCellNode']);
+      gridSpy.getData.and.returnValue([
+        {0: 'one', 1: 'two', __temp_PK: '123'},
+        {0: 'three', 1: 'four', __temp_PK: '456'},
+        {0: 'five', 1: 'six', __temp_PK: '789'},
+        {0: 'seven', 1: 'eight', __temp_PK: '432'}
+      ]);
+      deleteButton = $('<button id="btn-delete-row"></button>');
+      copyButton = $('<button id="btn-copy-row"></button>');
+      sqlEditorObj = {
+        grid: gridSpy,
+        editor: {
+          handler: {
+            data_store: {
+              staged_rows: {1: [1, 2]}
+            }
+          }
+        }
+      };
+      $('body').append(deleteButton);
+      $('body').append(copyButton);
+      deleteButton.prop('disabled', false);
+      copyButton.prop('disabled', false);
+    });
+
+    afterEach(function () {
+      copyButton.remove();
+      deleteButton.remove();
+    });
+
+    describe('when getSelectedRows is not present in the selection model', function () {
+      beforeEach(function () {
+        SetStagedRows.call(sqlEditorObj, {}, {});
+      });
+      it('should disable the delete row button', function () {
+        expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
+      });
+
+      it('should disable the copy row button', function () {
+        expect($('#btn-copy-row').prop('disabled')).toBeTruthy();
+      });
+
+      it('should clear staged rows', function () {
+        expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
+      });
+    });
+
+    describe('when getSelectedRows is present in the selection model', function () {
+      beforeEach(function () {
+        var selectionSpy = jasmine.createSpyObj('selectionSpy', ['getSelectedRows', 'setSelectedRows']);
+        selectionSpy.getSelectedRows.and.returnValue([]);
+        sqlEditorObj.selection = selectionSpy;
+        SetStagedRows.call(sqlEditorObj, {}, {});
+      });
+
+      it('should disable the delete row button', function () {
+        expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
+      });
+
+      it('should disable the copy row button', function () {
+        expect($('#btn-copy-row').prop('disabled')).toBeTruthy();
+      });
+
+      it('should clear staged rows', function () {
+        expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
+      });
+    });
+  });
+
+  describe('when 2 full rows are selected', function () {
+    describe('when getSelectedRows is present in the selection model', function () {
+      var sqlEditorObj, gridSpy, deleteButton, copyButton;
+      beforeEach(function () {
+        gridSpy = jasmine.createSpyObj('gridSpy', ['getData', 'getCellNode']);
+        gridSpy.getData.and.returnValue([
+          {0: 'one', 1: 'two', __temp_PK: '123'},
+          {0: 'three', 1: 'four', __temp_PK: '456'},
+          {0: 'five', 1: 'six', __temp_PK: '789'},
+          {0: 'seven', 1: 'eight', __temp_PK: '432'}
+        ]);
+
+        var selectionSpy = jasmine.createSpyObj('selectionSpy', ['getSelectedRows', 'setSelectedRows']);
+        selectionSpy.getSelectedRows.and.returnValue([1, 2]);
+
+        deleteButton = $('<button id="btn-delete-row"></button>');
+        copyButton = $('<button id="btn-copy-row"></button>');
+
+        sqlEditorObj = {
+          grid: gridSpy,
+          editor: {
+            handler: {
+              data_store: {
+                staged_rows: {'456': {}}
+              },
+              can_edit: false
+            }
+          },
+          keys: null,
+          selection: selectionSpy,
+          columns: [
+            {
+              name: 'a pk column',
+              pos: 0
+            },
+            {
+              name: 'some column',
+              pos: 1
+            }
+          ]
+        };
+
+        $('body').append(deleteButton);
+        $('body').append(copyButton);
+
+        deleteButton.prop('disabled', true);
+        copyButton.prop('disabled', true);
+
+      });
+
+      afterEach(function () {
+        copyButton.remove();
+        deleteButton.remove();
+      });
+
+      describe('when table does not have primary keys', function () {
+        it('should enable the copy row button', function () {
+          SetStagedRows.call(sqlEditorObj, {}, {});
+          expect($('#btn-copy-row').prop('disabled')).toBeFalsy();
+        });
+
+        it('should not enable the delete row button', function () {
+          SetStagedRows.call(sqlEditorObj, {}, {});
+          expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
+        });
+
+        it('should update staged rows with the __temp_PK value of the new Selected Rows', function () {
+          SetStagedRows.call(sqlEditorObj, {}, {});
+          expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({'456': {}, '789': {}});
+        });
+
+        describe('the user can edit', function () {
+          it('should enable the delete row button', function () {
+            sqlEditorObj.editor.handler.can_edit = true;
+            SetStagedRows.call(sqlEditorObj, {}, {});
+            expect($('#btn-delete-row').prop('disabled')).toBeFalsy();
+          });
+        });
+      });
+
+      describe('when table has primary keys', function () {
+        beforeEach(function () {
+          sqlEditorObj.keys = {'a pk column': 'varchar'};
+          sqlEditorObj.editor.handler.data_store.staged_rows = {'456': {0: 'three'}};
+        });
+
+        describe('selected rows have primary key', function () {
+          it('should set the staged rows correctly', function () {
+            SetStagedRows.call(sqlEditorObj, {}, {});
+            expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual(
+              {'456': {0: 'three'}, '789': {0: 'five'}});
+          });
+
+          it('should not clear selected rows in Cell Selection Model', function () {
+            SetStagedRows.call(sqlEditorObj, {}, {});
+            expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalledWith();
+          });
+
+        });
+
+        describe('selected rows missing primary key', function () {
+          beforeEach(function () {
+            gridSpy.getData.and.returnValue([
+              {0: 'one', 1: 'two', __temp_PK: '123'},
+              {1: 'four', __temp_PK: '456'},
+              {1: 'six', __temp_PK: '789'},
+              {0: 'seven', 1: 'eight', __temp_PK: '432'}
+            ]);
+          });
+
+          it('should clear the staged rows', function () {
+            SetStagedRows.call(sqlEditorObj, {}, {});
+            expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
+          });
+
+          it('should clear selected rows in Cell Selection Model', function () {
+            SetStagedRows.call(sqlEditorObj, {}, {});
+            expect(sqlEditorObj.selection.setSelectedRows).toHaveBeenCalledWith([]);
+          });
+
+        });
+
+        describe('when the selected row is a new row', function () {
+          var parentDiv;
+          beforeEach(function () {
+            var childDiv = $('<div></div>');
+            parentDiv = $('<div class="new_row"></div>');
+            parentDiv.append(childDiv);
+            $('body').append(parentDiv);
+            gridSpy.getCellNode.and.returnValue(childDiv);
+            SetStagedRows.call(sqlEditorObj, {}, {});
+          });
+
+          afterEach(function () {
+            parentDiv.remove();
+          });
+
+          it('should not clear the staged rows', function () {
+            expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({
+              '456': {0: 'three'},
+              '789': {0: 'five'}
+            });
+          });
+
+          it('should not clear selected rows in Cell Selection Model', function () {
+            expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalled();
+          });
+        });
+      });
+    });
+  });
+});
\ No newline at end of file

-- 
Sent via pgadmin-hackers mailing list ([email-masked])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers

---

From: Dave Page <[email-masked]>
Date: Wed, 7 Jun 2017 14:17:23 +0100

Thanks, applied.

On Mon, Jun 5, 2017 at 9:30 PM, George Gelashvili
<[email-masked]> wrote:
> Hello Hackers!
>
> Re.
> https://www.postgresql.org/message-id/CAFS4TJb-7VTQQrnOi0g6MaoxMfEK9LzCds2yG%2BByS-mLHwydQA%40mail.gmail.com
>

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company