Ensure the correct label is used in panel headers when viewing filtered rows. Fixes #4228

pull/24/head
Aditya Toshniwal 2019-06-11 15:11:13 +01:00 committed by Dave Page
parent d774a0ff67
commit 23364464c2
12 changed files with 245 additions and 237 deletions

View File

@ -15,6 +15,7 @@ Bug fixes
| `Bug #4171 <https://redmine.postgresql.org/issues/4171>`_ - Fix issue where reverse engineered SQL was failing for foreign tables, if it had "=" in the options.
| `Bug #4195 <https://redmine.postgresql.org/issues/4195>`_ - Fix keyboard navigation in "inner" tabsets such as the Query Tool and Debugger.
| `Bug #4228 <https://redmine.postgresql.org/issues/4228>`_ - Ensure the correct label is used in panel headers when viewing filtered rows.
| `Bug #4253 <https://redmine.postgresql.org/issues/4253>`_ - Fix issue where new column should be created with Default value.
| `Bug #4283 <https://redmine.postgresql.org/issues/4283>`_ - Initial support for PostgreSQL 12.
| `Bug #4288 <https://redmine.postgresql.org/issues/4288>`_ - Initial support for PostgreSQL 12.

View File

@ -70,7 +70,8 @@ class BrowserToolBarFeatureTest(BaseFeatureTest):
while retry_count < 5:
try:
self.page.find_by_css_selector(
".wcFrameButton[title='Query Tool']").click()
".wcFrameButton[title='Query Tool']:not(.disabled)")\
.click()
break
except StaleElementReferenceException:
retry_count += 1
@ -89,7 +90,7 @@ class BrowserToolBarFeatureTest(BaseFeatureTest):
while retry_count < 5:
try:
self.page.find_by_css_selector(
".wcFrameButton[title='View Data']").click()
".wcFrameButton[title='View Data']:not(.disabled)").click()
break
except StaleElementReferenceException:
retry_count += 1
@ -102,12 +103,13 @@ class BrowserToolBarFeatureTest(BaseFeatureTest):
while retry_count < 5:
try:
self.page.find_by_css_selector(
".wcFrameButton[title='Filtered Rows']").click()
".wcFrameButton[title='Filtered Rows']:not(.disabled)")\
.click()
break
except StaleElementReferenceException:
retry_count += 1
time.sleep(0.5)
self.page.find_by_css_selector(
".alertify .ajs-header[data-title='Data Filter']")
".alertify .ajs-header[data-title~='Filter']")
self.page.click_modal('Cancel')

View File

@ -11,6 +11,7 @@
color: $color-primary-fg;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
border-radius: 0rem;
border-top-left-radius: $panel-border-radius;
border-top-right-radius: $panel-border-radius;

View File

@ -968,3 +968,18 @@ table.table-empty-rows{
bottom: 0;
}
}
.filter-textarea {
height: 100%;
& .CodeMirror-scroll {
min-height: 120px;
max-height: 120px;
}
}
.dataview_filter_dialog {
left: 0 !important;
right: 0 !important;
padding: 0px !important;
position: absolute;
}

View File

@ -12,18 +12,16 @@ define('pgadmin.datagrid', [
'pgadmin.alertifyjs', 'sources/pgadmin', 'bundled_codemirror',
'sources/sqleditor_utils', 'backbone',
'tools/datagrid/static/js/show_data',
'tools/datagrid/static/js/get_panel_title',
'tools/datagrid/static/js/show_query_tool', 'pgadmin.browser.toolbar',
'wcdocker',
], function(
gettext, url_for, $, _, alertify, pgAdmin, codemirror, sqlEditorUtils,
Backbone, showData, panelTitle, showQueryTool, toolBar
Backbone, showData, showQueryTool, toolBar
) {
// Some scripts do export their object in the window only.
// Generally the one, which do no have AMD support.
var wcDocker = window.wcDocker,
pgBrowser = pgAdmin.Browser,
CodeMirror = codemirror.default;
pgBrowser = pgAdmin.Browser;
/* Return back, this has been called more than once */
if (pgAdmin.DataGrid)
@ -202,188 +200,14 @@ define('pgadmin.datagrid', [
// This is a callback function to show filtered data when user click on menu item.
show_filtered_row: function(data, i) {
var self = this,
d = pgAdmin.Browser.tree.itemData(i);
if (d === undefined) {
alertify.alert(
gettext('Data Grid Error'),
gettext('No object selected.')
);
return;
}
// Get the parent data from the tree node hierarchy.
var node = pgBrowser.Nodes[d._type],
parentData = node.getTreeNodeHierarchy(i);
// If server or database is undefined then return from the function.
if (parentData.server === undefined || parentData.database === undefined) {
return;
}
// If schema, view, catalog object all are undefined then return from the function.
if (parentData.schema === undefined && parentData.view === undefined &&
parentData.catalog === undefined) {
return;
}
let nsp_name = showData.retrieveNameSpaceName(parentData);
var url_params = {
'cmd_type': data.mnuid,
'obj_type': d._type,
'sgid': parentData.server_group._id,
'sid': parentData.server._id,
'did': parentData.database._id,
'obj_id': d._id,
};
var baseUrl = url_for('datagrid.initialize_datagrid', url_params);
// Create url to validate the SQL filter
var validateUrl = url_for('datagrid.filter_validate', {
'sid': url_params['sid'],
'did': url_params['did'],
'obj_id': url_params['obj_id'],
});
let grid_title = showData.generateDatagridTitle(parentData, nsp_name, d);
// Create filter dialog using alertify
if (!alertify.filterDialog) {
alertify.dialog('filterDialog', function factory() {
return {
main: function(title, message, baseUrl, validateUrl) {
this.set('title', title);
this.message = message;
this.baseUrl = baseUrl;
this.validateUrl = validateUrl;
},
setup:function() {
return {
buttons:[{
text: gettext('Cancel'),
key: 27,
className: 'btn btn-secondary fa fa-times pg-alertify-button',
},{
text: gettext('OK'),
key: 13,
className: 'btn btn-primary fa fa-check pg-alertify-button',
}],
options: {
modal: 0,
resizable: true,
maximizable: false,
pinnable: false,
autoReset: false,
},
};
},
build: function() {
alertify.pgDialogBuild.apply(this);
},
prepare:function() {
var that = this,
$content = $(this.message),
$sql_filter = $content.find('#sql_filter');
$(this.elements.header).attr('data-title', this.get('title'));
$(this.elements.body.childNodes[0]).addClass(
'dataview_filter_dialog'
);
this.setContent($content.get(0));
// Disable OK button
that.__internal.buttons[1].element.disabled = true;
// Apply CodeMirror to filter text area.
this.filter_obj = CodeMirror.fromTextArea($sql_filter.get(0), {
lineNumbers: true,
mode: 'text/x-pgsql',
extraKeys: pgBrowser.editor_shortcut_keys,
indentWithTabs: !self.preferences.use_spaces,
indentUnit: self.preferences.tab_size,
tabSize: self.preferences.tab_size,
lineWrapping: self.preferences.wrap_code,
autoCloseBrackets: self.preferences.insert_pair_brackets,
matchBrackets: self.preferences.brace_matching,
});
let sql_font_size = sqlEditorUtils.calcFontSize(self.preferences.sql_font_size);
$(this.filter_obj.getWrapperElement()).css('font-size', sql_font_size);
setTimeout(function() {
// Set focus on editor
that.filter_obj.refresh();
that.filter_obj.focus();
}, 500);
that.filter_obj.on('change', function() {
if (that.filter_obj.getValue() !== '') {
that.__internal.buttons[1].element.disabled = false;
} else {
that.__internal.buttons[1].element.disabled = true;
}
});
},
callback: function(closeEvent) {
if (closeEvent.button.text == gettext('OK')) {
var sql = this.filter_obj.getValue();
var that = this;
closeEvent.cancel = true; // Do not close dialog
// Make ajax call to include the filter by selection
$.ajax({
url: that.validateUrl,
method: 'POST',
async: false,
contentType: 'application/json',
data: JSON.stringify(sql),
})
.done(function(res) {
if (res.data.status) {
// Initialize the data grid.
self.create_transaction(that.baseUrl, null, 'false', parentData.server.server_type, '', grid_title, sql, false);
that.close(); // Close the dialog
}
else {
alertify.alert(
gettext('Validation Error'),
res.data.result
);
}
})
.fail(function(e) {
alertify.alert(
gettext('Validation Error'),
e
);
});
}
},
};
});
}
$.get(url_for('datagrid.filter'),
function(data) {
alertify.filterDialog('Data Filter', data, baseUrl, validateUrl)
.resizeTo(pgBrowser.stdW.sm,pgBrowser.stdH.sm);
}
);
showData.showDataGrid(this, pgBrowser, alertify, data, i, true, this.preferences);
},
get_panel_title: function() {
return panelTitle.getPanelTitle(pgBrowser);
},
// This is a callback function to show query tool when user click on menu item.
show_query_tool: function(url, aciTreeIdentifier, panelTitle) {
showQueryTool.showQueryTool(this, pgBrowser, alertify, url,
aciTreeIdentifier, panelTitle);
show_query_tool: function(url, aciTreeIdentifier) {
showQueryTool.showQueryTool(this, pgBrowser, alertify, url, aciTreeIdentifier);
},
create_transaction: function(baseUrl, target, is_query_tool, server_type, sURL, panel_title, sql_filter, recreate) {
var self = this;
target = target || self;
@ -439,28 +263,20 @@ define('pgadmin.datagrid', [
launch_grid: function(trans_obj) {
var self = this,
panel_title = trans_obj.panel_title,
grid_title = self.get_panel_title(),
grid_title = trans_obj.panel_title,
panel_icon = '',
panel_tooltip = '';
if (trans_obj.is_query_tool == 'false') {
// Edit grid titles
grid_title = panel_title + '/' + grid_title;
panel_tooltip = gettext('View/Edit Data - ') + grid_title;
panel_title = grid_title;
panel_icon = 'fa fa-table';
} else {
if (panel_title) {
// Script titles
panel_tooltip = panel_title.toUpperCase() + ' ' + gettext('Script - ') + grid_title;
panel_title = grid_title;
panel_icon = 'fa fa-file-text-o';
} else {
// Query tool titles
panel_tooltip = gettext('Query Tool - ') + grid_title;
panel_title = grid_title;
panel_icon = 'fa fa-bolt';
}
// Query tool titles
panel_tooltip = gettext('Query Tool - ') + grid_title;
panel_title = grid_title;
panel_icon = 'fa fa-bolt';
}
// Open the panel if frame is initialized

View File

@ -18,8 +18,10 @@ function isServerInformationAvailable(parentData) {
return parentData.server === undefined;
}
export function getPanelTitle(pgBrowser) {
const selected_item = pgBrowser.treeMenu.selected();
export function getPanelTitle(pgBrowser, selected_item=null) {
if(selected_item == null) {
selected_item = pgBrowser.treeMenu.selected();
}
const parentData = getTreeNodeHierarchyFromIdentifier
.call(pgBrowser, selected_item);

View File

@ -9,13 +9,19 @@
import gettext from '../../../../static/js/gettext';
import url_for from '../../../../static/js/url_for';
import {getTreeNodeHierarchyFromIdentifier} from '../../../../static/js/tree/pgadmin_tree_node';
import {getPanelTitle} from './get_panel_title';
import CodeMirror from 'bundled_codemirror';
import * as SqlEditorUtils from 'sources/sqleditor_utils';
import $ from 'jquery';
export function showDataGrid(
datagrid,
pgBrowser,
alertify,
connectionData,
aciTreeIdentifier
aciTreeIdentifier,
filter=false,
preferences=null
) {
const node = pgBrowser.treeMenu.findNodeByDomElement(aciTreeIdentifier);
if (node === undefined || !node.getData()) {
@ -36,19 +42,44 @@ export function showDataGrid(
return;
}
let namespaceName = retrieveNameSpaceName(parentData);
const baseUrl = generateUrl(connectionData, node.getData(), parentData);
const grid_title = generateDatagridTitle(parentData, namespaceName, node.getData());
const grid_title = generateDatagridTitle(pgBrowser, aciTreeIdentifier);
datagrid.create_transaction(
baseUrl,
null,
'false',
parentData.server.server_type,
'',
grid_title,
''
);
if(filter) {
initFilterDialog(alertify, pgBrowser, preferences);
const validateUrl = generateFilterValidateUrl(node.getData(), parentData);
let okCallback = function(sql) {
datagrid.create_transaction(
baseUrl,
null,
'false',
parentData.server.server_type,
'',
grid_title,
sql,
false
);
};
$.get(url_for('datagrid.filter'),
function(data) {
alertify.filterDialog(`Data Filter - ${grid_title}`, data, validateUrl, preferences, okCallback)
.resizeTo(pgBrowser.stdW.sm,pgBrowser.stdH.sm);
}
);
} else {
datagrid.create_transaction(
baseUrl,
null,
'false',
parentData.server.server_type,
'',
grid_title,
''
);
}
}
@ -78,6 +109,138 @@ function generateUrl(connectionData, nodeData, parentData) {
return url_for('datagrid.initialize_datagrid', url_params);
}
function generateFilterValidateUrl(nodeData, parentData) {
// Create url to validate the SQL filter
var url_params = {
'sid': parentData.server._id,
'did': parentData.database._id,
'obj_id': nodeData._id,
};
return url_for('datagrid.filter_validate', url_params);
}
function initFilterDialog(alertify, pgBrowser) {
// Create filter dialog using alertify
if (!alertify.filterDialog) {
alertify.dialog('filterDialog', function factory() {
return {
main: function(title, message, validateUrl, preferences, okCallback) {
this.set('title', title);
this.message = message;
this.validateUrl = validateUrl;
this.okCallback = okCallback;
this.preferences = preferences;
},
setup:function() {
return {
buttons:[{
text: gettext('Cancel'),
key: 27,
className: 'btn btn-secondary fa fa-times pg-alertify-button',
},{
text: gettext('OK'),
key: 13,
className: 'btn btn-primary fa fa-check pg-alertify-button',
}],
options: {
modal: 0,
resizable: true,
maximizable: false,
pinnable: false,
autoReset: false,
},
};
},
build: function() {
alertify.pgDialogBuild.apply(this);
},
prepare:function() {
var that = this,
$content = $(this.message),
$sql_filter = $content.find('#sql_filter');
$(this.elements.header).attr('data-title', this.get('title'));
$(this.elements.body.childNodes[0]).addClass(
'dataview_filter_dialog'
);
this.setContent($content.get(0));
// Disable OK button
that.__internal.buttons[1].element.disabled = true;
// Apply CodeMirror to filter text area.
this.filter_obj = CodeMirror.fromTextArea($sql_filter.get(0), {
lineNumbers: true,
mode: 'text/x-pgsql',
extraKeys: pgBrowser.editor_shortcut_keys,
indentWithTabs: !that.preferences.use_spaces,
indentUnit: that.preferences.tab_size,
tabSize: that.preferences.tab_size,
lineWrapping: that.preferences.wrap_code,
autoCloseBrackets: that.preferences.insert_pair_brackets,
matchBrackets: that.preferences.brace_matching,
});
let sql_font_size = SqlEditorUtils.calcFontSize(that.preferences.sql_font_size);
$(this.filter_obj.getWrapperElement()).css('font-size', sql_font_size);
setTimeout(function() {
// Set focus on editor
that.filter_obj.refresh();
that.filter_obj.focus();
}, 500);
that.filter_obj.on('change', function() {
if (that.filter_obj.getValue() !== '') {
that.__internal.buttons[1].element.disabled = false;
} else {
that.__internal.buttons[1].element.disabled = true;
}
});
},
callback: function(closeEvent) {
if (closeEvent.button.text == gettext('OK')) {
var sql = this.filter_obj.getValue();
var that = this;
closeEvent.cancel = true; // Do not close dialog
// Make ajax call to include the filter by selection
$.ajax({
url: that.validateUrl,
method: 'POST',
async: false,
contentType: 'application/json',
data: JSON.stringify(sql),
})
.done(function(res) {
if (res.data.status) {
that.okCallback(sql);
that.close(); // Close the dialog
}
else {
alertify.alert(
gettext('Validation Error'),
res.data.result
);
}
})
.fail(function(e) {
alertify.alert(
gettext('Validation Error'),
e
);
});
}
},
};
});
}
}
function hasServerOrDatabaseConfiguration(parentData) {
return parentData.server === undefined || parentData.database === undefined;
}
@ -87,6 +250,17 @@ function hasSchemaOrCatalogOrViewInformation(parentData) {
parentData.catalog !== undefined;
}
export function generateDatagridTitle(parentData, namespaceName, nodeData) {
return `${namespaceName}.${nodeData.label}`;
export function generateDatagridTitle(pgBrowser, aciTreeIdentifier) {
const baseTitle = getPanelTitle(pgBrowser, aciTreeIdentifier);
const parentData = getTreeNodeHierarchyFromIdentifier.call(
pgBrowser,
aciTreeIdentifier
);
const namespaceName = retrieveNameSpaceName(parentData);
const node = pgBrowser.treeMenu.findNodeByDomElement(aciTreeIdentifier);
return `${namespaceName}.${node.getData().label}/${baseTitle}`;
}

View File

@ -10,6 +10,7 @@
import gettext from '../../../../static/js/gettext';
import url_for from '../../../../static/js/url_for';
import {getTreeNodeHierarchyFromIdentifier} from '../../../../static/js/tree/pgadmin_tree_node';
import {getPanelTitle} from './get_panel_title';
function hasDatabaseInformation(parentData) {
return parentData.database;
@ -34,10 +35,14 @@ function hasServerInformations(parentData) {
return parentData.server === undefined;
}
export function showQueryTool(datagrid, pgBrowser, alertify, url,
aciTreeIdentifier, panelTitle) {
function generateTitle(pgBrowser, aciTreeIdentifier) {
const baseTitle = getPanelTitle(pgBrowser, aciTreeIdentifier);
return baseTitle;
}
export function showQueryTool(datagrid, pgBrowser, alertify, url, aciTreeIdentifier) {
const sURL = url || '';
const queryToolTitle = panelTitle || '';
const queryToolTitle = generateTitle(pgBrowser, aciTreeIdentifier);
const currentNode = pgBrowser.treeMenu.findNodeByDomElement(aciTreeIdentifier);
if (currentNode === undefined) {

View File

@ -1,15 +1,3 @@
<div class="filter-textarea">
<textarea id="sql_filter" rows="5" tabindex="0"></textarea>
<style>
.filter-textarea .CodeMirror-scroll {
min-height: 120px;
max-height: 120px;
}
.dataview_filter_dialog {
left: 0 !important;
right: 0 !important;
padding: 0px !important;
position: absolute;
}
</style>
</div>

View File

@ -63,6 +63,7 @@ describe('#show_data', () => {
label: 'server1',
server_type: 'pg',
_id: 2,
user: {name: 'someuser'},
}, ['parent', 'server_group1']);
pgBrowser.treeMenu.addChild(serverGroup1, server1);
@ -133,7 +134,7 @@ describe('#show_data', () => {
'false',
'pg',
'',
'schema1.schema1',
'schema1.schema1/database1/someuser@server1',
''
);
});
@ -148,7 +149,7 @@ describe('#show_data', () => {
'false',
'pg',
'',
'view1.view1',
'view1.view1/database1/someuser@server1',
''
);
});
@ -163,7 +164,7 @@ describe('#show_data', () => {
'false',
'pg',
'',
'catalog1.catalog1',
'catalog1.catalog1/database1/someuser@server1',
''
);
});

View File

@ -51,6 +51,8 @@ describe('#showQueryTool', () => {
label: 'server1',
server_type: 'pg',
_id: 2,
user: {name: 'someuser'},
db: 'otherdblabel',
});
pgBrowser.treeMenu.addChild(serverGroup1, server1);
@ -64,7 +66,7 @@ describe('#showQueryTool', () => {
context('cannot find the tree node', () => {
beforeEach(() => {
showQueryTool(queryTool, pgBrowser, alertify, '', [{id: '10'}], 'title');
showQueryTool(queryTool, pgBrowser, alertify, '', [{id: '10'}]);
});
it('does not create a transaction', () => {
expect(queryTool.create_transaction).not.toHaveBeenCalled();
@ -92,14 +94,14 @@ describe('#showQueryTool', () => {
context('current node is underneath a server', () => {
context('current node is not underneath a database', () => {
it('creates a transaction', () => {
showQueryTool(queryTool, pgBrowser, alertify, 'http://someurl', [{id: 'server1'}], 'title');
showQueryTool(queryTool, pgBrowser, alertify, 'http://someurl', [{id: 'server1'}]);
expect(queryTool.create_transaction).toHaveBeenCalledWith(
'/initialize/query_tool/1/2',
null,
'true',
'pg',
'http://someurl',
'title',
'otherdblabel/someuser@server1',
'',
false
);
@ -115,7 +117,7 @@ describe('#showQueryTool', () => {
'true',
'pg',
'http://someurl',
'title',
'database1/someuser@server1',
'',
false
);

View File

@ -233,6 +233,7 @@ def get_test_modules(arguments):
if test_setup.config_data['headless_chrome']:
options.add_argument("--headless")
options.add_argument("--window-size=1280,1024")
options.add_experimental_option('w3c', False)
driver = webdriver.Chrome(chrome_options=options)
# maximize browser window