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

@@ -422,7 +422,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
eventBus.current.deregisterListener(e[0], e[1]);
});
};
}, [qtState]);
}, [qtState.params, qtState.current_file]);
useEffect(()=>{
/* Fire query change so that title changes to latest */
@@ -602,11 +602,12 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
onResetLayout={onResetLayout}
docker={docker.current}
/>
<MainToolBar
containerRef={containerRef}
onManageMacros={onManageMacros}
onFilterClick={onFilterClick}
/>
{React.useMemo(()=>(
<MainToolBar
containerRef={containerRef}
onManageMacros={onManageMacros}
onFilterClick={onFilterClick}
/>), [containerRef.current, onManageMacros, onFilterClick])}
<Layout
getLayoutInstance={(obj)=>docker.current=obj}
defaultLayout={defaultLayout}

View File

@@ -170,8 +170,8 @@ export function TextEditor({row, column, onRowChange, onClose}) {
}, []);
const onOK = ()=>{
if(column.is_array && !isValidArray(value)) {
console.error(gettext('Arrays must start with "{" and end with "}"'));
if(column.is_array && !isValidArray(localVal)) {
Notifier.error(gettext('Arrays must start with "{" and end with "}"'));
} else {
let columnVal = textColumnFinalVal(localVal, column);
onRowChange({ ...row, [column.key]: columnVal}, true);
@@ -205,14 +205,14 @@ TextEditor.propTypes = EditorPropTypes;
export function NumberEditor({row, column, onRowChange, onClose}) {
const classes = useStyles();
const value = row[column.key] ?? '';
const onBlur = ()=>{
const isValidData = ()=>{
if(!column.is_array && isNaN(value)){
Notifier.error(gettext('Please enter a valid number'));
return;
return false;
} else if(column.is_array) {
if(!isValidArray(value)) {
Notifier.error(gettext('Arrays must start with "{" and end with "}"'));
return;
return false;
}
let checkVal = value.trim().slice(1, -1);
if(checkVal == '') {
@@ -223,16 +223,25 @@ export function NumberEditor({row, column, onRowChange, onClose}) {
for (const val of checkVal) {
if(isNaN(val)) {
Notifier.error(gettext('Arrays must start with "{" and end with "}"'));
return;
return false;
}
}
}
onClose(column.can_edit ? true : false);
return true;
};
const onBlur = ()=>{
if(isValidData()) {
onClose(column.can_edit ? true : false);
return true;
}
return false;
};
const onKeyDown = (e)=>{
if(e.code == 'Tab') {
e.preventDefault();
onBlur();
if(!onBlur()) {
e.stopPropagation();
}
}
};
return (
@@ -245,7 +254,7 @@ export function NumberEditor({row, column, onRowChange, onClose}) {
onRowChange({ ...row, [column.key]: e.target.value });
}
}}
// onBlur={onBlur}
onBlur={onBlur}
onKeyDown={onKeyDown}
/>
);
@@ -269,7 +278,7 @@ export function CheckboxEditor({row, column, onRowChange, onClose}) {
};
const onBlur = ()=>{onClose(true);};
let className = 'checked';
if(!value) {
if(!value && value != null) {
className = 'unchecked';
} else if(value == null){
className = 'intermediate';
@@ -323,7 +332,7 @@ export function JsonTextEditor({row, column, onRowChange, onClose}) {
value={localVal}
options={{
onChange: onChange,
onError: (error)=>console.error('Invalid Json: ' + error.message.split(':')[0]),
onError: (error)=>Notifier.error('Invalid Json: ' + error.message.split(':')[0]),
}}
className={'jsoneditor-div'}
/>

View File

@@ -63,6 +63,9 @@ class NewConnectionSchema extends BaseUISchema {
.then(({data: respData})=>{
let groupedOptions = [];
_.forIn(respData.data.result.server_list, (v, k)=>{
if(v.length == 0) {
return;
}
/* initial selection */
_.find(v, (o)=>o.value==obj.params.sid).selected = true;
groupedOptions.push({

View File

@@ -106,15 +106,15 @@ export function ConnectionBar({connected, connecting, connectionStatus, connecti
>
<Tooltip title={queryToolCtx.params.is_query_tool ? '' : connTitle}>
<Box display="flex" width="100%">
<Box textOverflow="ellipsis" overflow="hidden" marginRight="auto">{connecting && '(Obtaining connection)'}{connTitle}</Box>
<Box textOverflow="ellipsis" overflow="hidden" marginRight="auto">{connecting && gettext('(Obtaining connection)')}{connTitle}</Box>
{queryToolCtx.params.is_query_tool && <Box><KeyboardArrowDownIcon /></Box>}
</Box>
</Tooltip>
</DefaultButton>
<PgIconButton title="New query tool" icon={<QueryToolIcon />} onClick={onNewQueryToolClick}/>
<PgIconButton title={gettext('New query tool for current connection')} icon={<QueryToolIcon />} onClick={onNewQueryToolClick}/>
</PgButtonGroup>
<PgButtonGroup size="small" variant="text" style={{marginLeft: 'auto'}}>
<PgIconButton title="Reset layout" icon={<RotateLeftRoundedIcon />} onClick={onResetLayout}/>
<PgIconButton title={gettext('Reset layout')} icon={<RotateLeftRoundedIcon />} onClick={onResetLayout}/>
</PgButtonGroup>
</Box>
<PgMenu

View File

@@ -94,7 +94,7 @@ function parseEwkbData(rows, column) {
// generate map info content
if (tooLargeDataSize || tooManyGeometries) {
infoList.push(supportedGeometries.length + ' of ' + rows.length + ' geometries rendered.');
infoList.push(gettext('%s of %s geometries rendered.', supportedGeometries.length, rows.length));
}
if (geometries3D.length > 0) {
infoList.push(gettext('3D geometries not rendered.'));
@@ -116,7 +116,7 @@ function parseData(rows, columns, column) {
'geoJSONs': [],
'selectedSRID': 0,
'getPopupContent': undefined,
'infoList': ['Empty row.'],
'infoList': [gettext('Empty row.')],
};
}
@@ -294,19 +294,19 @@ function TheMap({data}) {
<>
{data.selectedSRID === 4326 &&
<LayersControl position="topright">
<LayersControl.BaseLayer checked name="Empty">
<LayersControl.BaseLayer checked name={gettext('Empty')}>
<TileLayer
url=""
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer checked name="Street">
<LayersControl.BaseLayer checked name={gettext('Street')}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
maxZoom={19}
attribution='&copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>'
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name="Topography">
<LayersControl.BaseLayer name={gettext('Topography')}>
<TileLayer
url="https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png"
maxZoom={17}
@@ -317,7 +317,7 @@ function TheMap({data}) {
}
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name="Gray Style">
<LayersControl.BaseLayer name={gettext('Gray Style')}>
<TileLayer
url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}{r}.png"
maxZoom={19}
@@ -328,7 +328,7 @@ function TheMap({data}) {
subdomains='abcd'
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name="Light Color">
<LayersControl.BaseLayer name={gettext('Light Color')}>
<TileLayer
url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/rastertiles/voyager/{z}/{x}/{y}{r}.pn"
maxZoom={19}
@@ -339,7 +339,7 @@ function TheMap({data}) {
subdomains='abcd'
/>
</LayersControl.BaseLayer>
<LayersControl.BaseLayer name="Dark Matter">
<LayersControl.BaseLayer name={gettext('Dark Matter')}>
<TileLayer
url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}{r}.png"
maxZoom={19}

View File

@@ -23,7 +23,7 @@ import FormatListNumberedRoundedIcon from '@material-ui/icons/FormatListNumbered
import HelpIcon from '@material-ui/icons/HelpRounded';
import {QUERY_TOOL_EVENTS, CONNECTION_STATUS} from '../QueryToolConstants';
import { QueryToolConnectionContext, QueryToolContext, QueryToolEventsContext } from '../QueryToolComponent';
import { PgMenu, PgMenuDivider, PgMenuItem } from '../../../../../../static/js/components/Menu';
import { PgMenu, PgMenuDivider, PgMenuItem, usePgMenuGroup } from '../../../../../../static/js/components/Menu';
import gettext from 'sources/gettext';
import { useKeyboardShortcuts } from '../../../../../../static/js/custom_hooks';
import {shortcut_key} from 'sources/keyboard_shortcuts';
@@ -150,7 +150,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
'filter': true,
'limit': false,
});
const [menuOpenId, setMenuOpenId] = React.useState(null);
const {openMenuName, toggleMenu, onMenuClose} = usePgMenuGroup();
const [checkedMenuItems, setCheckedMenuItems] = React.useState({});
/* Menu button refs */
const saveAsMenuRef = React.useRef(null);
@@ -189,14 +189,6 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
explain(true);
}, [explain]);
const openMenu = useCallback((e)=>{
setMenuOpenId(e.currentTarget.name);
}, []);
const handleMenuClose = useCallback(()=>{
setMenuOpenId(null);
}, []);
const checkMenuClick = useCallback((e)=>{
setCheckedMenuItems((prev)=>{
let newVal = !prev[e.value];
@@ -453,21 +445,21 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
accesskey={shortcut_key(queryToolPref.btn_save_file)} disabled={buttonsDisabled['save'] || !queryToolCtx.params.is_query_tool}
onClick={()=>{saveFile(false);}} />
<PgIconButton title={gettext('File')} icon={<KeyboardArrowDownIcon />} splitButton disabled={!queryToolCtx.params.is_query_tool}
name="menu-saveas" ref={saveAsMenuRef} onClick={openMenu}
name="menu-saveas" ref={saveAsMenuRef} onClick={toggleMenu}
/>
</PgButtonGroup>
<PgButtonGroup size="small">
<PgIconButton title={gettext('Edit')} icon={
<><EditRoundedIcon /><KeyboardArrowDownIcon style={{marginLeft: '-10px'}} /></>}
disabled={!queryToolCtx.params.is_query_tool}
name="menu-edit" ref={editMenuRef} onClick={openMenu} />
name="menu-edit" ref={editMenuRef} onClick={toggleMenu} />
</PgButtonGroup>
<PgButtonGroup size="small" color={highlightFilter ? 'primary' : 'default'}>
<PgIconButton title={gettext('Sort/Filter')} icon={<FilterIcon />}
onClick={onFilterClick} disabled={buttonsDisabled['filter']} accesskey={shortcut_key(queryToolPref.btn_filter_dialog)}/>
<PgIconButton title={gettext('Filter options')} icon={<KeyboardArrowDownIcon />} splitButton
disabled={buttonsDisabled['filter']} name="menu-filter" ref={filterMenuRef} accesskey={shortcut_key(queryToolPref.btn_filter_options)}
onClick={openMenu} />
onClick={toggleMenu} />
</PgButtonGroup>
<InputSelectNonSearch options={[
{label: gettext('No limit'), value: '-1'},
@@ -482,7 +474,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
onClick={executeQuery} disabled={buttonsDisabled['execute']} shortcut={queryToolPref.execute_query}/>
<PgIconButton title={gettext('Execute options')} icon={<KeyboardArrowDownIcon />} splitButton
name="menu-autocommit" ref={autoCommitMenuRef} accesskey={shortcut_key(queryToolPref.btn_delete_row)}
onClick={openMenu} />
onClick={toggleMenu} disabled={!queryToolCtx.params.is_query_tool}/>
</PgButtonGroup>
<PgButtonGroup size="small">
<PgIconButton title={gettext('Explain')} icon={<ExplicitRoundedIcon />}
@@ -491,7 +483,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
onClick={()=>{explainAnalyse();}} disabled={buttonsDisabled['explain_analyse'] || !queryToolCtx.params.is_query_tool} shortcut={queryToolPref.explain_analyze_query}/>
<PgIconButton title={gettext('Explain Settings')} icon={<KeyboardArrowDownIcon />} splitButton
disabled={!queryToolCtx.params.is_query_tool}
name="menu-explain" ref={explainMenuRef} onClick={openMenu} />
name="menu-explain" ref={explainMenuRef} onClick={toggleMenu} />
</PgButtonGroup>
<PgButtonGroup size="small">
<PgIconButton title={gettext('Commit')} icon={<CommitIcon />}
@@ -502,7 +494,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
<PgButtonGroup size="small">
<PgIconButton title={gettext('Macros')} icon={
<><FormatListNumberedRoundedIcon /><KeyboardArrowDownIcon style={{marginLeft: '-10px'}} /></>}
disabled={!queryToolCtx.params.is_query_tool} name="menu-macros" ref={macrosMenuRef} onClick={openMenu} />
disabled={!queryToolCtx.params.is_query_tool} name="menu-macros" ref={macrosMenuRef} onClick={toggleMenu} />
</PgButtonGroup>
<PgButtonGroup size="small">
<PgIconButton title={gettext('Help')} icon={<HelpIcon />} onClick={onHelpClick} />
@@ -510,15 +502,15 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
</Box>
<PgMenu
anchorRef={saveAsMenuRef}
open={menuOpenId=='menu-saveas'}
onClose={handleMenuClose}
open={openMenuName=='menu-saveas'}
onClose={onMenuClose}
>
<PgMenuItem onClick={()=>{saveFile(true);}}>{gettext('Save as')}</PgMenuItem>
</PgMenu>
<PgMenu
anchorRef={editMenuRef}
open={menuOpenId=='menu-edit'}
onClose={handleMenuClose}
open={openMenuName=='menu-edit'}
onClose={onMenuClose}
>
<PgMenuItem shortcut={FIXED_PREF.find}
onClick={()=>{eventBus.fireEvent(QUERY_TOOL_EVENTS.EDITOR_FIND_REPLACE, false);}}>{gettext('Find')}</PgMenuItem>
@@ -540,8 +532,8 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
</PgMenu>
<PgMenu
anchorRef={filterMenuRef}
open={menuOpenId=='menu-filter'}
onClose={handleMenuClose}
open={openMenuName=='menu-filter'}
onClose={onMenuClose}
>
<PgMenuItem onClick={()=>{eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_INCLUDE_EXCLUDE_FILTER, true);}}>{gettext('Filter by Selection')}</PgMenuItem>
<PgMenuItem onClick={()=>{eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_INCLUDE_EXCLUDE_FILTER, false);}}>{gettext('Exclude by Selection')}</PgMenuItem>
@@ -549,8 +541,8 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
</PgMenu>
<PgMenu
anchorRef={autoCommitMenuRef}
open={menuOpenId=='menu-autocommit'}
onClose={handleMenuClose}
open={openMenuName=='menu-autocommit'}
onClose={onMenuClose}
>
<PgMenuItem hasCheck value="auto_commit" checked={checkedMenuItems['auto_commit']}
onClick={checkMenuClick}>{gettext('Auto commit?')}</PgMenuItem>
@@ -559,8 +551,8 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
</PgMenu>
<PgMenu
anchorRef={explainMenuRef}
open={menuOpenId=='menu-explain'}
onClose={handleMenuClose}
open={openMenuName=='menu-explain'}
onClose={onMenuClose}
>
<PgMenuItem hasCheck value="explain_verbose" checked={checkedMenuItems['explain_verbose']}
onClick={checkMenuClick}>{gettext('Verbose')}</PgMenuItem>
@@ -577,8 +569,8 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
</PgMenu>
<PgMenu
anchorRef={macrosMenuRef}
open={menuOpenId=='menu-macros'}
onClose={handleMenuClose}
open={openMenuName=='menu-macros'}
onClose={onMenuClose}
>
<PgMenuItem onClick={onManageMacros}>{gettext('Manage macros')}</PgMenuItem>
<PgMenuDivider />

View File

@@ -19,7 +19,7 @@ import moment from 'moment';
import PlayArrowRoundedIcon from '@material-ui/icons/PlayArrowRounded';
import AssessmentRoundedIcon from '@material-ui/icons/AssessmentRounded';
import ExplicitRoundedIcon from '@material-ui/icons/ExplicitRounded';
import { SaveDataIcon, CommitIcon, RollbackIcon } from '../../../../../../static/js/components/ExternalIcon';
import { SaveDataIcon, CommitIcon, RollbackIcon, ViewDataIcon } from '../../../../../../static/js/components/ExternalIcon';
import { InputSwitch } from '../../../../../../static/js/components/FormComponents';
import CodeMirror from '../../../../../../static/js/components/CodeMirror';
import { DefaultButton } from '../../../../../../static/js/components/Buttons';
@@ -30,6 +30,7 @@ import { LayoutEventsContext, LAYOUT_EVENTS } from '../../../../../../static/js/
import PropTypes from 'prop-types';
import { parseApiError } from '../../../../../../static/js/api_instance';
import * as clipboard from '../../../../../../static/js/clipboard';
import EmptyPanelMessage from '../../../../../../static/js/components/EmptyPanelMessage';
const useStyles = makeStyles((theme)=>({
leftRoot: {
@@ -145,9 +146,9 @@ class QueryHistoryUtils {
getDatePrefix(date) {
let prefix = '';
if (this.isDaysBefore(date, 0)) {
prefix = 'Today - ';
prefix = gettext('Today - ');
} else if (this.isDaysBefore(date, 1)) {
prefix = 'Yesterday - ';
prefix = gettext('Yesterday - ');
}
return prefix;
}
@@ -247,7 +248,7 @@ function QuerySourceIcon({source}) {
case JSON.stringify(QuerySources.SAVE_DATA):
return <SaveDataIcon style={{marginLeft: '-4px'}}/>;
case JSON.stringify(QuerySources.VIEW_DATA):
return <SaveDataIcon style={{marginLeft: '-4px'}}/>;
return <ViewDataIcon style={{marginLeft: '-4px'}}/>;
default:
return <></>;
}
@@ -312,7 +313,9 @@ function QueryHistoryDetails({entry}) {
}, [entry]);
if(!entry) {
return <></>;
return <Box display="flex" height="100%">
<EmptyPanelMessage text={gettext('Select an history entry to see details.')} />
</Box>;
}
return (
@@ -460,39 +463,45 @@ export function QueryHistory() {
<Loader message={loaderText} />
{React.useMemo(()=>(
<Box display="flex" height="100%">
<Box flexBasis="50%" maxWidth="50%" className={classes.leftRoot}>
<Box padding="0.25rem" display="flex">
{gettext('Show queries generated internally by pgAdmin?')}
<InputSwitch value={showInternal} onChange={(e)=>{
setShowInternal(e.target.checked);
qhu.current.showInternal = e.target.checked;
setSelectedItemKey(qhu.current.getNextItemKey());
}} />
<Box marginLeft="auto">
<DefaultButton size="small" disabled={!selectedItemKey} onClick={onRemove}>Remove</DefaultButton>
<DefaultButton size="small" disabled={!qhu.current?.getGroups()?.length}
className={classes.removeBtnMargin} onClick={onRemoveAll}>Remove All</DefaultButton>
{qhu.current.size() == 0 ?
<EmptyPanelMessage text={gettext('No history found')} />:
<>
<Box flexBasis="50%" maxWidth="50%" className={classes.leftRoot}>
<Box padding="0.25rem" display="flex" flexWrap="wrap">
<Box marginRight="auto">
{gettext('Show queries generated internally by pgAdmin?')}
<InputSwitch value={showInternal} onChange={(e)=>{
setShowInternal(e.target.checked);
qhu.current.showInternal = e.target.checked;
setSelectedItemKey(qhu.current.getNextItemKey());
}} />
</Box>
<Box>
<DefaultButton size="small" disabled={!selectedItemKey} onClick={onRemove}>{gettext('Remove')}</DefaultButton>
<DefaultButton size="small" disabled={!qhu.current?.getGroups()?.length}
className={classes.removeBtnMargin} onClick={onRemoveAll}>{gettext('Remove All')}</DefaultButton>
</Box>
</Box>
<Box flexGrow="1" overflow="auto" className={classes.listRoot}>
<List innerRef={listRef} className={classes.root} subheader={<li />} tabIndex="0" onKeyDown={onKeyPressed}>
{qhu.current.getGroups().map(([groupKey, groupHeader]) => (
<ListItem key={`section-${groupKey}`} className={classes.removePadding}>
<List className={classes.removePadding}>
<ListSubheader className={classes.listSubheader}>{groupHeader}</ListSubheader>
{qhu.current.getGroupEntries(groupKey).map((entry) => (
<HistoryEntry key={entry.itemKey} entry={entry} formatEntryDate={qhu.current.formatEntryDate}
itemKey={entry.itemKey} selectedItemKey={selectedItemKey} onClick={()=>{setSelectedItemKey(entry.itemKey);}}/>
))}
</List>
</ListItem>
))}
</List>
</Box>
</Box>
</Box>
<Box flexGrow="1" overflow="auto" className={classes.listRoot}>
<List innerRef={listRef} className={classes.root} subheader={<li />} tabIndex="0" onKeyDown={onKeyPressed}>
{qhu.current.getGroups().map(([groupKey, groupHeader]) => (
<ListItem key={`section-${groupKey}`} className={classes.removePadding}>
<List className={classes.removePadding}>
<ListSubheader className={classes.listSubheader}>{groupHeader}</ListSubheader>
{qhu.current.getGroupEntries(groupKey).map((entry) => (
<HistoryEntry key={entry.itemKey} entry={entry} formatEntryDate={qhu.current.formatEntryDate}
itemKey={entry.itemKey} selectedItemKey={selectedItemKey} onClick={()=>{setSelectedItemKey(entry.itemKey);}}/>
))}
</List>
</ListItem>
))}
</List>
</Box>
</Box>
<Box flexBasis="50%" maxWidth="50%" overflow="auto">
<QueryHistoryDetails entry={selectedEntry}/>
</Box>
<Box flexBasis="50%" maxWidth="50%" overflow="auto">
<QueryHistoryDetails entry={selectedEntry}/>
</Box>
</>}
</Box>
), [selectedItemKey, showInternal, qhu.current.size()])}
</>

View File

@@ -627,7 +627,7 @@ export class ResultSetUtils {
data.primary_keys = (_.isEmpty(data.primary_keys) && data.has_oids) ? data.oids : data.primary_keys;
data.can_edit = !_.isEmpty(data.primary_keys);
let procColumns = this.processColumns(data);
onResultsAvailable(data, procColumns, this.processRows(result, procColumns, this.processRows(result, procColumns)));
onResultsAvailable(data, procColumns, this.processRows(result, procColumns));
this.setStartData(null);
let planJson = this.getPlanJson(result, data);
if(planJson) {
@@ -886,12 +886,14 @@ export function ResultSet() {
};
useEffect(()=>{
eventBus.registerListener(QUERY_TOOL_EVENTS.FETCH_MORE_ROWS, fetchMoreRows);
return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.FETCH_MORE_ROWS, fetchMoreRows);
}, [queryData, columns]);
return ()=>{
eventBus.deregisterListener(QUERY_TOOL_EVENTS.FETCH_MORE_ROWS, fetchMoreRows);
};
}, [queryData?.has_more_rows, columns]);
useEffect(()=>{
eventBus.fireEvent(QUERY_TOOL_EVENTS.ROWS_FETCHED, queryData?.rows_fetched_to, queryData?.rows_affected);
}, [queryData]);
}, [queryData?.rows_fetched_to, queryData?.rows_affected]);
const warnSaveDataClose = ()=>{
// No changes.
@@ -975,7 +977,7 @@ export function ResultSet() {
}
eventBus.fireEvent(QUERY_TOOL_EVENTS.SAVE_DATA_DONE, true);
if(!_.size(dataChangeStore.added)) {
if(_.size(dataChangeStore.added)) {
// Update the rows in a grid after addition
respData.data.query_results.forEach((qr)=>{
if(!_.isNull(qr.row_added)) {

View File

@@ -158,7 +158,7 @@ export function ResultSetToolbar({containerRef, canEdit}) {
open={menuOpenId=='menu-copyheader'}
onClose={handleMenuClose}
>
<PgMenuItem hasCheck value="copy_with_headers" checked={checkedMenuItems['copy_with_headers']} onClick={checkMenuClick}>Copy with headers</PgMenuItem>
<PgMenuItem hasCheck value="copy_with_headers" checked={checkedMenuItems['copy_with_headers']} onClick={checkMenuClick}>{gettext('Copy with headers')}</PgMenuItem>
</PgMenu>
</>
);

View File

@@ -15,6 +15,7 @@ import _ from 'lodash';
import { QUERY_TOOL_EVENTS } from '../QueryToolConstants';
import { useStopwatch } from '../../../../../../static/js/custom_hooks';
import { QueryToolEventsContext } from '../QueryToolComponent';
import gettext from 'sources/gettext';
const useStyles = makeStyles((theme)=>({
@@ -86,18 +87,18 @@ export function StatusBar() {
let stagedText = '';
if(dataRowChangeCounts.added > 0) {
stagedText += ` Added: ${dataRowChangeCounts.added};`;
stagedText += ' ' + gettext('Added: %s', dataRowChangeCounts.added);
}
if(dataRowChangeCounts.updated > 0) {
stagedText += ` Updated: ${dataRowChangeCounts.updated};`;
stagedText += ' ' + gettext('Updated: %s', dataRowChangeCounts.updated);
}
if(dataRowChangeCounts.deleted > 0) {
stagedText += ` Deleted: ${dataRowChangeCounts.deleted};`;
stagedText += ' ' + gettext('Deleted: %s', dataRowChangeCounts.deleted);
}
return (
<Box className={classes.root}>
<Box className={clsx(classes.padding, classes.divider)}>Total rows: {rowsCount[0]} of {rowsCount[1]}</Box>
<Box className={clsx(classes.padding, classes.divider)}>{gettext('Total rows: %s of %s', rowsCount[0], rowsCount[1])}</Box>
{lastTaskText &&
<Box className={clsx(classes.padding, classes.divider)}>{lastTaskText} {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}.{msec.toString().padStart(3, '0')}</Box>
}
@@ -105,13 +106,13 @@ export function StatusBar() {
<Box className={clsx(classes.padding, classes.divider)}>{lastTaskText} {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}.{msec.toString().padStart(3, '0')}</Box>
}
{Boolean(selectedRowsCount) &&
<Box className={clsx(classes.padding, classes.divider)}>Rows selected: {selectedRowsCount}</Box>}
<Box className={clsx(classes.padding, classes.divider)}>{gettext('Rows selected: %s',selectedRowsCount)}</Box>}
{stagedText &&
<Box className={clsx(classes.padding, classes.divider)}>
<span>Changes staged: {stagedText}</span>
<span>{gettext('Changes staged: %s', stagedText)}</span>
</Box>
}
<Box className={clsx(classes.padding, classes.mlAuto)}>Ln {position[0]}, Col {position[1]}</Box>
<Box className={clsx(classes.padding, classes.mlAuto)}>{gettext('Ln %s, Col %s', position[0], position[1])}</Box>
</Box>
);
}