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> + + '); + buttonCopyWithHeader = $(''); $('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 = $(''); + $('body').append(buttonCopyWithHeader); + spyOn(clipboard, 'copyTextToClipboard'); handleQueryOutputKeyboardEvent = HandleQueryOutputKeyboardEvent.bind(window); });