mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-11-25 18:20:20 -06:00
Add EXPLAIN options for SETTINGS and SUMMARY. Fixes #4335
Prevent flickering of large tooltips on the Graphical EXPLAIN canvas. Fixes #4224 EXPLAIN options should be Query Tool instance-specific. Fixes #4395
This commit is contained in:
parent
15556f9f89
commit
0340b8fb28
BIN
docs/en_US/images/query_output_explain.png
Executable file → Normal file
BIN
docs/en_US/images/query_output_explain.png
Executable file → Normal file
Binary file not shown.
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 84 KiB |
BIN
docs/en_US/images/query_output_explain_details.png
Executable file → Normal file
BIN
docs/en_US/images/query_output_explain_details.png
Executable file → Normal file
Binary file not shown.
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 122 KiB |
@ -133,10 +133,12 @@ Use the *Explain* tab to view a graphical representation of a query:
|
||||
|
||||
To generate a graphical explain diagram, open the *Explain* tab, and select
|
||||
*Explain*, *Explain Analyze*, or one or more options from the *Explain options*
|
||||
menu on the *Execute/Refresh* drop-down. Please note that *EXPLAIN VERBOSE*
|
||||
drop-down. Please note that *EXPLAIN VERBOSE*
|
||||
cannot be displayed graphically. Hover over an icon on the *Explain* tab to
|
||||
review information about that item; a popup window will display information
|
||||
about the selected object:
|
||||
about the selected object. For information on JIT statistics, triggers and a
|
||||
summary, hover over the icon on top-right corner; a similar popup window will
|
||||
be displayed when appropriate.
|
||||
|
||||
Use the download button on top left corner of the *Explain* canvas to download
|
||||
the plan as an SVG file.
|
||||
|
@ -11,6 +11,7 @@ notes for it.
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
release_notes_4_11
|
||||
release_notes_4_10
|
||||
release_notes_4_9
|
||||
release_notes_4_8
|
||||
|
18
docs/en_US/release_notes_4_11.rst
Normal file
18
docs/en_US/release_notes_4_11.rst
Normal file
@ -0,0 +1,18 @@
|
||||
************
|
||||
Version 4.10
|
||||
************
|
||||
|
||||
Release date: 2019-07-25
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin4 4.10.
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Feature #4335 <https://redmine.postgresql.org/issues/4335>`_ - Add EXPLAIN options for SETTINGS and SUMMARY.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Bug #4224 <https://redmine.postgresql.org/issues/4224>`_ - Prevent flickering of large tooltips on the Graphical EXPLAIN canvas.
|
||||
| `Bug #4395 <https://redmine.postgresql.org/issues/4395>`_ - EXPLAIN options should be Query Tool instance-specific.
|
@ -30,7 +30,6 @@
|
||||
.pg-explain-stats-btn {
|
||||
top: 5px;
|
||||
min-width: 25px;
|
||||
border: 1px solid transparent;
|
||||
pointer-events: none;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
@ -427,14 +427,7 @@ define('pgadmin.misc.explain', [
|
||||
|
||||
// Calculate co-ordinates for tooltip
|
||||
var toolTipX = ((currentXpos + pWIDTH) * zoomFactor - graphContainer.scrollLeft());
|
||||
var toolTipY = ((currentYpos + pHEIGHT) * zoomFactor - graphContainer.scrollTop());
|
||||
|
||||
// Recalculate x.y if tooltip is going out of screen
|
||||
if (graphContainer.width() < (toolTipX + toolTipContainer[0].clientWidth))
|
||||
toolTipX -= (toolTipContainer[0].clientWidth + (pWIDTH * zoomFactor));
|
||||
//if(document.children[0].clientHeight < (toolTipY + toolTipContainer[0].clientHeight))
|
||||
if (graphContainer.height() < (toolTipY + toolTipContainer[0].clientHeight))
|
||||
toolTipY -= (toolTipContainer[0].clientHeight + ((pHEIGHT / 2) * zoomFactor));
|
||||
var toolTipY = ((currentYpos) * zoomFactor - graphContainer.scrollTop());
|
||||
|
||||
toolTipX = toolTipX < 0 ? 0 : (toolTipX);
|
||||
toolTipY = toolTipY < 0 ? 0 : (toolTipY);
|
||||
@ -727,6 +720,19 @@ define('pgadmin.misc.explain', [
|
||||
delete data ['Triggers'];
|
||||
}
|
||||
|
||||
if(data) {
|
||||
let summKeys = ['Planning Time', 'Execution Time'],
|
||||
summary = {};
|
||||
|
||||
summKeys.forEach((key)=>{
|
||||
if (key in data) {
|
||||
summary[key] = data[key];
|
||||
}
|
||||
});
|
||||
|
||||
statistics.set('Summary', summary);
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
toJSON: function() {
|
||||
|
@ -15,28 +15,23 @@ let StatisticsModel = Backbone.Model.extend({
|
||||
defaults: {
|
||||
JIT: [],
|
||||
Triggers: [],
|
||||
Summary: {},
|
||||
},
|
||||
|
||||
set_statistics: function(toolTipContainer) {
|
||||
var jit_stats = this.get('JIT'),
|
||||
triggers_stats = this.get('Triggers');
|
||||
triggers_stats = this.get('Triggers'),
|
||||
summary = this.get('Summary');
|
||||
|
||||
if (Object.keys(jit_stats).length > 0 ||
|
||||
Object.keys(triggers_stats).length > 0) {
|
||||
Object.keys(triggers_stats).length > 0 ||
|
||||
Object.keys(summary).length > 0) {
|
||||
$('.pg-explain-stats-area').removeClass('d-none');
|
||||
}
|
||||
|
||||
$('.pg-explain-stats-area').on('mouseover', () => {
|
||||
// Empty the tooltip content if it has any and add new data
|
||||
toolTipContainer.empty();
|
||||
if (Object.keys(jit_stats).length == 0 &&
|
||||
Object.keys(triggers_stats).length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tooltip = $('<table></table>', {
|
||||
class: 'pgadmin-tooltip-table',
|
||||
}).appendTo(toolTipContainer);
|
||||
});
|
||||
|
||||
if (Object.keys(jit_stats).length > 0){
|
||||
tooltip.append('<tr><td class="label explain-tooltip">JIT:</td></tr>');
|
||||
@ -91,6 +86,32 @@ let StatisticsModel = Backbone.Model.extend({
|
||||
});
|
||||
}
|
||||
|
||||
if (Object.keys(summary).length > 0){
|
||||
tooltip.append('<tr><td class="label explain-tooltip">Summary:</td></tr>');
|
||||
_.each(summary, function(value, key) {
|
||||
key = _.escape(key);
|
||||
value = _.escape(value);
|
||||
tooltip.append(`
|
||||
<tr>
|
||||
<td class="label explain-tooltip"> ${key}</td>
|
||||
<td class="label explain-tooltip-val">${value}</td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
}
|
||||
|
||||
$('.pg-explain-stats-area').off('mouseover').on('mouseover', () => {
|
||||
// Empty the tooltip content if it has any and add new data
|
||||
|
||||
if (Object.keys(jit_stats).length == 0 &&
|
||||
Object.keys(triggers_stats).length == 0 &&
|
||||
Object.keys(summary).length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
toolTipContainer.empty();
|
||||
toolTipContainer.append(tooltip);
|
||||
|
||||
// Show toolTip at respective x,y coordinates
|
||||
toolTipContainer.css({
|
||||
'opacity': '0.8',
|
||||
@ -104,12 +125,13 @@ let StatisticsModel = Backbone.Model.extend({
|
||||
});
|
||||
|
||||
// Remove tooltip when mouse is out from node's area
|
||||
$('.pg-explain-stats-area').on('mouseout', () => {
|
||||
$('.pg-explain-stats-area').off('mouseout').on('mouseout', () => {
|
||||
toolTipContainer.empty();
|
||||
toolTipContainer.css({
|
||||
'opacity': '0',
|
||||
'left': 0,
|
||||
'top': 0,
|
||||
'right': '',
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -282,8 +282,9 @@ function keyboardShortcutsQueryTool(
|
||||
currLi = currLi.next();
|
||||
}
|
||||
|
||||
/*do not focus on divider and disabled */
|
||||
/*do not focus on divider, disabled and d-none */
|
||||
while(currLi.hasClass('dropdown-divider')
|
||||
|| currLi.hasClass('d-none')
|
||||
|| currLi.find('.dropdown-item').first().hasClass('disabled')) {
|
||||
if(keyCode === UP_KEY) {
|
||||
currLi = currLi.prev();
|
||||
|
@ -26,6 +26,14 @@ let queryToolActions = {
|
||||
return !$('.explain-timing').hasClass('visibility-hidden');
|
||||
},
|
||||
|
||||
_summary: function () {
|
||||
return !$('.explain-summary').hasClass('visibility-hidden');
|
||||
},
|
||||
|
||||
_settings: function () {
|
||||
return !$('.explain-settings').hasClass('visibility-hidden');
|
||||
},
|
||||
|
||||
_clearMessageTab: function () {
|
||||
$('.sql-editor-message').html('');
|
||||
},
|
||||
@ -41,36 +49,31 @@ let queryToolActions = {
|
||||
},
|
||||
|
||||
explainAnalyze: function (sqlEditorController) {
|
||||
let costEnabled = this._costsEnabled();
|
||||
let verbose = this._verbose();
|
||||
let buffers = this._buffers();
|
||||
let timing = this._timing();
|
||||
const explainObject = {
|
||||
format: 'json',
|
||||
analyze: true,
|
||||
verbose: verbose,
|
||||
costs: costEnabled,
|
||||
buffers: buffers,
|
||||
timing: timing,
|
||||
summary: false,
|
||||
verbose: this._verbose(),
|
||||
costs: this._costsEnabled(),
|
||||
buffers: this._buffers(),
|
||||
timing: this._timing(),
|
||||
summary: this._summary(),
|
||||
settings: this._settings(),
|
||||
};
|
||||
this._clearMessageTab();
|
||||
sqlEditorController.execute(explainObject);
|
||||
},
|
||||
|
||||
explain: function (sqlEditorController) {
|
||||
let costEnabled = this._costsEnabled();
|
||||
let verbose = this._verbose();
|
||||
|
||||
// let explainQuery = `EXPLAIN (FORMAT JSON, ANALYZE OFF, VERBOSE ${verbose}, COSTS ${costEnabled}, BUFFERS OFF, TIMING OFF) `;
|
||||
const explainObject = {
|
||||
format: 'json',
|
||||
analyze: false,
|
||||
verbose: verbose,
|
||||
costs: costEnabled,
|
||||
verbose: this._verbose(),
|
||||
costs: this._costsEnabled(),
|
||||
buffers: false,
|
||||
timing: false,
|
||||
summary: false,
|
||||
summary: this._summary(),
|
||||
settings: this._settings(),
|
||||
};
|
||||
this._clearMessageTab();
|
||||
sqlEditorController.execute(explainObject);
|
||||
|
@ -134,6 +134,20 @@ function updateUIPreferences(sqlEditor) {
|
||||
$el.find('.explain-timing').addClass('visibility-hidden');
|
||||
}
|
||||
|
||||
if (preferences.explain_summary) {
|
||||
$el.find('.explain-summary').removeClass('visibility-hidden');
|
||||
}
|
||||
else {
|
||||
$el.find('.explain-summary').addClass('visibility-hidden');
|
||||
}
|
||||
|
||||
if (preferences.explain_settings) {
|
||||
$el.find('.explain-settings').removeClass('visibility-hidden');
|
||||
}
|
||||
else {
|
||||
$el.find('.explain-settings').addClass('visibility-hidden');
|
||||
}
|
||||
|
||||
/* Connection status check */
|
||||
/* remove the status checker if present */
|
||||
if(sqlEditor.connIntervalId != null) {
|
||||
|
@ -234,6 +234,11 @@ def panel(trans_id, is_query_tool, editor_title):
|
||||
else:
|
||||
server_type = None
|
||||
|
||||
if request.args and 'server_ver' in request.args:
|
||||
server_ver = request.args['server_ver']
|
||||
else:
|
||||
server_ver = 0
|
||||
|
||||
# If title has slash(es) in it then replace it
|
||||
if request.args and request.args['fslashes'] != '':
|
||||
try:
|
||||
@ -311,6 +316,7 @@ def panel(trans_id, is_query_tool, editor_title):
|
||||
is_desktop_mode=app.PGADMIN_RUNTIME,
|
||||
is_linux=is_linux_platform,
|
||||
server_type=server_type,
|
||||
server_ver=server_ver,
|
||||
client_platform=user_agent.platform,
|
||||
bgcolor=bgcolor,
|
||||
fgcolor=fgcolor,
|
||||
@ -408,7 +414,8 @@ def initialize_query_tool(sgid, sid, did=None):
|
||||
|
||||
return make_json_response(
|
||||
data={
|
||||
'gridTransId': trans_id
|
||||
'gridTransId': trans_id,
|
||||
'serverVersion': manager.version,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -275,6 +275,7 @@ define('pgadmin.datagrid', [
|
||||
baseUrl = url_for('datagrid.panel', url_params) +
|
||||
'?' + 'query_url=' + encodeURI(trans_obj.sURL) +
|
||||
'&server_type=' + encodeURIComponent(trans_obj.server_type) +
|
||||
'&server_ver=' + trans_obj.serverVersion+
|
||||
'&fslashes=' + titileForURLObj.slashLocations;
|
||||
|
||||
if (self.preferences.new_browser_tab) {
|
||||
@ -283,12 +284,6 @@ define('pgadmin.datagrid', [
|
||||
// add a load listener to the window so that the title gets changed on page load
|
||||
newWin.addEventListener('load', function() {
|
||||
newWin.document.title = panel_title;
|
||||
|
||||
/* Set the initial version of pref cache the new window is having
|
||||
* This will be used by the poller to compare with window openers
|
||||
* pref cache version
|
||||
*/
|
||||
//newWin.pgAdmin.Browser.preference_version(pgBrowser.preference_version());
|
||||
});
|
||||
|
||||
} else {
|
||||
|
@ -285,6 +285,18 @@
|
||||
<span> {{ _('Timing') }} </span>
|
||||
</a>
|
||||
</li>
|
||||
<li data-min-ver="100000">
|
||||
<a class="dropdown-item" id="btn-explain-summary" href="#" tabindex="0">
|
||||
<i class="explain-summary fa fa-check visibility-hidden" aria-hidden="true"></i>
|
||||
<span> {{ _('Summary') }} </span>
|
||||
</a>
|
||||
</li>
|
||||
<li data-min-ver="120000">
|
||||
<a class="dropdown-item" id="btn-explain-settings" href="#" tabindex="0">
|
||||
<i class="explain-settings fa fa-check visibility-hidden" aria-hidden="true"></i>
|
||||
<span> {{ _('Settings') }} </span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="btn-group mr-1" role="group" aria-label="">
|
||||
@ -412,7 +424,8 @@
|
||||
script_type_url,
|
||||
"{{ server_type }}",
|
||||
{{ url_params|safe}},
|
||||
'{{ layout|safe }}'
|
||||
'{{ layout|safe }}',
|
||||
{{ server_ver }}
|
||||
);
|
||||
});
|
||||
{% endblock %}
|
||||
|
@ -95,7 +95,6 @@ class SqlEditorModule(PgAdminModule):
|
||||
return [
|
||||
'sqleditor.view_data_start',
|
||||
'sqleditor.query_tool_start',
|
||||
'sqleditor.query_tool_preferences',
|
||||
'sqleditor.poll',
|
||||
'sqleditor.fetch',
|
||||
'sqleditor.fetch_all',
|
||||
@ -330,38 +329,6 @@ def extract_sql_from_network_parameters(request_data, request_arguments,
|
||||
return request_arguments or request_form_data
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/query_tool/preferences/<int:trans_id>',
|
||||
methods=["PUT"], endpoint='query_tool_preferences'
|
||||
)
|
||||
@login_required
|
||||
def preferences(trans_id):
|
||||
"""
|
||||
This method is used to get/put explain options from/to preferences
|
||||
|
||||
Args:
|
||||
trans_id: unique transaction id
|
||||
"""
|
||||
|
||||
data = None
|
||||
if request.data:
|
||||
data = json.loads(request.data, encoding='utf-8')
|
||||
else:
|
||||
data = request.args or request.form
|
||||
for k, v in data.items():
|
||||
v = bool(v)
|
||||
if k == 'explain_verbose':
|
||||
blueprint.explain_verbose.set(v)
|
||||
elif k == 'explain_costs':
|
||||
blueprint.explain_costs.set(v)
|
||||
elif k == 'explain_buffers':
|
||||
blueprint.explain_buffers.set(v)
|
||||
elif k == 'explain_timing':
|
||||
blueprint.explain_timing.set(v)
|
||||
|
||||
return success_return()
|
||||
|
||||
|
||||
@blueprint.route('/poll/<int:trans_id>', methods=["GET"], endpoint='poll')
|
||||
@login_required
|
||||
def poll(trans_id):
|
||||
|
@ -81,6 +81,7 @@ define('tools.querytool', [
|
||||
this.handler.preferences = this.preferences;
|
||||
this.connIntervalId = null;
|
||||
this.layout = opts.layout;
|
||||
this.set_server_version(opts.server_ver);
|
||||
},
|
||||
|
||||
// Bind all the events
|
||||
@ -121,6 +122,8 @@ define('tools.querytool', [
|
||||
'click #btn-explain-costs': 'on_explain_costs',
|
||||
'click #btn-explain-buffers': 'on_explain_buffers',
|
||||
'click #btn-explain-timing': 'on_explain_timing',
|
||||
'click #btn-explain-summary': 'on_explain_summary',
|
||||
'click #btn-explain-settings': 'on_explain_settings',
|
||||
'change .limit': 'on_limit_change',
|
||||
'keydown': 'keyAction',
|
||||
// Comment options
|
||||
@ -166,6 +169,22 @@ define('tools.querytool', [
|
||||
docker.addPanel('notifications', wcDocker.DOCK.STACKED, data_output_panel);
|
||||
},
|
||||
|
||||
set_server_version: function(server_ver) {
|
||||
let self = this;
|
||||
self.server_ver = server_ver;
|
||||
|
||||
this.$el.find('*[data-min-ver]').map(function() {
|
||||
let minVer = 0,
|
||||
ele = $(this);
|
||||
minVer = parseInt(ele.attr('data-min-ver'));
|
||||
if(minVer > self.server_ver) {
|
||||
ele.addClass('d-none');
|
||||
} else {
|
||||
ele.removeClass('d-none');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// This function is used to render the template.
|
||||
render: function() {
|
||||
var self = this;
|
||||
@ -1855,6 +1874,31 @@ define('tools.querytool', [
|
||||
);
|
||||
},
|
||||
|
||||
on_explain_summary: function(ev) {
|
||||
var self = this;
|
||||
|
||||
this._stopEventPropogation(ev);
|
||||
|
||||
self.handler.trigger(
|
||||
'pgadmin-sqleditor:button:explain-summary',
|
||||
self,
|
||||
self.handler
|
||||
);
|
||||
},
|
||||
|
||||
on_explain_settings: function(ev) {
|
||||
var self = this;
|
||||
|
||||
this._stopEventPropogation(ev);
|
||||
|
||||
self.handler.trigger(
|
||||
'pgadmin-sqleditor:button:explain-settings',
|
||||
self,
|
||||
self.handler
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
do_not_close_menu: function(ev) {
|
||||
ev.stopPropagation();
|
||||
},
|
||||
@ -2117,7 +2161,7 @@ define('tools.querytool', [
|
||||
* header and loading icon and start execution of the sql query.
|
||||
*/
|
||||
start: function(transId, is_query_tool, editor_title, script_type_url,
|
||||
server_type, url_params, layout
|
||||
server_type, url_params, layout, server_ver
|
||||
) {
|
||||
var self = this;
|
||||
|
||||
@ -2140,6 +2184,7 @@ define('tools.querytool', [
|
||||
el: self.container,
|
||||
handler: self,
|
||||
layout: layout,
|
||||
server_ver: server_ver,
|
||||
});
|
||||
self.transId = self.gridView.transId = transId;
|
||||
|
||||
@ -2233,6 +2278,8 @@ define('tools.querytool', [
|
||||
self.on('pgadmin-sqleditor:button:explain-costs', self._explain_costs, self);
|
||||
self.on('pgadmin-sqleditor:button:explain-buffers', self._explain_buffers, self);
|
||||
self.on('pgadmin-sqleditor:button:explain-timing', self._explain_timing, self);
|
||||
self.on('pgadmin-sqleditor:button:explain-summary', self._explain_summary, self);
|
||||
self.on('pgadmin-sqleditor:button:explain-settings', self._explain_settings, self);
|
||||
// Indentation related
|
||||
self.on('pgadmin-sqleditor:indent_selected_code', self._indent_selected_code, self);
|
||||
self.on('pgadmin-sqleditor:unindent_selected_code', self._unindent_selected_code, self);
|
||||
@ -3888,108 +3935,37 @@ define('tools.querytool', [
|
||||
|
||||
},
|
||||
|
||||
explainPreferenceUpdate: function(subItem, data, caller) {
|
||||
let self = this;
|
||||
$.ajax({
|
||||
url: url_for('sqleditor.query_tool_preferences', {
|
||||
'trans_id': self.transId,
|
||||
}),
|
||||
method: 'PUT',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(data),
|
||||
})
|
||||
.done(function(res) {
|
||||
if (res.success == undefined || !res.success) {
|
||||
alertify.alert(gettext('Explain options error'),
|
||||
gettext('Error occurred while setting %(subItem)s option in explain.',
|
||||
{subItem : subItem})
|
||||
);
|
||||
}
|
||||
else
|
||||
self.call_cache_preferences();
|
||||
})
|
||||
.fail(function(e) {
|
||||
let msg = httpErrorHandler.handleQueryToolAjaxError(
|
||||
pgAdmin, self, e, caller, [], true
|
||||
);
|
||||
alertify.alert(gettext('Explain options error'), msg);
|
||||
});
|
||||
_toggle_explain_option: function(type) {
|
||||
let selector = `.explain-${type}`;
|
||||
$(selector).toggleClass('visibility-hidden');
|
||||
},
|
||||
|
||||
// This function will toggle "verbose" option in explain
|
||||
_explain_verbose: function() {
|
||||
var self = this;
|
||||
let explain_verbose = false;
|
||||
if ($('.explain-verbose').hasClass('visibility-hidden') === true) {
|
||||
$('.explain-verbose').removeClass('visibility-hidden');
|
||||
explain_verbose = true;
|
||||
} else {
|
||||
$('.explain-verbose').addClass('visibility-hidden');
|
||||
explain_verbose = false;
|
||||
}
|
||||
|
||||
self.explainPreferenceUpdate(
|
||||
'verbose', {
|
||||
'explain_verbose': explain_verbose,
|
||||
}, '_explain_verbose'
|
||||
);
|
||||
this._toggle_explain_option('verbose');
|
||||
},
|
||||
|
||||
// This function will toggle "costs" option in explain
|
||||
_explain_costs: function() {
|
||||
var self = this;
|
||||
let explain_costs = false;
|
||||
if ($('.explain-costs').hasClass('visibility-hidden') === true) {
|
||||
$('.explain-costs').removeClass('visibility-hidden');
|
||||
explain_costs = true;
|
||||
} else {
|
||||
$('.explain-costs').addClass('visibility-hidden');
|
||||
explain_costs = false;
|
||||
}
|
||||
|
||||
self.explainPreferenceUpdate(
|
||||
'costs', {
|
||||
'explain_costs': explain_costs,
|
||||
}, '_explain_costs'
|
||||
);
|
||||
this._toggle_explain_option('costs');
|
||||
},
|
||||
|
||||
// This function will toggle "buffers" option in explain
|
||||
_explain_buffers: function() {
|
||||
var self = this;
|
||||
let explain_buffers = false;
|
||||
if ($('.explain-buffers').hasClass('visibility-hidden') === true) {
|
||||
$('.explain-buffers').removeClass('visibility-hidden');
|
||||
explain_buffers = true;
|
||||
} else {
|
||||
$('.explain-buffers').addClass('visibility-hidden');
|
||||
explain_buffers = false;
|
||||
}
|
||||
|
||||
self.explainPreferenceUpdate(
|
||||
'buffers', {
|
||||
'explain_buffers': explain_buffers,
|
||||
}, '_explain_buffers'
|
||||
);
|
||||
this._toggle_explain_option('buffers');
|
||||
},
|
||||
|
||||
// This function will toggle "timing" option in explain
|
||||
_explain_timing: function() {
|
||||
var self = this;
|
||||
let explain_timing = false;
|
||||
if ($('.explain-timing').hasClass('visibility-hidden') === true) {
|
||||
$('.explain-timing').removeClass('visibility-hidden');
|
||||
explain_timing = true;
|
||||
} else {
|
||||
$('.explain-timing').addClass('visibility-hidden');
|
||||
explain_timing = false;
|
||||
}
|
||||
this._toggle_explain_option('timing');
|
||||
},
|
||||
|
||||
self.explainPreferenceUpdate(
|
||||
'timing', {
|
||||
'explain_timing': explain_timing,
|
||||
}, '_explain_timing'
|
||||
);
|
||||
_explain_summary: function() {
|
||||
this._toggle_explain_option('summary');
|
||||
},
|
||||
|
||||
_explain_settings: function() {
|
||||
this._toggle_explain_option('settings');
|
||||
},
|
||||
|
||||
/*
|
||||
|
@ -1,15 +1,16 @@
|
||||
{% import 'sql/macros/utils.macros' as UTILS %}
|
||||
EXPLAIN ({% if format -%}
|
||||
FORMAT {{ format.upper() }},
|
||||
FORMAT {{ format.upper() }}
|
||||
{%- endif %}{% if analyze is defined -%}
|
||||
ANALYZE {{ analyze }},
|
||||
, ANALYZE {{ UTILS.BOOL_TEXT(analyze) }}
|
||||
{%- endif %}{% if verbose is defined -%}
|
||||
VERBOSE {{ verbose }},
|
||||
, VERBOSE {{ UTILS.BOOL_TEXT(verbose) }}
|
||||
{%- endif %}{% if costs is defined -%}
|
||||
COSTS {{ costs }},
|
||||
, COSTS {{ UTILS.BOOL_TEXT(costs) }}
|
||||
{%- endif %}{% if timing is defined -%}
|
||||
TIMING {{ timing }},
|
||||
{%- endif %}{% if summary is defined -%}
|
||||
SUMMARY {{ summary }},
|
||||
, TIMING {{ UTILS.BOOL_TEXT(timing) }}
|
||||
{%- endif %}{% if buffers is defined -%}
|
||||
BUFFERS {{ buffers }}
|
||||
, BUFFERS {{ UTILS.BOOL_TEXT(buffers) }}
|
||||
{%- endif %}{% if summary is defined -%}
|
||||
, SUMMARY {{ UTILS.BOOL_TEXT(summary) }}
|
||||
{%- endif %}) {{ sql }}
|
||||
|
@ -0,0 +1,18 @@
|
||||
{% import 'sql/macros/utils.macros' as UTILS %}
|
||||
EXPLAIN ({% if format -%}
|
||||
FORMAT {{ format.upper() }}
|
||||
{%- endif %}{% if analyze is defined -%}
|
||||
, ANALYZE {{ UTILS.BOOL_TEXT(analyze) }}
|
||||
{%- endif %}{% if verbose is defined -%}
|
||||
, VERBOSE {{ UTILS.BOOL_TEXT(verbose) }}
|
||||
{%- endif %}{% if costs is defined -%}
|
||||
, COSTS {{ UTILS.BOOL_TEXT(costs) }}
|
||||
{%- endif %}{% if timing is defined -%}
|
||||
, TIMING {{ UTILS.BOOL_TEXT(timing) }}
|
||||
{%- endif %}{% if buffers is defined -%}
|
||||
, BUFFERS {{ UTILS.BOOL_TEXT(buffers) }}
|
||||
{%- endif %}{% if summary is defined -%}
|
||||
, SUMMARY {{ UTILS.BOOL_TEXT(summary) }}
|
||||
{%- endif %}{% if settings is defined -%}
|
||||
, SETTINGS {{ UTILS.BOOL_TEXT(settings) }}
|
||||
{%- endif %}) {{ sql }}
|
@ -1,13 +1,14 @@
|
||||
{% import 'sql/macros/utils.macros' as UTILS %}
|
||||
EXPLAIN ({% if format -%}
|
||||
FORMAT {{ format.upper() }},
|
||||
FORMAT {{ format.upper() }}
|
||||
{%- endif %}{% if analyze is defined -%}
|
||||
ANALYZE {{ analyze }},
|
||||
, ANALYZE {{ UTILS.BOOL_TEXT(analyze) }}
|
||||
{%- endif %}{% if verbose is defined -%}
|
||||
VERBOSE {{ verbose }},
|
||||
, VERBOSE {{ UTILS.BOOL_TEXT(verbose) }}
|
||||
{%- endif %}{% if costs is defined -%}
|
||||
COSTS {{ costs }},
|
||||
, COSTS {{ UTILS.BOOL_TEXT(costs) }}
|
||||
{%- endif %}{% if timing is defined -%}
|
||||
TIMING {{ timing }},
|
||||
, TIMING {{ UTILS.BOOL_TEXT(timing) }}
|
||||
{%- endif %}{% if buffers is defined -%}
|
||||
BUFFERS {{ buffers }}
|
||||
, BUFFERS {{ UTILS.BOOL_TEXT(buffers) }}
|
||||
{%- endif %}) {{ sql }}
|
||||
|
@ -1,10 +1,14 @@
|
||||
{% import 'sql/macros/utils.macros' as UTILS %}
|
||||
EXPLAIN ({% if format -%}
|
||||
FORMAT {{ format.upper() }},
|
||||
FORMAT {{ format.upper() }}
|
||||
{%- endif %}{% if analyze is defined -%}
|
||||
ANALYZE {{ analyze }},{%- endif %}{% if verbose is defined -%}
|
||||
VERBOSE {{ verbose }},
|
||||
, ANALYZE {{ UTILS.BOOL_TEXT(analyze) }}
|
||||
{%- endif %}{% if verbose is defined -%}
|
||||
, VERBOSE {{ UTILS.BOOL_TEXT(verbose) }}
|
||||
{%- endif %}{% if costs is defined -%}
|
||||
COSTS {{ costs }},
|
||||
, COSTS {{ UTILS.BOOL_TEXT(costs) }}
|
||||
{%- endif %}{% if timing is defined -%}
|
||||
, TIMING {{ UTILS.BOOL_TEXT(timing) }}
|
||||
{%- endif %}{% if buffers is defined -%}
|
||||
BUFFERS {{ buffers }}
|
||||
, BUFFERS {{ UTILS.BOOL_TEXT(buffers) }}
|
||||
{%- endif %}) {{ sql }}
|
||||
|
@ -10,10 +10,11 @@
|
||||
import os
|
||||
|
||||
from flask import Flask, render_template
|
||||
from jinja2 import FileSystemLoader
|
||||
from jinja2 import FileSystemLoader, ChoiceLoader
|
||||
|
||||
from pgadmin import VersionedTemplateLoader
|
||||
from pgadmin.utils.route import BaseTestGenerator
|
||||
from pgadmin import tools
|
||||
|
||||
|
||||
class TestExplainPlanTemplates(BaseTestGenerator):
|
||||
@ -34,9 +35,9 @@ class TestExplainPlanTemplates(BaseTestGenerator):
|
||||
),
|
||||
sql_statement='SELECT * FROM places',
|
||||
expected_return_value='EXPLAIN '
|
||||
'(FORMAT XML,ANALYZE True,'
|
||||
'VERBOSE True,COSTS False,'
|
||||
'BUFFERS True) SELECT * FROM places'
|
||||
'(FORMAT XML, ANALYZE true, '
|
||||
'VERBOSE true, COSTS false, '
|
||||
'BUFFERS true) SELECT * FROM places'
|
||||
)
|
||||
),
|
||||
(
|
||||
@ -52,7 +53,7 @@ class TestExplainPlanTemplates(BaseTestGenerator):
|
||||
),
|
||||
sql_statement='SELECT * FROM places',
|
||||
expected_return_value='EXPLAIN '
|
||||
'(FORMAT JSON,BUFFERS True) '
|
||||
'(FORMAT JSON, BUFFERS true) '
|
||||
'SELECT * FROM places'
|
||||
)
|
||||
),
|
||||
@ -70,8 +71,8 @@ class TestExplainPlanTemplates(BaseTestGenerator):
|
||||
),
|
||||
sql_statement='SELECT * FROM places',
|
||||
expected_return_value='EXPLAIN '
|
||||
'(FORMAT JSON,TIMING False,'
|
||||
'BUFFERS True) SELECT * FROM places'
|
||||
'(FORMAT JSON, TIMING false, '
|
||||
'BUFFERS true) SELECT * FROM places'
|
||||
)
|
||||
),
|
||||
(
|
||||
@ -89,8 +90,30 @@ class TestExplainPlanTemplates(BaseTestGenerator):
|
||||
),
|
||||
sql_statement='SELECT * FROM places',
|
||||
expected_return_value='EXPLAIN '
|
||||
'(FORMAT YAML,TIMING False,'
|
||||
'SUMMARY True,BUFFERS True) '
|
||||
'(FORMAT YAML, TIMING false, '
|
||||
'BUFFERS true, SUMMARY true) '
|
||||
'SELECT * FROM places'
|
||||
)
|
||||
),
|
||||
(
|
||||
'When rendering Postgres 12 template, '
|
||||
'when settings is present,'
|
||||
'it returns the explain plan with settings',
|
||||
dict(
|
||||
template_path='sqleditor/sql/12_plus/explain_plan.sql',
|
||||
input_parameters=dict(
|
||||
sql='SELECT * FROM places',
|
||||
format='json',
|
||||
buffers=False,
|
||||
timing=False,
|
||||
summary=False,
|
||||
settings=True
|
||||
),
|
||||
sql_statement='SELECT * FROM places',
|
||||
expected_return_value='EXPLAIN '
|
||||
'(FORMAT JSON, TIMING false, '
|
||||
'BUFFERS false, SUMMARY false, '
|
||||
'SETTINGS true) '
|
||||
'SELECT * FROM places'
|
||||
)
|
||||
),
|
||||
@ -153,6 +176,11 @@ class TestExplainPlanTemplates(BaseTestGenerator):
|
||||
class FakeApp(Flask):
|
||||
def __init__(self):
|
||||
super(FakeApp, self).__init__("")
|
||||
self.jinja_loader = FileSystemLoader(
|
||||
self.jinja_loader = ChoiceLoader([
|
||||
FileSystemLoader(
|
||||
os.path.dirname(os.path.realpath(__file__)) + "/../templates"
|
||||
),
|
||||
FileSystemLoader(
|
||||
os.path.join(os.path.dirname(tools.__file__), 'templates')
|
||||
)
|
||||
])
|
||||
|
@ -62,6 +62,18 @@ def RegisterQueryToolPreferences(self):
|
||||
category_label=gettext('Explain')
|
||||
)
|
||||
|
||||
self.explain_summary = self.preference.register(
|
||||
'Explain', 'explain_summary',
|
||||
gettext("Show summary?"), 'boolean', False,
|
||||
category_label=gettext('Explain')
|
||||
)
|
||||
|
||||
self.explain_settings = self.preference.register(
|
||||
'Explain', 'explain_settings',
|
||||
gettext("Show settings?"), 'boolean', False,
|
||||
category_label=gettext('Explain')
|
||||
)
|
||||
|
||||
self.auto_commit = self.preference.register(
|
||||
'Options', 'auto_commit',
|
||||
gettext("Auto commit?"), 'boolean', True,
|
||||
|
3
web/pgadmin/tools/templates/sql/macros/utils.macros
Normal file
3
web/pgadmin/tools/templates/sql/macros/utils.macros
Normal file
@ -0,0 +1,3 @@
|
||||
{% macro BOOL_TEXT(bool_val) %}
|
||||
{% if bool_val %}true{% else %}false{% endif %}
|
||||
{% endmacro %}
|
@ -30,6 +30,7 @@ describe('ExplainStatistics', () => {
|
||||
|
||||
statsModel.set('JIT', []);
|
||||
statsModel.set('Triggers', []);
|
||||
statsModel.set('Summary', {});
|
||||
statsModel.set_statistics(tooltipContainer);
|
||||
|
||||
expect($('.pg-explain-stats-area').hasClass('d-none')).toEqual(true);
|
||||
@ -93,4 +94,37 @@ describe('ExplainStatistics', () => {
|
||||
expect(tooltipContainer.css('opacity')).toEqual('0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Summary', () => {
|
||||
beforeEach(function() {
|
||||
$('body').append(statsDiv);
|
||||
statsModel.set('JIT', []);
|
||||
statsModel.set('Triggers', []);
|
||||
statsModel.set('Summary', {
|
||||
'Planning Time': 0.12,
|
||||
'Execution Time': 2.34,
|
||||
});
|
||||
statsModel.set_statistics(tooltipContainer);
|
||||
});
|
||||
|
||||
it('Statistics button should be visible', () => {
|
||||
expect($('.pg-explain-stats-area').hasClass('d-none')).toEqual(false);
|
||||
});
|
||||
|
||||
it('Mouse over event should be trigger', () => {
|
||||
// Trigger mouse over event
|
||||
var hoverEvent = new $.Event('mouseover');
|
||||
$('.pg-explain-stats-area').trigger(hoverEvent);
|
||||
|
||||
expect(tooltipContainer.css('opacity')).toEqual('0.8');
|
||||
});
|
||||
|
||||
it('Mouse out event should be trigger', () => {
|
||||
// Trigger mouse out event
|
||||
var hoverEvent = new $.Event('mouseout');
|
||||
$('.pg-explain-stats-area').trigger(hoverEvent);
|
||||
|
||||
expect(tooltipContainer.css('opacity')).toEqual('0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -55,6 +55,8 @@ describe('queryToolActions', () => {
|
||||
spyOn(queryToolActions, '_costsEnabled').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_buffers').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_timing').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_summary').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_settings').and.returnValue(false);
|
||||
});
|
||||
|
||||
it('calls the execute function', () => {
|
||||
@ -69,19 +71,22 @@ describe('queryToolActions', () => {
|
||||
buffers: false,
|
||||
timing: false,
|
||||
summary: false,
|
||||
settings: false,
|
||||
};
|
||||
|
||||
expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when verbose and costs and buffers and timing are all selected', () => {
|
||||
describe('when all options are selected', () => {
|
||||
beforeEach(() => {
|
||||
setUpSpies('', '');
|
||||
spyOn(queryToolActions, '_verbose').and.returnValue(true);
|
||||
spyOn(queryToolActions, '_costsEnabled').and.returnValue(true);
|
||||
spyOn(queryToolActions, '_buffers').and.returnValue(true);
|
||||
spyOn(queryToolActions, '_timing').and.returnValue(true);
|
||||
spyOn(queryToolActions, '_summary').and.returnValue(true);
|
||||
spyOn(queryToolActions, '_settings').and.returnValue(true);
|
||||
});
|
||||
it('calls the execute function', () => {
|
||||
queryToolActions.explainAnalyze(sqlEditorController);
|
||||
@ -92,7 +97,8 @@ describe('queryToolActions', () => {
|
||||
costs: true,
|
||||
buffers: true,
|
||||
timing: true,
|
||||
summary: false,
|
||||
summary: true,
|
||||
settings: true,
|
||||
};
|
||||
expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
|
||||
});
|
||||
@ -105,6 +111,8 @@ describe('queryToolActions', () => {
|
||||
spyOn(queryToolActions, '_costsEnabled').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_buffers').and.returnValue(true);
|
||||
spyOn(queryToolActions, '_timing').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_summary').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_settings').and.returnValue(false);
|
||||
});
|
||||
it('calls the execute function', () => {
|
||||
queryToolActions.explainAnalyze(sqlEditorController);
|
||||
@ -117,6 +125,7 @@ describe('queryToolActions', () => {
|
||||
buffers: true,
|
||||
timing: false,
|
||||
summary: false,
|
||||
settings: false,
|
||||
};
|
||||
|
||||
expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
|
||||
@ -130,6 +139,8 @@ describe('queryToolActions', () => {
|
||||
spyOn(queryToolActions, '_costsEnabled').and.returnValue(true);
|
||||
spyOn(queryToolActions, '_buffers').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_timing').and.returnValue(true);
|
||||
spyOn(queryToolActions, '_summary').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_settings').and.returnValue(false);
|
||||
});
|
||||
it('calls the execute function', () => {
|
||||
queryToolActions.explainAnalyze(sqlEditorController);
|
||||
@ -142,6 +153,35 @@ describe('queryToolActions', () => {
|
||||
buffers: false,
|
||||
timing: true,
|
||||
summary: false,
|
||||
settings: false,
|
||||
};
|
||||
|
||||
expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when all are not selected except summary and settings', () => {
|
||||
beforeEach(() => {
|
||||
setUpSpies('', '');
|
||||
spyOn(queryToolActions, '_verbose').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_costsEnabled').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_buffers').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_timing').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_summary').and.returnValue(true);
|
||||
spyOn(queryToolActions, '_settings').and.returnValue(true);
|
||||
});
|
||||
it('calls the execute function', () => {
|
||||
queryToolActions.explainAnalyze(sqlEditorController);
|
||||
|
||||
const explainObject = {
|
||||
format: 'json',
|
||||
analyze: true,
|
||||
verbose: false,
|
||||
costs: false,
|
||||
buffers: false,
|
||||
timing: false,
|
||||
summary: true,
|
||||
settings: true,
|
||||
};
|
||||
|
||||
expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
|
||||
@ -155,6 +195,10 @@ describe('queryToolActions', () => {
|
||||
setUpSpies('', '');
|
||||
spyOn(queryToolActions, '_verbose').and.returnValue(true);
|
||||
spyOn(queryToolActions, '_costsEnabled').and.returnValue(true);
|
||||
spyOn(queryToolActions, '_summary').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_settings').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_summary').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_settings').and.returnValue(false);
|
||||
});
|
||||
|
||||
it('calls the execute function', () => {
|
||||
@ -167,6 +211,7 @@ describe('queryToolActions', () => {
|
||||
buffers: false,
|
||||
timing: false,
|
||||
summary: false,
|
||||
settings: false,
|
||||
};
|
||||
expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
|
||||
});
|
||||
@ -177,6 +222,8 @@ describe('queryToolActions', () => {
|
||||
setUpSpies('', '');
|
||||
spyOn(queryToolActions, '_verbose').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_costsEnabled').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_summary').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_settings').and.returnValue(false);
|
||||
});
|
||||
|
||||
it('calls the execute function', () => {
|
||||
@ -189,6 +236,7 @@ describe('queryToolActions', () => {
|
||||
buffers: false,
|
||||
timing: false,
|
||||
summary: false,
|
||||
settings: false,
|
||||
};
|
||||
|
||||
expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
|
||||
@ -200,6 +248,8 @@ describe('queryToolActions', () => {
|
||||
setUpSpies('', '');
|
||||
spyOn(queryToolActions, '_verbose').and.returnValue(true);
|
||||
spyOn(queryToolActions, '_costsEnabled').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_summary').and.returnValue(false);
|
||||
spyOn(queryToolActions, '_settings').and.returnValue(false);
|
||||
});
|
||||
|
||||
it('calls the execute function', () => {
|
||||
@ -212,6 +262,7 @@ describe('queryToolActions', () => {
|
||||
buffers: false,
|
||||
timing: false,
|
||||
summary: false,
|
||||
settings: false,
|
||||
};
|
||||
expect(sqlEditorController.execute).toHaveBeenCalledWith(explainObject);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user