mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Improve the explain plan details by showing popup instead of tooltip on clicking of the specified node. Fixes #5488
This commit is contained in:
parent
3f089f31a3
commit
6589f82e77
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 |
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -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 {
|
||||
|
@ -245,6 +245,7 @@ legend {
|
||||
|
||||
.table {
|
||||
margin-bottom: 0rem;
|
||||
user-select: text;
|
||||
th {
|
||||
padding: $table-header-cell-padding $table-cell-padding;
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user