[pgAdmin4][Patch] - RM 3009 - Right click to copy from data grid, optionally with headers.
Khushboo Vashi <[email protected]>
| Newsgroups | gmane.comp.db.postgresql.pgadmin.devel |
|---|---|
| Message-ID | <CAFOhELeP1KDqLM8hhgMP+9spsycgnmpxnmLmgXjQKTnBc2ffFg@mail.gmail.com> |
Hi, Please find the attached patch for RM #3009 - Right click to copy from data grid, optionally with headers. Query Tool / View data: Currently the result-set can be copied without header. With this patch the result-set can be copied with the header also and that is optional. To copy the result-set with header, the option '*Copy with header'* is given next with the Copy button in the toolbar in the form of dropdown. Thanks, Khushboo
RM_3009.patch
(application/octet-stream, 11.6 KB)
diff --git a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py
index a3a55f32c..6f1b8ab68 100644
--- a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py
+++ b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py
@@ -58,6 +58,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
self._shift_resizes_rectangular_selection()
self._shift_resizes_column_selection()
self._mouseup_outside_grid_still_makes_a_selection()
+ self._copies_rows_with_header()
def _copies_rows(self):
pyperclip.copy("old clipboard contents")
@@ -72,6 +73,24 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
self.assertEqual('"Some-Name"\t"6"\t"some info"',
pyperclip.paste())
+ def _copies_rows_with_header(self):
+ self.page.find_by_css_selector('#btn-copy-row-dropdown').click()
+ self.page.find_by_css_selector('a#btn-copy-with-header').click()
+
+ pyperclip.copy("old clipboard contents")
+ select_all = self.page.find_by_xpath(
+ QueryToolLocators.select_all_column)
+ select_all.click()
+
+ copy_button = self.page.find_by_css_selector(
+ QueryToolLocators.copy_button_css)
+ copy_button.click()
+
+ self.assertEqual("""some_column\tvalue\tdetails
+\"Some-Name"\t"6"\t"some info"
+\"Some-Other-Name"\t"22"\t"some other info"
+\"Yet-Another-Name"\t"14"\t"cool info\"""", pyperclip.paste())
+
def _copies_columns(self):
pyperclip.copy("old clipboard contents")
column = self.page.find_by_css_selector(
diff --git a/web/pgadmin/static/js/selection/column_selector.js b/web/pgadmin/static/js/selection/column_selector.js
index a8c8c9c2b..c28d2c30b 100644
--- a/web/pgadmin/static/js/selection/column_selector.js
+++ b/web/pgadmin/static/js/selection/column_selector.js
@@ -49,6 +49,21 @@ define([
if (!(event.isPropagationStopped() || event.isImmediatePropagationStopped())) {
updateRanges(grid, columnDefinition.id);
}
+ } else if(!$('.copy-with-header').hasClass('visibility-hidden')) {
+ var selRowCnt = grid.getSelectedRows();
+ $('.slick-header-column').each(function (index, columnHeader) {
+ if (selRowCnt == 0) {
+ $(columnHeader).removeClass('selected');
+ grid.getColumns()[index].selected = false;
+ }
+ else {
+ if (index > 0) {
+ $(columnHeader).addClass('selected');
+ grid.getColumns()[index].selected = true;
+ }
+ }
+
+ });
}
};
@@ -59,8 +74,10 @@ define([
if (isColumnSelected(grid, selectedRanges, columnIndex)) {
$(columnHeader).addClass('selected');
+ if (columnIndex) grid.getColumns()[columnIndex].selected = true;
} else {
$(columnHeader).removeClass('selected');
+ if (columnIndex) grid.getColumns()[columnIndex].selected = false;
}
});
};
diff --git a/web/pgadmin/static/js/selection/copy_data.js b/web/pgadmin/static/js/selection/copy_data.js
index c6a18b8a8..2fc89485f 100644
--- a/web/pgadmin/static/js/selection/copy_data.js
+++ b/web/pgadmin/static/js/selection/copy_data.js
@@ -34,12 +34,17 @@ function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) {
self.copied_rows = [];
setPasteRowButtonEnablement(self.can_edit, false);
}
- var csvText = rangeBoundaryNavigator.rangesToCsv(dataView.getItems(), columnDefinitions, selectedRanges, CSVOptions);
+ var csvText = rangeBoundaryNavigator.rangesToCsv(dataView.getItems(), columnDefinitions,
+ selectedRanges, CSVOptions, copyWithHeader());
if (csvText) {
clipboard.copyTextToClipboard(csvText);
}
};
+ var copyWithHeader = function () {
+ return !$('.copy-with-header').hasClass('visibility-hidden');
+ };
+
var setPasteRowButtonEnablement = function (canEditFlag, isEnabled) {
if (canEditFlag) {
$('#btn-paste-row').prop('disabled', !isEnabled);
diff --git a/web/pgadmin/static/js/selection/range_boundary_navigator.js b/web/pgadmin/static/js/selection/range_boundary_navigator.js
index c810431d1..aed5ae60c 100644
--- a/web/pgadmin/static/js/selection/range_boundary_navigator.js
+++ b/web/pgadmin/static/js/selection/range_boundary_navigator.js
@@ -66,7 +66,21 @@ define(['sources/selection/range_selection_helper'],
}.bind(this));
},
- rangesToCsv: function (data, columnDefinitions, selectedRanges, CSVOptions) {
+ getHeaderData: function (columnDefinitions, CSVOptions) {
+ var headerData = [],
+ field_separator = CSVOptions.field_separator || '\t',
+ quote_char = CSVOptions.quote_char || '"';
+
+ _.each(columnDefinitions, function(col) {
+ if(col.display_name && col.selected) {
+ headerData.push(quote_char + col.display_name + quote_char);
+ }
+ });
+
+ return headerData.join(field_separator);
+ },
+
+ rangesToCsv: function (data, columnDefinitions, selectedRanges, CSVOptions, copyWithHeader) {
var rowRangeBounds = selectedRanges.map(function (range) {
return [range.fromRow, range.toRow];
@@ -84,6 +98,13 @@ define(['sources/selection/range_selection_helper'],
return rowData.join(field_separator);
});
+ if (copyWithHeader) {
+ var headerData = '';
+ headerData = this.getHeaderData(columnDefinitions, CSVOptions);
+
+ return headerData + '\n' + csvRows.join('\n');
+ }
+
return csvRows.join('\n');
},
diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html
index 3b3e3508d..3eb02e57e 100644
--- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html
+++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html
@@ -121,6 +121,18 @@
tabindex="0" disabled>
<i class="fa fa-files-o sql-icon-lg" aria-hidden="true"></i>
</button>
+ <button id="btn-copy-row-dropdown" type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split"
+ data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
+ tabindex="0">
+ </button>
+ <ul class="dropdown-menu">
+ <li>
+ <a class="dropdown-item" id="btn-copy-with-header" href="#" tabindex="0">
+ <i class="copy-with-header fa fa-check visibility-hidden" aria-hidden="true"></i>
+ <span> {{ _('Copy with headers') }} </span>
+ </a>
+ </li>
+ </ul>
<button id="btn-paste-row" type="button" class="btn btn-sm btn-secondary"
title=""
accesskey=""
diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
index 37d15a9c9..ffa882881 100644
--- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
@@ -108,6 +108,7 @@ define('tools.querytool', [
'click #btn-remove-filter': 'on_remove_filter',
'click #btn-cancel': 'on_cancel',
'click #btn-copy-row': 'on_copy_row',
+ 'click #btn-copy-with-header': 'on_copy_row_with_header',
'click #btn-paste-row': 'on_paste_row',
'click #btn-flash': 'on_flash',
'click #btn-flash-menu': 'on_flash',
@@ -1606,6 +1607,19 @@ define('tools.querytool', [
},
+ // Callback function for copy with header button click.
+ on_copy_row_with_header: function() {
+ var self = this;
+
+ // Toggle the button
+ self.handler.trigger(
+ 'pgadmin-sqleditor:button:copy_row_with_header',
+ self,
+ self.handler
+ );
+
+ },
+
// Callback function for paste button click.
on_paste_row: function() {
var self = this;
@@ -2314,6 +2328,7 @@ define('tools.querytool', [
self.on('pgadmin-sqleditor:button:exclude_filter', self._exclude_filter, self);
self.on('pgadmin-sqleditor:button:remove_filter', self._remove_filter, self);
self.on('pgadmin-sqleditor:button:copy_row', self._copy_row, self);
+ self.on('pgadmin-sqleditor:button:copy_row_with_header', self._copy_row_with_header, self);
self.on('pgadmin-sqleditor:button:paste_row', self._paste_row, self);
self.on('pgadmin-sqleditor:button:limit', self._set_limit, self);
self.on('pgadmin-sqleditor:button:cancel-query', self._cancel_query, self);
@@ -3644,6 +3659,10 @@ define('tools.querytool', [
// This function will copy the selected row.
_copy_row: copyData,
+ _copy_row_with_header: function() {
+ $('.copy-with-header').toggleClass('visibility-hidden');
+ },
+
// This function will paste the selected row.
_paste_row: function() {
var self = this,
diff --git a/web/regression/javascript/selection/copy_data_spec.js b/web/regression/javascript/selection/copy_data_spec.js
index 3ce49e927..9b87752ab 100644
--- a/web/regression/javascript/selection/copy_data_spec.js
+++ b/web/regression/javascript/selection/copy_data_spec.js
@@ -17,7 +17,7 @@ import copyData from '../../../pgadmin/static/js/selection/copy_data';
import RangeSelectionHelper from 'sources/selection/range_selection_helper';
import XCellSelectionModel from 'sources/selection/xcell_selection_model';
describe('copyData', function () {
- var grid, sqlEditor, gridContainer, buttonPasteRow;
+ var grid, sqlEditor, gridContainer, buttonPasteRow, buttonCopyWithHeader;
var SlickGrid;
beforeEach(function () {
@@ -65,7 +65,9 @@ describe('copyData', function () {
gridContainer = $('<div id="grid"></div>');
$('body').append(gridContainer);
buttonPasteRow = $('<button id="btn-paste-row" disabled></button>');
+ buttonCopyWithHeader = $('<button class="copy-with-header visibility-hidden"></button>');
$('body').append(buttonPasteRow);
+ $('body').append(buttonCopyWithHeader);
grid = new SlickGrid('#grid', dataView, columns, {});
grid.CSVOptions = CSVOptions;
dataView.setItems(data, '__temp_PK');
@@ -77,6 +79,7 @@ describe('copyData', function () {
grid.destroy();
gridContainer.remove();
buttonPasteRow.remove();
+ buttonCopyWithHeader.remove();
});
describe('when rows are selected', function () {
diff --git a/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js b/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js
index 407d5efe5..644db37a9 100644
--- a/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js
+++ b/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js
@@ -18,7 +18,7 @@ import $ from 'jquery';
describe('#handleQueryOutputKeyboardEvent', function () {
var event, grid, slickEvent;
- var handleQueryOutputKeyboardEvent;
+ var handleQueryOutputKeyboardEvent, buttonCopyWithHeader;
beforeEach(function () {
event = {
@@ -47,6 +47,9 @@ describe('#handleQueryOutputKeyboardEvent', function () {
grid: grid,
};
+ buttonCopyWithHeader = $('<button class="copy-with-header visibility-hidden"></button>');
+ $('body').append(buttonCopyWithHeader);
+
spyOn(clipboard, 'copyTextToClipboard');
handleQueryOutputKeyboardEvent = HandleQueryOutputKeyboardEvent.bind(window);
});