Improve the explain plan details by showing popup instead of tooltip on clicking of the specified node. Fixes #5488

This commit is contained in:
Aditya Toshniwal 2021-01-12 16:43:30 +05:30 committed by Akshay Joshi
parent 3f089f31a3
commit 6589f82e77
9 changed files with 106 additions and 147 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 190 KiB

View File

@ -186,11 +186,11 @@ under the Explain panel.
* Graphical
Please note that *EXPLAIN VERBOSE* cannot be displayed graphically. Hover over
an icon on the *Graphical* tab to review information about that item; a popup
window will display information 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.
Please note that *EXPLAIN VERBOSE* cannot be displayed graphically. Click on
a node icon on the *Graphical* tab to review information about that item; a popup
window will display on the right side with the information about the selected
object. For information on JIT statistics, triggers and a summary, click on the
button 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.

View File

@ -22,6 +22,7 @@ Bug fixes
| `Issue #4892 <https://redmine.postgresql.org/issues/4892>`_ - Fixed an issue where pressing the back button will show another instance of the main page inside of the Query Tool tab.
| `Issue #5282 <https://redmine.postgresql.org/issues/5282>`_ - Added 'Count Rows' option to the partition sub tables.
| `Issue #5488 <https://redmine.postgresql.org/issues/5488>`_ - Improve the explain plan details by showing popup instead of tooltip on clicking of the specified node.
| `Issue #5571 <https://redmine.postgresql.org/issues/5571>`_ - Added support for expression in exclusion constraints.
| `Issue #5875 <https://redmine.postgresql.org/issues/5875>`_ - Ensure that the 'template1' database should not be visible after pg_upgrade.
| `Issue #5965 <https://redmine.postgresql.org/issues/5965>`_ - Ensure that the macro query result should be download properly.

View File

@ -753,6 +753,12 @@ define('pgadmin.misc.explain', [
return data;
},
getLabel: function() {
return this.get('Schema') == undefined ?
this.get('image_text') :
(this.get('Schema') + '.' + this.get('image_text'));
},
// Draw image, its name and its tooltip
draw: function(
s, xpos, ypos, pXpos, pYpos, graphContainer, toolTipContainer,
@ -802,9 +808,7 @@ define('pgadmin.misc.explain', [
// Draw text below the node
var node_label = this.get('Schema') == undefined ?
this.get('image_text') :
(this.get('Schema') + '.' + this.get('image_text'));
var node_label = this.getLabel();
g.multitext(
currentXpos + (pWIDTH / 2) + TXT_ALIGN,
currentYpos + pHEIGHT - TXT_ALIGN,
@ -943,34 +947,29 @@ define('pgadmin.misc.explain', [
IMAGE_WIDTH,
IMAGE_HEIGHT
);
image.attr({style: 'cursor: pointer'});
// Draw tooltip
var image_data = this.toJSON();
var title = '<title>';
_.each(image_data, function(value, key) {
if (key !== 'image' && key !== 'Plans' &&
key !== 'level' && key !== 'image' &&
key !== 'image_text' && key !== 'xpos' &&
key !== 'ypos' && key !== 'width' &&
key !== 'height') {
title += `${key}: ${value}\n`;
}
});
title += '</title>';
image.append(Snap.parse(title));
image.mouseover(() => {
var image_data = this.toJSON(),
nodeLabel = this.getLabel();
image.click(() => {
// Empty the tooltip content if it has any and add new data
toolTipContainer.empty();
let toolTipBody = toolTipContainer.find('.details-body');
let toolTipTitle = toolTipContainer.find('.details-title');
toolTipTitle.text(nodeLabel);
toolTipBody.empty();
// Remove the title content so that we can show our custom build tooltips.
image.node.textContent = '';
var tooltip = $('<table></table>', {
class: 'pgadmin-tooltip-table',
}).appendTo(toolTipContainer);
var tooltipTable = $(`
<table class='pgadmin-tooltip-table table table-bordered table-noouter-border table-bottom-border table-hover'>
<tbody></tbody>
</table>`
).appendTo(toolTipBody);
var tooltip = tooltipTable.find('tbody');
_.each(image_data, function(value, key) {
if (key !== 'image' && key !== 'Plans' &&
key !== 'level' && key !== 'image' &&
@ -987,42 +986,8 @@ define('pgadmin.misc.explain', [
`);
}
});
var zoomFactor = graphContainer.data('zoom-factor');
// Calculate co-ordinates for tooltip
var toolTipX = ((currentXpos + pWIDTH) * zoomFactor - graphContainer.scrollLeft());
var toolTipY = ((currentYpos) * zoomFactor - graphContainer.scrollTop());
toolTipX = toolTipX < 0 ? 0 : (toolTipX);
toolTipY = toolTipY < 0 ? 0 : (toolTipY);
toolTipX = toolTipX > graphContainer.width() - toolTipContainer[0].clientWidth ? toolTipX - (toolTipContainer[0].clientWidth+(pWIDTH* zoomFactor)) : toolTipX;
toolTipY = toolTipY > graphContainer.height() - toolTipContainer[0].clientHeight ? graphContainer.height() - toolTipContainer[0].clientHeight : toolTipY;
// Show toolTip at respective x,y coordinates
toolTipContainer.css({
'opacity': '0.8',
});
toolTipContainer.css('left', toolTipX);
toolTipContainer.css('top', toolTipY);
$('.pgadmin-explain-tooltip').css('padding', '5px');
$('.pgadmin-explain-tooltip').css('border', '1px solid white');
});
// Remove tooltip when mouse is out from node's area
image.mouseout(() => {
/* Append the title again which we have removed on mouse over event, so
* that our custom tooltip should be visible.
*/
image.append(Snap.parse(title));
toolTipContainer.empty();
toolTipContainer.css({
'opacity': '0',
});
toolTipContainer.css('left', 0);
toolTipContainer.css('top', 0);
toolTipContainer.removeClass('d-none');
toolTipBody.scrollTop(0);
});
},
});
@ -1362,16 +1327,23 @@ define('pgadmin.misc.explain', [
// Main div to be drawn all images on
var planDiv = $('<div></div>', {
class: 'pgadmin-explain-container w-100 h-100 overflow-auto',
}).appendTo(graphicalContainer),
// Div to draw tool-tip on
toolTip = $('<div></div>', {
id: 'toolTip',
class: 'pgadmin-explain-tooltip',
}).appendTo(graphicalContainer);
toolTip.empty();
class: 'pgadmin-explain-container w-100 h-100 overflow-auto',
}).appendTo(graphicalContainer);
planDiv.data('zoom-factor', curr_zoom_factor);
var explainDetails = $(
`<div class="pgadmin-explain-details card d-none" data-bs-backdrop="false" tabindex="-1" aria-hidden="true">
<div class="card-header details-header d-flex">
<div class="details-title my-auto"></div>
<div class="ml-auto"><button class="btn btn-sm fa fa-times ml-auto details-close"/></div>
</div>
<div class="card-body details-body"></div>
</div>`
).appendTo(graphicalContainer);
explainDetails.find('.details-close').on('click', ()=>{
explainDetails.addClass('d-none');
});
var w = 0,
h = yMargin;
@ -1420,7 +1392,7 @@ define('pgadmin.misc.explain', [
var s = Snap(w, h),
$svg = $(s.node).detach();
planDiv.append($svg);
main_plan.draw(s, w - xMargin, yMargin, planDiv, toolTip, ctx);
main_plan.draw(s, w - xMargin, yMargin, planDiv, explainDetails, ctx);
var initPanelWidth = planDiv.width();

View File

@ -30,12 +30,15 @@ let StatisticsModel = Backbone.Model.extend({
$('.pg-explain-stats-area').removeClass('d-none');
}
var tooltip = $('<table></table>', {
class: 'pgadmin-tooltip-table',
});
var tooltipTable = $(`
<table class='pgadmin-tooltip-table table table-bordered table-noouter-border table-bottom-border table-hover'>
<tbody></tbody>
</table>`
);
var tooltip = tooltipTable.find('tbody');
if (Object.keys(jit_stats).length > 0){
tooltip.append('<tr><td class="label explain-tooltip">' + gettext('JIT:') + '</td></tr>');
tooltip.append('<tr><td colspan="2" class="label explain-tooltip">' + gettext('JIT:') + '</td></tr>');
_.each(jit_stats, function(value, key) {
key = _.escape(key);
value = _.escape(value);
@ -49,7 +52,7 @@ let StatisticsModel = Backbone.Model.extend({
}
if (Object.keys(triggers_stats).length > 0){
tooltip.append('<tr><td class="label explain-tooltip">' + gettext('Triggers:') + '</td></tr>');
tooltip.append('<tr><td colspan="2" class="label explain-tooltip">' + gettext('Triggers:') + '</td></tr>');
_.each(triggers_stats, function(triggers, key_id) {
if (triggers instanceof Object) {
_.each(triggers, function(value, key) {
@ -88,7 +91,7 @@ let StatisticsModel = Backbone.Model.extend({
}
if (Object.keys(summary).length > 0){
tooltip.append('<tr><td class="label explain-tooltip">' + gettext('Summary:') + '</td></tr>');
tooltip.append('<tr><td colspan="2" class="label explain-tooltip">' + gettext('Summary:') + '</td></tr>');
_.each(summary, function(value, key) {
key = _.escape(key);
value = _.escape(value);
@ -101,7 +104,7 @@ let StatisticsModel = Backbone.Model.extend({
});
}
$('.pg-explain-stats-area').off('mouseover').on('mouseover', () => {
$('.pg-explain-stats-area').off('click').on('click', () => {
// Empty the tooltip content if it has any and add new data
if (Object.keys(jit_stats).length == 0 &&
@ -110,30 +113,15 @@ let StatisticsModel = Backbone.Model.extend({
return;
}
toolTipContainer.empty();
toolTipContainer.append(tooltip);
let toolTipBody = toolTipContainer.find('.details-body');
let toolTipTitle = toolTipContainer.find('.details-title');
toolTipTitle.text('Statistics');
// Show toolTip at respective x,y coordinates
toolTipContainer.css({
'opacity': '0.8',
'left': '',
'right': '65px',
'top': '15px',
});
toolTipBody.empty();
toolTipBody.append(tooltipTable);
$('.pgadmin-explain-tooltip').css('padding', '5px');
$('.pgadmin-explain-tooltip').css('border', '1px solid white');
});
// Remove tooltip when mouse is out from node's area
$('.pg-explain-stats-area').off('mouseout').on('mouseout', () => {
toolTipContainer.empty();
toolTipContainer.css({
'opacity': '0',
'left': 0,
'top': 0,
'right': '',
});
toolTipContainer.removeClass('d-none');
toolTipBody.scrollTop(0);
});
},
});

View File

@ -1,12 +1,29 @@
.pgadmin-explain-tooltip {
.pgadmin-explain-details {
min-width: 200px;
max-width: 300px;
position: absolute;
opacity: 0;
color: $popover-body-color;
background-color: $popover-bg;
top: 0.25rem;
bottom: 0.25rem;
right: 0.25rem;
border-color: $popover-border-color;
box-shadow: $popover-box-shadow;
word-break: break-all;
display: flex;
flex-direction: column;
z-index: 99;
.details-header {
padding: 0.25rem;
font-weight: unset;
}
.details-body {
overflow: auto;
flex-grow: 1;
}
}
.sql-editor-explain {
.backform-tab {
.tab-content {

View File

@ -245,6 +245,7 @@ legend {
.table {
margin-bottom: 0rem;
user-select: text;
th {
padding: $table-header-cell-padding $table-cell-padding;
}

View File

@ -18,10 +18,14 @@ describe('ExplainStatistics', () => {
beforeEach(function() {
statsModel = new StatisticsModel();
statsDiv = '<div class="pg-explain-stats-area btn-group d-none"></div>';
tooltipContainer = $('<div></div>', {
id: 'toolTip',
class: 'pgadmin-explain-tooltip',
});
tooltipContainer = $(
`<div class="pgadmin-explain-details d-none">
<div class="details-header">
<div class="details-title"></div>
</div>
<div class="details-body"></div>
</div>`
);
});
describe('No Statistics', () => {
@ -49,20 +53,12 @@ describe('ExplainStatistics', () => {
expect($('.pg-explain-stats-area').hasClass('d-none')).toEqual(false);
});
it('Mouse over event should be trigger', () => {
it('Mouse click event should be trigger', () => {
// Trigger mouse over event
var hoverEvent = new $.Event('mouseover');
$('.pg-explain-stats-area').trigger(hoverEvent);
var clickEvent = new $.Event('click');
$('.pg-explain-stats-area').trigger(clickEvent);
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');
expect(tooltipContainer.hasClass('d-none')).toBe(false);
});
});
@ -78,20 +74,12 @@ describe('ExplainStatistics', () => {
expect($('.pg-explain-stats-area').hasClass('d-none')).toEqual(false);
});
it('Mouse over event should be trigger', () => {
it('Mouse click event should be trigger', () => {
// Trigger mouse over event
var hoverEvent = new $.Event('mouseover');
$('.pg-explain-stats-area').trigger(hoverEvent);
var clickEvent = new $.Event('click');
$('.pg-explain-stats-area').trigger(clickEvent);
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');
expect(tooltipContainer.hasClass('d-none')).toBe(false);
});
});
@ -111,20 +99,12 @@ describe('ExplainStatistics', () => {
expect($('.pg-explain-stats-area').hasClass('d-none')).toEqual(false);
});
it('Mouse over event should be trigger', () => {
it('Mouse click event should be trigger', () => {
// Trigger mouse over event
var hoverEvent = new $.Event('mouseover');
$('.pg-explain-stats-area').trigger(hoverEvent);
var clickEvent = new $.Event('click');
$('.pg-explain-stats-area').trigger(clickEvent);
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');
expect(tooltipContainer.hasClass('d-none')).toBe(false);
});
});
});