From 15df12c924e87945d7f2a4e2b7af860077cc53e2 Mon Sep 17 00:00:00 2001 From: Murtuza Zabuawala Date: Fri, 2 Sep 2016 16:05:00 +0100 Subject: [PATCH] Highlight invalid rows when saving data in the edit grid. Fixes #1637 --- web/pgadmin/tools/sqleditor/__init__.py | 9 +- web/pgadmin/tools/sqleditor/command.py | 12 +- .../tools/sqleditor/static/css/sqleditor.css | 15 ++ .../templates/sqleditor/js/sqleditor.js | 156 +++++++++++++----- 4 files changed, 149 insertions(+), 43 deletions(-) diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py index 1ddcdebe0..607d0ec2f 100644 --- a/web/pgadmin/tools/sqleditor/__init__.py +++ b/web/pgadmin/tools/sqleditor/__init__.py @@ -584,13 +584,18 @@ def save(trans_id): 'result': gettext('No primary key found for this object, so unable to save records.')} ) - status, res, query_res = trans_obj.save(changed_data) + status, res, query_res, _rowid = trans_obj.save(changed_data) else: status = False res = error_msg query_res = None - return make_json_response(data={'status': status, 'result': res, 'query_result': query_res}) + return make_json_response( + data={ 'status': status, + 'result': res, + 'query_result': query_res, + '_rowid': _rowid } + ) @blueprint.route('/filter/get/', methods=["GET"]) diff --git a/web/pgadmin/tools/sqleditor/command.py b/web/pgadmin/tools/sqleditor/command.py index 0421e98d1..4b8db6e2b 100644 --- a/web/pgadmin/tools/sqleditor/command.py +++ b/web/pgadmin/tools/sqleditor/command.py @@ -416,7 +416,9 @@ class TableCommand(GridCommand): res = None query_res = dict() count = 0 + list_of_rowid = [] list_of_sql = [] + _rowid = None if conn.connected(): @@ -435,6 +437,7 @@ class TableCommand(GridCommand): for each_row in changed_data[of_type]: data = changed_data[of_type][each_row]['data'] data_type = changed_data[of_type][each_row]['data_type'] + list_of_rowid.append(data.get('__temp_PK')) # Remove our unique tracking key data.pop('__temp_PK', None) sql = render_template("/".join([self.sql_path, 'insert.sql']), @@ -458,6 +461,7 @@ class TableCommand(GridCommand): nsp_name=self.nsp_name, data_type=data_type) list_of_sql.append(sql) + list_of_rowid.append(data) # For deleted rows elif of_type == 'deleted': @@ -468,8 +472,9 @@ class TableCommand(GridCommand): object_name=self.object_name, nsp_name=self.nsp_name) list_of_sql.append(sql) + list_of_rowid.append(data) - for sql in list_of_sql: + for i, sql in enumerate(list_of_sql): if sql: status, res = conn.execute_void(sql) rows_affected = conn.rows_affected() @@ -486,13 +491,14 @@ class TableCommand(GridCommand): for val in query_res: if query_res[val]['status']: query_res[val]['result'] = 'Transaction ROLLBACK' + _rowid = list_of_rowid[i] - return status, res, query_res + return status, res, query_res, _rowid # Commit the transaction if there is no error found conn.execute_void('COMMIT;') - return status, res, query_res + return status, res, query_res, _rowid class ViewCommand(GridCommand): diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css index e6788f91f..64833bd31 100644 --- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css +++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css @@ -392,3 +392,18 @@ input.editor-checkbox { .slick-cell.selected { background-color: #eeeeee !important; } + +/* To highlight all newly inserted rows */ +.grid-canvas .new_row { + background: #f3f2d8; +} + +/* To highlight all the updated rows */ +.grid-canvas .updated_row { + background: #c1c1c1; +} + +/* To highlight row at fault */ +.grid-canvas .new_row.error, .grid-canvas .updated_row.error { + background: #e46b6b; +} diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js index da45b7669..c22fdd6a8 100644 --- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js @@ -431,6 +431,17 @@ define( render_grid: function(collection, columns, is_editable) { var self = this; + // This will work as data store and holds all the + // inserted/updated/deleted data from grid + self.handler.data_store = { + updated: {}, + added: {}, + staged_rows: {}, + deleted: {}, + updated_index: {}, + added_index: {} + }; + // To store primary keys before they gets changed self.handler.primary_keys_data = {}; @@ -451,6 +462,7 @@ define( checkboxSelector = new Slick.CheckboxSelectColumn({ cssClass: "slick-cell-checkboxsel" }); + grid_columns.push(checkboxSelector.getColumnDefinition()); _.each(columns, function(c) { @@ -461,18 +473,15 @@ define( name: c.label }; - // If gird is editable then add editor + // If grid is editable then add editor if(is_editable) { if(c.cell == 'Json') { options['editor'] = Slick.Editors.JsonText; - } - else if(c.cell == 'number') { + } else if(c.cell == 'number') { options['editor'] = Slick.Editors.Text; - } - else if(c.cell == 'boolean') { + } else if(c.cell == 'boolean') { options['editor'] = Slick.Editors.Checkbox; - } - else { + } else { options['editor'] = Slick.Editors.pgText; } } @@ -483,7 +492,6 @@ define( } else if(c.cell == 'boolean') { options['formatter'] = Slick.Formatters.Checkmark; } - grid_columns.push(options) }); @@ -506,6 +514,26 @@ define( row['__temp_PK'] = epicRandomString(15); }); + // Add-on function which allow us to identify the faulty row after insert/update + // and apply css accordingly + collection.getItemMetadata = function(i) { + var res = {}, cssClass = 'normal_row'; + if (_.has(self.handler, 'data_store')) { + if (i in self.handler.data_store.added_index) { + cssClass = 'new_row'; + if (self.handler.data_store.added[self.handler.data_store.added_index[i]].err) { + cssClass += ' error'; + } + } else if (i in self.handler.data_store.updated_index) { + cssClass = 'updated_row'; + if (self.handler.data_store.updated[self.handler.data_store.updated_index[i]].err) { + cssClass += ' error'; + } + } + } + return {'cssClasses': cssClass}; + } + var grid = new Slick.Grid($data_grid, collection, grid_columns, grid_options); grid.registerPlugin( new Slick.AutoTooltips({ enableForHeaderCells: false }) ); grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false})); @@ -522,15 +550,6 @@ define( self.handler.slickgrid = grid; - // This will work as data store and holds all the - // inserted/updated/deleted data from grid - self.handler.data_store = { - updated: {}, - added: {}, - staged_rows: {}, - deleted: {} - }; - // Listener function to watch selected rows from grid if (editor_data.selection) { editor_data.selection.onSelectedRangesChanged.subscribe(function(e, args) { @@ -634,27 +653,37 @@ define( var changed_column = args.grid.getColumns()[args.cell].field, // Current filed name updated_data = args.item[changed_column], // New value for current field _pk = args.item.__temp_PK || null, // Unique key to identify row - col_val = {}, + column_data = {}, _type; - col_val[changed_column] = updated_data; + column_data[changed_column] = updated_data; if(_pk) { // Check if it is in newly added row by user? if(_pk in self.handler.data_store.added) { - _.extend(self.handler.data_store.added[_pk]['data'], col_val); + _.extend( + self.handler.data_store.added[_pk]['data'], + column_data); //Find type for current column + self.handler.data_store.added[_pk]['err'] = false self.handler.data_store.added[_pk]['data_type'][changed_column] = _.where(this.columns, {name: changed_column})[0]['type']; // Check if it is updated data from existing rows? } else if(_pk in self.handler.data_store.updated) { - _.extend(self.handler.data_store.updated[_pk]['data'], col_val); + _.extend( + self.handler.data_store.updated[_pk], { + 'data': column_data, + 'err': false + } + ); //Find type for current column self.handler.data_store.updated[_pk]['data_type'][changed_column] = _.where(this.columns, {name: changed_column})[0]['type']; } else { // First updated data for this primary key - self.handler.data_store.updated[_pk] = {}; - self.handler.data_store.updated[_pk]['data'] = col_val; - self.handler.data_store.updated[_pk]['primary_keys'] = self.handler.primary_keys_data[_pk]; + self.handler.data_store.updated[_pk] = { + 'err': false, 'data': column_data, + 'primary_keys': self.handler.primary_keys_data[_pk] + }; + self.handler.data_store.updated_index[args.row] = _pk; // Find & add column data type for current changed column var temp = {}; temp[changed_column] = _.where(this.columns, {name: changed_column})[0]['type']; @@ -670,14 +699,14 @@ define( // self.handler.data_store.added will holds all the newly added rows/data var _key = epicRandomString(10), column = args.column, - item = args.item; + item = args.item, data_length = this.grid.getDataLength(); if(item) { item.__temp_PK = _key; } collection.push(item); - self.handler.data_store.added[_key] = {}; - self.handler.data_store.added[_key]['data'] = item; + self.handler.data_store.added[_key] = {'err': false, 'data': item}; + self.handler.data_store.added_index[data_length] = _key; // Fetch data type & add it for the column var temp = {}; temp[column.field] = _.where(this.columns, {name: column.field})[0]['type']; @@ -1861,8 +1890,7 @@ define( grid.resetActiveCell(); grid.setData(data, true); grid.setSelectedRows([]); - grid.invalidateAllRows(); - grid.render(); + grid.invalidate(); // Nothing to copy or delete here $("#btn-delete-row").prop('disabled', true); $("#btn-copy-row").prop('disabled', true); @@ -1951,12 +1979,12 @@ define( contentType: "application/json", data: JSON.stringify(self.data_store), success: function(res) { + var grid = self.slickgrid, + data = grid.getData();; if (res.data.status) { // Remove deleted rows from client as well if(is_deleted) { - var grid = self.slickgrid, - data = grid.getData(), - rows = grid.getSelectedRows(); + var rows = grid.getSelectedRows(); // Reverse the deletion from array // so that when we remove it does not affect index rows = rows.sort().reverse(); @@ -1965,23 +1993,41 @@ define( }); grid.setData(data, true); grid.setSelectedRows([]); - grid.invalidateAllRows(); - grid.render(); } // Reset data store - self.data_store.updated = {}; - self.data_store.added = {}; - self.data_store.deleted = {}; + self.data_store = { + 'added': {}, + 'updated': {}, + 'deleted': {}, + 'added_index': {}, + 'updated_index': {} + } + // Reset old primary key data now self.primary_keys_data = {}; + // Clear msgs after successful save $('.sql-editor-message').html(''); } else { + // Something went wrong while saving data on the db server self.trigger('pgadmin-sqleditor:loading-icon:hide'); $("#btn-flash").prop('disabled', false); $('.sql-editor-message').text(res.data.result); - self.gridView.messages_panel.focus(); + var err_msg = S('{{ _('%s.') }}').sprintf(res.data.result).value(); + alertify.notify(err_msg, 'error', 20); + + // To highlight the row at fault + if(_.has(res.data, '_rowid') && + (!_.isUndefined(res.data._rowid)|| !_.isNull(res.data._rowid))) { + var _row_index = self._find_rowindex(res.data._rowid); + if(_row_index in self.data_store.added_index) { + self.data_store.added[self.data_store.added_index[_row_index]].err = true + } else if (_row_index in self.data_store.updated_index) { + self.data_store.updated[self.data_store.updated_index[_row_index]].err = true + } + } + grid.gotoCell(_row_index, 1); } // Update the sql results in history tab @@ -1992,6 +2038,8 @@ define( 'total_time': self.total_time, 'message': r.result }); }); + + grid.invalidate(); }, error: function(e) { if (e.readyState == 0) { @@ -2012,6 +2060,38 @@ define( } }, + // Find index of row at fault from grid data + _find_rowindex: function(rowid) { + var self = this; + var grid = self.slickgrid, + data = grid.getData(), _rowid, count = 0, _idx = -1; + // If _rowid is object then it's update/delete operation + if(_.isObject(rowid)) { + _rowid = rowid; + } else if (_.isString(rowid)) { // Insert opration + _rowid = { '__temp_PK': rowid }; + } else { + // Something is wrong with unique id + return _idx; + } + + _.find(data, function(d) { + // search for unique id in row data if found than its the row + // which error out on server side + var tmp = []; //_.findWhere needs array of object to work + tmp.push(d); + if(_.findWhere(tmp, _rowid)) { + _idx = count; + // Now exit the loop by returning true + return true; + } + count++; + }); + + // Not able to find in grid Data + return _idx; + }, + // Save as _save_as: function() { return this._save(true);