Fixed following issues for query tool after react porting:

1) Add New Server Connection > Server options keep loading(For empty Server group).
 2) After clicking indent/Unindent(for all operations) for large query option left as it is till operation completes
 3) Check sign beside options in Execute Option/Copy Header is little bit big
 4) In explain > Analysis tab does not show ROWS column
 5) In explain > Explain > analysis previous explain output is NOT cleared. New rows are appended. Same applies to the statistics tab.
 6) Update new query tool connection tool tip. Fixes #7289
 7) Explain-Analyze > Loops column is empty.
 8) Explain-Analyze with Verbose & Costs > in ROW X columns upward arrows are missing.
 9) Explain-Analyze with all option checked > background colors are missing for timing.
 10) Explain-Analyze > Additional bullet is added before Hash Cond.
 11) Browser Tree > Filtered rows icon is not working.
 12) Create table with timestamp and default value as function now() > Add new row > Enter mandatory columns except column where default value is function(now()) > Click Save > New row added but column with default value has value [default]. not updated to actual value. / Default values are not considered for any column while adding a new entry.
 13) Disable execute options in View/Edit data.
 14) The Boolean column always shows null.
 15) In Query history Remove & Remove all buttons are stuck to each other.
 16) On Remove all, the right panel is empty.
 17) Create a column with boolean[]/ text[], Try to add a new entry from data grid, enter “” quotes > Click Ok > Now try edit cell > You can not change value.
 18) In query history - Select queries are suffixed by ’Save Data’ icon
 19) Edit any table with PK > Try to insert duplicate PK > Error thrown > Correct pK value > Still old error shown > Not able to add new entry (This works when focus is moved from edited cell)
 20) Clicking arrows after opening dropdown options, does not collapse dropdown.

refs #6131
This commit is contained in:
Aditya Toshniwal
2022-04-18 12:50:51 +05:30
committed by Akshay Joshi
parent 74e3f976c1
commit 9c30d983bd
22 changed files with 343 additions and 185 deletions

View File

@@ -24,16 +24,19 @@ const useStyles = makeStyles((theme)=>({
borderBottom: '2px dashed '+theme.palette.primary.main,
},
level2: {
backgroundColor: '#fff',
color: '#000',
backgroundColor: theme.otherVars.explain.sev2.bg,
color: theme.otherVars.explain.sev2.color,
},
level3: {
backgroundColor: '#fff',
color: '#000',
backgroundColor: theme.otherVars.explain.sev3.bg,
color: theme.otherVars.explain.sev3.color,
},
level4: {
backgroundColor: '#fff',
color: '#000',
backgroundColor: theme.otherVars.explain.sev4.bg,
color: theme.otherVars.explain.sev4.color,
},
textRight: {
textAlign: 'right',
},
}));
@@ -43,15 +46,6 @@ function getRowClassname(data, collapseParent) {
if(data['Plans']?.length > 0) {
className.push(classes.collapsible);
}
if(!_.isEmpty(data['exclusive_flag'])) {
className.push(classes['level'+data['exclusive_flag']]);
}
if(!_.isEmpty(data['inclusive_flag'])) {
className.push(classes['level'+data['inclusive_flag']]);
}
if(!_.isEmpty(data['rowsx_flag'])) {
className.push(classes['level'+data['rowsx_flag']]);
}
if(collapseParent) {
className.push(classes.collapseParent);
}
@@ -61,10 +55,10 @@ function getRowClassname(data, collapseParent) {
function NodeText({displayText, extraInfo}) {
return (
<>
<ArrowRightAltIcon fontSize="small" /> {displayText}
{extraInfo?.length > 0 && <ul>
<ArrowRightAltIcon fontSize="small" style={{marginLeft: '-24px'}} /> {displayText}
{extraInfo?.length > 0 && <ul style={{fontSize: '13px'}}>
{extraInfo.map((item, i)=>{
return <li key={i}>{HTMLReactParse(item)}</li>;
return <li key={i} style={{opacity: '0.8'}}>{HTMLReactParse(item)}</li>;
})}
</ul>}
</>);
@@ -76,6 +70,7 @@ NodeText.propTypes = {
function ExplainRow({row, show, activeExId, setActiveExId, collapsedExId, toggleCollapseExId}) {
let data = row['data'];
const classes = useStyles();
const exId = `pga_ex_${data['level'].join('_')}`;
const parentExId = `pga_ex_${data['parent_node']}`;
const collapsed = collapsedExId.findIndex((v)=>parentExId.startsWith(v)) > -1;
@@ -94,29 +89,28 @@ function ExplainRow({row, show, activeExId, setActiveExId, collapsedExId, toggle
<td>
<FiberManualRecordIcon fontSize="small" style={{visibility: activeExId==parentExId ? 'visible' : 'hidden'}} />
</td>
<td>{data['_serial']}.</td>
<td className={classes.textRight}>{data['_serial']}.</td>
<td style={{paddingLeft: data['level'].length*30+'px'}} title={row['tooltip_text']}>
<NodeText displayText={row['display_text']} extraInfo={row['node_extra_info']} />
</td>
<td style={show.show_timings ? {} : {display: 'none'}}>
<td className={clsx(classes.textRight, classes['level'+data['exclusive_flag']])} style={show.show_timings ? {} : {display: 'none'}}>
{data['exclusive'] && (data['exclusive']+' ms')}
</td>
<td style={show.show_timings ? {} : {display: 'none'}}>
<td className={clsx(classes.textRight, classes['level'+data['inclusive_flag']])} style={show.show_timings ? {} : {display: 'none'}}>
{data['inclusive'] && (data['inclusive']+' ms')}
</td>
<td style={{display: 'none'}}>{!_.isUndefined(data['rowsx_flag'])
&& (data['rowsx_direction'] == 'positive' ? '&uarr;' : '&darr;')
}</td>
<td style={show.show_rowsx ? {} : {display: 'none'}}>
{data['rowsx']}
<td className={clsx(classes.textRight, classes['level'+data['rowsx_flag']])} style={show.show_rowsx ? {} : {display: 'none'}}>
{!_.isUndefined(data['rowsx_flag'])
&& (data['rowsx_direction'] == 'positive' ? <>&uarr;</> : <>&darr;</>)
}&nbsp;{data['rowsx']}
</td>
<td style={(show.show_rowsx || show.show_rows) ? {} : {display: 'none'}}>
<td className={classes.textRight} style={(show.show_rowsx || show.show_rows) ? {} : {display: 'none'}}>
{data['Actual Rows']}
</td>
<td style={(show.show_rowsx || show.show_plan_rows) ? {} : {display: 'none'}}>
<td className={classes.textRight} style={(show.show_rowsx || show.show_plan_rows) ? {} : {display: 'none'}}>
{data['Plan Rows']}
</td>
<td style={(show.show_rowsx || show.show_rows) ? {} : {display: 'none'}}>
<td className={classes.textRight} style={(show.show_rowsx || show.show_rows) ? {} : {display: 'none'}}>
{data['Actual Loops']}
</td>
</tr>
@@ -130,7 +124,9 @@ ExplainRow.propTypes = {
_serial: PropTypes.number,
parent_node: PropTypes.number,
exclusive: PropTypes.number,
exclusive_flag: PropTypes.string,
inclusive: PropTypes.number,
inclusive_flag: PropTypes.string,
rowsx_direction: PropTypes.string,
rowsx: PropTypes.number,
rowsx_flag: PropTypes.number,
@@ -178,7 +174,8 @@ export default function Analysis({explainTable}) {
<th colSpan="2" style={explainTable.show_timings ? {} : {display: 'none'}}>
<button disabled="">Timings</button>
</th>
<th style={(explainTable.show_rowsx || explainTable.show_rows) ? {} : {display: 'none'}} colSpan="3">
<th style={(explainTable.show_rowsx || explainTable.show_rows) ? {} : {display: 'none'}}
colSpan={(explainTable.show_rowsx) ? '3' : '1'}>
<button disabled="">Rows</button>
</th>
<th style={(explainTable.show_rowsx || explainTable.show_rows) ? {} : {display: 'none'}} rowSpan="2">

View File

@@ -250,6 +250,7 @@ function PlanContent({plan, pXpos, pYpos, ...props}) {
strokeWidth={1.2}
fill="gray"
fillOpacity={0.2}
pointerEvents="none"
/>
<tspan x={currentXpos + pWIDTH - (plan.width / 2) - xMargin}
y={currentYpos + pHEIGHT - (plan.height / 2) - yMargin}

View File

@@ -23,7 +23,7 @@ const useStyles = makeStyles((theme)=>({
tabPanel: {
padding: 0,
backgroundColor: theme.palette.background.default,
}
},
}));
// Some predefined constants used to calculate image location and its border
@@ -261,7 +261,11 @@ function parsePlan(data, ctx) {
totalCost = data['Total Cost'];
if (startCost != undefined && totalCost != undefined) {
arrowSize = Math.round(Math.log((startCost + totalCost) / 2 + startCost));
arrowSize = arrowSize < 1 ? 1 : arrowSize > 10 ? 10 : arrowSize;
if (arrowSize < 1) {
arrowSize = 1;
} else if (arrowSize > 10) {
arrowSize = 10;
}
}
data['arr_id'] = _.uniqueId('arr');
ctx.arrows[data['arr_id']] = arrowSize;
@@ -292,15 +296,21 @@ function parsePlan(data, ctx) {
if ('Actual Total Time' in data && 'Actual Loops' in data) {
data['inclusive'] = Math.ceil10(
data['Actual Total Time'] * data['Actual Loops'], -3
data['Actual Total Time'], -3
);
data['exclusive'] = data['inclusive'];
data['inclusive_factor'] = data['inclusive'] / (
data['total_time'] || data['Actual Total Time']
);
data['inclusive_flag'] = data['inclusive_factor'] <= 0.1 ? '1' :
data['inclusive_factor'] < 0.5 ? '2' :
data['inclusive_factor'] <= 0.9 ? '3' : '4';
if (data['inclusive_factor'] <= 0.1) {
data['inclusive_flag'] = '1';
} else if (data['inclusive_factor'] < 0.5) {
data['inclusive_flag'] = '2';
} else if (data['inclusive_factor'] <= 0.9) {
data['inclusive_flag'] = '3';
} else {
data['inclusive_flag'] = '4';
}
}
if ('Actual Rows' in data && 'Plan Rows' in data) {
@@ -316,10 +326,20 @@ function parsePlan(data, ctx) {
);
data['rowsx_direction'] = 'positive';
}
data['rowsx_flag'] = data['rowsx'] <= 10 ? '1' : (
data['rowsx'] <= 100 ? '2' : (data['rowsx'] <= 1000 ? '3' : '4')
);
data['rowsx'] = Math.ceil10(data['rowsx'], -2);
if (data['rowsx'] <= 10) {
data['rowsx_flag'] = '1';
} else if (data['rowsx'] <= 100 ) {
data['rowsx_flag'] = '2';
} else if (data['rowsx'] <= 1000 ) {
data['rowsx_flag'] = '3';
} else {
data['rowsx_flag'] = '4';
}
if('loops' in data) {
data['rowsx'] = Math.ceil10(data['rowsx'] / data['loops'] || 1, -2);
} else {
data['rowsx'] = Math.ceil10(data['rowsx'], -2);
}
}
// Start calculating xpos, ypos, width and height for child plans if any
@@ -337,6 +357,7 @@ function parsePlan(data, ctx) {
ypos: ypos,
total_time: data['total_time'] || data['Actual Total Time'],
parent_node: lvl.join('_'),
loops: data['Actual Loops']
}, ctx);
if (maxChildWidth < plan.width) {
@@ -344,7 +365,7 @@ function parsePlan(data, ctx) {
}
if ('exclusive' in data) {
if (plan.inclusive) {
if (plan.inclusive < data['exclusive']) {
data['exclusive'] -= plan.inclusive;
}
}
@@ -361,6 +382,11 @@ function parsePlan(data, ctx) {
plans.push(plan);
idx++;
});
} else{
if('loops' in data && 'exclusive' in data) {
data['inclusive'] = Math.ceil10(data['Actual Total Time'] / data['loops'] || 1, -3);
data['exclusive'] = data['inclusive'];
}
}
if ('exclusive' in data) {
@@ -368,9 +394,15 @@ function parsePlan(data, ctx) {
data['exclusive_factor'] = (
data['exclusive'] / (data['total_time'] || data['Actual Total Time'])
);
data['exclusive_flag'] = data['exclusive_factor'] <= 0.1 ? '1' :
data['exclusive_factor'] < 0.5 ? '2' :
data['exclusive_factor'] <= 0.9 ? '3' : '4';
if (data['exclusive_factor'] <= 0.1) {
data['exclusive_flag'] = '1';
} else if (data['exclusive_factor'] < 0.5) {
data['exclusive_flag'] = '2';
} else if (data['exclusive_factor'] <= 0.9) {
data['exclusive_flag'] = '3';
} else {
data['exclusive_flag'] = '4';
}
}
// Final Width and Height of current node
@@ -389,6 +421,7 @@ function parsePlanData(data, ctx) {
...data['Plan'],
xpos: 0,
ypos: 0,
loops: 1,
}, ctx);
retPlan['Plan'] = plan;
retPlan['xpos'] = 0;
@@ -431,20 +464,23 @@ export default function Explain({plans=[]}) {
const classes = useStyles();
const [tabValue, setTabValue] = React.useState(0);
let ctx = React.useRef({
totalNodes: 0,
totalDownloadedNodes: 0,
isDownloaded: 0,
explainTable: {
rows: [],
statistics: {
tables: {},
nodes: {},
let ctx = React.useRef({});
let planData = React.useMemo(()=>{
ctx.current = {
totalNodes: 0,
totalDownloadedNodes: 0,
isDownloaded: 0,
explainTable: {
rows: [],
statistics: {
tables: {},
nodes: {},
},
},
},
arrows: {},
});
let planData = React.useMemo(()=>(plans && parsePlanData(plans[0], ctx.current)), [plans]);
arrows: {},
};
return plans && parsePlanData(plans[0], ctx.current);
}, [plans]);
if(_.isEmpty(plans)) {
return <Box height="100%" display="flex" flexDirection="column">

View File

@@ -19,6 +19,7 @@ import CustomPropTypes from '../custom_prop_types';
import getStandardTheme from './standard';
import getDarkTheme from './dark';
import getHightContrastTheme from './high_contrast';
import { CssBaseline } from '@material-ui/core';
/* Common settings across all themes */
let basicSettings = createMuiTheme();
@@ -278,6 +279,19 @@ function getFinalTheme(baseTheme) {
return createMuiTheme({
mixins: mixins,
overrides: {
MuiCssBaseline: {
'@global': {
ul: {
margin: 0,
padding: 0,
},
li: {
listStyle: 'none',
margin: 0,
padding: 0,
}
},
},
MuiOutlinedInput: {
root: {
'&.Mui-disabled .MuiOutlinedInput-notchedOutline': {
@@ -535,6 +549,7 @@ export default function Theme(props) {
}, []);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{props.children}
</ThemeProvider>
);
@@ -561,6 +576,7 @@ export const commonTableStyles = makeStyles((theme)=>({
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
userSelect: 'text',
maxWidth: '250px',
'&:first-of-type':{
borderLeft: 'none',
},

View File

@@ -105,6 +105,20 @@ export default function(basicSettings) {
qtDatagridSelectFg: '#222',
cardHeaderBg: '#fff',
emptySpaceBg: '#ebeef3',
explain: {
sev2: {
color: '#222222',
bg: '#FFEE88',
},
sev3: {
color: '#FFFFFF',
bg: '#EE8800'
},
sev4: {
color: '#FFFFFF',
bg: '#880000'
},
}
}
});
}

View File

@@ -405,6 +405,22 @@ export default function CodeMirror({currEditor, name, value, options, events, re
}
Object.keys(events||{}).forEach((eventName)=>{
if(eventName === 'change') {
let timeoutId;
const change = (...args)=>{
/* In case of indent, change is triggered for each line */
/* This can be avoided and taking only the latest */
if(timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(()=>{
events[eventName](...args);
timeoutId = null;
}, 0);
};
editor.current.on(eventName, change);
return;
}
editor.current.on(eventName, events[eventName]);
});
editor.current.on('drop', handleDrop);

View File

@@ -1,5 +1,6 @@
import React from 'react';
import QueryToolSvg from '../../img/fonticon/query_tool.svg?svgr';
import ViewDataSvg from '../../img/fonticon/view_data.svg?svgr';
import SaveDataSvg from '../../img/fonticon/save_data_changes.svg?svgr';
import PasteSvg from '../../img/content_paste.svg?svgr';
import FilterSvg from '../../img/filter_alt_black.svg?svgr';
@@ -22,16 +23,44 @@ ExternalIcon.propTypes = {
Icon: PropTypes.elementType.isRequired,
};
export const QueryToolIcon = ()=><ExternalIcon Icon={QueryToolSvg} style={{height: '1rem'}} />;
export const SaveDataIcon = ()=><ExternalIcon Icon={SaveDataSvg} style={{height: '1rem'}} />;
export const PasteIcon = ()=><ExternalIcon Icon={PasteSvg} />;
export const FilterIcon = ()=><ExternalIcon Icon={FilterSvg} />;
export const CommitIcon = ()=><ExternalIcon Icon={CommitSvg} />;
export const RollbackIcon = ()=><ExternalIcon Icon={RollbackSvg} />;
export const ClearIcon = ()=><ExternalIcon Icon={ClearSvg} />;
export const ConnectedIcon = ()=><ExternalIcon Icon={ConnectedSvg} style={{height: '1rem'}} />;
export const DisonnectedIcon = ()=><ExternalIcon Icon={DisconnectedSvg} style={{height: '1rem'}} />;
export const RegexIcon = ()=><ExternalIcon Icon={RegexSvg} />;
export const FormatCaseIcon = ()=><ExternalIcon Icon={FormatCaseSvg} />;
export const ExpandDialogIcon = ()=><ExternalIcon Icon={Expand} style={{height: '1.2rem'}} />;
export const MinimizeDialogIcon = ()=><ExternalIcon Icon={Collapse} style={{height: '1.4rem'}} />;
export const QueryToolIcon = ({style})=><ExternalIcon Icon={QueryToolSvg} style={{height: '1rem', ...style}} />;
QueryToolIcon.propTypes = {style: PropTypes.object};
export const ViewDataIcon = ({style})=><ExternalIcon Icon={ViewDataSvg} style={{height: '0.8rem', ...style}} />;
ViewDataIcon.propTypes = {style: PropTypes.object};
export const SaveDataIcon = ({style})=><ExternalIcon Icon={SaveDataSvg} style={{height: '1rem', ...style}} />;
SaveDataIcon.propTypes = {style: PropTypes.object};
export const PasteIcon = ({style})=><ExternalIcon Icon={PasteSvg} style={style} />;
PasteIcon.propTypes = {style: PropTypes.object};
export const FilterIcon = ({style})=><ExternalIcon Icon={FilterSvg} style={style} />;
FilterIcon.propTypes = {style: PropTypes.object};
export const CommitIcon = ({style})=><ExternalIcon Icon={CommitSvg} style={style} />;
CommitIcon.propTypes = {style: PropTypes.object};
export const RollbackIcon = ({style})=><ExternalIcon Icon={RollbackSvg} style={style} />;
RollbackIcon.propTypes = {style: PropTypes.object};
export const ClearIcon = ({style})=><ExternalIcon Icon={ClearSvg} style={style} />;
ClearIcon.propTypes = {style: PropTypes.object};
export const ConnectedIcon = ({style})=><ExternalIcon Icon={ConnectedSvg} style={{height: '1rem', ...style}} />;
ConnectedIcon.propTypes = {style: PropTypes.object};
export const DisonnectedIcon = ({style})=><ExternalIcon Icon={DisconnectedSvg} style={{height: '1rem', ...style}} />;
DisonnectedIcon.propTypes = {style: PropTypes.object};
export const RegexIcon = ({style})=><ExternalIcon Icon={RegexSvg} style={style} />;
RegexIcon.propTypes = {style: PropTypes.object};
export const FormatCaseIcon = ({style})=><ExternalIcon Icon={FormatCaseSvg} style={style} />;
FormatCaseIcon.propTypes = {style: PropTypes.object};
export const ExpandDialogIcon = ({style})=><ExternalIcon Icon={Expand} style={{height: '1.2rem', ...style}} />;QueryToolIcon.propTypes = {style: PropTypes.object};
ExpandDialogIcon.propTypes = {style: PropTypes.object};
export const MinimizeDialogIcon = ({style})=><ExternalIcon Icon={Collapse} style={{height: '1.4rem', ...style}} />;
MinimizeDialogIcon.propTypes = {style: PropTypes.object};

View File

@@ -1,5 +1,5 @@
import { makeStyles } from '@material-ui/styles';
import React from 'react';
import React, { useRef } from 'react';
import CheckIcon from '@material-ui/icons/Check';
import PropTypes from 'prop-types';
@@ -34,6 +34,9 @@ const useStyles = makeStyles((theme)=>({
color: theme.palette.primary.contrastText,
}
},
checkIcon: {
width: '1.3rem',
},
hideCheck: {
visibility: 'hidden',
},
@@ -70,7 +73,7 @@ export const PgMenuItem = applyStatics(MenuItem)(({hasCheck=false, checked=false
};
}
return <MenuItem {...props} onClick={onClick} className={classes.menuItem}>
{hasCheck && <CheckIcon style={checked ? {} : {visibility: 'hidden'}}/>}
{hasCheck && <CheckIcon className={classes.checkIcon} style={checked ? {} : {visibility: 'hidden'}} />}
{children}
{(shortcut || accesskey) && <div className={classes.shortcut}>({shortcutToString(shortcut, accesskey)})</div>}
</MenuItem>;
@@ -84,3 +87,32 @@ PgMenuItem.propTypes = {
children: CustomPropTypes.children,
onClick: PropTypes.func,
};
export function usePgMenuGroup() {
const [openMenuName, setOpenMenuName] = React.useState(null);
const prevMenuOpenIdRef = useRef(null);
const toggleMenu = React.useCallback((e)=>{
setOpenMenuName(()=>{
return prevMenuOpenIdRef.current == e.currentTarget?.name ? null : e.currentTarget?.name;
});
prevMenuOpenIdRef.current = null;
}, []);
const handleMenuClose = React.useCallback(()=>{
/* We have no way here to know if the menu was closed using menu button or not
We will keep the last menu name ref for sometime so that the menu does not
open again if menu button is clicked to close the menu */
prevMenuOpenIdRef.current = openMenuName;
setTimeout(()=>{
prevMenuOpenIdRef.current = null;
}, 300);
setOpenMenuName(null);
}, [openMenuName]);
return {
openMenuName: openMenuName,
toggleMenu: toggleMenu,
onMenuClose: handleMenuClose,
};
}

View File

@@ -20,7 +20,7 @@ export default class EventBus {
if(e.event === event) {
return e.callback.toString()!=callback.toString();
}
return e.event!=event && e.callback.toString()!=callback.toString();
return true;
});
} else {
this._eventListeners = this._eventListeners.filter((e)=>e.event!=event);