diff --git a/docs/en_US/images/viewdata_filter_dialog.png b/docs/en_US/images/viewdata_filter_dialog.png index b451b28ec..0fe4148ae 100644 Binary files a/docs/en_US/images/viewdata_filter_dialog.png and b/docs/en_US/images/viewdata_filter_dialog.png differ diff --git a/docs/en_US/release_notes_6_9.rst b/docs/en_US/release_notes_6_9.rst index da2f654d2..2958d67f1 100644 --- a/docs/en_US/release_notes_6_9.rst +++ b/docs/en_US/release_notes_6_9.rst @@ -22,6 +22,7 @@ Housekeeping | `Issue #6131 `_ - Port query tool to React. | `Issue #6746 `_ - Improve the Kerberos Documentation. | `Issue #7255 `_ - Ensure the database and schema restriction controls are not shown as a drop-down. + | `Issue #7340 `_ - Port data filter dialog to React. Bug fixes ********* diff --git a/docs/en_US/viewdata_filter.rst b/docs/en_US/viewdata_filter.rst index 7655120d7..e34d78de2 100644 --- a/docs/en_US/viewdata_filter.rst +++ b/docs/en_US/viewdata_filter.rst @@ -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. \ No newline at end of file diff --git a/web/pgadmin/browser/static/js/utility_view.jsx b/web/pgadmin/browser/static/js/utility_view.jsx index c4265beaf..626e031da 100644 --- a/web/pgadmin/browser/static/js/utility_view.jsx +++ b/web/pgadmin/browser/static/js/utility_view.jsx @@ -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); diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py index 00f2c728f..8485052e1 100644 --- a/web/pgadmin/tools/sqleditor/__init__.py +++ b/web/pgadmin/tools/sqleditor/__init__.py @@ -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: diff --git a/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js b/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js index 298148a1d..811ca1b00 100644 --- a/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js +++ b/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js @@ -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. diff --git a/web/pgadmin/tools/sqleditor/static/js/show_view_data.js b/web/pgadmin/tools/sqleditor/static/js/show_view_data.js index a76fd5f15..619435686 100644 --- a/web/pgadmin/tools/sqleditor/static/js/show_view_data.js +++ b/web/pgadmin/tools/sqleditor/static/js/show_view_data.js @@ -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) { diff --git a/web/webpack.test.config.js b/web/webpack.test.config.js index 197395d99..160b75c98 100644 --- a/web/webpack.test.config.js +++ b/web/webpack.test.config.js @@ -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'), }, }, };