mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
There is no option to Copy data with headers from Query Tool/View Data.
Added drop-down 'Copy with headers' near to the copy button in Query Tool/View Data. Fixes #3009
This commit is contained in:
parent
051e5038b5
commit
56e00d74ad
@ -9,6 +9,8 @@ This release contains a number of bug fixes and new features since the release o
|
|||||||
New features
|
New features
|
||||||
************
|
************
|
||||||
|
|
||||||
|
| `Issue #3009 <https://redmine.postgresql.org/issues/3009>`_ - Added Copy with headers functionality when copy data from Query Tool/View Data.
|
||||||
|
|
||||||
Housekeeping
|
Housekeeping
|
||||||
************
|
************
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
|
|||||||
self._shift_resizes_rectangular_selection()
|
self._shift_resizes_rectangular_selection()
|
||||||
self._shift_resizes_column_selection()
|
self._shift_resizes_column_selection()
|
||||||
self._mouseup_outside_grid_still_makes_a_selection()
|
self._mouseup_outside_grid_still_makes_a_selection()
|
||||||
|
self._copies_rows_with_header()
|
||||||
|
|
||||||
def _copies_rows(self):
|
def _copies_rows(self):
|
||||||
pyperclip.copy("old clipboard contents")
|
pyperclip.copy("old clipboard contents")
|
||||||
@ -72,6 +73,24 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
|
|||||||
self.assertEqual('"Some-Name"\t"6"\t"some info"',
|
self.assertEqual('"Some-Name"\t"6"\t"some info"',
|
||||||
pyperclip.paste())
|
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"\t"value"\t"details"
|
||||||
|
\"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):
|
def _copies_columns(self):
|
||||||
pyperclip.copy("old clipboard contents")
|
pyperclip.copy("old clipboard contents")
|
||||||
column = self.page.find_by_css_selector(
|
column = self.page.find_by_css_selector(
|
||||||
|
@ -49,18 +49,44 @@ define([
|
|||||||
if (!(event.isPropagationStopped() || event.isImmediatePropagationStopped())) {
|
if (!(event.isPropagationStopped() || event.isImmediatePropagationStopped())) {
|
||||||
updateRanges(grid, columnDefinition.id);
|
updateRanges(grid, columnDefinition.id);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
toggleColumnHeaderForCopyHeader(grid);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var toggleColumnHeaderForCopyHeader = function(grid) {
|
||||||
|
if(!$('.copy-with-header').hasClass('visibility-hidden')) {
|
||||||
|
var selRowCnt = grid.getSelectedRows();
|
||||||
|
$('.slick-header-column').each(function (index, columnHeader) {
|
||||||
|
if (selRowCnt.length == 0) {
|
||||||
|
$(columnHeader).removeClass('selected');
|
||||||
|
grid.getColumns()[index].selected = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (index > 0 && grid.getColumns()[index].selectable) {
|
||||||
|
$(columnHeader).addClass('selected');
|
||||||
|
grid.getColumns()[index].selected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$('.slick-header-column').each(function (index, columnHeader) {
|
||||||
|
$(columnHeader).removeClass('selected');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}.bind(RangeSelectionHelper);
|
||||||
|
|
||||||
var handleSelectedRangesChanged = function (grid, event, selectedRanges) {
|
var handleSelectedRangesChanged = function (grid, event, selectedRanges) {
|
||||||
$('.slick-header-column').each(function (index, columnHeader) {
|
$('.slick-header-column').each(function (index, columnHeader) {
|
||||||
var $spanHeaderColumn = $(columnHeader).find('[data-cell-type="column-header-row"]');
|
var $spanHeaderColumn = $(columnHeader).find('[data-cell-type="column-header-row"]');
|
||||||
var columnIndex = grid.getColumnIndex($spanHeaderColumn.data('column-id'));
|
var columnIndex = grid.getColumnIndex($spanHeaderColumn.data('column-id'));
|
||||||
|
|
||||||
if (isColumnSelected(grid, selectedRanges, columnIndex)) {
|
if (isColumnSelected(grid, selectedRanges, columnIndex)) {
|
||||||
$(columnHeader).addClass('selected');
|
$(columnHeader).addClass('selected');
|
||||||
} else {
|
if (columnIndex) grid.getColumns()[columnIndex].selected = true;
|
||||||
|
} else if(!RangeSelectionHelper.areAllRangesCompleteRows(grid, selectedRanges)){
|
||||||
$(columnHeader).removeClass('selected');
|
$(columnHeader).removeClass('selected');
|
||||||
|
if (columnIndex) grid.getColumns()[columnIndex].selected = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -132,6 +158,7 @@ define([
|
|||||||
'getColumnDefinitions': getColumnDefinitions,
|
'getColumnDefinitions': getColumnDefinitions,
|
||||||
'onBeforeColumnSelectAll': onBeforeColumnSelectAll,
|
'onBeforeColumnSelectAll': onBeforeColumnSelectAll,
|
||||||
'onColumnSelectAll': onColumnSelectAll,
|
'onColumnSelectAll': onColumnSelectAll,
|
||||||
|
'toggleColumnHeaderForCopyHeader': toggleColumnHeaderForCopyHeader,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return ColumnSelector;
|
return ColumnSelector;
|
||||||
|
@ -34,12 +34,17 @@ function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) {
|
|||||||
self.copied_rows = [];
|
self.copied_rows = [];
|
||||||
setPasteRowButtonEnablement(self.can_edit, false);
|
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) {
|
if (csvText) {
|
||||||
clipboard.copyTextToClipboard(csvText);
|
clipboard.copyTextToClipboard(csvText);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var copyWithHeader = function () {
|
||||||
|
return !$('.copy-with-header').hasClass('visibility-hidden');
|
||||||
|
};
|
||||||
|
|
||||||
var setPasteRowButtonEnablement = function (canEditFlag, isEnabled) {
|
var setPasteRowButtonEnablement = function (canEditFlag, isEnabled) {
|
||||||
if (canEditFlag) {
|
if (canEditFlag) {
|
||||||
$('#btn-paste-row').prop('disabled', !isEnabled);
|
$('#btn-paste-row').prop('disabled', !isEnabled);
|
||||||
|
@ -66,7 +66,21 @@ define(['sources/selection/range_selection_helper'],
|
|||||||
}.bind(this));
|
}.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) {
|
var rowRangeBounds = selectedRanges.map(function (range) {
|
||||||
return [range.fromRow, range.toRow];
|
return [range.fromRow, range.toRow];
|
||||||
@ -84,6 +98,13 @@ define(['sources/selection/range_selection_helper'],
|
|||||||
return rowData.join(field_separator);
|
return rowData.join(field_separator);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (copyWithHeader) {
|
||||||
|
var headerData = '';
|
||||||
|
headerData = this.getHeaderData(columnDefinitions, CSVOptions);
|
||||||
|
|
||||||
|
return headerData + '\n' + csvRows.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
return csvRows.join('\n');
|
return csvRows.join('\n');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -10,12 +10,14 @@
|
|||||||
define([
|
define([
|
||||||
'jquery',
|
'jquery',
|
||||||
'sources/selection/range_selection_helper',
|
'sources/selection/range_selection_helper',
|
||||||
|
'sources/selection/column_selector',
|
||||||
'slickgrid',
|
'slickgrid',
|
||||||
], function ($, RangeSelectionHelper) {
|
], function ($, RangeSelectionHelper, ColumnSelector) {
|
||||||
var RowSelector = function () {
|
var RowSelector = function () {
|
||||||
var Slick = window.Slick;
|
var Slick = window.Slick;
|
||||||
|
|
||||||
var gridEventBus = new Slick.EventHandler();
|
var gridEventBus = new Slick.EventHandler();
|
||||||
|
var columnSelector = new ColumnSelector();
|
||||||
|
|
||||||
var init = function (grid) {
|
var init = function (grid) {
|
||||||
grid.getSelectionModel().onSelectedRangesChanged
|
grid.getSelectionModel().onSelectedRangesChanged
|
||||||
@ -34,6 +36,7 @@ define([
|
|||||||
|
|
||||||
$rowHeaderSpan.parent().toggleClass('selected');
|
$rowHeaderSpan.parent().toggleClass('selected');
|
||||||
updateRanges(grid, args.row);
|
updateRanges(grid, args.row);
|
||||||
|
columnSelector.toggleColumnHeaderForCopyHeader(grid);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -121,6 +121,18 @@
|
|||||||
tabindex="0" disabled>
|
tabindex="0" disabled>
|
||||||
<i class="fa fa-files-o sql-icon-lg" aria-hidden="true"></i>
|
<i class="fa fa-files-o sql-icon-lg" aria-hidden="true"></i>
|
||||||
</button>
|
</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"
|
<button id="btn-paste-row" type="button" class="btn btn-sm btn-secondary"
|
||||||
title=""
|
title=""
|
||||||
accesskey=""
|
accesskey=""
|
||||||
|
@ -109,6 +109,7 @@ define('tools.querytool', [
|
|||||||
'click #btn-remove-filter': 'on_remove_filter',
|
'click #btn-remove-filter': 'on_remove_filter',
|
||||||
'click #btn-cancel': 'on_cancel',
|
'click #btn-cancel': 'on_cancel',
|
||||||
'click #btn-copy-row': 'on_copy_row',
|
'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-paste-row': 'on_paste_row',
|
||||||
'click #btn-flash': 'on_flash',
|
'click #btn-flash': 'on_flash',
|
||||||
'click #btn-flash-menu': 'on_flash',
|
'click #btn-flash-menu': 'on_flash',
|
||||||
@ -1606,6 +1607,21 @@ define('tools.querytool', [
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Callback function for copy with header button click.
|
||||||
|
on_copy_row_with_header: function(ev) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this._stopEventPropogation(ev);
|
||||||
|
|
||||||
|
// Toggle the button
|
||||||
|
self.handler.trigger(
|
||||||
|
'pgadmin-sqleditor:button:copy_row_with_header',
|
||||||
|
self,
|
||||||
|
self.handler
|
||||||
|
);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
// Callback function for paste button click.
|
// Callback function for paste button click.
|
||||||
on_paste_row: function() {
|
on_paste_row: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -2314,6 +2330,7 @@ define('tools.querytool', [
|
|||||||
self.on('pgadmin-sqleditor:button:exclude_filter', self._exclude_filter, self);
|
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:remove_filter', self._remove_filter, self);
|
||||||
self.on('pgadmin-sqleditor:button:copy_row', self._copy_row, 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:paste_row', self._paste_row, self);
|
||||||
self.on('pgadmin-sqleditor:button:limit', self._set_limit, self);
|
self.on('pgadmin-sqleditor:button:limit', self._set_limit, self);
|
||||||
self.on('pgadmin-sqleditor:button:cancel-query', self._cancel_query, self);
|
self.on('pgadmin-sqleditor:button:cancel-query', self._cancel_query, self);
|
||||||
@ -3644,6 +3661,10 @@ define('tools.querytool', [
|
|||||||
// This function will copy the selected row.
|
// This function will copy the selected row.
|
||||||
_copy_row: copyData,
|
_copy_row: copyData,
|
||||||
|
|
||||||
|
_copy_row_with_header: function() {
|
||||||
|
$('.copy-with-header').toggleClass('visibility-hidden');
|
||||||
|
},
|
||||||
|
|
||||||
// This function will paste the selected row.
|
// This function will paste the selected row.
|
||||||
_paste_row: function() {
|
_paste_row: function() {
|
||||||
var self = this,
|
var self = this,
|
||||||
|
@ -280,19 +280,6 @@ describe('ColumnSelector', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the column is deselected through setSelectedRanges', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
container.find('.slick-header-column')[1].click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removes selected class from header', function () {
|
|
||||||
cellSelectionModel.setSelectedRanges([]);
|
|
||||||
|
|
||||||
expect($(container.find('.slick-header-column')[1]).hasClass('selected'))
|
|
||||||
.toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when a non-column range was already selected', function () {
|
describe('when a non-column range was already selected', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
var selectedRanges = [new Slick.Range(0, 0, 2, 0)];
|
var selectedRanges = [new Slick.Range(0, 0, 2, 0)];
|
||||||
@ -385,7 +372,7 @@ describe('ColumnSelector', function () {
|
|||||||
|
|
||||||
it('no column should have the class \'selected\'', function () {
|
it('no column should have the class \'selected\'', function () {
|
||||||
expect($(container.find('.slick-header-column:contains(some-column-name)')).hasClass('selected'))
|
expect($(container.find('.slick-header-column:contains(some-column-name)')).hasClass('selected'))
|
||||||
.toBeFalsy();
|
.toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -17,7 +17,7 @@ import copyData from '../../../pgadmin/static/js/selection/copy_data';
|
|||||||
import RangeSelectionHelper from 'sources/selection/range_selection_helper';
|
import RangeSelectionHelper from 'sources/selection/range_selection_helper';
|
||||||
import XCellSelectionModel from 'sources/selection/xcell_selection_model';
|
import XCellSelectionModel from 'sources/selection/xcell_selection_model';
|
||||||
describe('copyData', function () {
|
describe('copyData', function () {
|
||||||
var grid, sqlEditor, gridContainer, buttonPasteRow;
|
var grid, sqlEditor, gridContainer, buttonPasteRow, buttonCopyWithHeader;
|
||||||
var SlickGrid;
|
var SlickGrid;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
@ -65,7 +65,9 @@ describe('copyData', function () {
|
|||||||
gridContainer = $('<div id="grid"></div>');
|
gridContainer = $('<div id="grid"></div>');
|
||||||
$('body').append(gridContainer);
|
$('body').append(gridContainer);
|
||||||
buttonPasteRow = $('<button id="btn-paste-row" disabled></button>');
|
buttonPasteRow = $('<button id="btn-paste-row" disabled></button>');
|
||||||
|
buttonCopyWithHeader = $('<button class="copy-with-header visibility-hidden"></button>');
|
||||||
$('body').append(buttonPasteRow);
|
$('body').append(buttonPasteRow);
|
||||||
|
$('body').append(buttonCopyWithHeader);
|
||||||
grid = new SlickGrid('#grid', dataView, columns, {});
|
grid = new SlickGrid('#grid', dataView, columns, {});
|
||||||
grid.CSVOptions = CSVOptions;
|
grid.CSVOptions = CSVOptions;
|
||||||
dataView.setItems(data, '__temp_PK');
|
dataView.setItems(data, '__temp_PK');
|
||||||
@ -77,6 +79,7 @@ describe('copyData', function () {
|
|||||||
grid.destroy();
|
grid.destroy();
|
||||||
gridContainer.remove();
|
gridContainer.remove();
|
||||||
buttonPasteRow.remove();
|
buttonPasteRow.remove();
|
||||||
|
buttonCopyWithHeader.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when rows are selected', function () {
|
describe('when rows are selected', function () {
|
||||||
|
@ -18,7 +18,7 @@ import $ from 'jquery';
|
|||||||
|
|
||||||
describe('#handleQueryOutputKeyboardEvent', function () {
|
describe('#handleQueryOutputKeyboardEvent', function () {
|
||||||
var event, grid, slickEvent;
|
var event, grid, slickEvent;
|
||||||
var handleQueryOutputKeyboardEvent;
|
var handleQueryOutputKeyboardEvent, buttonCopyWithHeader;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
event = {
|
event = {
|
||||||
@ -47,6 +47,9 @@ describe('#handleQueryOutputKeyboardEvent', function () {
|
|||||||
grid: grid,
|
grid: grid,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
buttonCopyWithHeader = $('<button class="copy-with-header visibility-hidden"></button>');
|
||||||
|
$('body').append(buttonCopyWithHeader);
|
||||||
|
|
||||||
spyOn(clipboard, 'copyTextToClipboard');
|
spyOn(clipboard, 'copyTextToClipboard');
|
||||||
handleQueryOutputKeyboardEvent = HandleQueryOutputKeyboardEvent.bind(window);
|
handleQueryOutputKeyboardEvent = HandleQueryOutputKeyboardEvent.bind(window);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user