mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Use uplot for Dashboard graphs to reduce CPU usage. #5794
This commit is contained in:
parent
f3bb4776e4
commit
4a3bcfa202
@ -150,6 +150,7 @@
|
||||
"react-draggable": "^4.4.4",
|
||||
"react-dropzone": "^14.2.1",
|
||||
"react-leaflet": "^3.2.2",
|
||||
"react-resize-detector": "^8.0.3",
|
||||
"react-rnd": "^10.3.5",
|
||||
"react-router-dom": "^6.2.2",
|
||||
"react-select": "^4.2.1",
|
||||
@ -164,6 +165,8 @@
|
||||
"styled-components": "^5.2.1",
|
||||
"tempusdominus-bootstrap-4": "^5.1.2",
|
||||
"tempusdominus-core": "^5.19.3",
|
||||
"uplot": "^1.6.24",
|
||||
"uplot-react": "^1.1.4",
|
||||
"valid-filename": "^2.0.1",
|
||||
"webcabin-docker": "git+https://github.com/pgadmin-org/wcdocker/#3df8aac825ee2892f4d824de273b779cc6dbcad8",
|
||||
"wkx": "^0.5.0",
|
||||
|
@ -175,9 +175,9 @@ define('pgadmin.browser.node', [
|
||||
// Show query tool only in context menu of supported nodes.
|
||||
if (_.indexOf(pgAdmin.unsupported_nodes, self.type) == -1) {
|
||||
let enable = function(itemData) {
|
||||
if (itemData._type == 'database' && itemData.allowConn)
|
||||
if (itemData?._type == 'database' && itemData?.allowConn)
|
||||
return true;
|
||||
else if (itemData._type != 'database')
|
||||
else if (itemData?._type != 'database')
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
|
@ -75,7 +75,7 @@ class DashboardModule(PgAdminModule):
|
||||
self.session_stats_refresh = self.dashboard_preference.register(
|
||||
'dashboards', 'session_stats_refresh',
|
||||
gettext("Session statistics refresh rate"), 'integer',
|
||||
1, min_val=1, max_val=999999,
|
||||
5, min_val=1, max_val=999999,
|
||||
category_label=PREF_LABEL_REFRESH_RATES,
|
||||
help_str=help_string
|
||||
)
|
||||
@ -83,7 +83,7 @@ class DashboardModule(PgAdminModule):
|
||||
self.tps_stats_refresh = self.dashboard_preference.register(
|
||||
'dashboards', 'tps_stats_refresh',
|
||||
gettext("Transaction throughput refresh rate"), 'integer',
|
||||
1, min_val=1, max_val=999999,
|
||||
5, min_val=1, max_val=999999,
|
||||
category_label=PREF_LABEL_REFRESH_RATES,
|
||||
help_str=help_string
|
||||
)
|
||||
@ -91,7 +91,7 @@ class DashboardModule(PgAdminModule):
|
||||
self.ti_stats_refresh = self.dashboard_preference.register(
|
||||
'dashboards', 'ti_stats_refresh',
|
||||
gettext("Tuples in refresh rate"), 'integer',
|
||||
1, min_val=1, max_val=999999,
|
||||
5, min_val=1, max_val=999999,
|
||||
category_label=PREF_LABEL_REFRESH_RATES,
|
||||
help_str=help_string
|
||||
)
|
||||
@ -99,7 +99,7 @@ class DashboardModule(PgAdminModule):
|
||||
self.to_stats_refresh = self.dashboard_preference.register(
|
||||
'dashboards', 'to_stats_refresh',
|
||||
gettext("Tuples out refresh rate"), 'integer',
|
||||
1, min_val=1, max_val=999999,
|
||||
5, min_val=1, max_val=999999,
|
||||
category_label=PREF_LABEL_REFRESH_RATES,
|
||||
help_str=help_string
|
||||
)
|
||||
@ -107,7 +107,7 @@ class DashboardModule(PgAdminModule):
|
||||
self.bio_stats_refresh = self.dashboard_preference.register(
|
||||
'dashboards', 'bio_stats_refresh',
|
||||
gettext("Block I/O statistics refresh rate"), 'integer',
|
||||
1, min_val=1, max_val=999999,
|
||||
5, min_val=1, max_val=999999,
|
||||
category_label=PREF_LABEL_REFRESH_RATES,
|
||||
help_str=help_string
|
||||
)
|
||||
|
@ -911,13 +911,20 @@ export function ChartContainer(props) {
|
||||
<div className="card-header">
|
||||
<div className="d-flex">
|
||||
<div id={props.id}>{props.title}</div>
|
||||
<div className="ml-auto my-auto legend" ref={props.legendRef}></div>
|
||||
<div className="ml-auto my-auto legend">
|
||||
<div className="d-flex">
|
||||
{props.datasets?.map((datum, i)=>(
|
||||
<div className="legend-value" key={i}>
|
||||
<span style={{backgroundColor: datum.borderColor}}> </span>
|
||||
<span className="legend-label">{datum.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body dashboard-graph-body">
|
||||
<div className={'chart-wrapper ' + (props.errorMsg ? 'd-none' : '')}>
|
||||
{props.children}
|
||||
</div>
|
||||
{!props.errorMsg && !props.isTest && props.children}
|
||||
<ChartError message={props.errorMsg} />
|
||||
</div>
|
||||
</div>
|
||||
@ -927,12 +934,10 @@ export function ChartContainer(props) {
|
||||
ChartContainer.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
legendRef: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.shape({ current: PropTypes.any }),
|
||||
]).isRequired,
|
||||
datasets: PropTypes.array.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
errorMsg: PropTypes.string,
|
||||
isTest: PropTypes.bool
|
||||
};
|
||||
|
||||
export function ChartError(props) {
|
||||
|
@ -6,8 +6,8 @@
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import React, { useEffect, useRef, useState, useReducer, useCallback, useMemo } from 'react';
|
||||
import { LineChart, DATA_POINT_STYLE, DATA_POINT_SIZE } from 'sources/chartjs';
|
||||
import React, { useEffect, useRef, useState, useReducer, useMemo } from 'react';
|
||||
import { DATA_POINT_SIZE } from 'sources/chartjs';
|
||||
import {ChartContainer, DashboardRowCol, DashboardRow} from './Dashboard';
|
||||
import url_for from 'sources/url_for';
|
||||
import axios from 'axios';
|
||||
@ -15,45 +15,28 @@ import gettext from 'sources/gettext';
|
||||
import {getGCD, getEpoch} from 'sources/utils';
|
||||
import {useInterval, usePrevious} from 'sources/custom_hooks';
|
||||
import PropTypes from 'prop-types';
|
||||
import StreamingChart from '../../../static/js/components/PgChart/StreamingChart';
|
||||
|
||||
export const X_AXIS_LENGTH = 75;
|
||||
|
||||
/* Transform the labels data to suit ChartJS */
|
||||
export function transformData(labels, refreshRate, use_diff_point_style) {
|
||||
export function transformData(labels, refreshRate) {
|
||||
const colors = ['#00BCD4', '#9CCC65', '#E64A19'];
|
||||
let datasets = Object.keys(labels).map((label, i)=>{
|
||||
return {
|
||||
label: label,
|
||||
data: labels[label] || [],
|
||||
borderColor: colors[i],
|
||||
backgroundColor: colors[i],
|
||||
pointHitRadius: DATA_POINT_SIZE,
|
||||
pointStyle: use_diff_point_style ? DATA_POINT_STYLE[i] : 'circle'
|
||||
};
|
||||
}) || [];
|
||||
|
||||
return {
|
||||
labels: [...Array(X_AXIS_LENGTH).keys()],
|
||||
datasets: datasets,
|
||||
refreshRate: refreshRate,
|
||||
};
|
||||
}
|
||||
|
||||
/* Custom ChartJS legend callback */
|
||||
export function generateLegend(chart) {
|
||||
let text = [];
|
||||
text.push('<div class="' + chart.id + '-legend d-flex">');
|
||||
for (let chart_val of chart.data.datasets) {
|
||||
text.push('<div class="legend-value"><span style="background-color:' + chart_val.backgroundColor + '"> </span>');
|
||||
if (chart_val.label) {
|
||||
text.push('<span class="legend-label">' + chart_val.label + '</span>');
|
||||
}
|
||||
text.push('</div>');
|
||||
}
|
||||
text.push('</div>');
|
||||
return text.join('');
|
||||
}
|
||||
|
||||
/* URL for fetching graphs data */
|
||||
export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
|
||||
let base_url = url_for('dashboard.dashboard_stats');
|
||||
@ -104,7 +87,7 @@ const chartsDefault = {
|
||||
'bio_stats': {'Reads': [], 'Hits': []},
|
||||
};
|
||||
|
||||
export default function Graphs({preferences, sid, did, pageVisible, enablePoll=true}) {
|
||||
export default function Graphs({preferences, sid, did, pageVisible, enablePoll=true, isTest}) {
|
||||
const refreshOn = useRef(null);
|
||||
const prevPrefernces = usePrevious(preferences);
|
||||
|
||||
@ -238,6 +221,7 @@ export default function Graphs({preferences, sid, did, pageVisible, enablePoll=t
|
||||
showDataPoints={preferences['graph_data_points']}
|
||||
lineBorderWidth={preferences['graph_line_border_width']}
|
||||
isDatabase={did > 0}
|
||||
isTest={isTest}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
@ -256,92 +240,45 @@ Graphs.propTypes = {
|
||||
]),
|
||||
pageVisible: PropTypes.bool,
|
||||
enablePoll: PropTypes.bool,
|
||||
isTest: PropTypes.bool,
|
||||
};
|
||||
|
||||
export function GraphsWrapper(props) {
|
||||
const sessionStatsLegendRef = useRef();
|
||||
const tpsStatsLegendRef = useRef();
|
||||
const tiStatsLegendRef = useRef();
|
||||
const toStatsLegendRef = useRef();
|
||||
const bioStatsLegendRef = useRef();
|
||||
const options = useMemo(()=>({
|
||||
elements: {
|
||||
point: {
|
||||
radius: props.showDataPoints ? DATA_POINT_SIZE : 0,
|
||||
},
|
||||
line: {
|
||||
borderWidth: props.lineBorderWidth,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: props.showTooltip,
|
||||
callbacks: {
|
||||
title: function(tooltipItem) {
|
||||
let title = '';
|
||||
try {
|
||||
title = parseInt(tooltipItem[0].label) * tooltipItem[0].chart?.data.refreshRate + gettext(' seconds ago');
|
||||
} catch (error) {
|
||||
title = '';
|
||||
}
|
||||
return title;
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
reverse: true,
|
||||
},
|
||||
y: {
|
||||
min: 0,
|
||||
}
|
||||
},
|
||||
showDataPoints: props.showDataPoints,
|
||||
showTooltip: props.showTooltip,
|
||||
lineBorderWidth: props.lineBorderWidth,
|
||||
}), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
|
||||
const updateOptions = useMemo(()=>({duration: 0}), []);
|
||||
|
||||
const onInitCallback = useCallback(
|
||||
(legendRef)=>(chart)=>{
|
||||
legendRef.current.innerHTML = generateLegend(chart);
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DashboardRow>
|
||||
<DashboardRowCol breakpoint='md' parts={6}>
|
||||
<ChartContainer id='sessions-graph' title={props.isDatabase ? gettext('Database sessions') : gettext('Server sessions')} legendRef={sessionStatsLegendRef} errorMsg={props.errorMsg}>
|
||||
<LineChart options={options} data={props.sessionStats} updateOptions={updateOptions}
|
||||
onInit={onInitCallback(sessionStatsLegendRef)}/>
|
||||
<ChartContainer id='sessions-graph' title={props.isDatabase ? gettext('Database sessions') : gettext('Server sessions')}
|
||||
datasets={props.sessionStats.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
|
||||
<StreamingChart data={props.sessionStats} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
|
||||
</ChartContainer>
|
||||
</DashboardRowCol>
|
||||
<DashboardRowCol breakpoint='md' parts={6}>
|
||||
<ChartContainer id='tps-graph' title={gettext('Transactions per second')} legendRef={tpsStatsLegendRef} errorMsg={props.errorMsg}>
|
||||
<LineChart options={options} data={props.tpsStats} updateOptions={updateOptions}
|
||||
onInit={onInitCallback(tpsStatsLegendRef)}/>
|
||||
<ChartContainer id='tps-graph' title={gettext('Transactions per second')} datasets={props.tpsStats.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
|
||||
<StreamingChart data={props.tpsStats} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
|
||||
</ChartContainer>
|
||||
</DashboardRowCol>
|
||||
</DashboardRow>
|
||||
<DashboardRow>
|
||||
<DashboardRowCol breakpoint='md' parts={4}>
|
||||
<ChartContainer id='ti-graph' title={gettext('Tuples in')} legendRef={tiStatsLegendRef} errorMsg={props.errorMsg}>
|
||||
<LineChart options={options} data={props.tiStats} updateOptions={updateOptions}
|
||||
onInit={onInitCallback(tiStatsLegendRef)}/>
|
||||
<ChartContainer id='ti-graph' title={gettext('Tuples in')} datasets={props.tiStats.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
|
||||
<StreamingChart data={props.tiStats} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
|
||||
</ChartContainer>
|
||||
</DashboardRowCol>
|
||||
<DashboardRowCol breakpoint='md' parts={4}>
|
||||
<ChartContainer id='to-graph' title={gettext('Tuples out')} legendRef={toStatsLegendRef} errorMsg={props.errorMsg}>
|
||||
<LineChart options={options} data={props.toStats} updateOptions={updateOptions}
|
||||
onInit={onInitCallback(toStatsLegendRef)}/>
|
||||
<ChartContainer id='to-graph' title={gettext('Tuples out')} datasets={props.toStats.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
|
||||
<StreamingChart data={props.toStats} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
|
||||
</ChartContainer>
|
||||
</DashboardRowCol>
|
||||
<DashboardRowCol breakpoint='md' parts={4}>
|
||||
<ChartContainer id='bio-graph' title={gettext('Block I/O')} legendRef={bioStatsLegendRef} errorMsg={props.errorMsg}>
|
||||
<LineChart options={options} data={props.bioStats} updateOptions={updateOptions}
|
||||
onInit={onInitCallback(bioStatsLegendRef)}/>
|
||||
<ChartContainer id='bio-graph' title={gettext('Block I/O')} datasets={props.bioStats.datasets} errorMsg={props.errorMsg} isTest={props.isTest}>
|
||||
<StreamingChart data={props.bioStats} dataPointSize={DATA_POINT_SIZE} xRange={X_AXIS_LENGTH} options={options} />
|
||||
</ChartContainer>
|
||||
</DashboardRowCol>
|
||||
</DashboardRow>
|
||||
@ -350,7 +287,6 @@ export function GraphsWrapper(props) {
|
||||
}
|
||||
|
||||
const propTypeStats = PropTypes.shape({
|
||||
labels: PropTypes.array.isRequired,
|
||||
datasets: PropTypes.array,
|
||||
refreshRate: PropTypes.number.isRequired,
|
||||
});
|
||||
@ -365,4 +301,5 @@ GraphsWrapper.propTypes = {
|
||||
showDataPoints: PropTypes.bool.isRequired,
|
||||
lineBorderWidth: PropTypes.number.isRequired,
|
||||
isDatabase: PropTypes.bool.isRequired,
|
||||
isTest: PropTypes.bool,
|
||||
};
|
||||
|
@ -17,3 +17,6 @@
|
||||
@import 'node_modules/react-checkbox-tree/lib/react-checkbox-tree.css';
|
||||
|
||||
@import 'node_modules/@simonwep/pickr/dist/themes/monolith.min.css';
|
||||
|
||||
@import 'node_modules/uplot/dist/uPlot.min.css';
|
||||
|
||||
|
@ -21,6 +21,7 @@ import getDarkTheme from './dark';
|
||||
import getHightContrastTheme from './high_contrast';
|
||||
import { CssBaseline } from '@material-ui/core';
|
||||
import pickrOverride from './overrides/pickr.override';
|
||||
import uplotOverride from './overrides/uplot.override';
|
||||
|
||||
/* Common settings across all themes */
|
||||
let basicSettings = createMuiTheme();
|
||||
@ -313,6 +314,7 @@ function getFinalTheme(baseTheme) {
|
||||
padding: 0,
|
||||
},
|
||||
...pickrOverride(baseTheme),
|
||||
...uplotOverride(baseTheme),
|
||||
},
|
||||
},
|
||||
MuiOutlinedInput: {
|
||||
|
32
web/pgadmin/static/js/Theme/overrides/uplot.override.js
Normal file
32
web/pgadmin/static/js/Theme/overrides/uplot.override.js
Normal file
@ -0,0 +1,32 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
export default function uplotOverride(theme) {
|
||||
return {
|
||||
'.uplot': {
|
||||
'& .u-legend': {
|
||||
display: 'none',
|
||||
}
|
||||
},
|
||||
'.uplot-tooltip': {
|
||||
position: 'absolute',
|
||||
fontSize: '0.9em',
|
||||
padding: '4px 8px',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
color: theme.palette.background.default,
|
||||
backgroundColor: theme.palette.text.primary,
|
||||
|
||||
'& .uplot-tooltip-label': {
|
||||
display: 'flex',
|
||||
gap: '4px',
|
||||
alignItems: 'center',
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
124
web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
Normal file
124
web/pgadmin/static/js/components/PgChart/StreamingChart.jsx
Normal file
@ -0,0 +1,124 @@
|
||||
import React, { useRef, useMemo } from 'react';
|
||||
import UplotReact from 'uplot-react';
|
||||
import { useResizeDetector } from 'react-resize-detector';
|
||||
import gettext from 'sources/gettext';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function tooltipPlugin(refreshRate) {
|
||||
let tooltipTopOffset = -20;
|
||||
let tooltipLeftOffset = 10;
|
||||
let tooltip;
|
||||
|
||||
function showTooltip() {
|
||||
if(!tooltip) {
|
||||
tooltip = document.createElement('div');
|
||||
tooltip.className = 'uplot-tooltip';
|
||||
tooltip.style.display = 'block';
|
||||
document.body.appendChild(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
function hideTooltip() {
|
||||
tooltip?.remove();
|
||||
tooltip = null;
|
||||
}
|
||||
|
||||
function setTooltip(u) {
|
||||
if(u.cursor.top <= 0) {
|
||||
hideTooltip();
|
||||
return;
|
||||
}
|
||||
showTooltip();
|
||||
let tooltipHtml=`<div>${(u.data[1].length-1-parseInt(u.legend.values[0]['_'])) * refreshRate + gettext(' seconds ago')}</div>`;
|
||||
for(let i=1; i<u.series.length; i++) {
|
||||
tooltipHtml += `<div class="uplot-tooltip-label"><div style="height:12px; width:12px; background-color:${u.series[i].stroke()}"></div> ${u.series[i].label}: ${u.legend.values[i]['_']}</div>`;
|
||||
}
|
||||
tooltip.innerHTML = tooltipHtml;
|
||||
|
||||
let overBBox = u.over.getBoundingClientRect();
|
||||
let tooltipBBox = tooltip.getBoundingClientRect();
|
||||
let left = (tooltipLeftOffset + u.cursor.left + overBBox.left);
|
||||
/* Should not outside the graph right */
|
||||
if((left+tooltipBBox.width) > overBBox.right) {
|
||||
left = left - tooltipBBox.width - tooltipLeftOffset*2;
|
||||
}
|
||||
tooltip.style.left = left + 'px';
|
||||
tooltip.style.top = (tooltipTopOffset + u.cursor.top + overBBox.top) + 'px';
|
||||
}
|
||||
|
||||
return {
|
||||
hooks: {
|
||||
setCursor: [
|
||||
u => {
|
||||
setTooltip(u);
|
||||
}
|
||||
],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default function StreamingChart({xRange=75, data, options}) {
|
||||
const chartRef = useRef();
|
||||
const { width, height, ref:containerRef } = useResizeDetector();
|
||||
const defaultOptions = useMemo(()=>({
|
||||
title: '',
|
||||
width: width,
|
||||
height: height,
|
||||
padding: [10, 0, 10, 0],
|
||||
focus: {
|
||||
alpha: 0.3,
|
||||
},
|
||||
cursor: {
|
||||
drag: {
|
||||
setScale: false,
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{},
|
||||
...data.datasets?.map((datum)=>({
|
||||
label: datum.label,
|
||||
stroke: datum.borderColor,
|
||||
width: options.lineBorderWidth ?? 1,
|
||||
points: { show: options.showDataPoints ?? false, size: datum.pointHitRadius*2 }
|
||||
}))
|
||||
],
|
||||
scales: {
|
||||
x: {
|
||||
time: false,
|
||||
}
|
||||
},
|
||||
axes: [
|
||||
{
|
||||
show: false,
|
||||
},
|
||||
],
|
||||
plugins: options.showTooltip ? [tooltipPlugin(data.refreshRate)] : [],
|
||||
}), [data.refreshRate, data?.datasets?.length, width, height, options]);
|
||||
|
||||
const initialState = [
|
||||
Array.from(new Array(xRange).keys()),
|
||||
...data.datasets?.map((d)=>{
|
||||
let ret = [...d.data];
|
||||
ret.reverse();
|
||||
return ret;
|
||||
}),
|
||||
];
|
||||
|
||||
chartRef.current?.setScale('x', {min: data.datasets[0]?.data?.length-xRange, max: data.datasets[0]?.data?.length-1});
|
||||
return (
|
||||
<div ref={containerRef} style={{width: '100%', height: '100%'}}>
|
||||
<UplotReact target={containerRef.current} options={defaultOptions} data={initialState} onCreate={(obj)=>chartRef.current=obj} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const propTypeData = PropTypes.shape({
|
||||
datasets: PropTypes.array,
|
||||
refreshRate: PropTypes.number.isRequired,
|
||||
});
|
||||
|
||||
StreamingChart.propTypes = {
|
||||
xRange: PropTypes.number.isRequired,
|
||||
data: propTypeData.isRequired,
|
||||
options: PropTypes.object,
|
||||
};
|
@ -4,27 +4,22 @@ import {mount} from 'enzyme';
|
||||
import '../helper/enzyme.helper';
|
||||
import { DATA_POINT_SIZE } from 'sources/chartjs';
|
||||
|
||||
import Graphs, {GraphsWrapper, X_AXIS_LENGTH, transformData,
|
||||
import Graphs, {GraphsWrapper, transformData,
|
||||
getStatsUrl, statsReducer} from '../../../pgadmin/dashboard/static/js/Graphs';
|
||||
|
||||
describe('Graphs.js', ()=>{
|
||||
it('transformData', ()=>{
|
||||
expect(transformData({'Label1': [], 'Label2': []}, 1, false)).toEqual({
|
||||
labels: [...Array(X_AXIS_LENGTH).keys()],
|
||||
datasets: [{
|
||||
label: 'Label1',
|
||||
data: [],
|
||||
borderColor: '#00BCD4',
|
||||
backgroundColor: '#00BCD4',
|
||||
pointHitRadius: DATA_POINT_SIZE,
|
||||
pointStyle: 'circle',
|
||||
},{
|
||||
label: 'Label2',
|
||||
data: [],
|
||||
borderColor: '#9CCC65',
|
||||
backgroundColor: '#9CCC65',
|
||||
pointHitRadius: DATA_POINT_SIZE,
|
||||
pointStyle: 'circle',
|
||||
}],
|
||||
refreshRate: 1,
|
||||
});
|
||||
@ -112,7 +107,7 @@ describe('Graphs.js', ()=>{
|
||||
graph_line_border_width: 2
|
||||
};
|
||||
|
||||
graphComp = mount(<Graphs preferences={dashboardPref} sid={sid} did={did} enablePoll={false} pageVisible={true} />);
|
||||
graphComp = mount(<Graphs preferences={dashboardPref} sid={sid} did={did} enablePoll={false} pageVisible={true} isTest={true} />);
|
||||
});
|
||||
|
||||
it('GraphsWrapper is rendered', (done)=>{
|
||||
|
@ -35,7 +35,8 @@ describe('<GraphsWrapper /> component', ()=>{
|
||||
showTooltip={true}
|
||||
showDataPoints={true}
|
||||
lineBorderWidth={2}
|
||||
isDatabase={false} />);
|
||||
isDatabase={false}
|
||||
isTest={true} />);
|
||||
});
|
||||
|
||||
it('graph containers are rendered', (done)=>{
|
||||
@ -61,24 +62,9 @@ describe('<GraphsWrapper /> component', ()=>{
|
||||
done();
|
||||
});
|
||||
|
||||
it('graph body has the canvas', (done)=>{
|
||||
let found = graphComp.find('.card.dashboard-graph .dashboard-graph-body canvas');
|
||||
expect(found.at(0).length).toBe(1);
|
||||
expect(found.at(1).length).toBe(1);
|
||||
expect(found.at(2).length).toBe(1);
|
||||
expect(found.at(3).length).toBe(1);
|
||||
expect(found.at(4).length).toBe(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('graph body shows the error', (done)=>{
|
||||
graphComp.setProps({errorMsg: 'Some error occurred'});
|
||||
let found = graphComp.find('.card.dashboard-graph .dashboard-graph-body .chart-wrapper');
|
||||
expect(found.at(0)).toHaveClassName('d-none');
|
||||
expect(found.at(1)).toHaveClassName('d-none');
|
||||
expect(found.at(2)).toHaveClassName('d-none');
|
||||
expect(found.at(3)).toHaveClassName('d-none');
|
||||
expect(found.at(4)).toHaveClassName('d-none');
|
||||
let found = graphComp.find('.card.dashboard-graph .dashboard-graph-body');
|
||||
|
||||
found = graphComp.find('.card.dashboard-graph .dashboard-graph-body .pg-panel-error.pg-panel-message');
|
||||
expect(found.at(0)).toIncludeText('Some error occurred');
|
||||
|
@ -259,7 +259,7 @@ describe('ERDCore', ()=>{
|
||||
return new FakeNode({}, `id-${data.name}`);
|
||||
});
|
||||
spyOn(erdCoreObj, 'addLink');
|
||||
spyOn(erdCoreObj, 'dagreDistributeNodes');
|
||||
spyOn(erdCoreObj, 'dagreDistributeNodes').and.callFake(()=>{/* intentionally empty */});
|
||||
|
||||
erdCoreObj.deserializeData(TEST_TABLES_DATA);
|
||||
expect(erdCoreObj.addNode).toHaveBeenCalledTimes(TEST_TABLES_DATA.length);
|
||||
|
@ -319,7 +319,7 @@ describe('ERDTool', ()=>{
|
||||
});
|
||||
|
||||
it('onAutoDistribute', ()=>{
|
||||
spyOn(bodyInstance.diagram, 'dagreDistributeNodes');
|
||||
spyOn(bodyInstance.diagram, 'dagreDistributeNodes').and.callFake(()=>{/* intentionally empty */});
|
||||
bodyInstance.onAutoDistribute();
|
||||
expect(bodyInstance.diagram.dagreDistributeNodes).toHaveBeenCalled();
|
||||
});
|
||||
|
@ -8609,6 +8609,13 @@ react-property@2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.0.tgz#2156ba9d85fa4741faf1918b38efc1eae3c6a136"
|
||||
integrity sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==
|
||||
|
||||
react-resize-detector@^8.0.3:
|
||||
version "8.0.3"
|
||||
resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-8.0.3.tgz#dab4470aae23bb07deb857230ccf945d000ef99b"
|
||||
integrity sha512-c3eqm5BVcluVhxHsBQnhyPO/5uYB3XHIHz6D1ZOHzU2WcnZF0Cr3KLl5OIozRC2RSsdQlu5vn1PHEqrvKRnIYA==
|
||||
dependencies:
|
||||
lodash "^4.17.21"
|
||||
|
||||
react-rnd@^10.3.5:
|
||||
version "10.3.7"
|
||||
resolved "https://registry.yarnpkg.com/react-rnd/-/react-rnd-10.3.7.tgz#037ce277e6c5e682989b51278e44a6ba299990af"
|
||||
@ -10192,6 +10199,16 @@ update-browserslist-db@^1.0.9:
|
||||
escalade "^3.1.1"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
uplot-react@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/uplot-react/-/uplot-react-1.1.4.tgz#02b9918a199da9983fc0d375fb44e443749e2ac0"
|
||||
integrity sha512-qO1UkQwjVKdj5vTm3O3yldvu1T6hwY4++rH4KznLhjqpnLdncq1zsRxq/zQz/HUHPVD0j7WBcEISbNM61JsuAQ==
|
||||
|
||||
uplot@^1.6.24:
|
||||
version "1.6.24"
|
||||
resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.6.24.tgz#dfa213fa7da92763261920ea972ed1a5f9f6af12"
|
||||
integrity sha512-WpH2BsrFrqxkMu+4XBvc0eCDsRBhzoq9crttYeSI0bfxpzR5YoSVzZXOKFVWcVC7sp/aDXrdDPbDZGCtck2PVg==
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
|
||||
|
Loading…
Reference in New Issue
Block a user