Port data filter dialog to React. Fixes #7340

This commit is contained in:
Akshay Joshi 2022-04-28 12:04:56 +05:30
parent a86a8c6a34
commit 6e2ee9a21f
8 changed files with 68 additions and 187 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -22,6 +22,7 @@ Housekeeping
| `Issue #6131 <https://redmine.postgresql.org/issues/6131>`_ - Port query tool to React.
| `Issue #6746 <https://redmine.postgresql.org/issues/6746>`_ - Improve the Kerberos Documentation.
| `Issue #7255 <https://redmine.postgresql.org/issues/7255>`_ - Ensure the database and schema restriction controls are not shown as a drop-down.
| `Issue #7340 <https://redmine.postgresql.org/issues/7340>`_ - Port data filter dialog to React.
Bug fixes
*********

View File

@ -15,5 +15,3 @@ in the edit grid window:
.. image:: images/viewdata_filter_dialog.png
:alt: View Data filter dialog window
:align: center
.. note:: Use SHIFT + ENTER keys to apply filter.

View File

@ -16,7 +16,9 @@ import SchemaView from 'sources/SchemaView';
import 'wcdocker';
/* The entry point for rendering React based view in properties, called in node.js */
export function getUtilityView(schema, treeNodeInfo, actionType, formType, container, containerPanel, onSave, extraData, saveBtnName, urlBase, sqlHelpUrl, helpUrl) {
export function getUtilityView(schema, treeNodeInfo, actionType, formType, container, containerPanel,
onSave, extraData, saveBtnName, urlBase, sqlHelpUrl, helpUrl, isTabView=true) {
let serverInfo = treeNodeInfo && ('server' in treeNodeInfo) &&
pgAdmin.Browser.serverInfo && pgAdmin.Browser.serverInfo[treeNodeInfo.server._id];
let inCatalog = treeNodeInfo && ('catalog' in treeNodeInfo);
@ -90,6 +92,7 @@ export function getUtilityView(schema, treeNodeInfo, actionType, formType, conta
onDataChange={()=>{/*This is intentional (SonarQube)*/}}
confirmOnCloseReset={confirmOnReset}
hasSQL={false}
isTabView={isTabView}
disableSqlHelp={sqlHelpUrl == undefined || sqlHelpUrl == ''}
disableDialogHelp={helpUrl == undefined || helpUrl == ''}
/>, container);

View File

@ -594,16 +594,18 @@ def validate_filter(sid, did, obj_id):
obj_id: Id of currently selected object
"""
if request.data:
filter_sql = json.loads(request.data, encoding='utf-8')
filter_data = json.loads(request.data, encoding='utf-8')
else:
filter_sql = request.args or request.form
filter_data = request.args or request.form
try:
# Create object of SQLFilter class
sql_filter_obj = SQLFilter(sid=sid, did=did, obj_id=obj_id)
# Call validate_filter method to validate the SQL.
status, res = sql_filter_obj.validate_filter(filter_sql)
status, res = sql_filter_obj.validate_filter(filter_data['filter_sql'])
if not status:
return internal_server_error(errormsg=str(res))
except ObjectGone:
raise
except Exception as e:

View File

@ -207,13 +207,13 @@ export default class SQLEditor {
// This is a callback function to show data when user click on menu item.
showViewData(data, i) {
const transId = commonUtils.getRandomInt(1, 9999999);
showViewData.showViewData(this, pgBrowser, alertify, data, i, transId);
showViewData.showViewData(this, pgBrowser, data, i, transId);
}
// This is a callback function to show filtered data when user click on menu item.
showFilteredRow(data, i) {
const transId = commonUtils.getRandomInt(1, 9999999);
showViewData.showViewData(this, pgBrowser, alertify, data, i, transId, true, this.preferences);
showViewData.showViewData(this, pgBrowser, data, i, transId, true);
}
// This is a callback function to show query tool when user click on menu item.

View File

@ -9,21 +9,51 @@
import gettext from '../../../../static/js/gettext';
import url_for from '../../../../static/js/url_for';
import {getDatabaseLabel, generateTitle} from './sqleditor_title';
import CodeMirror from 'bundled_codemirror';
import * as SqlEditorUtils from 'sources/sqleditor_utils';
import $ from 'jquery';
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import _ from 'underscore';
import Notify from '../../../../static/js/helpers/Notifier';
import { isEmptyString } from 'sources/validators';
import { getUtilityView } from '../../../../browser/static/js/utility_view';
export default class DataFilterSchema extends BaseUISchema {
constructor(fieldOptions = {}) {
super({
filter_sql: ''
});
this.fieldOptions = {
...fieldOptions,
};
}
get baseFields() {
return [{
id: 'filter_sql',
label: gettext('Data Filter'),
type: 'sql', isFullTab: true, cell: 'text',
}];
}
validate(state, setError) {
let errmsg = null;
if (isEmptyString(state.filter_sql)) {
errmsg = gettext('Data filter can not be empty.');
setError('filter_sql', errmsg);
return true;
} else {
setError('filter_sql', null);
}
}
}
export function showViewData(
queryToolMod,
pgBrowser,
alertify,
connectionData,
treeIdentifier,
transId,
filter=false,
preferences=null
filter=false
) {
const node = pgBrowser.tree.findNodeByDomElement(treeIdentifier);
if (node === undefined || !node.getData()) {
@ -49,27 +79,17 @@ export function showViewData(
const gridUrl = generateUrl(transId, connectionData, node.getData(), parentData);
const queryToolTitle = generateViewDataTitle(pgBrowser, treeIdentifier);
if(filter) {
initFilterDialog(alertify, pgBrowser);
const validateUrl = generateFilterValidateUrl(node.getData(), parentData);
let okCallback = function(sql) {
queryToolMod.launch(transId, gridUrl, false, queryToolTitle, null, sql);
};
$.get(url_for('sqleditor.filter'),
function(data) {
alertify.filterDialog(gettext('Data Filter - %s', queryToolTitle), data, validateUrl, preferences, okCallback)
.resizeTo(pgBrowser.stdW.sm,pgBrowser.stdH.sm);
}
);
// Show Data Filter Dialog
showFilterDialog(pgBrowser, node, queryToolMod, transId, gridUrl,
queryToolTitle, validateUrl);
} else {
queryToolMod.launch(transId, gridUrl, false, queryToolTitle);
}
}
export function retrieveNameSpaceName(parentData) {
if(!parentData) {
return null;
@ -130,170 +150,26 @@ function generateFilterValidateUrl(nodeData, parentData) {
return url_for('sqleditor.filter_validate', url_params);
}
function initFilterDialog(alertify, pgBrowser) {
// Create filter dialog using alertify
let filter_editor = null;
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;
},
function showFilterDialog(pgBrowser, treeNodeInfo, queryToolMod, transId,
gridUrl, queryToolTitle, validateUrl) {
setup:function() {
return {
buttons:[{
text: '',
key: 112,
className: 'btn btn-primary-icon pull-left fa fa-question pg-alertify-icon-button',
attrs: {
name: 'dialog_help',
type: 'button',
label: gettext('Data Filter'),
'aria-label': gettext('Help'),
url: url_for('help.static', {
'filename': 'viewdata_filter.html',
}),
},
},{
text: gettext('Cancel'),
key: 27,
className: 'btn btn-secondary fa fa-times pg-alertify-button',
},{
text: gettext('OK'),
key: null,
className: 'btn btn-primary fa fa-check pg-alertify-button',
}],
options: {
modal: 0,
resizable: true,
maximizable: false,
pinnable: false,
autoReset: false,
},
};
},
build: function() {
var that = this;
alertify.pgDialogBuild.apply(that);
let schema = new DataFilterSchema();
// Set the tooltip of OK
$(that.__internal.buttons[2].element).attr('title', gettext('Use SHIFT + ENTER to apply filter...'));
// Register dialog panel
pgBrowser.Node.registerUtilityPanel();
var panel = pgBrowser.Node.addUtilityPanel(pgBrowser.stdW.md),
j = panel.$container.find('.obj_properties').first();
panel.title(gettext('Data Filter - %s', queryToolTitle));
panel.focus();
// For sort/filter dialog we capture the keypress event
// and on "shift + enter" we clicked on "OK" button.
$(that.elements.body).on('keypress', function(evt) {
if (evt.shiftKey && evt.keyCode == 13) {
that.__internal.buttons[2].element.click();
}
});
},
prepare:function() {
var that = this,
$content = $(this.message),
$sql_filter = $content.find('#sql_filter');
var helpUrl = url_for('help.static', {'filename': 'viewdata_filter.html'});
$(this.elements.header).attr('data-title', this.get('title'));
$(this.elements.body.childNodes[0]).addClass(
'dataview_filter_dialog'
);
let okCallback = function() {
queryToolMod.launch(transId, gridUrl, false, queryToolTitle, null, schema._sessData.filter_sql);
};
this.setContent($content.get(0));
// Disable OK button
that.__internal.buttons[2].element.disabled = true;
// Apply CodeMirror to filter text area.
filter_editor = 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,
screenReaderLabel: gettext('Filter SQL'),
});
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[2].element.disabled = false;
} else {
that.__internal.buttons[2].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 {
Notify.alert(
gettext('Validation Error'),
gettext(res.data.result),
function(){
filter_editor.focus();
},
);
}
})
.fail(function(e) {
if (e.status === 410){
pgBrowser.report_error(gettext('Error filtering rows - %s.', e.statusText), e.responseJSON.errormsg);
} else {
Notify.alert(
gettext('Validation Error'),
e
);
}
});
} else if(closeEvent.index == 0) {
/* help Button */
closeEvent.cancel = true;
pgBrowser.showHelp(
closeEvent.button.element.name,
closeEvent.button.element.getAttribute('url'),
null, null
);
return;
}
},
};
});
}
getUtilityView(schema, treeNodeInfo, 'create', 'dialog', j[0], panel,
okCallback, [], 'OK', validateUrl, undefined, helpUrl, false);
}
function hasServerOrDatabaseConfiguration(parentData) {

View File

@ -217,6 +217,7 @@ module.exports = {
'tools': path.join(__dirname, './pgadmin/tools/'),
'pgadmin.user_management.current_user': regressionDir + '/javascript/fake_current_user',
'pgadmin.browser.constants': regressionDir + '/javascript/fake_constants',
'pgadmin.help': path.join(__dirname, './pgadmin/help/static/js/help'),
},
},
};