mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-11-25 18:20:20 -06:00
Allow sorting when viewing/editing data. Fixes #1894
This commit is contained in:
parent
659390493d
commit
fa1854bd85
@ -95,6 +95,29 @@ To delete a row, press the *Delete* toolbar button. A popup will open, asking y
|
||||
|
||||
To commit the changes to the server, select the *Save* toolbar button. Modifications to a row are written to the server automatically when you select a different row.
|
||||
|
||||
**Sort/Filter options dialog**
|
||||
|
||||
You can access *Sort/Filter options dialog* by clicking on Sort/Filter button. This allows you to specify an SQL Filter to limit the data displayed and data sorting options in the edit grid window:
|
||||
|
||||
.. image:: images/editgrid_filter_dialog.png
|
||||
:alt: Edit grid filter dialog window
|
||||
|
||||
* Use *SQL Filter* to provide SQL filtering criteria. These will be added to the "WHERE" clause of the query used to retrieve the data. For example, you might enter:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
id > 25 AND created > '2018-01-01'
|
||||
|
||||
* Use *Data Sorting* to sort the data in the output grid
|
||||
|
||||
To add new column(s) in data sorting grid, click on the [+] icon.
|
||||
|
||||
* Use the drop-down *Column* to select the column you want to sort.
|
||||
* Use the drop-down *Order* to select the sort order for the column.
|
||||
|
||||
To delete a row from the grid, click the trash icon.
|
||||
|
||||
* Click the *Help* button (?) to access online help.
|
||||
* Click the *Ok* button to save work.
|
||||
* Click the *Close* button to discard current changes and close the dialog.
|
||||
|
||||
|
BIN
docs/en_US/images/editgrid_filter_dialog.png
Normal file
BIN
docs/en_US/images/editgrid_filter_dialog.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
@ -10,7 +10,7 @@ This release contains a number of features and fixes reported since the release
|
||||
Features
|
||||
********
|
||||
|
||||
| `Feature #1305 <https://redmine.postgresql.org/issues/1305>`_ - Enable building of the runtime from the top level Makefile
|
||||
| `Feature #1894 <https://redmine.postgresql.org/issues/1894>`_ - Allow sorting when viewing/editing data
|
||||
| `Feature #1978 <https://redmine.postgresql.org/issues/1978>`_ - Add the ability to enable/disable UI animations
|
||||
| `Feature #2895 <https://redmine.postgresql.org/issues/2895>`_ - Add keyboard navigation options for the main browser windows
|
||||
| `Feature #2896 <https://redmine.postgresql.org/issues/2896>`_ - Add keyboard navigation in Query tool module via Tab/Shift-Tab key
|
||||
|
243
web/pgadmin/static/js/sqleditor/filter_dialog.js
Normal file
243
web/pgadmin/static/js/sqleditor/filter_dialog.js
Normal file
@ -0,0 +1,243 @@
|
||||
define([
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'underscore.string',
|
||||
'pgadmin.alertifyjs', 'sources/pgadmin', 'backbone',
|
||||
'pgadmin.backgrid', 'pgadmin.backform', 'axios',
|
||||
'sources/sqleditor/query_tool_actions',
|
||||
'sources/sqleditor/filter_dialog_model',
|
||||
//'pgadmin.browser.node.ui',
|
||||
], function(
|
||||
gettext, url_for, $, _, S, Alertify, pgAdmin, Backbone,
|
||||
Backgrid, Backform, axios, queryToolActions, filterDialogModel
|
||||
) {
|
||||
|
||||
let FilterDialog = {
|
||||
'dialog': function(handler) {
|
||||
let title = gettext('Sort/Filter options');
|
||||
axios.get(
|
||||
url_for('sqleditor.get_filter_data', {
|
||||
'trans_id': handler.transId,
|
||||
}),
|
||||
{ headers: {'Cache-Control' : 'no-cache'} }
|
||||
).then(function (res) {
|
||||
let response = res.data.data.result;
|
||||
|
||||
// Check the alertify dialog already loaded then delete it to clear
|
||||
// the cache
|
||||
if (Alertify.filterDialog) {
|
||||
delete Alertify.filterDialog;
|
||||
}
|
||||
|
||||
// Create Dialog
|
||||
Alertify.dialog('filterDialog', function factory() {
|
||||
let $container = $('<div class=\'data_sorting_dialog\'></div>');
|
||||
return {
|
||||
main: function() {
|
||||
this.set('title', gettext('Sort/Filter options'));
|
||||
},
|
||||
build: function() {
|
||||
this.elements.content.appendChild($container.get(0));
|
||||
Alertify.pgDialogBuild.apply(this);
|
||||
},
|
||||
setup: function() {
|
||||
return {
|
||||
buttons: [{
|
||||
text: '',
|
||||
key: 112,
|
||||
className: 'btn btn-default pull-left fa fa-lg fa-question',
|
||||
attrs: {
|
||||
name: 'dialog_help',
|
||||
type: 'button',
|
||||
label: gettext('Help'),
|
||||
url: url_for('help.static', {
|
||||
'filename': 'editgrid.html',
|
||||
}),
|
||||
},
|
||||
}, {
|
||||
text: gettext('Ok'),
|
||||
className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button',
|
||||
'data-btn-name': 'ok',
|
||||
}, {
|
||||
text: gettext('Cancel'),
|
||||
key: 27,
|
||||
className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button',
|
||||
'data-btn-name': 'cancel',
|
||||
}],
|
||||
// Set options for dialog
|
||||
options: {
|
||||
title: title,
|
||||
//disable both padding and overflow control.
|
||||
padding: !1,
|
||||
overflow: !1,
|
||||
model: 0,
|
||||
resizable: true,
|
||||
maximizable: true,
|
||||
pinnable: false,
|
||||
closableByDimmer: false,
|
||||
modal: false,
|
||||
autoReset: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
hooks: {
|
||||
// triggered when the dialog is closed
|
||||
onclose: function() {
|
||||
if (this.view) {
|
||||
this.filterCollectionModel.stopSession();
|
||||
this.view.model.stopSession();
|
||||
this.view.remove({
|
||||
data: true,
|
||||
internal: true,
|
||||
silent: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
prepare: function() {
|
||||
let self = this;
|
||||
$container.html('');
|
||||
// Disable Ok button
|
||||
this.__internal.buttons[1].element.disabled = true;
|
||||
|
||||
// Status bar
|
||||
this.statusBar = $('<div class=\'pg-prop-status-bar pg-el-xs-12 hide\'>' +
|
||||
' <div class=\'media error-in-footer bg-red-1 border-red-2 font-red-3 text-14\'>' +
|
||||
' <div class=\'media-body media-middle\'>' +
|
||||
' <div class=\'alert-icon error-icon\'>' +
|
||||
' <i class=\'fa fa-exclamation-triangle\' aria-hidden=\'true\'></i>' +
|
||||
' </div>' +
|
||||
' <div class=\'alert-text\'>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
'</div>', {
|
||||
text: '',
|
||||
}).appendTo($container);
|
||||
|
||||
// To show progress on filter Saving/Updating on AJAX
|
||||
this.showFilterProgress = $(
|
||||
'<div id="show_filter_progress" class="wcLoadingIconContainer busy-fetching hidden">' +
|
||||
'<div class="wcLoadingBackground"></div>' +
|
||||
'<span class="wcLoadingIcon fa fa-spinner fa-pulse"></span>' +
|
||||
'<span class="busy-text wcLoadingLabel">' + gettext('Loading data...') + '</span>' +
|
||||
'</div>').appendTo($container);
|
||||
|
||||
$(
|
||||
self.showFilterProgress[0]
|
||||
).removeClass('hidden');
|
||||
|
||||
self.filterCollectionModel = filterDialogModel(response);
|
||||
|
||||
let fields = Backform.generateViewSchema(
|
||||
null, self.filterCollectionModel, 'create', null, null, true
|
||||
);
|
||||
|
||||
let view = this.view = new Backform.Dialog({
|
||||
el: '<div></div>',
|
||||
model: self.filterCollectionModel,
|
||||
schema: fields,
|
||||
});
|
||||
|
||||
$(this.elements.body.childNodes[0]).addClass(
|
||||
'alertify_tools_dialog_properties obj_properties'
|
||||
);
|
||||
|
||||
$container.append(view.render().$el);
|
||||
|
||||
// Enable/disable save button and show/hide statusbar based on session
|
||||
view.listenTo(view.model, 'pgadmin-session:start', function() {
|
||||
view.listenTo(view.model, 'pgadmin-session:invalid', function(msg) {
|
||||
self.statusBar.removeClass('hide');
|
||||
$(self.statusBar.find('.alert-text')).html(msg);
|
||||
// Disable Okay button
|
||||
self.__internal.buttons[1].element.disabled = true;
|
||||
});
|
||||
|
||||
view.listenTo(view.model, 'pgadmin-session:valid', function() {
|
||||
self.statusBar.addClass('hide');
|
||||
$(self.statusBar.find('.alert-text')).html('');
|
||||
// Enable Okay button
|
||||
self.__internal.buttons[1].element.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
view.listenTo(view.model, 'pgadmin-session:stop', function() {
|
||||
view.stopListening(view.model, 'pgadmin-session:invalid');
|
||||
view.stopListening(view.model, 'pgadmin-session:valid');
|
||||
});
|
||||
|
||||
// Starts monitoring changes to model
|
||||
view.model.startNewSession();
|
||||
|
||||
// Set data in collection
|
||||
let viewDataSortingModel = view.model.get('data_sorting');
|
||||
viewDataSortingModel.add(response['data_sorting']);
|
||||
|
||||
// Hide Progress ...
|
||||
$(
|
||||
self.showFilterProgress[0]
|
||||
).addClass('hidden');
|
||||
|
||||
},
|
||||
// Callback functions when click on the buttons of the Alertify dialogs
|
||||
callback: function(e) {
|
||||
let self = this;
|
||||
|
||||
if (e.button.element.name == 'dialog_help') {
|
||||
e.cancel = true;
|
||||
pgAdmin.Browser.showHelp(e.button.element.name, e.button.element.getAttribute('url'),
|
||||
null, null, e.button.element.getAttribute('label'));
|
||||
return;
|
||||
} else if (e.button['data-btn-name'] === 'ok') {
|
||||
e.cancel = true; // Do not close dialog
|
||||
|
||||
let filterCollectionModel = this.filterCollectionModel.toJSON();
|
||||
|
||||
// Show Progress ...
|
||||
$(
|
||||
self.showFilterProgress[0]
|
||||
).removeClass('hidden');
|
||||
|
||||
axios.put(
|
||||
url_for('sqleditor.set_filter_data', {
|
||||
'trans_id': handler.transId,
|
||||
}),
|
||||
filterCollectionModel
|
||||
).then(function () {
|
||||
// Hide Progress ...
|
||||
$(
|
||||
self.showFilterProgress[0]
|
||||
).addClass('hidden');
|
||||
setTimeout(
|
||||
function() {
|
||||
self.close(); // Close the dialog now
|
||||
Alertify.success(gettext('Filter updated successfully'));
|
||||
queryToolActions.executeQuery(handler);
|
||||
}, 10
|
||||
);
|
||||
|
||||
}).catch(function (error) {
|
||||
// Hide Progress ...
|
||||
$(
|
||||
self.showFilterProgress[0]
|
||||
).addClass('hidden');
|
||||
handler.onExecuteHTTPError(error);
|
||||
|
||||
setTimeout(
|
||||
function() {
|
||||
Alertify.error(error);
|
||||
}, 10
|
||||
);
|
||||
});
|
||||
} else {
|
||||
self.close();
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
Alertify.filterDialog(title).resizeTo('65%', '60%');
|
||||
});
|
||||
},
|
||||
};
|
||||
return FilterDialog;
|
||||
});
|
133
web/pgadmin/static/js/sqleditor/filter_dialog_model.js
Normal file
133
web/pgadmin/static/js/sqleditor/filter_dialog_model.js
Normal file
@ -0,0 +1,133 @@
|
||||
define([
|
||||
'sources/gettext', 'underscore', 'sources/pgadmin',
|
||||
'pgadmin.backform', 'pgadmin.backgrid',
|
||||
], function(
|
||||
gettext, _, pgAdmin, Backform, Backgrid
|
||||
) {
|
||||
|
||||
let initModel = function(response) {
|
||||
|
||||
let order_mapping = {
|
||||
'asc': gettext('ASC'),
|
||||
'desc': gettext('DESC'),
|
||||
};
|
||||
|
||||
let DataSortingModel = pgAdmin.Browser.DataModel.extend({
|
||||
idAttribute: 'name',
|
||||
defaults: {
|
||||
name: undefined,
|
||||
order: 'asc',
|
||||
},
|
||||
schema: [{
|
||||
id: 'name',
|
||||
name: 'name',
|
||||
label: gettext('Column'),
|
||||
cell: 'select2',
|
||||
editable: true,
|
||||
cellHeaderClasses: 'width_percent_60',
|
||||
headerCell: Backgrid.Extension.CustomHeaderCell,
|
||||
disabled: false,
|
||||
control: 'select2',
|
||||
select2: {
|
||||
allowClear: false,
|
||||
},
|
||||
options: function() {
|
||||
return _.map(response.column_list, (obj) => {
|
||||
return {
|
||||
value: obj,
|
||||
label: obj,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'order',
|
||||
name: 'order',
|
||||
label: gettext('Order'),
|
||||
control: 'select2',
|
||||
cell: 'select2',
|
||||
cellHeaderClasses: 'width_percent_40',
|
||||
headerCell: Backgrid.Extension.CustomHeaderCell,
|
||||
editable: true,
|
||||
deps: ['type'],
|
||||
select2: {
|
||||
allowClear: false,
|
||||
},
|
||||
options: function() {
|
||||
return _.map(order_mapping, (val, key) => {
|
||||
return {
|
||||
value: key,
|
||||
label: val,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
validate: function() {
|
||||
let msg = null;
|
||||
this.errorModel.clear();
|
||||
if (_.isUndefined(this.get('name')) ||
|
||||
_.isNull(this.get('name')) ||
|
||||
String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Please select a column.');
|
||||
this.errorModel.set('name', msg);
|
||||
return msg;
|
||||
} else if (_.isUndefined(this.get('order')) ||
|
||||
_.isNull(this.get('order')) ||
|
||||
String(this.get('order')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Please select the order.');
|
||||
this.errorModel.set('order', msg);
|
||||
return msg;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
let FilterCollectionModel = pgAdmin.Browser.DataModel.extend({
|
||||
idAttribute: 'sql',
|
||||
defaults: {
|
||||
sql: response.sql || null,
|
||||
},
|
||||
schema: [{
|
||||
id: 'sql',
|
||||
label: gettext('SQL Filter'),
|
||||
cell: 'string',
|
||||
type: 'text', mode: ['create'],
|
||||
control: Backform.SqlFieldControl.extend({
|
||||
render: function() {
|
||||
let obj = Backform.SqlFieldControl.prototype.render.apply(this, arguments);
|
||||
// We need to set focus on editor after the dialog renders
|
||||
setTimeout(() => {
|
||||
obj.sqlCtrl.focus();
|
||||
}, 1000);
|
||||
return obj;
|
||||
},
|
||||
}),
|
||||
extraClasses:['custom_height_css_class'],
|
||||
},{
|
||||
id: 'data_sorting',
|
||||
name: 'data_sorting',
|
||||
label: gettext('Data Sorting'),
|
||||
model: DataSortingModel,
|
||||
editable: true,
|
||||
type: 'collection',
|
||||
mode: ['create'],
|
||||
control: 'unique-col-collection',
|
||||
uniqueCol: ['name'],
|
||||
canAdd: true,
|
||||
canEdit: false,
|
||||
canDelete: true,
|
||||
visible: true,
|
||||
version_compatible: true,
|
||||
}],
|
||||
validate: function() {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
let model = new FilterCollectionModel();
|
||||
return model;
|
||||
};
|
||||
|
||||
return initModel;
|
||||
});
|
@ -79,7 +79,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<a id="btn-find-menu-find-next" href="#" tabindex="0">
|
||||
<span> {{ _('Find next') }}{% if client_platform == 'macos' -%}
|
||||
<span> {{ _('Find Next') }}{% if client_platform == 'macos' -%}
|
||||
{{ _(' (Cmd+G)') }}
|
||||
{% else %}
|
||||
{{ _(' (Ctrl+G)') }}{%- endif %}</span>
|
||||
@ -87,7 +87,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<a id="btn-find-menu-find-previous" href="#" tabindex="0">
|
||||
<span> {{ _('Find previous') }}{% if client_platform == 'macos' -%}
|
||||
<span> {{ _('Find Previous') }}{% if client_platform == 'macos' -%}
|
||||
{{ _(' (Cmd+Shift+G)') }}
|
||||
{% else %}
|
||||
{{ _(' (Ctrl+Shift+G)') }}{%- endif %}</span>
|
||||
@ -95,7 +95,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<a id="btn-find-menu-find-persistent" href="#" tabindex="0">
|
||||
<span>{{ _('Persistent find') }}</span>
|
||||
<span>{{ _('Persistent Find') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
@ -109,7 +109,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<a id="btn-find-menu-replace-all" href="#" tabindex="0">
|
||||
<span>{{ _('Replace all') }}</span>
|
||||
<span>{{ _('Replace All') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
@ -194,10 +194,10 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li>
|
||||
<a id="btn-filter-menu" href="#" tabindex="0">{{ _('Filter') }}</a>
|
||||
<a id="btn-remove-filter" href="#" tabindex="0">{{ _('Remove Filter') }}</a>
|
||||
<a id="btn-include-filter" href="#" tabindex="0">{{ _('By Selection') }}</a>
|
||||
<a id="btn-exclude-filter" href="#" tabindex="0">{{ _('Exclude Selection') }}</a>
|
||||
<a id="btn-filter-menu" href="#" tabindex="0">{{ _('Sort/Filter') }}</a>
|
||||
<a id="btn-include-filter" href="#" tabindex="0">{{ _('Filter by Selection') }}</a>
|
||||
<a id="btn-exclude-filter" href="#" tabindex="0">{{ _('Exclude by Selection') }}</a>
|
||||
<a id="btn-remove-filter" href="#" tabindex="0">{{ _('Remove Sort/Filter') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -341,23 +341,6 @@
|
||||
<div class="editor-title"
|
||||
style="background-color: {% if fgcolor %}{{ bgcolor or '#FFFFFF' }}{% else %}{{ bgcolor or '#2C76B4' }}{% endif %}; color: {{ fgcolor or 'white' }};"></div>
|
||||
</div>
|
||||
|
||||
<div id="filter" class="filter-container hidden">
|
||||
<div class="filter-title">Filter</div>
|
||||
<div class="sql-textarea">
|
||||
<textarea id="sql_filter" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button id="btn-cancel" type="button" class="btn btn-danger" title="{{ _('Cancel') }}" tabindex="0">
|
||||
<i class="fa fa-times" aria-hidden="true"></i> {{ _('Cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button id="btn-apply" type="button" class="btn btn-primary" title="{{ _('Apply') }}" tabindex="0">
|
||||
<i class="fa fa-check" aria-hidden="true"></i> {{ _('Apply') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="editor-panel" tabindex="0"></div>
|
||||
<iframe id="download-csv" style="display:none"></iframe>
|
||||
</div>
|
||||
|
@ -40,6 +40,7 @@ from pgadmin.tools.sqleditor.utils.query_tool_preferences import \
|
||||
RegisterQueryToolPreferences
|
||||
from pgadmin.tools.sqleditor.utils.query_tool_fs_utils import \
|
||||
read_file_generator
|
||||
from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog
|
||||
|
||||
MODULE_NAME = 'sqleditor'
|
||||
|
||||
@ -92,8 +93,6 @@ class SqlEditorModule(PgAdminModule):
|
||||
'sqleditor.fetch',
|
||||
'sqleditor.fetch_all',
|
||||
'sqleditor.save',
|
||||
'sqleditor.get_filter',
|
||||
'sqleditor.apply_filter',
|
||||
'sqleditor.inclusive_filter',
|
||||
'sqleditor.exclusive_filter',
|
||||
'sqleditor.remove_filter',
|
||||
@ -106,7 +105,9 @@ class SqlEditorModule(PgAdminModule):
|
||||
'sqleditor.load_file',
|
||||
'sqleditor.save_file',
|
||||
'sqleditor.query_tool_download',
|
||||
'sqleditor.connection_status'
|
||||
'sqleditor.connection_status',
|
||||
'sqleditor.get_filter_data',
|
||||
'sqleditor.set_filter_data'
|
||||
]
|
||||
|
||||
def register_preferences(self):
|
||||
@ -783,80 +784,6 @@ def save(trans_id):
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/filter/get/<int:trans_id>',
|
||||
methods=["GET"], endpoint='get_filter'
|
||||
)
|
||||
@login_required
|
||||
def get_filter(trans_id):
|
||||
"""
|
||||
This method is used to get the existing filter.
|
||||
|
||||
Args:
|
||||
trans_id: unique transaction id
|
||||
"""
|
||||
|
||||
# Check the transaction and connection status
|
||||
status, error_msg, conn, trans_obj, session_obj = \
|
||||
check_transaction_status(trans_id)
|
||||
|
||||
if error_msg == gettext('Transaction ID not found in the session.'):
|
||||
return make_json_response(success=0, errormsg=error_msg,
|
||||
info='DATAGRID_TRANSACTION_REQUIRED',
|
||||
status=404)
|
||||
if status and conn is not None and \
|
||||
trans_obj is not None and session_obj is not None:
|
||||
|
||||
res = trans_obj.get_filter()
|
||||
else:
|
||||
status = False
|
||||
res = error_msg
|
||||
|
||||
return make_json_response(data={'status': status, 'result': res})
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/filter/apply/<int:trans_id>',
|
||||
methods=["PUT", "POST"], endpoint='apply_filter'
|
||||
)
|
||||
@login_required
|
||||
def apply_filter(trans_id):
|
||||
"""
|
||||
This method is used to apply the filter.
|
||||
|
||||
Args:
|
||||
trans_id: unique transaction id
|
||||
"""
|
||||
if request.data:
|
||||
filter_sql = json.loads(request.data, encoding='utf-8')
|
||||
else:
|
||||
filter_sql = request.args or request.form
|
||||
|
||||
# Check the transaction and connection status
|
||||
status, error_msg, conn, trans_obj, session_obj = \
|
||||
check_transaction_status(trans_id)
|
||||
|
||||
if error_msg == gettext('Transaction ID not found in the session.'):
|
||||
return make_json_response(success=0, errormsg=error_msg,
|
||||
info='DATAGRID_TRANSACTION_REQUIRED',
|
||||
status=404)
|
||||
|
||||
if status and conn is not None and \
|
||||
trans_obj is not None and session_obj is not None:
|
||||
|
||||
status, res = trans_obj.set_filter(filter_sql)
|
||||
|
||||
# As we changed the transaction object we need to
|
||||
# restore it and update the session variable.
|
||||
session_obj['command_obj'] = pickle.dumps(trans_obj, -1)
|
||||
update_session_grid_transaction(trans_id, session_obj)
|
||||
else:
|
||||
status = False
|
||||
res = error_msg
|
||||
|
||||
return make_json_response(data={'status': status, 'result': res})
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/filter/inclusive/<int:trans_id>',
|
||||
methods=["PUT", "POST"], endpoint='inclusive_filter'
|
||||
@ -1561,3 +1488,37 @@ def query_tool_status(trans_id):
|
||||
return internal_server_error(
|
||||
errormsg=gettext("Transaction status check failed.")
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/filter_dialog/<int:trans_id>',
|
||||
methods=["GET"], endpoint='get_filter_data'
|
||||
)
|
||||
@login_required
|
||||
def get_filter_data(trans_id):
|
||||
"""
|
||||
This method is used to get all the columns for data sorting dialog.
|
||||
|
||||
Args:
|
||||
trans_id: unique transaction id
|
||||
"""
|
||||
return FilterDialog.get(*check_transaction_status(trans_id))
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/filter_dialog/<int:trans_id>',
|
||||
methods=["PUT"], endpoint='set_filter_data'
|
||||
)
|
||||
@login_required
|
||||
def set_filter_data(trans_id):
|
||||
"""
|
||||
This method is used to update the columns for data sorting dialog.
|
||||
|
||||
Args:
|
||||
trans_id: unique transaction id
|
||||
"""
|
||||
return FilterDialog.save(
|
||||
*check_transaction_status(trans_id),
|
||||
request=request,
|
||||
trans_id=trans_id
|
||||
)
|
||||
|
@ -141,6 +141,10 @@ class SQLFilter(object):
|
||||
- This method removes the filter applied.
|
||||
* validate_filter(row_filter)
|
||||
- This method validates the given filter.
|
||||
* get_data_sorting()
|
||||
- This method returns columns for data sorting
|
||||
* set_data_sorting()
|
||||
- This method saves columns for data sorting
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@ -160,8 +164,8 @@ class SQLFilter(object):
|
||||
self.sid = kwargs['sid']
|
||||
self.did = kwargs['did']
|
||||
self.obj_id = kwargs['obj_id']
|
||||
self.__row_filter = kwargs['sql_filter'] if 'sql_filter' in kwargs \
|
||||
else None
|
||||
self.__row_filter = kwargs.get('sql_filter', None)
|
||||
self.__dara_sorting = kwargs.get('data_sorting', None)
|
||||
|
||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(self.sid)
|
||||
conn = manager.connection(did=self.did)
|
||||
@ -210,20 +214,41 @@ class SQLFilter(object):
|
||||
|
||||
return status, msg
|
||||
|
||||
def get_data_sorting(self):
|
||||
"""
|
||||
This function returns the filter.
|
||||
"""
|
||||
if self.__dara_sorting and len(self.__dara_sorting) > 0:
|
||||
return self.__dara_sorting
|
||||
return None
|
||||
|
||||
def set_data_sorting(self, data_filter):
|
||||
"""
|
||||
This function validates the filter and set the
|
||||
given filter to member variable.
|
||||
"""
|
||||
self.__dara_sorting = data_filter['data_sorting']
|
||||
|
||||
def is_filter_applied(self):
|
||||
"""
|
||||
This function returns True if filter is applied else False.
|
||||
"""
|
||||
is_filter_applied = True
|
||||
if self.__row_filter is None or self.__row_filter == '':
|
||||
return False
|
||||
is_filter_applied = False
|
||||
|
||||
return True
|
||||
if not is_filter_applied:
|
||||
if self.__dara_sorting and len(self.__dara_sorting) > 0:
|
||||
is_filter_applied = True
|
||||
|
||||
return is_filter_applied
|
||||
|
||||
def remove_filter(self):
|
||||
"""
|
||||
This function remove the filter by setting value to None.
|
||||
"""
|
||||
self.__row_filter = None
|
||||
self.__dara_sorting = None
|
||||
|
||||
def append_filter(self, row_filter):
|
||||
"""
|
||||
@ -325,13 +350,58 @@ class GridCommand(BaseCommand, SQLFilter, FetchedRowTracker):
|
||||
self.cmd_type = kwargs['cmd_type'] if 'cmd_type' in kwargs else None
|
||||
self.limit = -1
|
||||
|
||||
if self.cmd_type == VIEW_FIRST_100_ROWS or \
|
||||
self.cmd_type == VIEW_LAST_100_ROWS:
|
||||
if self.cmd_type in (VIEW_FIRST_100_ROWS, VIEW_LAST_100_ROWS):
|
||||
self.limit = 100
|
||||
|
||||
def get_primary_keys(self, *args, **kwargs):
|
||||
return None, None
|
||||
|
||||
def get_all_columns_with_order(self, default_conn):
|
||||
"""
|
||||
Responsible for fetching columns from given object
|
||||
|
||||
Args:
|
||||
default_conn: Connection object
|
||||
|
||||
Returns:
|
||||
all_sorted_columns: Columns which are already sorted which will
|
||||
be used to populate the Grid in the dialog
|
||||
all_columns: List of all the column for given object which will
|
||||
be used to fill columns options
|
||||
"""
|
||||
driver = get_driver(PG_DEFAULT_DRIVER)
|
||||
if default_conn is None:
|
||||
manager = driver.connection_manager(self.sid)
|
||||
conn = manager.connection(did=self.did, conn_id=self.conn_id)
|
||||
else:
|
||||
conn = default_conn
|
||||
|
||||
all_sorted_columns = []
|
||||
data_sorting = self.get_data_sorting()
|
||||
all_columns = []
|
||||
if conn.connected():
|
||||
# Fetch the rest of the column names
|
||||
query = render_template(
|
||||
"/".join([self.sql_path, 'get_columns.sql']),
|
||||
obj_id=self.obj_id
|
||||
)
|
||||
status, result = conn.execute_dict(query)
|
||||
if not status:
|
||||
raise Exception(result)
|
||||
|
||||
for row in result['rows']:
|
||||
all_columns.append(row['attname'])
|
||||
else:
|
||||
raise Exception(
|
||||
gettext('Not connected to server or connection with the '
|
||||
'server has been closed.')
|
||||
)
|
||||
# If user has custom data sorting then pass as it as it is
|
||||
if data_sorting and len(data_sorting) > 0:
|
||||
all_sorted_columns = data_sorting
|
||||
|
||||
return all_sorted_columns, all_columns
|
||||
|
||||
def save(self, changed_data, default_conn=None):
|
||||
return forbidden(
|
||||
errmsg=gettext("Data cannot be saved for the current object.")
|
||||
@ -351,6 +421,17 @@ class GridCommand(BaseCommand, SQLFilter, FetchedRowTracker):
|
||||
"""
|
||||
self.limit = limit
|
||||
|
||||
def get_pk_order(self):
|
||||
"""
|
||||
This function gets the order required for primary keys
|
||||
"""
|
||||
if self.cmd_type in (VIEW_FIRST_100_ROWS, VIEW_ALL_ROWS):
|
||||
return 'asc'
|
||||
elif self.cmd_type == VIEW_LAST_100_ROWS:
|
||||
return 'desc'
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class TableCommand(GridCommand):
|
||||
"""
|
||||
@ -385,6 +466,7 @@ class TableCommand(GridCommand):
|
||||
has_oids = self.has_oids(default_conn)
|
||||
|
||||
sql_filter = self.get_filter()
|
||||
data_sorting = self.get_data_sorting()
|
||||
|
||||
if sql_filter is None:
|
||||
sql = render_template(
|
||||
@ -392,7 +474,8 @@ class TableCommand(GridCommand):
|
||||
object_name=self.object_name,
|
||||
nsp_name=self.nsp_name, pk_names=pk_names,
|
||||
cmd_type=self.cmd_type, limit=self.limit,
|
||||
primary_keys=primary_keys, has_oids=has_oids
|
||||
primary_keys=primary_keys, has_oids=has_oids,
|
||||
data_sorting=data_sorting
|
||||
)
|
||||
else:
|
||||
sql = render_template(
|
||||
@ -401,7 +484,7 @@ class TableCommand(GridCommand):
|
||||
nsp_name=self.nsp_name, pk_names=pk_names,
|
||||
cmd_type=self.cmd_type, sql_filter=sql_filter,
|
||||
limit=self.limit, primary_keys=primary_keys,
|
||||
has_oids=has_oids
|
||||
has_oids=has_oids, data_sorting=data_sorting
|
||||
)
|
||||
|
||||
return sql
|
||||
@ -447,6 +530,73 @@ class TableCommand(GridCommand):
|
||||
|
||||
return pk_names, primary_keys
|
||||
|
||||
def get_all_columns_with_order(self, default_conn=None):
|
||||
"""
|
||||
It is overridden method specially for Table because we all have to
|
||||
fetch primary keys and rest of the columns both.
|
||||
|
||||
Args:
|
||||
default_conn: Connection object
|
||||
|
||||
Returns:
|
||||
all_sorted_columns: Sorted columns for the Grid
|
||||
all_columns: List of columns for the select2 options
|
||||
"""
|
||||
driver = get_driver(PG_DEFAULT_DRIVER)
|
||||
if default_conn is None:
|
||||
manager = driver.connection_manager(self.sid)
|
||||
conn = manager.connection(did=self.did, conn_id=self.conn_id)
|
||||
else:
|
||||
conn = default_conn
|
||||
|
||||
all_sorted_columns = []
|
||||
data_sorting = self.get_data_sorting()
|
||||
all_columns = []
|
||||
if conn.connected():
|
||||
|
||||
# Fetch the primary key column names
|
||||
query = render_template(
|
||||
"/".join([self.sql_path, 'primary_keys.sql']),
|
||||
obj_id=self.obj_id
|
||||
)
|
||||
|
||||
status, result = conn.execute_dict(query)
|
||||
if not status:
|
||||
raise Exception(result)
|
||||
|
||||
for row in result['rows']:
|
||||
all_columns.append(row['attname'])
|
||||
all_sorted_columns.append(
|
||||
{
|
||||
'name': row['attname'],
|
||||
'order': self.get_pk_order()
|
||||
}
|
||||
)
|
||||
|
||||
# Fetch the rest of the column names
|
||||
query = render_template(
|
||||
"/".join([self.sql_path, 'get_columns.sql']),
|
||||
obj_id=self.obj_id
|
||||
)
|
||||
status, result = conn.execute_dict(query)
|
||||
if not status:
|
||||
raise Exception(result)
|
||||
|
||||
for row in result['rows']:
|
||||
# Only append if not already present in the list
|
||||
if row['attname'] not in all_columns:
|
||||
all_columns.append(row['attname'])
|
||||
else:
|
||||
raise Exception(
|
||||
gettext('Not connected to server or connection with the '
|
||||
'server has been closed.')
|
||||
)
|
||||
# If user has custom data sorting then pass as it as it is
|
||||
if data_sorting and len(data_sorting) > 0:
|
||||
all_sorted_columns = data_sorting
|
||||
|
||||
return all_sorted_columns, all_columns
|
||||
|
||||
def can_edit(self):
|
||||
return True
|
||||
|
||||
@ -771,20 +921,22 @@ class ViewCommand(GridCommand):
|
||||
to fetch the data for the specified view
|
||||
"""
|
||||
sql_filter = self.get_filter()
|
||||
data_sorting = self.get_data_sorting()
|
||||
|
||||
if sql_filter is None:
|
||||
sql = render_template(
|
||||
"/".join([self.sql_path, 'objectquery.sql']),
|
||||
object_name=self.object_name,
|
||||
nsp_name=self.nsp_name, cmd_type=self.cmd_type,
|
||||
limit=self.limit
|
||||
limit=self.limit, data_sorting=data_sorting
|
||||
)
|
||||
else:
|
||||
sql = render_template(
|
||||
"/".join([self.sql_path, 'objectquery.sql']),
|
||||
object_name=self.object_name,
|
||||
nsp_name=self.nsp_name, cmd_type=self.cmd_type,
|
||||
sql_filter=sql_filter, limit=self.limit
|
||||
sql_filter=sql_filter, limit=self.limit,
|
||||
data_sorting=data_sorting
|
||||
)
|
||||
|
||||
return sql
|
||||
@ -832,20 +984,22 @@ class ForeignTableCommand(GridCommand):
|
||||
to fetch the data for the specified foreign table
|
||||
"""
|
||||
sql_filter = self.get_filter()
|
||||
data_sorting = self.get_data_sorting()
|
||||
|
||||
if sql_filter is None:
|
||||
sql = render_template(
|
||||
"/".join([self.sql_path, 'objectquery.sql']),
|
||||
object_name=self.object_name,
|
||||
nsp_name=self.nsp_name, cmd_type=self.cmd_type,
|
||||
limit=self.limit
|
||||
limit=self.limit, data_sorting=data_sorting
|
||||
)
|
||||
else:
|
||||
sql = render_template(
|
||||
"/".join([self.sql_path, 'objectquery.sql']),
|
||||
object_name=self.object_name,
|
||||
nsp_name=self.nsp_name, cmd_type=self.cmd_type,
|
||||
sql_filter=sql_filter, limit=self.limit
|
||||
sql_filter=sql_filter, limit=self.limit,
|
||||
data_sorting=data_sorting
|
||||
)
|
||||
|
||||
return sql
|
||||
@ -883,20 +1037,22 @@ class CatalogCommand(GridCommand):
|
||||
to fetch the data for the specified catalog object
|
||||
"""
|
||||
sql_filter = self.get_filter()
|
||||
data_sorting = self.get_data_sorting()
|
||||
|
||||
if sql_filter is None:
|
||||
sql = render_template(
|
||||
"/".join([self.sql_path, 'objectquery.sql']),
|
||||
object_name=self.object_name,
|
||||
nsp_name=self.nsp_name, cmd_type=self.cmd_type,
|
||||
limit=self.limit
|
||||
limit=self.limit, data_sorting=data_sorting
|
||||
)
|
||||
else:
|
||||
sql = render_template(
|
||||
"/".join([self.sql_path, 'objectquery.sql']),
|
||||
object_name=self.object_name,
|
||||
nsp_name=self.nsp_name, cmd_type=self.cmd_type,
|
||||
sql_filter=sql_filter, limit=self.limit
|
||||
sql_filter=sql_filter, limit=self.limit,
|
||||
data_sorting=data_sorting
|
||||
)
|
||||
|
||||
return sql
|
||||
@ -929,6 +1085,9 @@ class QueryToolCommand(BaseCommand, FetchedRowTracker):
|
||||
def get_sql(self, default_conn=None):
|
||||
return None
|
||||
|
||||
def get_all_columns_with_order(self, default_conn=None):
|
||||
return None
|
||||
|
||||
def can_edit(self):
|
||||
return False
|
||||
|
||||
|
@ -602,3 +602,26 @@ input.editor-checkbox:focus {
|
||||
font-size: 13px;
|
||||
line-height: 3em;
|
||||
}
|
||||
|
||||
/* For Filter status bar */
|
||||
.data_sorting_dialog .pg-prop-status-bar {
|
||||
position: absolute;
|
||||
bottom: 37px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.data_sorting_dialog .CodeMirror-gutter-wrapper {
|
||||
left: -30px !important;
|
||||
}
|
||||
|
||||
.data_sorting_dialog .CodeMirror-gutters {
|
||||
left: 0px !important;
|
||||
}
|
||||
|
||||
.data_sorting_dialog .custom_height_css_class {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.data_sorting_dialog .data_sorting {
|
||||
padding: 10px 0px;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ define('tools.querytool', [
|
||||
'sources/sqleditor_utils',
|
||||
'sources/sqleditor/execute_query',
|
||||
'sources/sqleditor/query_tool_http_error_handler',
|
||||
'sources/sqleditor/filter_dialog',
|
||||
'sources/history/index.js',
|
||||
'sources/../jsx/history/query_history',
|
||||
'react', 'react-dom',
|
||||
@ -33,7 +34,7 @@ define('tools.querytool', [
|
||||
], function(
|
||||
babelPollyfill, gettext, url_for, $, _, S, alertify, pgAdmin, Backbone, codemirror,
|
||||
pgExplain, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent,
|
||||
XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler,
|
||||
XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler,
|
||||
HistoryBundle, queryHistory, React, ReactDOM,
|
||||
keyboardShortcuts, queryToolActions, Datagrid, modifyAnimation,
|
||||
calculateQueryRunTime, callRenderAfterPoll) {
|
||||
@ -112,8 +113,7 @@ define('tools.querytool', [
|
||||
|
||||
// This function is used to render the template.
|
||||
render: function() {
|
||||
var self = this,
|
||||
filter = self.$el.find('#sql_filter');
|
||||
var self = this;
|
||||
|
||||
$('.editor-title').text(_.unescape(self.editor_title));
|
||||
self.checkConnectionStatus();
|
||||
@ -121,31 +121,6 @@ define('tools.querytool', [
|
||||
// Fetch and assign the shortcuts to current instance
|
||||
self.keyboardShortcutConfig = queryToolActions.getKeyboardShortcuts(self);
|
||||
|
||||
self.filter_obj = CodeMirror.fromTextArea(filter.get(0), {
|
||||
tabindex: '0',
|
||||
lineNumbers: true,
|
||||
mode: self.handler.server_type === 'gpdb' ? 'text/x-gpsql' : 'text/x-pgsql',
|
||||
foldOptions: {
|
||||
widget: '\u2026',
|
||||
},
|
||||
foldGutter: {
|
||||
rangeFinder: CodeMirror.fold.combine(
|
||||
CodeMirror.pgadminBeginRangeFinder,
|
||||
CodeMirror.pgadminIfRangeFinder,
|
||||
CodeMirror.pgadminLoopRangeFinder,
|
||||
CodeMirror.pgadminCaseRangeFinder
|
||||
),
|
||||
},
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
extraKeys: pgBrowser.editor_shortcut_keys,
|
||||
indentWithTabs: pgAdmin.Browser.editor_options.indent_with_tabs,
|
||||
indentUnit: pgAdmin.Browser.editor_options.tabSize,
|
||||
tabSize: pgAdmin.Browser.editor_options.tabSize,
|
||||
lineWrapping: pgAdmin.Browser.editor_options.wrapCode,
|
||||
autoCloseBrackets: pgAdmin.Browser.editor_options.insert_pair_brackets,
|
||||
matchBrackets: pgAdmin.Browser.editor_options.brace_matching,
|
||||
});
|
||||
|
||||
// Updates connection status flag
|
||||
self.gain_focus = function() {
|
||||
setTimeout(function() {
|
||||
@ -2141,11 +2116,11 @@ define('tools.querytool', [
|
||||
if (self.can_filter && res.data.filter_applied) {
|
||||
$('#btn-filter').removeClass('btn-default');
|
||||
$('#btn-filter-dropdown').removeClass('btn-default');
|
||||
$('#btn-filter').addClass('btn-warning');
|
||||
$('#btn-filter-dropdown').addClass('btn-warning');
|
||||
$('#btn-filter').addClass('btn-primary');
|
||||
$('#btn-filter-dropdown').addClass('btn-primary');
|
||||
} else {
|
||||
$('#btn-filter').removeClass('btn-warning');
|
||||
$('#btn-filter-dropdown').removeClass('btn-warning');
|
||||
$('#btn-filter').removeClass('btn-primary');
|
||||
$('#btn-filter-dropdown').removeClass('btn-primary');
|
||||
$('#btn-filter').addClass('btn-default');
|
||||
$('#btn-filter-dropdown').addClass('btn-default');
|
||||
}
|
||||
@ -3044,50 +3019,8 @@ define('tools.querytool', [
|
||||
|
||||
// This function will show the filter in the text area.
|
||||
_show_filter: function() {
|
||||
var self = this;
|
||||
|
||||
self.trigger(
|
||||
'pgadmin-sqleditor:loading-icon:show',
|
||||
gettext('Loading the existing filter options...')
|
||||
);
|
||||
$.ajax({
|
||||
url: url_for('sqleditor.get_filter', {
|
||||
'trans_id': self.transId,
|
||||
}),
|
||||
method: 'GET',
|
||||
success: function(res) {
|
||||
self.trigger('pgadmin-sqleditor:loading-icon:hide');
|
||||
if (res.data.status) {
|
||||
$('#filter').removeClass('hidden');
|
||||
$('#editor-panel').addClass('sql-editor-busy-fetching');
|
||||
self.gridView.filter_obj.refresh();
|
||||
|
||||
if (res.data.result == null)
|
||||
self.gridView.filter_obj.setValue('');
|
||||
else
|
||||
self.gridView.filter_obj.setValue(res.data.result);
|
||||
// Set focus on filter area
|
||||
self.gridView.filter_obj.focus();
|
||||
} else {
|
||||
setTimeout(
|
||||
function() {
|
||||
alertify.alert(gettext('Get Filter Error'), res.data.result);
|
||||
}, 10
|
||||
);
|
||||
}
|
||||
},
|
||||
error: function(e) {
|
||||
self.trigger('pgadmin-sqleditor:loading-icon:hide');
|
||||
let msg = httpErrorHandler.handleQueryToolAjaxError(
|
||||
pgAdmin, self, e, '_show_filter', [], true
|
||||
);
|
||||
setTimeout(
|
||||
function() {
|
||||
alertify.alert(gettext('Get Filter Error'), msg);
|
||||
}, 10
|
||||
);
|
||||
},
|
||||
});
|
||||
let self = this;
|
||||
FilterHandler.dialog(self);
|
||||
},
|
||||
|
||||
// This function will include the filter by selection.
|
||||
|
@ -0,0 +1,9 @@
|
||||
{# ============= Fetch the columns ============= #}
|
||||
{% if obj_id %}
|
||||
SELECT at.attname, ty.typname
|
||||
FROM pg_attribute at
|
||||
LEFT JOIN pg_type ty ON (ty.oid = at.atttypid)
|
||||
WHERE attrelid={{obj_id}}::oid
|
||||
AND at.attnum > 0
|
||||
AND at.attisdropped = FALSE
|
||||
{% endif %}
|
@ -3,7 +3,11 @@ SELECT {% if has_oids %}oid, {% endif %}* FROM {{ conn|qtIdent(nsp_name, object_
|
||||
{% if sql_filter %}
|
||||
WHERE {{ sql_filter }}
|
||||
{% endif %}
|
||||
{% if primary_keys %}
|
||||
{% if data_sorting and data_sorting|length > 0 %}
|
||||
ORDER BY {% for obj in data_sorting %}
|
||||
{{ conn|qtIdent(obj.name) }} {{ obj.order|upper }}{% if not loop.last %}, {% else %} {% endif %}
|
||||
{% endfor %}
|
||||
{% elif primary_keys %}
|
||||
ORDER BY {% for p in primary_keys %}{{conn|qtIdent(p)}}{% if cmd_type == 1 or cmd_type == 3 %} ASC{% elif cmd_type == 2 %} DESC{% endif %}
|
||||
{% if not loop.last %}, {% else %} {% endif %}{% endfor %}
|
||||
{% endif %}
|
||||
|
95
web/pgadmin/tools/sqleditor/utils/filter_dialog.py
Normal file
95
web/pgadmin/tools/sqleditor/utils/filter_dialog.py
Normal file
@ -0,0 +1,95 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""Code to handle data sorting in view data mode."""
|
||||
import pickle
|
||||
import simplejson as json
|
||||
from flask_babelex import gettext
|
||||
from pgadmin.utils.ajax import make_json_response, internal_server_error
|
||||
from pgadmin.tools.sqleditor.utils.update_session_grid_transaction import \
|
||||
update_session_grid_transaction
|
||||
|
||||
|
||||
class FilterDialog(object):
|
||||
@staticmethod
|
||||
def get(*args):
|
||||
"""To fetch the current sorted columns"""
|
||||
status, error_msg, conn, trans_obj, session_obj = args
|
||||
if error_msg == gettext('Transaction ID not found in the session.'):
|
||||
return make_json_response(
|
||||
success=0,
|
||||
errormsg=error_msg,
|
||||
info='DATAGRID_TRANSACTION_REQUIRED',
|
||||
status=404
|
||||
)
|
||||
column_list = []
|
||||
if status and conn is not None and \
|
||||
trans_obj is not None and session_obj is not None:
|
||||
msg = gettext('Success')
|
||||
columns, column_list = trans_obj.get_all_columns_with_order(conn)
|
||||
sql = trans_obj.get_filter()
|
||||
else:
|
||||
status = False
|
||||
msg = error_msg
|
||||
columns = None
|
||||
sql = None
|
||||
|
||||
return make_json_response(
|
||||
data={
|
||||
'status': status,
|
||||
'msg': msg,
|
||||
'result': {
|
||||
'data_sorting': columns,
|
||||
'column_list': column_list,
|
||||
'sql': sql
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def save(*args, **kwargs):
|
||||
"""To save the sorted columns"""
|
||||
# Check the transaction and connection status
|
||||
status, error_msg, conn, trans_obj, session_obj = args
|
||||
trans_id = kwargs['trans_id']
|
||||
request = kwargs['request']
|
||||
|
||||
if request.data:
|
||||
data = json.loads(request.data, encoding='utf-8')
|
||||
else:
|
||||
data = request.args or request.form
|
||||
|
||||
if error_msg == gettext('Transaction ID not found in the session.'):
|
||||
return make_json_response(
|
||||
success=0,
|
||||
errormsg=error_msg,
|
||||
info='DATAGRID_TRANSACTION_REQUIRED',
|
||||
status=404
|
||||
)
|
||||
|
||||
if status and conn is not None and \
|
||||
trans_obj is not None and session_obj is not None:
|
||||
trans_obj.set_data_sorting(data)
|
||||
trans_obj.set_filter(data.get('sql'))
|
||||
# As we changed the transaction object we need to
|
||||
# restore it and update the session variable.
|
||||
session_obj['command_obj'] = pickle.dumps(trans_obj, -1)
|
||||
update_session_grid_transaction(trans_id, session_obj)
|
||||
res = gettext('Data sorting object updated successfully')
|
||||
else:
|
||||
return internal_server_error(
|
||||
errormsg=gettext('Failed to update the data on server.')
|
||||
)
|
||||
|
||||
return make_json_response(
|
||||
data={
|
||||
'status': status,
|
||||
'result': res
|
||||
}
|
||||
)
|
@ -0,0 +1,103 @@
|
||||
#######################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""Apply Explain plan wrapper to sql object."""
|
||||
from pgadmin.utils.ajax import make_json_response, internal_server_error
|
||||
from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog
|
||||
from pgadmin.utils.route import BaseTestGenerator
|
||||
|
||||
TX_ID_ERROR_MSG = 'Transaction ID not found in the session.'
|
||||
FAILED_TX_MSG = 'Failed to update the data on server.'
|
||||
|
||||
|
||||
class MockRequest(object):
|
||||
"To mock request object"
|
||||
def __init__(self):
|
||||
self.data = None
|
||||
self.args = "Test data",
|
||||
|
||||
|
||||
class StartRunningDataSortingTest(BaseTestGenerator):
|
||||
"""
|
||||
Check that the DataSorting methods works as
|
||||
intended
|
||||
"""
|
||||
scenarios = [
|
||||
('When we do not find Transaction ID in session in get', dict(
|
||||
input_parameters=(None, TX_ID_ERROR_MSG, None, None, None),
|
||||
expected_return_response={
|
||||
'success': 0,
|
||||
'errormsg': TX_ID_ERROR_MSG,
|
||||
'info': 'DATAGRID_TRANSACTION_REQUIRED',
|
||||
'status': 404
|
||||
},
|
||||
type='get'
|
||||
)),
|
||||
('When we pass all the values as None in get', dict(
|
||||
input_parameters=(None, None, None, None, None),
|
||||
expected_return_response={
|
||||
'data': {
|
||||
'status': False,
|
||||
'msg': None,
|
||||
'result': {
|
||||
'data_sorting': None,
|
||||
'column_list': []
|
||||
}
|
||||
}
|
||||
},
|
||||
type='get'
|
||||
)),
|
||||
|
||||
('When we do not find Transaction ID in session in save', dict(
|
||||
input_arg_parameters=(None, TX_ID_ERROR_MSG, None, None, None),
|
||||
input_kwarg_parameters={
|
||||
'trans_id': None,
|
||||
'request': MockRequest()
|
||||
},
|
||||
expected_return_response={
|
||||
'success': 0,
|
||||
'errormsg': TX_ID_ERROR_MSG,
|
||||
'info': 'DATAGRID_TRANSACTION_REQUIRED',
|
||||
'status': 404
|
||||
},
|
||||
type='save'
|
||||
)),
|
||||
|
||||
('When we pass all the values as None in save', dict(
|
||||
input_arg_parameters=(None, None, None, None, None),
|
||||
input_kwarg_parameters={
|
||||
'trans_id': None,
|
||||
'request': MockRequest()
|
||||
},
|
||||
expected_return_response={
|
||||
'status': 500,
|
||||
'success': 0,
|
||||
'errormsg': FAILED_TX_MSG
|
||||
|
||||
},
|
||||
type='save'
|
||||
))
|
||||
]
|
||||
|
||||
def runTest(self):
|
||||
expected_response = make_json_response(
|
||||
**self.expected_return_response
|
||||
)
|
||||
if self.type == 'get':
|
||||
result = FilterDialog.get(*self.input_parameters)
|
||||
self.assertEquals(
|
||||
result.status_code, expected_response.status_code
|
||||
)
|
||||
else:
|
||||
result = FilterDialog.save(
|
||||
*self.input_arg_parameters, **self.input_kwarg_parameters
|
||||
)
|
||||
self.assertEquals(
|
||||
result.status_code, expected_response.status_code
|
||||
)
|
31
web/regression/javascript/sqleditor/filter_dialog_specs.js
Normal file
31
web/regression/javascript/sqleditor/filter_dialog_specs.js
Normal file
@ -0,0 +1,31 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
import filterDialog from 'sources/sqleditor/filter_dialog';
|
||||
import filterDialogModel from 'sources/sqleditor/filter_dialog_model';
|
||||
|
||||
describe('filterDialog', () => {
|
||||
let sqlEditorController;
|
||||
sqlEditorController = jasmine.createSpy('sqlEditorController')
|
||||
describe('filterDialog', () => {
|
||||
describe('when using filter dialog', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(filterDialog, 'dialog');
|
||||
});
|
||||
|
||||
it("it should be defined as function", function() {
|
||||
expect(filterDialog.dialog).toBeDefined();
|
||||
});
|
||||
|
||||
it('it should call without proper handler', () => {
|
||||
expect(filterDialog.dialog).not.toHaveBeenCalledWith({});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user