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 = $(''); + copyButton = $(''); + 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 = $(''); + copyButton = $(''); + + 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 = $('
'); + parentDiv = $('
'); + 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