1) Port schema diff to React. Fixes #6133

2) Remove SlickGrid.
This commit is contained in:
Nikhil Mohite 2022-09-07 19:20:03 +05:30 committed by Akshay Joshi
parent ad59380676
commit e1942d8c9e
78 changed files with 2794 additions and 7888 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 431 KiB

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 199 KiB

View File

@ -19,6 +19,7 @@ New features
Housekeeping Housekeeping
************ ************
| `Issue #6133 <https://redmine.postgresql.org/issues/6133>`_ - Port schema diff to React.
| `Issue #7343 <https://redmine.postgresql.org/issues/7343>`_ - Port the remaining components of the ERD Tool to React. | `Issue #7343 <https://redmine.postgresql.org/issues/7343>`_ - Port the remaining components of the ERD Tool to React.
| `Issue #7622 <https://redmine.postgresql.org/issues/7622>`_ - Port search object dialog to React. | `Issue #7622 <https://redmine.postgresql.org/issues/7622>`_ - Port search object dialog to React.

View File

@ -33,7 +33,6 @@ module.exports = function (config) {
}), }),
], ],
files: [ files: [
'pgadmin/static/bundle/slickgrid.js',
{pattern: 'pgadmin/static/**/*.js', included: false, watched: true}, {pattern: 'pgadmin/static/**/*.js', included: false, watched: true},
{pattern: 'pgadmin/browser/static/js/**/*.js', included: false, watched: true}, {pattern: 'pgadmin/browser/static/js/**/*.js', included: false, watched: true},
{pattern: 'regression/javascript/**/*.js', watched: true}, {pattern: 'regression/javascript/**/*.js', watched: true},
@ -50,7 +49,6 @@ module.exports = function (config) {
preprocessors: { preprocessors: {
'pgadmin/**/js/**/*.js?': ['sourcemap'], 'pgadmin/**/js/**/*.js?': ['sourcemap'],
'regression/javascript/**/*.js': ['webpack', 'sourcemap'], 'regression/javascript/**/*.js': ['webpack', 'sourcemap'],
'pgadmin/static/bundle/slickgrid.js': ['webpack', 'sourcemap'],
}, },
// optionally, configure the reporter // optionally, configure the reporter

View File

@ -163,7 +163,6 @@
"react-window": "^1.8.5", "react-window": "^1.8.5",
"select2": "^4.0.13", "select2": "^4.0.13",
"shim-loader": "^1.0.1", "shim-loader": "^1.0.1",
"slickgrid": "git+https://github.com/6pac/SlickGrid.git#2.3.16",
"snapsvg-cjs": "^0.0.6", "snapsvg-cjs": "^0.0.6",
"socket.io-client": "^4.0.0", "socket.io-client": "^4.0.0",
"split.js": "^1.5.10", "split.js": "^1.5.10",

View File

@ -1,4 +1,4 @@
{ {
"dark": { "dark": {
"disp_name": "dark_(Beta)", "disp_name": "dark_(Beta)",
"cssfile": "pgadmin.theme.dark", "cssfile": "pgadmin.theme.dark",

View File

@ -18,7 +18,7 @@ define([
pgAdmin.Browser.Frame = function(options) { pgAdmin.Browser.Frame = function(options) {
var defaults = [ var defaults = [
'name', 'title', 'width', 'height', 'showTitle', 'isCloseable', 'name', 'title', 'width', 'height', 'showTitle', 'isCloseable',
'isPrivate', 'url', 'icon', 'onCreate', 'isLayoutMember', 'isPrivate', 'url', 'icon', 'onCreate', 'isLayoutMember', 'isRenamable',
]; ];
_.extend(this, _.pick(options, defaults)); _.extend(this, _.pick(options, defaults));
}; };

View File

@ -11,7 +11,7 @@
{% block init_script %} {% block init_script %}
try { try {
require( require(
['sources/generated/app.bundle', 'sources/generated/codemirror', 'sources/generated/browser_nodes', 'sources/generated/slickgrid'], ['sources/generated/app.bundle', 'sources/generated/codemirror', 'sources/generated/browser_nodes'],
function() { function() {
}, },
function() { function() {

View File

@ -1,25 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import 'slickgrid/lib/jquery.event.drag-2.3.0';
import 'slickgrid/slick.core';
import 'slickgrid/slick.grid';
import 'slickgrid/slick.dataview';
import 'slickgrid/slick.editors';
import 'slickgrid/slick.formatters';
import 'slickgrid/slick.groupitemmetadataprovider';
import 'slickgrid/plugins/slick.autotooltips';
import 'slickgrid/plugins/slick.cellrangedecorator';
import 'slickgrid/plugins/slick.cellrangeselector';
import 'slickgrid/plugins/slick.checkboxselectcolumn';
import 'slickgrid/plugins/slick.rowselectionmodel';
import 'sources/slickgrid/custom_header_buttons';
import 'sources/slickgrid/plugins/slick.autocolumnsize';
export default window.Slick;

View File

@ -14,10 +14,6 @@
@import 'node_modules/codemirror/addon/dialog/dialog.css'; @import 'node_modules/codemirror/addon/dialog/dialog.css';
@import 'node_modules/codemirror/addon/scroll/simplescrollbars.css'; @import 'node_modules/codemirror/addon/scroll/simplescrollbars.css';
@import 'node_modules/slickgrid/slick.grid.css';
@import 'node_modules/slickgrid/slick-default-theme.css';
@import 'node_modules/slickgrid/css/smoothness/jquery-ui-1.11.3.custom.css';
@import '../vendor/backgrid/backgrid.css'; @import '../vendor/backgrid/backgrid.css';
@import '../vendor/backgrid/backgrid-select-all.css'; @import '../vendor/backgrid/backgrid-select-all.css';

View File

@ -0,0 +1,115 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import React, { useState, useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Box } from '@material-ui/core';
import CloseIcon from '@material-ui/icons/CloseRounded';
import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
import pgAdmin from 'sources/pgadmin';
import gettext from 'sources/gettext';
import { DefaultButton, PrimaryButton } from '../components/Buttons';
import { useModalStyles } from '../helpers/ModalProvider';
import { FormFooterMessage, InputText, MESSAGE_TYPE } from '../components/FormComponents';
import { setDebuggerTitle } from '../../../tools/debugger/static/js/debugger_utils';
import * as queryToolPanelTitleFunc from '../../../tools/sqleditor/static/js/sqleditor_title';
import * as queryToolPanelViewDataFunc from '../../../tools/sqleditor/static/js/show_view_data';
export default function RenamePanelContent({ closeModal, panel, title, preferences, setHeight, tabType, data }) {
const classes = useModalStyles();
const containerRef = useRef();
const firstEleRef = useRef();
const okBtnRef = useRef();
const [formData, setFormData] = useState({
title: title
});
const onTextChange = (e, id) => {
let val = e;
if (e && e.target) {
val = e.target.value;
}
setFormData((prev) => ({ ...prev, [id]: val }));
};
const onKeyDown = (e) => {
// If enter key is pressed then click on OK button
if (e.key === 'Enter') {
okBtnRef.current?.click();
}
};
useEffect(() => {
setTimeout(() => {
firstEleRef.current && firstEleRef.current.focus();
}, 350);
}, [firstEleRef.current]);
useEffect(() => {
setHeight?.(containerRef.current?.offsetHeight);
}, [containerRef.current, formData]);
return (
<Box display="flex" flexDirection="column" className={classes.container} ref={containerRef}>
<Box flexGrow="1" p={2}>
<Box marginTop='12px'>
<InputText inputRef={firstEleRef} type="text" value={formData['title']} controlProps={{ maxLength: null }}
onChange={(e) => onTextChange(e, 'title')} onKeyDown={(e) => onKeyDown(e)} />
</Box>
<FormFooterMessage type={MESSAGE_TYPE.ERROR} message={formData['title'].length == 0 ? gettext('Title cannot be empty') : ''} closable={false} style={{
position: 'unset', padding: '12px 0px 0px'
}} />
</Box>
<Box className={classes.footer}>
<DefaultButton data-test="close" startIcon={<CloseIcon />} onClick={() => {
closeModal();
}} >{gettext('Cancel')}</DefaultButton>
<PrimaryButton ref={okBtnRef} data-test="save" className={classes.margin} disabled={formData['title'].length == 0 ? true : false} startIcon={<CheckRoundedIcon />} onClick={() => {
if (tabType == 'debugger') {
setDebuggerTitle(panel, preferences, data.function_name, data.schema_name, data.database_name, _.escape(formData['title']), pgAdmin.Browser);
} else if (tabType == 'querytool') {
let selected_item = pgAdmin.Browser.tree.selected();
let panel_titles = '';
if (data.is_query_tool) {
panel_titles = queryToolPanelTitleFunc.getPanelTitle(pgAdmin.Browser, selected_item, formData['title']);
} else {
panel_titles = queryToolPanelViewDataFunc.generateViewDataTitle(pgAdmin.Browser, selected_item, formData['title']);
}
// Set title to the selected tab.
if (data.is_dirty_editor) {
panel_titles = panel_titles + ' *';
}
queryToolPanelTitleFunc.setQueryToolDockerTitle(panel, data.is_query_tool, panel_titles, data.is_file);
} else {
panel.title(_.escape(formData.title));
}
closeModal();
}} >{gettext('OK')}</PrimaryButton>
</Box>
</Box>
);
}
RenamePanelContent.propTypes = {
closeModal: PropTypes.func,
data: PropTypes.object,
setHeight: PropTypes.func,
panel: PropTypes.object,
title: PropTypes.string,
preferences: PropTypes.object,
tabType: PropTypes.string,
'panel.title': PropTypes.string,
};

View File

@ -22,6 +22,7 @@ import ChangePasswordContent from './ChangePasswordContent';
import NamedRestoreContent from './NamedRestoreContent'; import NamedRestoreContent from './NamedRestoreContent';
import ChangeOwnershipContent from './ChangeOwnershipContent'; import ChangeOwnershipContent from './ChangeOwnershipContent';
import UrlDialogContent from './UrlDialogContent'; import UrlDialogContent from './UrlDialogContent';
import RenamePanelContent from './RenamePanelContent';
function mountDialog(title, getDialogContent, docker=undefined, width, height) { function mountDialog(title, getDialogContent, docker=undefined, width, height) {
// Register dialog panel // Register dialog panel
@ -353,3 +354,29 @@ export function showUrlDialog() {
{ isFullScreen: false, isResizeable: true, showFullScreen: true, isFullWidth: true, { isFullScreen: false, isResizeable: true, showFullScreen: true, isFullWidth: true,
dialogWidth: width || pgAdmin.Browser.stdW.md, dialogHeight: height || pgAdmin.Browser.stdH.md}); dialogWidth: width || pgAdmin.Browser.stdW.md, dialogHeight: height || pgAdmin.Browser.stdH.md});
} }
export function showRenamePanel() {
let title = arguments[0],
preferences = arguments[1],
panel = arguments[2],
tabType= arguments[3],
data= arguments[4];
mountDialog(gettext(`Rename Panel ${_.escape(title)}`), (onClose, setNewSize)=> {
return <Theme>
<RenamePanelContent
setHeight={(containerHeight)=>{
setNewSize(pgAdmin.Browser.stdW.md, containerHeight);
}}
closeModal={()=>{
onClose();
}}
panel={panel}
tabType={tabType}
title={title}
data={data}
preferences={preferences}
/>
</Theme>;
});
}

View File

@ -103,6 +103,13 @@ export default function(basicSettings) {
textMuted: '#8A8A8A', textMuted: '#8A8A8A',
erdCanvasBg: '#303030', erdCanvasBg: '#303030',
erdGridColor: '#444952', erdGridColor: '#444952',
schemaDiff: {
diffRowColor: '#807a48',
sourceRowColor: '#402025',
targetRowColor: '#6b5438',
diffColorFg: '#d4d4d4',
diffSelectFG: '#d4d4d4'
}
} }
}); });
} }

View File

@ -101,6 +101,13 @@ export default function(basicSettings) {
textMuted: '#8b9cad', textMuted: '#8b9cad',
erdCanvasBg: '#010B15', erdCanvasBg: '#010B15',
erdGridColor: '#1F2932', erdGridColor: '#1F2932',
schemaDiff: {
diffRowColor: '#CFC56E',
sourceRowColor: '#EE97A5',
targetRowColor: '#FFAD65',
diffColorFg: '#FFFFFF',
diffSelectFG: '#010B15'
}
} }
}); });
} }

View File

@ -121,6 +121,13 @@ export default function(basicSettings) {
color: '#FFFFFF', color: '#FFFFFF',
bg: '#880000' bg: '#880000'
}, },
},
schemaDiff: {
diffRowColor: '#fff9c4',
sourceRowColor: '#ffebee',
targetRowColor: '#fbe3bf',
diffColorFg: '#222',
diffSelectFG: '#222'
} }
} }
}); });

View File

@ -37,6 +37,7 @@ const useStyles = makeStyles((theme)=>({
}, },
message: { message: {
marginLeft: '0.5rem', marginLeft: '0.5rem',
fontSize: '16px',
} }
})); }));

View File

@ -128,7 +128,7 @@ CustomRow.propTypes = {
}; };
export default function PgReactDataGrid({gridRef, className, hasSelectColumn=true, onItemEnter, onItemSelect, export default function PgReactDataGrid({gridRef, className, hasSelectColumn=true, onItemEnter, onItemSelect,
onItemClick, noRowsText, ...props}) { onItemClick, noRowsText, noRowsIcon,...props}) {
const classes = useStyles(); const classes = useStyles();
let finalClassName = [classes.root]; let finalClassName = [classes.root];
hasSelectColumn && finalClassName.push(classes.hasSelectColumn); hasSelectColumn && finalClassName.push(classes.hasSelectColumn);
@ -142,7 +142,7 @@ export default function PgReactDataGrid({gridRef, className, hasSelectColumn=tru
components={{ components={{
sortIcon: CutomSortIcon, sortIcon: CutomSortIcon,
rowRenderer: CustomRow, rowRenderer: CustomRow,
noRowsFallback: <Box textAlign="center" gridColumn="1/-1" p={1}>{noRowsText || gettext('No rows found.')}</Box>, noRowsFallback: <Box textAlign="center" gridColumn="1/-1" p={1}>{noRowsIcon}{noRowsText || gettext('No rows found.')}</Box>,
}} }}
{...props} {...props}
/> />
@ -158,5 +158,6 @@ PgReactDataGrid.propTypes = {
onItemEnter: PropTypes.func, onItemEnter: PropTypes.func,
onItemSelect: PropTypes.func, onItemSelect: PropTypes.func,
onItemClick: PropTypes.func, onItemClick: PropTypes.func,
noRowsText: PropTypes.string noRowsText: PropTypes.string,
noRowsIcon: PropTypes.elementType
}; };

View File

@ -67,6 +67,12 @@ const useStyles = makeStyles((theme)=>({
'& .dock-tabpane': { '& .dock-tabpane': {
backgroundColor: theme.palette.background.default, backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary, color: theme.palette.text.primary,
},
'& #id-schema-diff': {
overflowY: 'auto'
},
'& #id-results': {
overflowY: 'auto'
} }
}, },
'& .dock-tab': { '& .dock-tab': {

View File

@ -1,203 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define([
'jquery',
'sources/selection/range_selection_helper',
], function ($, RangeSelectionHelper) {
return function () {
var KEY_RIGHT = 39;
var KEY_LEFT = 37;
var KEY_UP = 38;
var KEY_DOWN = 40;
var bypassDefaultActiveCellRangeChange = false;
var isColumnsResized = false;
var isMouseInHeader = false;
var grid;
var init = function (slickGrid) {
grid = slickGrid;
grid.onDragEnd.subscribe(onDragEndHandler);
grid.onHeaderClick.subscribe(onHeaderClickHandler);
grid.onClick.subscribe(onClickHandler);
grid.onActiveCellChanged.subscribe(onActiveCellChangedHandler);
grid.onKeyDown.subscribe(onKeyDownHandler);
grid.onHeaderMouseEnter.subscribe(onHeaderMouseEnterHandler);
grid.onHeaderMouseLeave.subscribe(onHeaderMouseLeaveHandler);
grid.onColumnsResized.subscribe(onColumnsResizedHandler);
};
var destroy = function () {
grid.onDragEnd.unsubscribe(onDragEndHandler);
grid.onHeaderClick.unsubscribe(onHeaderClickHandler);
grid.onActiveCellChanged.unsubscribe(onActiveCellChangedHandler);
grid.onKeyDown.unsubscribe(onKeyDownHandler);
grid.onHeaderMouseEnter.unsubscribe(onHeaderMouseEnterHandler);
grid.onHeaderMouseLeave.unsubscribe(onHeaderMouseLeaveHandler);
grid.onColumnsResized.unsubscribe(onColumnsResizedHandler);
};
$.extend(this, {
'init': init,
'destroy': destroy,
});
function onDragEndHandler(event, dragData) {
bypassDefaultActiveCellRangeChange = true;
grid.setActiveCell(dragData.range.start.row, dragData.range.start.cell);
}
function onHeaderClickHandler(event, args) {
if (isColumnsResizedAndCurrentlyInHeader()) {
isColumnsResized = false;
event.stopPropagation();
return;
}
/* Skip if clicked on resize handler */
if($(event.target).hasClass('slick-resizable-handle')) {
return;
}
bypassDefaultActiveCellRangeChange = true;
var clickedColumn = args.column.pos + 1;
if (isClickingLastClickedHeader(0, clickedColumn)) {
if (isSingleRangeSelected()) {
grid.resetActiveCell();
} else {
grid.setActiveCell(0, retrievePreviousSelectedRange().fromCell);
}
} else if (!isClickingInSelectedColumn(clickedColumn)) {
grid.setActiveCell(0, clickedColumn);
}
}
function isEditableNewRow(row) {
return row >= grid.getDataLength();
}
function onHeaderMouseLeaveHandler() {
isMouseInHeader = false;
}
function onHeaderMouseEnterHandler() {
isMouseInHeader = true;
isColumnsResized = false;
}
function onColumnsResizedHandler() {
isColumnsResized = true;
}
function onClickHandler(event, args) {
if (isRowHeader(args.cell)) {
bypassDefaultActiveCellRangeChange = true;
var rowClicked = args.row;
if (isEditableNewRow(rowClicked)) {
return;
}
if (isClickingLastClickedHeader(rowClicked, 1)) {
if (isSingleRangeSelected() || grid.getSelectionModel().getSelectedRanges().length === 0) {
grid.resetActiveCell();
} else {
grid.setActiveCell(retrievePreviousSelectedRange().fromRow, 1);
}
} else if (!isClickingInSelectedRow(rowClicked)) {
grid.setActiveCell(rowClicked, 1);
}
}
}
function onActiveCellChangedHandler(event) {
if (bypassDefaultActiveCellRangeChange) {
bypassDefaultActiveCellRangeChange = false;
event.stopPropagation();
}
}
function onKeyDownHandler(event) {
if (hasActiveCell() && isShiftArrowKey(event)) {
selectOnlyRangeOfActiveCell();
}
}
function isColumnsResizedAndCurrentlyInHeader() {
return isMouseInHeader && isColumnsResized;
}
function isClickingLastClickedHeader(clickedRow, clickedColumn) {
return hasActiveCell() && grid.getActiveCell().row === clickedRow && grid.getActiveCell().cell === clickedColumn;
}
function isClickingInSelectedColumn(clickedColumn) {
var column = RangeSelectionHelper.rangeForColumn(grid, clickedColumn);
var cellSelectionModel = grid.getSelectionModel();
var ranges = cellSelectionModel.getSelectedRanges();
return RangeSelectionHelper.isRangeSelected(ranges, column);
}
function isRowHeader(cellClicked) {
return grid.getColumns()[cellClicked].id === 'row-header-column';
}
function isClickingInSelectedRow(rowClicked) {
var row = RangeSelectionHelper.rangeForRow(grid, rowClicked);
var cellSelectionModel = grid.getSelectionModel();
var ranges = cellSelectionModel.getSelectedRanges();
return RangeSelectionHelper.isRangeSelected(ranges, row);
}
function isSingleRangeSelected() {
var cellSelectionModel = grid.getSelectionModel();
var ranges = cellSelectionModel.getSelectedRanges();
return ranges.length === 1;
}
function retrievePreviousSelectedRange() {
var cellSelectionModel = grid.getSelectionModel();
var ranges = cellSelectionModel.getSelectedRanges();
return ranges[ranges.length - 2];
}
function isArrowKey(event) {
return event.which === KEY_RIGHT
|| event.which === KEY_UP
|| event.which === KEY_LEFT
|| event.which === KEY_DOWN;
}
function isModifiedByShiftOnly(event) {
return event.shiftKey
&& !event.ctrlKey
&& !event.altKey;
}
function isShiftArrowKey(event) {
return isModifiedByShiftOnly(event) && isArrowKey(event);
}
function hasActiveCell() {
return !!grid.getActiveCell();
}
function selectOnlyRangeOfActiveCell() {
var cellSelectionModel = grid.getSelectionModel();
var ranges = cellSelectionModel.getSelectedRanges();
if (ranges.length > 1) {
cellSelectionModel.setSelectedRanges([ranges.pop()]);
}
}
};
});

View File

@ -1,164 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define([
'jquery',
'sources/selection/range_selection_helper',
'slickgrid',
], function ($, RangeSelectionHelper) {
return function () {
var Slick = window.Slick,
gridEventBus = new Slick.EventHandler(),
onBeforeColumnSelectAll = new Slick.Event(),
onColumnSelectAll = new Slick.Event();
var init = function (grid) {
gridEventBus.subscribe(grid.onHeaderClick, handleHeaderClick.bind(null, grid));
grid.getSelectionModel().onSelectedRangesChanged
.subscribe(handleSelectedRangesChanged.bind(null, grid));
onColumnSelectAll.subscribe(function(e, args) {
updateRanges(args.grid, args.column.id);
});
};
var handleHeaderClick = function (grid, event, args) {
var columnDefinition = args.column;
grid.focus();
if (isColumnSelectable(columnDefinition)) {
var $columnHeader = $(event.target);
if (hasClickedChildOfColumnHeader(event)) {
if ($(event.target).hasClass('slick-resizable-handle')) {
return;
}
$columnHeader = $(event.target).parents('.slick-header-column');
}
$columnHeader.toggleClass('selected');
if ($columnHeader.hasClass('selected')) {
onBeforeColumnSelectAll.notify(args, event);
}
if (!(event.isPropagationStopped() || event.isImmediatePropagationStopped())) {
updateRanges(grid, columnDefinition.id);
}
} else {
toggleColumnHeaderForCopyHeader(grid);
}
};
var toggleColumnHeaderForCopyHeader = function(grid) {
if(!$('.copy-with-header').hasClass('visibility-hidden')) {
var selRowCnt = grid.getSelectedRows();
$('.slick-header-column').each(function (index, columnHeader) {
if (selRowCnt.length == 0) {
$(columnHeader).removeClass('selected');
grid.getColumns()[index].selected = false;
}
else {
if (index > 0 && grid.getColumns()[index].selectable) {
$(columnHeader).addClass('selected');
grid.getColumns()[index].selected = true;
}
}
});
} else {
$('.slick-header-column').each(function (index, columnHeader) {
$(columnHeader).removeClass('selected');
});
}
}.bind(RangeSelectionHelper);
var handleSelectedRangesChanged = function (grid, event, selectedRanges) {
$('.slick-header-column').each(function (index, columnHeader) {
var $spanHeaderColumn = $(columnHeader).find('[data-cell-type="column-header-row"]');
var columnIndex = grid.getColumnIndex($spanHeaderColumn.data('column-id'));
if (isColumnSelected(grid, selectedRanges, columnIndex)) {
$(columnHeader).addClass('selected');
if (columnIndex) grid.getColumns()[columnIndex].selected = true;
} else if(!RangeSelectionHelper.areAllRangesCompleteRows(grid, selectedRanges)){
$(columnHeader).removeClass('selected');
if (columnIndex) grid.getColumns()[columnIndex].selected = false;
}
});
};
var updateRanges = function (grid, columnId) {
var selectionModel = grid.getSelectionModel();
var ranges = selectionModel.getSelectedRanges();
var columnIndex = grid.getColumnIndex(columnId);
var columnRange = RangeSelectionHelper.rangeForColumn(grid, columnIndex);
var newRanges;
if (RangeSelectionHelper.isRangeSelected(ranges, columnRange)) {
newRanges = RangeSelectionHelper.removeRange(ranges, columnRange);
} else {
if (RangeSelectionHelper.areAllRangesSingleColumns(ranges, grid)) {
newRanges = RangeSelectionHelper.addRange(ranges, columnRange);
} else {
newRanges = [columnRange];
}
}
selectionModel.setSelectedRanges(newRanges);
};
var hasClickedChildOfColumnHeader = function (event) {
return !$(event.target).hasClass('slick-header-column');
};
var isColumnSelectable = function (columnDefinition) {
return columnDefinition.selectable !== false;
};
var isColumnSelected = function (grid, selectedRanges, columnIndex) {
var allRangesAreRows = RangeSelectionHelper.areAllRangesCompleteRows(grid, selectedRanges);
return isAnyCellSelectedInColumn(grid, selectedRanges, columnIndex) && !allRangesAreRows;
};
var isAnyCellSelectedInColumn = function (grid, selectedRanges, columnIndex) {
var isStillSelected = RangeSelectionHelper.isRangeEntirelyWithinSelectedRanges(selectedRanges,
RangeSelectionHelper.rangeForColumn(grid, columnIndex));
var cellSelectedInColumn = RangeSelectionHelper.isAnyCellOfColumnSelected(selectedRanges, columnIndex);
return isStillSelected || cellSelectedInColumn;
};
var getColumnDefinitions = function (columnDefinitions) {
return _.map(columnDefinitions, function (columnDefinition) {
if (isColumnSelectable(columnDefinition)) {
var name =
'<span data-cell-type=\'column-header-row\' ' +
' data-test=\'output-column-header\'' +
' data-column-id=\'' + columnDefinition.id + '\'>' +
' <span class=\'column-description\'>' +
' <span class=\'column-name\'>' + columnDefinition.display_name + '</span>' +
' <span class=\'column-type\'>' + columnDefinition.column_type + '</span>' +
' </span>' +
'</span>';
return _.extend(columnDefinition, {
name: name,
});
} else {
return columnDefinition;
}
});
};
$.extend(this, {
'init': init,
'getColumnDefinitions': getColumnDefinitions,
'onBeforeColumnSelectAll': onBeforeColumnSelectAll,
'onColumnSelectAll': onColumnSelectAll,
'toggleColumnHeaderForCopyHeader': toggleColumnHeaderForCopyHeader,
});
};
});

View File

@ -1,58 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define([
'jquery',
'underscore',
'sources/selection/clipboard',
'sources/selection/range_selection_helper',
'sources/selection/range_boundary_navigator',
],
function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) {
var copyData = function () {
var self = this || window;
var grid = self.slickgrid;
var columnDefinitions = grid.getColumns();
var selectedRanges = grid.getSelectionModel().getSelectedRanges();
var dataView = grid.getData();
var rows = grid.getSelectedRows();
var CSVOptions = grid.CSVOptions;
if (RangeSelectionHelper.areAllRangesCompleteRows(grid, selectedRanges)) {
self.copied_rows = rows.map(function (rowIndex) {
return grid.getDataItem(rowIndex);
});
setPasteRowButtonEnablement(self.can_edit, true);
} else {
self.copied_rows = [];
setPasteRowButtonEnablement(self.can_edit, false);
}
var csvText = rangeBoundaryNavigator.rangesToCsv(dataView.getItems(), columnDefinitions,
selectedRanges, CSVOptions, copyWithHeader());
if (csvText) {
clipboard.copyTextToClipboard(csvText);
}
};
var copyWithHeader = function () {
return !$('.copy-with-header').hasClass('visibility-hidden');
};
var setPasteRowButtonEnablement = function (canEditFlag, isEnabled) {
if (canEditFlag) {
$('#btn-paste-row').prop('disabled', !isEnabled);
if(isEnabled && window.parent.$) {
// trigger copied event to all sessions
window.parent.$(window.parent.document).trigger('pgadmin-sqleditor:rows-copied', copyWithHeader());
}
}
};
return copyData;
});

View File

@ -1,92 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define(['jquery',
'sources/gettext',
'sources/selection/column_selector',
'sources/selection/row_selector',
'sources/selection/range_selection_helper',
'sources/url_for',
], function ($, gettext, ColumnSelector, RowSelector, RangeSelectionHelper, url_for) {
return function (columnDefinitions) {
var Slick = window.Slick,
rowSelector = new RowSelector(columnDefinitions),
columnSelector = new ColumnSelector(columnDefinitions),
onBeforeGridSelectAll = new Slick.Event(),
onGridSelectAll = new Slick.Event(),
onBeforeGridColumnSelectAll = columnSelector.onBeforeColumnSelectAll,
onGridColumnSelectAll = columnSelector.onColumnSelectAll;
var init = function (grid) {
this.grid = grid;
grid.onHeaderClick.subscribe(function (event, eventArguments) {
if (eventArguments.column.selectAllOnClick && !$(event.target).hasClass('slick-resizable-handle')) {
toggleSelectAll(grid, event, eventArguments);
}
});
grid.getSelectionModel().onSelectedRangesChanged
.subscribe(handleSelectedRangesChanged.bind(null, grid));
grid.registerPlugin(rowSelector);
grid.registerPlugin(columnSelector);
onGridSelectAll.subscribe(function(e, args) {
RangeSelectionHelper.selectAll(args.grid);
});
};
var getColumnDefinitions = function (columnDefinition) {
columnDefinition = columnSelector.getColumnDefinitions(columnDefinition);
columnDefinition = rowSelector.getColumnDefinitions(columnDefinition);
columnDefinition[0].selectAllOnClick = true;
columnDefinition[0].name = '<span data-id="select-all" ' +
'title="' + gettext('Select/Deselect All') + '">' +
'<br>' +
columnDefinition[0].name +
'<img class="select-all-icon" src="' + url_for('static', {'filename': 'img/select-all-icon.png'}) + '"></img>' +
'</span>';
return columnDefinition;
};
function handleSelectedRangesChanged(grid) {
if(RangeSelectionHelper.isEntireGridSelected(grid)) {
$('[data-id=\'select-all\']').addClass('selected');
} else {
$('[data-id=\'select-all\']').removeClass('selected');
}
}
function toggleSelectAll(grid, event, eventArguments) {
if (RangeSelectionHelper.isEntireGridSelected(grid)) {
selectNone(grid);
} else {
onBeforeGridSelectAll.notify(eventArguments, event);
if (!(event.isPropagationStopped() || event.isImmediatePropagationStopped())) {
RangeSelectionHelper.selectAll(grid);
}
}
}
function selectNone(grid) {
var selectionModel = grid.getSelectionModel();
selectionModel.setSelectedRanges([]);
}
$.extend(this, {
'init': init,
'getColumnDefinitions': getColumnDefinitions,
'onBeforeGridSelectAll': onBeforeGridSelectAll,
'onGridSelectAll': onGridSelectAll,
'onBeforeGridColumnSelectAll': onBeforeGridColumnSelectAll,
'onGridColumnSelectAll': onGridColumnSelectAll,
});
};
});

View File

@ -1,171 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define(['slickgrid'], function () {
var Slick = window.Slick;
var isSameRange = function (range, otherRange) {
return range.fromCell == otherRange.fromCell && range.toCell == otherRange.toCell &&
range.fromRow == otherRange.fromRow && range.toRow == otherRange.toRow;
};
var isRangeSelected = function (selectedRanges, range) {
return _.any(selectedRanges, function (selectedRange) {
return isSameRange(selectedRange, range);
});
};
var isAnyCellOfColumnSelected = function (selectedRanges, column) {
return _.any(selectedRanges, function (selectedRange) {
return selectedRange.fromCell <= column && selectedRange.toCell >= column;
});
};
var isAnyCellOfRowSelected = function (selectedRanges, row) {
return _.any(selectedRanges, function (selectedRange) {
return selectedRange.fromRow <= row && selectedRange.toRow >= row;
});
};
var isRangeEntirelyWithinSelectedRanges = function (selectedRanges, range) {
return _.any(selectedRanges, function (selectedRange) {
return selectedRange.fromCell <= range.fromCell && selectedRange.toCell >= range.toCell &&
selectedRange.fromRow <= range.fromRow && selectedRange.toRow >= range.toRow;
});
};
var removeRange = function (selectedRanges, range) {
return _.filter(selectedRanges, function (selectedRange) {
return !(isSameRange(selectedRange, range));
});
};
var addRange = function (ranges, range) {
ranges.push(range);
return ranges;
};
var areAllRangesSingleRows = function (ranges, grid) {
return _.every(ranges, function (range) {
return range.fromRow == range.toRow && rangeHasCompleteRows(grid, range);
});
};
var areAllRangesSingleColumns = function (ranges, grid) {
return _.every(ranges, isRangeAColumn.bind(this, grid));
};
var rangeForRow = function (grid, rowId) {
var columnDefinitions = grid.getColumns();
if (isFirstColumnData(columnDefinitions)) {
return new Slick.Range(rowId, 0, rowId, grid.getColumns().length - 1);
}
return new Slick.Range(rowId, 1, rowId, grid.getColumns().length - 1);
};
var rangeForColumn = function (grid, columnIndex) {
return new Slick.Range(0, columnIndex, grid.getDataLength() - 1, columnIndex);
};
var getRangeOfWholeGrid = function (grid) {
return new Slick.Range(0, 1, grid.getDataLength() - 1, grid.getColumns().length - 1);
};
var isEntireGridSelected = function (grid) {
var selectionModel = grid.getSelectionModel();
var selectedRanges = selectionModel.getSelectedRanges();
return selectedRanges.length == 1 && isSameRange(selectedRanges[0], getRangeOfWholeGrid(grid));
};
var isFirstColumnData = function (columnDefinitions) {
return !_.isUndefined(columnDefinitions[0].pos);
};
var areAllRangesCompleteColumns = function (grid, ranges) {
return _.every(ranges, function (range) {
return rangeHasCompleteColumns(grid, range);
});
};
var areAllRangesCompleteRows = function (grid, ranges) {
return _.every(ranges, function (range) {
return rangeHasCompleteRows(grid, range);
});
};
var getIndexesOfCompleteRows = function (grid, ranges) {
var indexArray = [];
ranges.forEach(function (range) {
if (rangeHasCompleteRows(grid, range))
indexArray = indexArray.concat(_.range(range.fromRow, range.toRow + 1));
});
return indexArray;
};
var isRangeAColumn = function (grid, range) {
return range.fromCell == range.toCell &&
range.fromRow == 0 && range.toRow == grid.getDataLength() - 1;
};
var rangeHasCompleteColumns = function (grid, range) {
return range.fromRow === 0 && range.toRow === grid.getDataLength() - 1;
};
var rangeHasCompleteRows = function (grid, range) {
return range.fromCell === getFirstDataColumnIndex(grid) &&
range.toCell === getLastDataColumnIndex(grid);
};
function getFirstDataColumnIndex(grid) {
return _.findIndex(grid.getColumns(), function (columnDefinition) {
var pos = columnDefinition.pos;
return !_.isUndefined(pos) && isSelectable(columnDefinition);
});
}
function getLastDataColumnIndex(grid) {
return _.findLastIndex(grid.getColumns(), isSelectable);
}
function isSelectable(columnDefinition) {
return (_.isUndefined(columnDefinition.selectable) || columnDefinition.selectable === true);
}
function selectAll(grid) {
var range = getRangeOfWholeGrid(grid);
var selectionModel = grid.getSelectionModel();
selectionModel.setSelectedRanges([range]);
}
return {
addRange: addRange,
removeRange: removeRange,
isRangeSelected: isRangeSelected,
areAllRangesSingleRows: areAllRangesSingleRows,
areAllRangesSingleColumns: areAllRangesSingleColumns,
areAllRangesCompleteRows: areAllRangesCompleteRows,
areAllRangesCompleteColumns: areAllRangesCompleteColumns,
rangeForRow: rangeForRow,
rangeForColumn: rangeForColumn,
isEntireGridSelected: isEntireGridSelected,
getRangeOfWholeGrid: getRangeOfWholeGrid,
isFirstColumnData: isFirstColumnData,
getIndexesOfCompleteRows: getIndexesOfCompleteRows,
selectAll: selectAll,
isRangeAColumn: isRangeAColumn,
rangeHasCompleteColumns: rangeHasCompleteColumns,
rangeHasCompleteRows: rangeHasCompleteRows,
isAnyCellOfColumnSelected: isAnyCellOfColumnSelected,
isRangeEntirelyWithinSelectedRanges: isRangeEntirelyWithinSelectedRanges,
isAnyCellOfRowSelected: isAnyCellOfRowSelected,
};
});

View File

@ -1,110 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define([
'jquery',
'sources/selection/range_selection_helper',
'sources/selection/column_selector',
'slickgrid',
], function ($, RangeSelectionHelper, ColumnSelector) {
return function () {
var Slick = window.Slick;
var gridEventBus = new Slick.EventHandler();
var columnSelector = new ColumnSelector();
var init = function (grid) {
grid.getSelectionModel().onSelectedRangesChanged
.subscribe(handleSelectedRangesChanged.bind(null, grid));
gridEventBus
.subscribe(grid.onClick, handleClick.bind(null, grid));
};
var handleClick = function (grid, event, args) {
if (grid.getColumns()[args.cell].id === 'row-header-column') {
var $rowHeaderSpan = $(event.target);
if ($rowHeaderSpan.data('cell-type') != 'row-header-selector') {
$rowHeaderSpan = $(event.target).find('[data-cell-type="row-header-selector"]');
}
$rowHeaderSpan.parent().toggleClass('selected');
updateRanges(grid, args.row);
columnSelector.toggleColumnHeaderForCopyHeader(grid);
}
};
var handleSelectedRangesChanged = function (grid, event, selectedRanges) {
$('[data-cell-type="row-header-selector"]').each(function (index, rowHeaderSpan) {
var $rowHeaderSpan = $(rowHeaderSpan);
var row = parseInt($rowHeaderSpan.data('row'));
if (isRowSelected(grid, selectedRanges, row)) {
$rowHeaderSpan.parent().addClass('selected');
} else {
$rowHeaderSpan.parent().removeClass('selected');
}
});
};
var updateRanges = function (grid, rowId) {
var selectionModel = grid.getSelectionModel();
var ranges = selectionModel.getSelectedRanges();
var rowRange = RangeSelectionHelper.rangeForRow(grid, rowId);
var newRanges;
if (RangeSelectionHelper.isRangeSelected(ranges, rowRange)) {
newRanges = RangeSelectionHelper.removeRange(ranges, rowRange);
} else {
if (RangeSelectionHelper.areAllRangesSingleRows(ranges, grid)) {
newRanges = RangeSelectionHelper.addRange(ranges, rowRange);
} else {
newRanges = [rowRange];
}
}
selectionModel.setSelectedRanges(newRanges);
};
var isAnyCellSelectedInRow = function (grid, selectedRanges, row) {
var isStillSelected = RangeSelectionHelper.isRangeEntirelyWithinSelectedRanges(selectedRanges,
RangeSelectionHelper.rangeForRow(grid, row));
var cellSelectedInRow = RangeSelectionHelper.isAnyCellOfRowSelected(selectedRanges, row);
return isStillSelected || cellSelectedInRow;
};
var isRowSelected = function (grid, selectedRanges, row) {
var allRangesAreColumns = RangeSelectionHelper.areAllRangesCompleteColumns(grid, selectedRanges);
return isAnyCellSelectedInRow(grid, selectedRanges, row) && !allRangesAreColumns;
};
var getColumnDefinitions = function (columnDefinitions) {
columnDefinitions.unshift({
id: 'row-header-column',
name: '',
selectable: false,
focusable: false,
formatter: function (rowIndex) {
return '<span ' +
'data-row="' + rowIndex + '" ' +
'data-cell-type="row-header-selector">' +
(rowIndex+1) + '</span>';
},
width: 30,
});
return columnDefinitions;
};
$.extend(this, {
'init': init,
'getColumnDefinitions': getColumnDefinitions,
});
};
});

View File

@ -1,240 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define([
'jquery',
'underscore',
'sources/selection/range_selection_helper',
'sources/window',
'slickgrid',
], function ($, _, RangeSelectionHelper, pgWindow) {
return function (options) {
var KEY_ARROW_RIGHT = 39;
var KEY_ARROW_LEFT = 37;
var KEY_ARROW_UP = 38;
var KEY_ARROW_DOWN = 40;
var Slick = window.Slick;
var _grid;
var _ranges = [];
var _self = this;
var _selector = new Slick.CellRangeSelector({
selectionCss: {
border: '2px solid black',
},
offset: {
top: 0,
left: -1,
height: 2,
width: 1,
},
});
var _options;
var _defaults = {
selectActiveCell: true,
};
function init(grid) {
_options = $.extend(true, {}, _defaults, options);
_grid = grid;
_grid.onActiveCellChanged.subscribe(handleActiveCellChange);
_grid.onKeyDown.subscribe(handleKeyDown);
grid.registerPlugin(_selector);
_selector.onCellRangeSelected.subscribe(handleCellRangeSelected);
_selector.onBeforeCellRangeSelected.subscribe(handleBeforeCellRangeSelected);
$(pgWindow.default).on('mouseup',handleWindowMouseUp);
}
function destroy() {
_grid.onActiveCellChanged.unsubscribe(handleActiveCellChange);
_grid.onKeyDown.unsubscribe(handleKeyDown);
_selector.onCellRangeSelected.unsubscribe(handleCellRangeSelected);
_selector.onBeforeCellRangeSelected.unsubscribe(handleBeforeCellRangeSelected);
_grid.unregisterPlugin(_selector);
$(pgWindow.default).off('mouseup', handleWindowMouseUp);
}
function removeInvalidRanges(ranges) {
var result = [];
for (let range_val of ranges) {
var r = range_val;
if (_grid.canCellBeSelected(r.fromRow, r.fromCell) && _grid.canCellBeSelected(r.toRow, r.toCell)) {
result.push(r);
}
}
return result;
}
function setSelectedRanges(ranges) {
// simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged
if ((!_ranges || _ranges.length === 0) && (!ranges || ranges.length === 0)) { return; }
_ranges = removeInvalidRanges(ranges);
_self.onSelectedRangesChanged.notify(_ranges);
}
function getSelectedRanges() {
return _ranges;
}
function setSelectedRows(rows) {
_ranges = [];
for(let row_val of rows) {
_ranges.push(RangeSelectionHelper.rangeForRow(_grid, row_val));
}
}
function handleBeforeCellRangeSelected(e) {
if (_grid.getEditorLock().isActive()) {
e.stopPropagation();
return false;
}
}
function handleCellRangeSelected(e, args) {
setSelectedRanges([args.range]);
}
function handleActiveCellChange(e, args) {
if (_options.selectActiveCell && args.row != null && args.cell != null) {
setSelectedRanges([new Slick.Range(args.row, args.cell)]);
}
}
function arrowKeyPressed(event) {
return event.which == KEY_ARROW_RIGHT
|| event.which == KEY_ARROW_LEFT
|| event.which == KEY_ARROW_UP
|| event.which == KEY_ARROW_DOWN;
}
function shiftArrowKeyPressed(event) {
return event.shiftKey && !event.ctrlKey && !event.altKey &&
(arrowKeyPressed(event));
}
function needUpdateRange(newRange) {
return removeInvalidRanges([newRange]).length;
}
function handleKeyDown(e) {
var ranges;
var lastSelectedRange;
var anchorActiveCell = _grid.getActiveCell();
function isKey(key) { return e.which === key; }
function getKeycode() { return e.which; }
function shouldScrollToBottommostRow() { return anchorActiveCell.row === newSelectedRange.fromRow; }
function shouldScrollToRightmostColumn() { return anchorActiveCell.cell === newSelectedRange.fromCell; }
function getMobileCellFromRange(range, activeCell) {
var localMobileCell = {};
localMobileCell.row = range.fromRow === activeCell.row ? range.toRow : range.fromRow;
localMobileCell.cell = range.fromCell === activeCell.cell ? range.toCell : range.fromCell;
return localMobileCell;
}
function getNewRange(rangeCorner, oppositeCorner) {
var newFromCell = rangeCorner.cell <= oppositeCorner.cell ? rangeCorner.cell : oppositeCorner.cell;
var newToCell = rangeCorner.cell <= oppositeCorner.cell ? oppositeCorner.cell : rangeCorner.cell;
var newFromRow = rangeCorner.row <= oppositeCorner.row ? rangeCorner.row : oppositeCorner.row;
var newToRow = rangeCorner.row <= oppositeCorner.row ? oppositeCorner.row : rangeCorner.row;
return new Slick.Range(
newFromRow,
newFromCell,
newToRow,
newToCell
);
}
if (anchorActiveCell && shiftArrowKeyPressed(e)) {
ranges = getSelectedRanges();
if (!ranges.length) {
ranges.push(new Slick.Range(anchorActiveCell.row, anchorActiveCell.cell));
}
// keyboard can work with last range only
lastSelectedRange = ranges.pop();
// can't handle selection out of active cell
if (!lastSelectedRange.contains(anchorActiveCell.row, anchorActiveCell.cell)) {
lastSelectedRange = new Slick.Range(anchorActiveCell.row, anchorActiveCell.cell);
}
var mobileCell = getMobileCellFromRange(lastSelectedRange, anchorActiveCell);
switch (getKeycode()) {
case KEY_ARROW_LEFT:
mobileCell.cell -= 1;
break;
case KEY_ARROW_RIGHT:
mobileCell.cell += 1;
break;
case KEY_ARROW_UP:
mobileCell.row -= 1;
break;
case KEY_ARROW_DOWN:
mobileCell.row += 1;
break;
}
var newSelectedRange = getNewRange(anchorActiveCell, mobileCell);
if (needUpdateRange(newSelectedRange)) {
var rowToView = shouldScrollToBottommostRow() ? newSelectedRange.toRow : newSelectedRange.fromRow;
var columnToView = shouldScrollToRightmostColumn() ? newSelectedRange.toCell : newSelectedRange.fromCell;
if (isKey(KEY_ARROW_RIGHT) || isKey(KEY_ARROW_LEFT)) {
_grid.scrollColumnIntoView(columnToView);
} else if (isKey(KEY_ARROW_UP) || isKey(KEY_ARROW_DOWN)) {
_grid.scrollRowIntoView(rowToView);
}
ranges.push(newSelectedRange);
} else {
ranges.push(lastSelectedRange);
}
setSelectedRanges(ranges);
e.preventDefault();
e.stopPropagation();
}
}
function handleWindowMouseUp() {
var selectedRange = _selector.getCurrentRange();
if (!_.isUndefined(selectedRange)) {
_grid.onDragEnd.notify({range: selectedRange});
}
}
$.extend(this, {
'getSelectedRanges': getSelectedRanges,
'setSelectedRanges': setSelectedRanges,
'setSelectedRows': setSelectedRows,
'init': init,
'destroy': destroy,
'onSelectedRangesChanged': new Slick.Event(),
});
};
});

View File

@ -1,27 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define(['slickgrid'], function () {
var Slick = window.Slick;
return function () {
this.init = function (grid) {
grid.onActiveCellChanged.subscribe(function (event, slickEvent) {
grid.getSelectionModel().setSelectedRanges([
new Slick.Range(
slickEvent.row,
slickEvent.cell,
slickEvent.row,
slickEvent.cell
),
]);
});
};
};
});

View File

@ -1,189 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
(function ($) {
// register namespace
$.extend(true, window, {
'Slick': {
'Plugins': {
'HeaderButtons': HeaderButtons,
},
},
});
/***
* custom header button modified from slick.headerbuttons.js
*
* USAGE:
*
* Add the plugin .js & .css files and register it with the grid.
*
* To specify a custom button in a column header, extend the column definition like so:
*
* var columns = [
* {
* id: 'myColumn',
* name: 'My column',
*
* // This is the relevant part
* header: {
* buttons: [
* {
* // button options
* },
* {
* // button options
* }
* ]
* }
* }
* ];
*
* Available button options:
* cssClass: CSS class to add to the button.
* image: Relative button image path.
* tooltip: Button tooltip.
* showOnHover: Only show the button on hover.
* handler: Button click handler.
* command: A command identifier to be passed to the onCommand event handlers.
*
* The plugin exposes the following events:
* onCommand: Fired on button click for buttons with 'command' specified.
* Event args:
* grid: Reference to the grid.
* column: Column definition.
* command: Button command identified.
* button: Button options. Note that you can change the button options in your
* event handler, and the column header will be automatically updated to
* reflect them. This is useful if you want to implement something like a
* toggle button.
*
*
* @param options {Object} Options:
* buttonCssClass: a CSS class to use for buttons (default 'slick-header-button')
* @class Slick.Plugins.HeaderButtons
* @constructor
*/
function HeaderButtons(options) {
var _grid;
var _self = this;
var _handler = new window.Slick.EventHandler();
var _defaults = {
buttonCssClass: 'slick-header-button',
};
function init(grid) {
options = $.extend(true, {}, _defaults, options);
_grid = grid;
_handler
.subscribe(_grid.onHeaderCellRendered, handleHeaderCellRendered)
.subscribe(_grid.onBeforeHeaderCellDestroy, handleBeforeHeaderCellDestroy);
// Force the grid to re-render the header now that the events are hooked up.
_grid.setColumns(_grid.getColumns());
}
function destroy() {
_handler.unsubscribeAll();
}
function handleHeaderCellRendered(e, args) {
var column = args.column;
if (column.header && column.header.buttons) {
// Append buttons in reverse order since they are floated to the right.
var i = column.header.buttons.length;
while (i--) {
var button = column.header.buttons[i];
var btn = $('<div></div>')
.addClass(options.buttonCssClass)
.data('column', column)
.data('button', button);
if (button.content){
btn.append(button.content);
}
if (button.showOnHover) {
btn.addClass('slick-header-button-hidden');
}
if (button.image) {
btn.css('backgroundImage', 'url(' + button.image + ')');
}
if (button.cssClass) {
btn.addClass(button.cssClass);
}
if (button.tooltip) {
btn.attr('title', button.tooltip);
}
if (button.command) {
btn.data('command', button.command);
}
if (button.handler) {
btn.bind('click', button.handler);
}
btn
.bind('click', handleButtonClick)
.prependTo(args.node);
}
}
}
function handleBeforeHeaderCellDestroy(e, args) {
var column = args.column;
if (column.header && column.header.buttons) {
// Removing buttons via jQuery will also clean up any event handlers and data.
// NOTE: If you attach event handlers directly or using a different framework,
// you must also clean them up here to avoid memory leaks.
$(args.node).find('.' + options.buttonCssClass).remove();
}
}
function handleButtonClick(e) {
var command = $(this).data('command');
var columnDef = $(this).data('column');
var button = $(this).data('button');
if (command != null) {
_self.onCommand.notify({
'grid': _grid,
'column': columnDef,
'command': command,
'button': button,
}, e, _self);
// Update the header in case the user updated the button definition in the handler.
_grid.updateColumnHeader(columnDef.id);
}
// Stop propagation so that it doesn't register as a header click event.
e.preventDefault();
e.stopPropagation();
}
$.extend(this, {
'init': init,
'destroy': destroy,
'onCommand': new window.Slick.Event(),
});
}
})(window.jQuery);

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define([
'sources/selection/copy_data',
'sources/selection/range_selection_helper',
],
function (copyData, RangeSelectionHelper) {
return function handleQueryOutputKeyboardEvent(event, args) {
var KEY_C = 67;
var KEY_A = 65;
var modifiedKey = event.keyCode;
var isModifierDown = event.ctrlKey || event.metaKey;
var self = this || window;
self.slickgrid = args.grid;
if (isModifierDown && modifiedKey == KEY_C) {
copyData.apply(self);
}
if (isModifierDown && modifiedKey == KEY_A) {
RangeSelectionHelper.selectAll(self.slickgrid);
}
};
});

View File

@ -1,132 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
/***
* Contains pgAdmin4 related SlickGrid formatters.
*
* @module Formatters
* @namespace Slick
*/
(function($) {
// register namespace
$.extend(true, window, {
'Slick': {
'Formatters': {
'JsonString': JsonFormatter,
'Numbers': NumbersFormatter,
'Checkmark': CheckmarkFormatter,
'Text': TextFormatter,
'Binary': BinaryFormatter,
},
},
});
function NullAndDefaultFormatter(row, cell, value, columnDef) {
if (_.isUndefined(value) && columnDef.has_default_val) {
return '<span class=\'pull-left disabled_cell\'>[default]</span>';
} else if (
(_.isUndefined(value) && columnDef.not_null) ||
(_.isUndefined(value) || value === null)
) {
return '<span class=\'pull-left disabled_cell\'>[null]</span>';
}
return null;
}
function NullAndDefaultNumberFormatter(row, cell, value, columnDef) {
if (_.isUndefined(value) && columnDef.has_default_val) {
return '<span class=\'pull-right disabled_cell\'>[default]</span>';
} else if (
(_.isUndefined(value) && columnDef.not_null) ||
(_.isUndefined(value) || value === null)
) {
return '<span class=\'pull-right disabled_cell\'>[null]</span>';
}
return null;
}
function JsonFormatter(row, cell, value, columnDef) {
// If column has default value, set placeholder
var data = NullAndDefaultFormatter(row, cell, value, columnDef);
if (data) {
return data;
} else {
// Stringify only if it's json object
if (typeof value === 'object' && !Array.isArray(value)) {
return _.escape(JSON.stringify(value));
} else if (Array.isArray(value)) {
var temp = [];
$.each(value, function(i, val) {
if (typeof val === 'object') {
temp.push(JSON.stringify(val));
} else {
temp.push(val);
}
});
return _.escape('[' + temp.join() + ']');
} else {
return _.escape(value);
}
}
}
function NumbersFormatter(row, cell, value, columnDef) {
// If column has default value, set placeholder
var data = NullAndDefaultNumberFormatter(row, cell, value, columnDef);
if (data) {
return data;
} else {
return '<span style=\'float:right\'>' + _.escape(value) + '</span>';
}
}
function CheckmarkFormatter(row, cell, value, columnDef) {
/* Checkbox has 3 states
* 1) checked=true
* 2) unchecked=false
* 3) indeterminate=null
*/
var data = NullAndDefaultFormatter(row, cell, value, columnDef);
if (data) {
return data;
} else {
return value ? 'true' : 'false';
}
}
function TextFormatter(row, cell, value, columnDef) {
// If column has default value, set placeholder
var raw = NullAndDefaultFormatter(row, cell, value, columnDef);
var data;
if (raw) {
data = raw;
} else {
data = _.escape(value);
}
// Replace leading whitespace with a marker so we don't just show blank lines
if (data.trimStart() != data) {
data = '[...] ' + data.trimStart();
}
return data;
}
function BinaryFormatter(row, cell, value, columnDef) {
// If column has default value, set placeholder
var data = NullAndDefaultFormatter(row, cell, value, columnDef);
if (data) {
return data;
} else {
return '<span class=\'pull-left disabled_cell\'>[' + _.escape(value) + ']</span>';
}
}
})(window.jQuery);

View File

@ -1,169 +0,0 @@
/*
* https://github.com/naresh-n/slickgrid-column-data-autosize
*/
(function($) {
$.extend(true, window, {
'Slick': {
'AutoColumnSize': AutoColumnSize,
},
});
function AutoColumnSize() {
var grid, $container, context,
keyCodes = {
'A': 65,
};
function init(_grid) {
grid = _grid;
$container = $(grid.getContainerNode());
$container.on('dblclick.autosize', '.slick-resizable-handle', reSizeColumn);
$container.keydown(handleControlKeys);
context = document.createElement('canvas').getContext('2d');
// Expose resizeAllColumns method to call from outside of this file.
grid.resizeAllColumns = resizeAllColumns;
}
function destroy() {
$container.off();
}
function handleControlKeys(event) {
if (event.ctrlKey && event.shiftKey && event.keyCode === keyCodes.A) {
resizeAllColumns();
}
}
function resizeAllColumns(maxWidth, max_width_changed=false) {
var elHeaders = $container.find('.slick-header-column');
var allColumns = grid.getColumns();
elHeaders.each(function(index, el) {
var columnDef = $(el).data('column');
// Check if width is set then no need to resize that column.
if (typeof(columnDef.width) !== 'undefined' && !isNaN(columnDef.width) && !max_width_changed) {
return;
}
var headerWidth = getElementWidth(el);
var colIndex = grid.getColumnIndex(columnDef.id);
var column = allColumns[colIndex];
var autoSizeWidth = Math.max(headerWidth, getMaxColumnTextWidth(columnDef, colIndex)) + 1;
// If max width is provided and it is greater than 0
if (typeof(maxWidth) !== 'undefined' && maxWidth > 0) {
autoSizeWidth = Math.min(maxWidth, autoSizeWidth);
}
column.width = autoSizeWidth + (columnDef.addWidth || 0);
});
grid.setColumns(allColumns);
grid.onColumnsResized.notify();
}
function reSizeColumn(e) {
var headerEl = $(e.currentTarget).closest('.slick-header-column');
var columnDef = headerEl.data('column');
if (!columnDef || !columnDef.resizable) {
return;
}
e.preventDefault();
e.stopPropagation();
var headerWidth = getElementWidth(headerEl[0]);
var colIndex = grid.getColumnIndex(columnDef.id);
var allColumns = grid.getColumns();
var column = allColumns[colIndex];
var autoSizeWidth = Math.max(headerWidth, getMaxColumnTextWidth(columnDef, colIndex)) + 1;
if (autoSizeWidth !== column.width) {
column.width = autoSizeWidth;
grid.setColumns(allColumns);
grid.onColumnsResized.notify();
}
}
function getMaxColumnTextWidth(columnDef, colIndex) {
var texts = [];
var rowEl = createRow();
var data = grid.getData();
if (window.Slick.Data && data instanceof window.Slick.Data.DataView) {
data = data.getItems();
}
for (let data_val of data) {
texts.push(data_val[columnDef.field]);
}
var template = getMaxTextTemplate(texts, columnDef, colIndex, data, rowEl);
var width = getTemplateWidth(rowEl, template);
deleteRow(rowEl);
return width;
}
function getTemplateWidth(rowEl, template) {
var cell = $(rowEl.find('.slick-cell'));
cell.append(template);
cell.find('*').css('position', 'relative');
return cell.outerWidth() + 1;
}
function getMaxTextTemplate(texts, columnDef, colIndex, data, rowEl) {
var max = 0,
maxTemplate = null;
var formatFun = columnDef.formatter;
$(texts).each(function(index, text) {
var template;
if (formatFun) {
template = $('<span>' + formatFun(index, colIndex, text, columnDef, data[index]) + '</span>');
text = template.text() || text;
}
var length = text ? getElementWidthUsingCanvas(rowEl, text) : 0;
if (length > max) {
max = length;
maxTemplate = template || text;
}
});
return maxTemplate;
}
function createRow() {
var rowEl = $('<div class="slick-row"><div class="slick-cell"></div></div>');
rowEl.find('.slick-cell').css({
'visibility': 'hidden',
'text-overflow': 'initial',
'white-space': 'nowrap',
});
var gridCanvas = $container.find('.grid-canvas').first();
$(gridCanvas).append(rowEl);
return rowEl;
}
function deleteRow(rowEl) {
$(rowEl).remove();
}
function getElementWidth(element) {
var width, clone = element.cloneNode(true);
clone.style.cssText = 'position: absolute; visibility: hidden;right: auto;text-overflow: initial;white-space: nowrap;';
element.parentNode.insertBefore(clone, element);
width = clone.offsetWidth;
clone.parentNode.removeChild(clone);
return width;
}
function getElementWidthUsingCanvas(element, text) {
context.font = element.css('font-size') + ' ' + element.css('font-family');
var metrics = context.measureText(text);
return metrics.width;
}
return {
init: init,
destroy: destroy,
};
}
}(window.jQuery));

View File

@ -833,18 +833,6 @@ body {
font-size: inherit !important; font-size: inherit !important;
} }
/* CSS for custom checkbox editor in SlickGrid */
.multi-checkbox .check {
display: inline-block;
vertical-align: top;
width: 16px;
height: 16px;
border: 1px solid $color-gray-dark;
margin: 3px;
text-align: center;
line-height: 16px;
}
.multi-checkbox .check.checked, .multi-checkbox .check.checked,
.multi-checkbox .check.unchecked { .multi-checkbox .check.unchecked {
background: $color-bg; background: $color-bg;

View File

@ -1,83 +0,0 @@
.slick-row .cell-actions {
text-align: left;
}
.slick-row.selected .cell-selection {
background-color: transparent; /* show default selected row background */
}
.slick-cell span[data-cell-type="row-header-selector"] {
display: block;
text-align: center;
}
/*
SlickGrid, To fix the issue of width misalignment between Column Header &
actual Column in Mozilla Firefox browser
Ref: https://github.com/mleibman/SlickGrid/issues/742
*/
.slickgrid, .slickgrid *, .slick-header-column {
box-sizing: content-box;
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box;
-ms-box-sizing: content-box;
}
.slick-cell.selected span[data-cell-type="row-header-selector"] {
color: $color-primary-fg;
}
.slick-cell.cell-move-handle {
font-weight: bold;
text-align: right;
border-right: solid $border-color;
background: $color-gray-lighter;
cursor: move;
&:hover {
background: $color-gray-light;
}
}
.cell-selection {
border-right-color: $border-color;
border-right-style: solid;
background: $color-gray-lighter;
color: $color-gray;
text-align: right;
font-size: 10px;
}
.slick-row.selected .cell-move-handle {
background: $color-warning-light;
}
.slick-row.complete {
background-color: $color-success-light;
color: $color-gray-dark;
}
.slick-row:hover .slick-cell{
border-top: $table-hover-border;
border-bottom: $table-hover-border;
background-color: $table-hover-bg-color;
}
.slick-row .slick-cell {
border-bottom: $panel-border;
border-right: $panel-border;
z-index: 0;
}
.slick-cell.active {
border: 1px solid transparent;
border-right: 1px solid $color-gray-light;
border-bottom-color: $color-gray-light;
}
.ui-widget-content.slick-row {
&.even, &.odd {
background: none;
background-color: $table-bg;
}
}

View File

@ -34,6 +34,5 @@ $theme-colors: (
@import 'jsoneditor.overrides'; @import 'jsoneditor.overrides';
@import 'pgadmin4-tree.overrides'; @import 'pgadmin4-tree.overrides';
@import 'pgadmin4-tree/src/css/styles'; @import 'pgadmin4-tree/src/css/styles';
@import 'slickgrid.overrides';
@import 'rc-dock/dist/rc-dock.css'; @import 'rc-dock/dist/rc-dock.css';
@import '@szhsin/react-menu/dist/index.css'; @import '@szhsin/react-menu/dist/index.css';

View File

@ -16,7 +16,6 @@ import gettext from 'sources/gettext';
import { sprintf, registerDetachEvent } from 'sources/utils'; import { sprintf, registerDetachEvent } from 'sources/utils';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import pgWindow from 'sources/window'; import pgWindow from 'sources/window';
import alertify from 'pgadmin.alertifyjs';
import Kerberos from 'pgadmin.authenticate.kerberos'; import Kerberos from 'pgadmin.authenticate.kerberos';
import { refresh_db_node } from 'tools/sqleditor/static/js/sqleditor_title'; import { refresh_db_node } from 'tools/sqleditor/static/js/sqleditor_title';
@ -28,6 +27,7 @@ import FunctionArguments from './debugger_ui';
import ModalProvider from '../../../../static/js/helpers/ModalProvider'; import ModalProvider from '../../../../static/js/helpers/ModalProvider';
import DebuggerComponent from './components/DebuggerComponent'; import DebuggerComponent from './components/DebuggerComponent';
import Theme from '../../../../static/js/Theme'; import Theme from '../../../../static/js/Theme';
import { showRenamePanel } from '../../../../static/js/Dialogs';
export default class DebuggerModule { export default class DebuggerModule {
static instance; static instance;
@ -713,20 +713,13 @@ export default class DebuggerModule {
} }
panel_rename_event(panel_data, panel, treeInfo) { panel_rename_event(panel_data, panel, treeInfo) {
alertify.prompt('', panel_data.$titleText[0].textContent, let name = getAppropriateLabel(treeInfo);
// We will execute this function when user clicks on the OK button
function (evt, value) {
if (value) {
// Remove the leading and trailing white spaces.
value = value.trim();
let preferences = this.pgBrowser.get_preferences_for_module('browser'); let preferences = this.pgBrowser.get_preferences_for_module('browser');
var name = getAppropriateLabel(treeInfo); let data = {
setDebuggerTitle(panel, preferences, name, treeInfo.schema.label, treeInfo.database.label, value, this.pgBrowser); function_name: name,
} schema_name: treeInfo.schema.label,
}, database_name: treeInfo.database.label
// We will execute this function when user clicks on the Cancel };
// button. Do nothing just close it. showRenamePanel(panel_data.$titleText[0].textContent, preferences, panel, 'debugger', data);
function (evt) { evt.cancel = false; }
).set({ 'title': gettext('Rename Panel') });
} }
} }

View File

@ -22,7 +22,6 @@ import gettext from 'sources/gettext';
import * as commonUtils from 'sources/utils'; import * as commonUtils from 'sources/utils';
import pgAdmin from 'sources/pgadmin'; import pgAdmin from 'sources/pgadmin';
import Loader from 'sources/components/Loader'; import Loader from 'sources/components/Loader';
import Alertify from 'pgadmin.alertifyjs';
import SchemaView from '../../../../../static/js/SchemaView'; import SchemaView from '../../../../../static/js/SchemaView';
import getApiInstance from '../../../../../static/js/api_instance'; import getApiInstance from '../../../../../static/js/api_instance';
@ -31,7 +30,7 @@ import { getAppropriateLabel, setDebuggerTitle } from '../debugger_utils';
import Notify from '../../../../../static/js/helpers/Notifier'; import Notify from '../../../../../static/js/helpers/Notifier';
import { DebuggerArgumentSchema } from './DebuggerArgs.ui'; import { DebuggerArgumentSchema } from './DebuggerArgs.ui';
import { DEBUGGER_ARGS } from '../DebuggerConstants'; import { DEBUGGER_ARGS } from '../DebuggerConstants';
import { showRenamePanel } from '../../../../../static/js/Dialogs';
const useStyles = makeStyles((theme) => const useStyles = makeStyles((theme) =>
@ -677,7 +676,6 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug,
} }
function startDebugging() { function startDebugging() {
var self = this;
setLoaderText('Starting debugger.'); setLoaderText('Starting debugger.');
try { try {
/* Initialize the target once the debug button is clicked and create asynchronous connection /* Initialize the target once the debug button is clicked and create asynchronous connection
@ -759,20 +757,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug,
// Panel Rename event // Panel Rename event
panel.on(wcDocker.EVENT.RENAME, function (panel_data) { panel.on(wcDocker.EVENT.RENAME, function (panel_data) {
Alertify.prompt('', panel_data.$titleText[0].textContent, panelRenameEvent(panel_data, panel, treeInfo);
// We will execute this function when user clicks on the OK button
function (evt, value) {
if (value) {
// Remove the leading and trailing white spaces.
value = value.trim();
var name = getAppropriateLabel(treeInfo);
setDebuggerTitle(panel, self.preferences, name, treeInfo.schema.label, treeInfo.database.label, value, pgAdmin.Browser);
}
},
// We will execute this function when user clicks on the Cancel
// button. Do nothing just close it.
function (evt) { evt.cancel = false; }
).set({ 'title': gettext('Rename Panel') });
}); });
} }
@ -861,6 +846,17 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug,
} }
function panelRenameEvent(panel_data, panel, treeInfo) {
let name = getAppropriateLabel(treeInfo);
let preferences = pgAdmin.Browser.get_preferences_for_module('browser');
let data = {
function_name: name,
schema_name: treeInfo.schema.label,
database_name: treeInfo.database.label
};
showRenamePanel(panel_data.$titleText[0].textContent, preferences, panel, 'debugger', data);
}
return ( return (
<Box className={classes.root}> <Box className={classes.root}>
<Box className={classes.body}> <Box className={classes.body}>

View File

@ -129,18 +129,7 @@ def panel(trans_id, editor_title):
trans_id=trans_id, trans_id=trans_id,
requirejs=True, requirejs=True,
basejs=True, basejs=True,
editor_title=editor_title editor_title=editor_title,
)
@blueprint.route("/schema_diff.js")
@login_required
def script():
"""render the required javascript"""
return Response(
response=render_template("schema_diff/js/schema_diff.js", _=gettext),
status=200,
mimetype=MIMETYPE_APP_JS
) )

View File

@ -1,155 +0,0 @@
.icon-compare:before {
font-size: 1.3em !important;
}
.icon-script {
display: inline-block;
align-content: center;
vertical-align: middle;
height: 18px;
width: 18px;
background-size: 20px !important;
background-repeat: no-repeat;
background-position-x: center;
background-position-y: center;
background-image: url('../img/script.svg') !important;
}
.really-hidden {
display: none !important;
}
#schema-diff-header {
padding: 0.75rem 0.7rem;
}
#schema-diff-header .control-label {
width: 120px !important;
padding: 5px 5px !important;
}
#schema-diff-grid .slick-header-column.ui-state-default {
height: 32px !important;
line-height: 25px !important;
}
#schema-diff-grid .grid-header label {
display: inline-block;
font-weight: bold;
margin: auto auto auto 6px;
}
.grid-header .ui-icon {
margin: 4px 4px auto 6px;
background-color: transparent;
border-color: transparent;
}
.slick-row .cell-actions {
text-align: left;
}
/* Slick.Editors.Text, Slick.Editors.Date */
#schema-diff-grid .slick-header > input.editor-text {
width: 100%;
height: 100%;
border: 0;
margin: 0;
background: transparent;
outline: 0;
padding: 0;
}
/* Slick.Editors.Checkbox */
#schema-diff-grid .slick-header > input.editor-checkbox {
margin: 0;
height: 100%;
padding: 0;
border: 0;
}
.slick-row.selected .cell-selection {
background-color: transparent; /* show default selected row background */
}
#schema-diff-grid .slick-header .ui-state-default,
#schema-diff-grid .slick-header .ui-widget-content.ui-state-default,
#schema-diff-grid .slick-header .ui-widget-header .ui-state-default {
background: none;
}
#schema-diff-grid .slick-header .slick-header-column {
font-weight: bold;
display: block;
}
.slick-group-toggle.collapsed, .slick-group-toggle.expanded {
background: none !important;
width: 20px;
}
.slick-group-toggle {
margin-right: 0px !important;
height: 11px !important;
}
#schema-diff-ddl-comp .badge .caret {
display: inline-block;
margin-left: 2px;
margin-right: 4px;
width: 0.7rem;
}
#schema-diff-ddl-comp .badge {
font-size: inherit;
padding: 7px;
}
#schema-diff-ddl-comp .accordian-group {
padding: 0px;
}
#ddl_comp_fetching_data.pg-sp-container {
height: 100%;
bottom: 10px;
.pg-sp-content {
position: absolute;
width: 100%;
}
}
.ddl-copy {
z-index: 10;
position: absolute;
right: 1px;
top: 1px;
}
#schema-diff-grid .pg-panel-message {
font-size: 0.875rem;
}
#schema-diff-ddl-comp .sql_field_layout {
overflow: auto !important;
height: 100%;
}
#schema-diff-ddl-comp .source_ddl, #schema-diff-ddl-comp .target_ddl, #schema-diff-ddl-comp .diff_ddl {
height: 300px;
overflow: hidden;
}
.target-buttons {
flex-wrap: wrap;
max-width: 40% !important;
}
.slick-cell .ml-2 {
margin-left: 2rem !important;
}
.slick-cell .ml-3 {
margin-left: 3rem !important;
}

View File

@ -0,0 +1,65 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
export const PANELS = {
SCHEMADIFF: 'id-schema-diff',
RESULTS: 'id-results',
};
export const TYPE = {
SOURCE: 1,
TARGET: 2
};
export const MENUS = {
COMPARE: 'schema-diff-compare',
GENERATE_SCRIPT: 'schema-diff-generate-script',
FILTER: 'schema-diff-filter'
};
export const STYLE_CONSTANT = {
IDENTICAL: 'identical',
DIFFERENT :'different',
SOURCE_ONLY: 'source',
TARGET_ONLY: 'target'
};
export const MENUS_COMPARE_CONSTANT = {
COMPARE_IGNORE_OWNER: 1,
COMPARE_IGNORE_WHITESPACE: 2
};
export const MENUS_FILTER_CONSTANT = {
FILTER_IDENTICAL: 1,
FILTER_DIFFERENT: 2,
FILTER_SOURCE_ONLY: 3,
FILTER_TARGET_ONLY: 4,
};
export const SCHEMA_DIFF_EVENT = {
TRIGGER_SELECT_SERVER: 'TRIGGER_SELECT_SERVER',
TRIGGER_SELECT_DATABASE: 'TRIGGER_SELECT_DATABASE',
TRIGGER_SELECT_SCHEMA: 'TRIGGER_SELECT_DATABASE',
TRIGGER_COMPARE_DIFF: 'TRIGGER_COMPARE_DIFF',
TRIGGER_GENERATE_SCRIPT: 'TRIGGER_GENERATE_SCRIPT',
TRIGGER_CHANGE_FILTER: 'TRIGGER_CHANGE_FILTER',
TRIGGER_CHANGE_RESULT_SQL: 'TRIGGER_CHANGE_RESULT_SQL',
TRIGGER_ROW_SELECT: 'TRIGGER_ROW_SELECT',
};
export const FILTER_NAME = {
IDENTICAL : gettext('Identical'),
DIFFERENT : gettext('Different'),
SOURCE_ONLY: gettext('Source Only'),
TARGET_ONLY: gettext('Target Only')
};

View File

@ -0,0 +1,165 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react';
import ReactDOM from 'react-dom';
import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
import pgWindow from 'sources/window';
import { registerDetachEvent } from 'sources/utils';
import { _set_dynamic_tab } from '../../../sqleditor/static/js/show_query_tool';
import getApiInstance from '../../../../static/js/api_instance';
import Theme from '../../../../static/js/Theme';
import ModalProvider from '../../../../static/js/helpers/ModalProvider';
import Notify from '../../../../static/js/helpers/Notifier';
import SchemaDiffComponent from './components/SchemaDiffComponent';
import { showRenamePanel } from '../../../../static/js/Dialogs';
export default class SchemaDiff {
static instance;
static getInstance(...args) {
if (!SchemaDiff.instance) {
SchemaDiff.instance = new SchemaDiff(...args);
}
return SchemaDiff.instance;
}
constructor(pgAdmin, pgBrowser) {
this.pgAdmin = pgAdmin;
this.pgBrowser = pgBrowser;
this.wcDocker = window.wcDocker;
this.api = getApiInstance();
}
init() {
let self = this;
if (self.initialized)
return;
self.initialized = true;
// Define the nodes on which the menus to be appear
self.pgBrowser.add_menus([{
name: 'schema_diff',
module: self,
applies: ['tools'],
callback: 'showSchemaDiffTool',
priority: 1,
label: gettext('Schema Diff'),
enable: true,
below: true,
}]);
/* Create and load the new frame required for schema diff panel */
self.frame = new self.pgBrowser.Frame({
name: 'frm_schemadiff',
title: gettext('Schema Diff'),
showTitle: true,
isCloseable: true,
isRenamable: true,
isPrivate: true,
icon: 'pg-font-icon icon-compare',
url: 'about:blank',
});
/* Cache may take time to load for the first time. Keep trying till available */
let cacheIntervalId = setInterval(function () {
if (self.pgBrowser.preference_version() > 0) {
self.preferences = self.pgBrowser.get_preferences_for_module('schema_diff');
clearInterval(cacheIntervalId);
}
}, 0);
self.pgBrowser.onPreferencesChange('schema_diff', function () {
self.preferences = self.pgBrowser.get_preferences_for_module('schema_diff');
});
self.frame.load(self.pgBrowser.docker);
}
showSchemaDiffTool() {
let self = this;
self.api({
url: url_for('schema_diff.initialize', null),
method: 'GET',
})
.then(function (res) {
self.trans_id = res.data.data.schemaDiffTransId;
res.data.data.panel_title = gettext('Schema Diff');
self.launchSchemaDiff(res.data.data);
})
.catch(function (error) {
Notify.error(gettext(`Error in schema diff initialize ${error.response.data}`));
});
}
launchSchemaDiff(data) {
let self = this;
let panelTitle = data.panel_title,
trans_id = data.schemaDiffTransId,
panelTooltip = '';
let url_params = {
'trans_id': trans_id,
'editor_title': panelTitle,
},
baseUrl = url_for('schema_diff.panel', url_params);
let browserPreferences = this.pgBrowser.get_preferences_for_module('browser');
let openInNewTab = browserPreferences.new_browser_tab_open;
if (openInNewTab && openInNewTab.includes('schema_diff')) {
window.open(baseUrl, '_blank');
// Send the signal to runtime, so that proper zoom level will be set.
setTimeout(function () {
this.pgBrowser.send_signal_to_runtime('Runtime new window opened');
}, 500);
} else {
this.pgBrowser.Events.once(
'pgadmin-browser:frame:urlloaded:frm_schemadiff',
function (frame) {
frame.openURL(baseUrl);
});
let propertiesPanel = this.pgBrowser.docker.findPanels('properties'),
schemaDiffPanel = this.pgBrowser.docker.addPanel('frm_schemadiff', this.wcDocker.DOCK.STACKED, propertiesPanel[0]);
registerDetachEvent(schemaDiffPanel);
// Panel Rename event
schemaDiffPanel.on(self.wcDocker.EVENT.RENAME, function (panel_data) {
self.panel_rename_event(panel_data, schemaDiffPanel, browserPreferences);
});
_set_dynamic_tab(this.pgBrowser, browserPreferences['dynamic_tabs']);
// Set panel title and icon
schemaDiffPanel.title('<span title="' + panelTooltip + '">' + panelTitle + '</span>');
schemaDiffPanel.icon('pg-font-icon icon-compare');
schemaDiffPanel.focus();
}
}
panel_rename_event(panel_data, panel) {
showRenamePanel(panel_data.$titleText[0].textContent, null, panel);
}
load(container, trans_id) {
ReactDOM.render(
<Theme>
<ModalProvider>
<SchemaDiffComponent params={{ transId: trans_id, pgAdmin: pgWindow.pgAdmin }}></SchemaDiffComponent>
</ModalProvider>
</Theme>,
container
);
}
}

View File

@ -0,0 +1,143 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { useContext, useState } from 'react';
import { Box, Grid, Typography } from '@material-ui/core';
import { makeStyles } from '@material-ui/styles';
import { InputSelect } from '../../../../../static/js/components/FormComponents';
import { SchemaDiffEventsContext } from './SchemaDiffComponent';
import { SCHEMA_DIFF_EVENT } from '../SchemaDiffConstants';
const useStyles = makeStyles(() => ({
root: {
padding: '0rem'
},
spanLabel: {
alignSelf: 'center',
marginRight: '4px',
},
inputLabel: {
padding: '0.3rem',
},
}));
export function InputComponent({ label, serverList, databaseList, schemaList, diff_type, selectedSid = null, selectedDid=null, selectedScid=null }) {
const classes = useStyles();
const [selectedServer, setSelectedServer] = useState(selectedSid);
const [selectedDatabase, setSelectedDatabase] = useState(selectedDid);
const [selectedSchema, setSelectedSchema] = useState(selectedScid);
const eventBus = useContext(SchemaDiffEventsContext);
const [disableDBSelection, setDisableDBSelection] = useState(selectedSid == null ? true : false);
const [disableSchemaSelection, setDisableSchemaSelection] = useState(selectedDid == null ? true : false);
const changeServer = (selectedOption) => {
setDisableDBSelection(false);
setSelectedServer(selectedOption);
// Reset the Database selection if user deselect server from DD
if(selectedOption == null){
setSelectedDatabase(null);
setDisableDBSelection(true);
setSelectedSchema(null);
setDisableSchemaSelection(true);
}
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_SELECT_SERVER, { selectedOption, diff_type, serverList });
};
const changeDatabase = (selectedDB) => {
setSelectedDatabase(selectedDB);
setDisableSchemaSelection(false);
// Reset the Schema selection if user deselect database from DD
if(selectedDB == null){
setSelectedSchema(null);
setDisableSchemaSelection(true);
}
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_SELECT_DATABASE, {selectedServer, selectedDB, diff_type, databaseList});
};
const changeSchema = (selectedSC) => {
setSelectedSchema(selectedSC);
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_SELECT_SCHEMA, { selectedSC, diff_type });
};
return (
<Box className={classes.root}>
<Grid
container
direction="row"
alignItems="center"
key={_.uniqueId('c')}
>
<Grid item lg={2} md={2} sm={2} xs={2} className={classes.inputLabel} key={_.uniqueId('c')}>
<Typography id={label}>{label}</Typography>
</Grid>
<Grid item lg={4} md={4} sm={4} xs={4} className={classes.inputLabel} key={_.uniqueId('c')}>
<InputSelect
options={serverList}
onChange={changeServer}
value={selectedServer}
controlProps={
{
placeholder: 'Select server...'
}
}
key={'server_' + diff_type}
></InputSelect>
</Grid>
<Grid item lg={3} md={3} sm={3} xs={3} className={classes.inputLabel} key={_.uniqueId('c')}>
<InputSelect
options={databaseList}
onChange={changeDatabase}
value={selectedDatabase}
controlProps={
{
placeholder: 'Select Database...'
}
}
key={'database_' + diff_type}
readonly={disableDBSelection}
></InputSelect>
</Grid>
<Grid item lg={3} md={3} sm={3} xs={3} className={classes.inputLabel} key={_.uniqueId('c')}>
<InputSelect
options={schemaList}
onChange={changeSchema}
value={selectedSchema}
controlProps={
{
placeholder: 'Select Schema...'
}
}
key={'schema' + diff_type}
readonly={disableSchemaSelection}
></InputSelect>
</Grid>
</Grid>
</Box >
);
}
InputComponent.propTypes = {
label: PropTypes.string,
serverList: PropTypes.array,
databaseList:PropTypes.array,
schemaList:PropTypes.array,
diff_type:PropTypes.number,
selectedSid: PropTypes.number,
selectedDid: PropTypes.number,
selectedScid:PropTypes.number,
};

View File

@ -0,0 +1,739 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { SelectColumn } from 'react-data-grid';
import React, { useContext, useEffect, useLayoutEffect, useReducer, useRef, useState } from 'react';
import { Box } from '@material-ui/core';
import { makeStyles } from '@material-ui/styles';
import KeyboardArrowRightRoundedIcon from '@material-ui/icons/KeyboardArrowRightRounded';
import KeyboardArrowDownRoundedIcon from '@material-ui/icons/KeyboardArrowDownRounded';
import InfoIcon from '@material-ui/icons/InfoRounded';
import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
import { FILTER_NAME, SCHEMA_DIFF_EVENT } from '../SchemaDiffConstants';
import { SchemaDiffContext, SchemaDiffEventsContext } from './SchemaDiffComponent';
import { InputCheckbox } from '../../../../../static/js/components/FormComponents';
import PgReactDataGrid from '../../../../../static/js/components/PgReactDataGrid';
import Notifier from '../../../../../static/js/helpers/Notifier';
const useStyles = makeStyles((theme) => ({
root: {
paddingTop: '0.5rem',
display: 'flex',
height: '100%',
flexDirection: 'column',
color: theme.palette.text.primary,
backgroundColor: theme.otherVars.qtDatagridBg,
border: 'none',
fontSize: '13px',
'& .rdg': {
flex: 1,
borderTop: '1px solid' + theme.otherVars.borderColor,
},
'--rdg-background-color': theme.palette.default.main,
'--rdg-selection-color': theme.palette.primary.main,
'& .rdg-cell': {
padding: 0,
boxShadow: 'none',
color: theme.otherVars.schemaDiff.diffColorFg + ' !important',
...theme.mixins.panelBorder.right,
...theme.mixins.panelBorder.bottom,
'&[aria-colindex="1"]': {
padding: 0,
},
'&[aria-selected=true]:not([role="columnheader"]):not([aria-colindex="1"])': {
outlineWidth: '0',
outlineOffset: '-1px',
color: theme.otherVars.qtDatagridSelectFg,
},
'&[aria-selected=true][aria-colindex="1"]': {
outlineWidth: 0,
}
},
'& .rdg-header-row .rdg-cell': {
padding: 0,
paddingLeft: '0.5rem',
boxShadow: 'none',
},
'& .rdg-header-row': {
backgroundColor: theme.palette.background.default,
},
'& .rdg-row': {
backgroundColor: theme.palette.background.default,
'&[aria-selected=true]': {
backgroundColor: theme.palette.primary.light,
color: theme.otherVars.qtDatagridSelectFg,
'& .rdg-cell:nth-child(1)': {
backgroundColor: 'transparent',
outlineColor: 'transparent',
color: theme.palette.primary.contrastText,
}
},
}
},
grid: {
fontSize: '13px',
'--rdg-selection-color': 'none'
},
subRow: {
paddingLeft: '1rem'
},
recordRow: {
marginLeft: '2.7rem',
height: '1.3rem',
width: '1.3rem',
display: 'inline-block',
marginRight: '0.3rem',
paddingLeft: '0.5rem',
},
rowIcon: {
display: 'inline-block !important',
height: '1.3rem',
width: '1.3rem'
},
cellExpand: {
float: 'inline-end',
display: 'table',
blockSize: '100%',
'& span': {
display: 'table-cell',
verticalAlign: 'middle',
cursor: 'pointer',
}
},
gridPanel: {
'--rdg-background-color': theme.palette.default.main + ' !important',
},
source: {
backgroundColor: theme.otherVars.schemaDiff.sourceRowColor,
color: theme.otherVars.schemaDiff.diffSelectFG,
paddingLeft: '0.5rem',
},
target: {
backgroundColor: theme.otherVars.schemaDiff.targetRowColor,
color: theme.otherVars.schemaDiff.diffSelectFG,
paddingLeft: '0.5rem',
},
different: {
backgroundColor: theme.otherVars.schemaDiff.diffRowColor,
color: theme.otherVars.schemaDiff.diffSelectFG,
paddingLeft: '0.5rem',
},
identical: {
paddingLeft: '0.5rem',
color: theme.otherVars.schemaDiff.diffSelectFG,
},
selectCell: {
padding: '0 0.3rem'
},
headerSelectCell: {
padding: '0rem 0.3rem 0 0.3rem'
},
count: {
display: 'inline-block !important',
},
countStyle: {
fontWeight: 900,
fontSize: '0.8rem',
paddingLeft: '0.3rem',
},
countLabel: {
paddingLeft: '1rem',
},
selectedRow: {
paddingLeft: '0.5rem',
backgroundColor: theme.palette.primary.light
},
selectedRowCheckBox: {
paddingLeft: '0.5rem',
backgroundColor: theme.palette.primary.light,
},
selChBox: {
paddingLeft: 0,
},
noRowsIcon:{
width: '1.1rem',
height: '1.1rem',
marginRight: '0.5rem',
}
}));
function useFocusRef(isSelected) {
const ref = useRef(null);
useLayoutEffect(() => {
if (!isSelected) return;
ref.current?.focus({ preventScroll: true });
}, [isSelected]);
return {
ref,
tabIndex: isSelected ? 0 : -1
};
}
function setRecordCount(row, filterParams) {
row['identicalCount'] = 0;
row['differentCount'] = 0;
row['sourceOnlyCount'] = 0;
row['targetOnlyCount'] = 0;
row.children.map((ch) => {
if (filterParams.includes(ch.status)) {
if (ch.status == FILTER_NAME.IDENTICAL) {
row['identicalCount'] = row['identicalCount'] + 1;
} else if (ch.status == FILTER_NAME.DIFFERENT) {
row['differentCount'] = row['differentCount'] + 1;
} else if (ch.status == FILTER_NAME.SOURCE_ONLY) {
row['sourceOnlyCount'] = row['sourceOnlyCount'] + 1;
} else if (ch.status == FILTER_NAME.TARGET_ONLY) {
row['targetOnlyCount'] = row['targetOnlyCount'] + 1;
}
}
});
}
function CellExpanderFormatter({
row,
isCellSelected,
expanded,
filterParams,
onCellExpand
}) {
const classes = useStyles();
const { ref, tabIndex } = useFocusRef(isCellSelected);
'identicalCount' in row && setRecordCount(row, filterParams);
function handleKeyDown(e) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
onCellExpand();
}
}
return (
<div className={classes.cellExpand}>
<span onClick={onCellExpand} onKeyDown={handleKeyDown}>
<span ref={ref} tabIndex={tabIndex} className={'identicalCount' in row ? classes.subRow : null}>
{expanded ? <KeyboardArrowDownRoundedIcon /> : <KeyboardArrowRightRoundedIcon />} <span className={clsx(row.icon, classes.rowIcon)}></span>{row.label}
{
'identicalCount' in row ?
<span className={clsx(classes.count)}>
<span className={classes.countLabel}>{FILTER_NAME.IDENTICAL}:</span> <span className={classes.countStyle}>{row.identicalCount} </span>
<span className={classes.countLabel}>{FILTER_NAME.DIFFERENT}:</span> <span className={classes.countStyle}>{row.differentCount} </span>
<span className={classes.countLabel}>{FILTER_NAME.SOURCE_ONLY}:</span> <span className={classes.countStyle}>{row.sourceOnlyCount} </span>
<span className={classes.countLabel}>{FILTER_NAME.TARGET_ONLY}: </span><span className={classes.countStyle}>{row.targetOnlyCount}</span>
</span>
: null
}
</span>
</span>
</div>
);
}
CellExpanderFormatter.propTypes = {
row: PropTypes.object,
isCellSelected: PropTypes.bool,
expanded: PropTypes.bool,
onCellExpand: PropTypes.func,
filterParams: PropTypes.array,
};
function toggleSubRow(rows, id, filterParams) {
const newRows = [...rows];
const rowIndex = newRows.findIndex((r) => r.id === id);
const row = newRows[rowIndex];
if (!row) return newRows;
const { children } = row;
if (!children) return newRows;
if (children.length > 0) {
newRows[rowIndex] = { ...row, isExpanded: !row.isExpanded };
if (!row.isExpanded) {
let tempChild = [];
expandRows(children, filterParams, tempChild, newRows, rowIndex);
} else {
collapseRows(newRows, filterParams, rowIndex);
}
return newRows;
} else {
newRows.splice(rowIndex, 1);
return newRows;
}
}
function expandRows(children, filterParams, tempChild, newRows, rowIndex) {
children.map((child) => {
if ('children' in child) {
let tempSubChild = [];
child.children.map((subChild) => {
if (filterParams.includes(subChild.status)) {
tempSubChild.push(subChild);
}
});
if (tempSubChild.length > 0) {
tempChild.push(child);
}
}
else {
if (filterParams.includes(child.status)) {
tempChild.push(child);
}
}
});
if (tempChild.length > 0) {
newRows.splice(rowIndex + 1, 0, ...tempChild);
} else {
newRows.splice(rowIndex, 1);
}
}
function collapseRows(newRows, filterParams, rowIndex) {
let totalChild = 0;
let filteredChild = newRows[rowIndex].children.filter((el) => {
if (el?.children) {
let clist = el.children.filter((subch) => {
return filterParams.includes(subch.status);
});
if (clist.length > 0) {
return el;
}
} else {
return el?.status ? filterParams.includes(el.status) : el;
}
});
let totalChCount = filteredChild.length;
for (let i = 0; i <= filteredChild.length; i++) {
let _index = i + 1;
let indx = totalChild ? rowIndex + totalChild + _index : rowIndex + _index;
if (newRows[indx]?.isExpanded) {
let filteredSubChild = newRows[indx].children.filter((el) => {
return filterParams.includes(el.status);
});
totalChild += filteredSubChild.length;
}
}
newRows.splice(rowIndex + 1, totalChild ? totalChCount + totalChild : totalChCount);
}
function getChildrenRows(data) {
if ('children' in data) {
return data.children;
}
return data;
}
function checkRowExpanedStatus(rows, record) {
if (rows.length > 0) {
let tempRecord = rows.filter((rec) => {
return rec.parentId == record.parentId && rec.label == record.label;
});
return tempRecord.length > 0 ? tempRecord[0].isExpanded : false;
}
return false;
}
function prepareRows(rows, gridData, filterParams) {
let newRows = [];
let adedIds = [];
gridData.map((record) => {
let childrens = getChildrenRows(record);
if (childrens.length > 0) {
childrens.map((child) => {
let subChildrens = getChildrenRows(child);
let tempChildList = [];
subChildrens.map((subChild) => {
if (filterParams.includes(subChild.status)) {
tempChildList.push(subChild);
adedIds.push(subChild.id);
}
});
if (!adedIds.includes(record.id) && tempChildList.length > 0) {
adedIds.push(record.id);
record.isExpanded = true;
newRows.push(record);
}
if (!adedIds.includes(child.id) && tempChildList.length > 0) {
adedIds.push(child.id);
child.isExpanded = checkRowExpanedStatus(rows, child);
newRows.push(child);
newRows = checkAndAddChild(child, newRows, tempChildList);
}
});
}
});
return newRows;
}
function checkAndAddChild(child, newRows, tempChildList) {
if (child.isExpanded) {
newRows = newRows.concat(tempChildList);
}
return newRows;
}
function reducer(rows, { type, id, filterParams, gridData }) {
switch (type) {
case 'toggleSubRow':
return toggleSubRow(rows, id, filterParams);
case 'applyFilter':
return prepareRows(rows, gridData, filterParams);
default:
return rows;
}
}
function getStyleClassName(row, selectedRowIds, isCellSelected, activeRowId, isCheckbox = false) {
const classes = useStyles();
let clsName = null;
if (selectedRowIds.includes(`${row.id}`) || isCellSelected || row.id == activeRowId) {
clsName = isCheckbox ? classes.selectedRowCheckBox : classes.selectedRow;
} else {
if (row.status == FILTER_NAME.DIFFERENT) {
clsName = classes.different;
} else if (row.status == FILTER_NAME.SOURCE_ONLY) {
clsName = classes.source;
} else if (row.status == FILTER_NAME.TARGET_ONLY) {
clsName = classes.target;
} else if (row.status == FILTER_NAME.IDENTICAL) {
clsName = classes.identical;
}
}
return clsName;
}
export function ResultGridComponent({ gridData, allRowIds, filterParams, selectedRowIds, transId, sourceData, targetData }) {
const classes = useStyles();
const [rows, dispatch] = useReducer(reducer, [...gridData]);
const [selectedRows, setSelectedRows] = useState([]);
const [rootSelection, setRootSelection] = useState(false);
const [activeRow, setActiveRow] = useState(null);
const schemaDiffToolContext = useContext(SchemaDiffContext);
function checkAllChildInclude(row, tempSelectedRows) {
let isChildAllInclude = true;
row.metadata.children.map((id) => {
if (!tempSelectedRows.includes(id) && id !== `${row.id}`) {
isChildAllInclude = false;
}
});
return isChildAllInclude;
}
function selectedResultRows(row, tempSelectedRows) {
if (row.metadata.isRoot) {
tempSelectedRows.push(`${row.id}`, ...row.metadata.children);
tempSelectedRows.push(...row.metadata.subChildren);
} else if (row.metadata.subChildren) {
tempSelectedRows.push(...row.metadata.dependencies);
tempSelectedRows.push(`${row.id}`, ...row.metadata.subChildren);
let isChildAllInclude = checkAllChildInclude(row, tempSelectedRows);
isChildAllInclude && tempSelectedRows.push(`${row.metadata.parentId}`);
} else {
tempSelectedRows.push(...row.dependencieRowIds);
tempSelectedRows.push(`${row.id}`);
let isChildAllInclude = checkAllChildInclude(row, tempSelectedRows);
isChildAllInclude && tempSelectedRows.push(`${row.metadata.parentId}`);
for (let i = 0; i < rows.length; i++) {
if (rows[i].id == row.metadata.parentId) {
let isChildInclude = checkAllChildInclude(rows[i], tempSelectedRows);
isChildInclude && tempSelectedRows.push(`${rows[i].metadata.parentId}`);
break;
}
}
}
}
function deselectChildAndSubChild(children, tempSelectedRows) {
children.map((chid) => {
let indx = tempSelectedRows.indexOf(chid);
indx != -1 && tempSelectedRows.splice(indx, 1);
});
}
function deselectResultRows(row, tempSelectedRows) {
if (row.metadata.isRoot) {
deselectChildAndSubChild(row.metadata.subChildren, tempSelectedRows);
deselectChildAndSubChild(row.metadata.children, tempSelectedRows);
let rootIndex = tempSelectedRows.indexOf(`${row.id}`);
rootIndex != -1 && tempSelectedRows.splice(rootIndex, 1);
} else if (row.metadata.subChildren) {
deselectChildAndSubChild(row.metadata.subChildren, tempSelectedRows);
let isChildAllInclude = true;
row.metadata.children.map((id) => {
if (tempSelectedRows.includes(id)) {
isChildAllInclude = false;
}
});
let rootIndex = tempSelectedRows.indexOf(`${row.id}`);
rootIndex != -1 && tempSelectedRows.splice(rootIndex, 1);
let parentIndex = tempSelectedRows.indexOf(`${row.metadata.parentId}`);
(!isChildAllInclude && parentIndex != -1) && tempSelectedRows.splice(parentIndex, 1);
row.metadata.dependencies.map((depid) => {
let depIndex = tempSelectedRows.indexOf(`${depid}`);
depIndex != -1 && tempSelectedRows.splice(depIndex, 1);
});
} else {
let elementIndex = tempSelectedRows.indexOf(`${row.id}`);
elementIndex != -1 && tempSelectedRows.splice(elementIndex, 1);
let parentElIndex = tempSelectedRows.indexOf(`${row.metadata.parentId}`);
parentElIndex != -1 && tempSelectedRows.splice(parentElIndex, 1);
let rootIndex = tempSelectedRows.indexOf(`${row.metadata.rootId}`);
rootIndex != -1 && tempSelectedRows.splice(rootIndex, 1);
row.dependencieRowIds.map((id) => {
let deptRowIndex = tempSelectedRows.indexOf(`${id}`);
deptRowIndex != -1 && tempSelectedRows.splice(deptRowIndex, 1);
});
}
}
const columns = [
{
key: 'id',
...SelectColumn,
minWidth: 30,
width: 30,
headerRenderer() {
return (
<InputCheckbox
cid={_.uniqueId('rgc')}
className={classes.headerSelectCell}
value={selectedRows.length == allRowIds.length ? rootSelection : false}
size='small'
onChange={(e) => {
if (e.target.checked) {
setRootSelection(true);
setSelectedRows([...allRowIds]);
selectedRowIds([...allRowIds]);
} else {
setRootSelection(false);
setSelectedRows([]);
selectedRowIds([]);
}
}
}
></InputCheckbox>
);
},
formatter({ row, isCellSelected }) {
isCellSelected && setActiveRow(row.id);
return (
<Box className={!row?.children && clsx(getStyleClassName(row, selectedRows, isCellSelected, activeRow, true), classes.selChBox)}>
<InputCheckbox
className={classes.selectCell}
cid={`${row.id}`}
value={selectedRows.includes(`${row.id}`)}
size='small'
onChange={(e) => {
setSelectedRows((prev) => {
let tempSelectedRows = [...prev];
if (!prev.includes(e.target.id)) {
selectedResultRows(row, tempSelectedRows);
tempSelectedRows.length === allRowIds.length && setRootSelection(true);
} else {
deselectResultRows(row, tempSelectedRows);
}
tempSelectedRows = new Set(tempSelectedRows);
selectedRowIds([...tempSelectedRows]);
return [...tempSelectedRows];
});
}
}
></InputCheckbox>
</Box>
);
}
},
{
key: 'label',
name: 'Objects',
width: '80%',
colSpan(args) {
if (args.type === 'ROW' && 'children' in args.row) {
return 2;
}
return 1;
},
formatter({ row, isCellSelected }) {
const hasChildren = row.children !== undefined;
isCellSelected && setActiveRow(row.id);
return (
<>
{hasChildren && (
<CellExpanderFormatter
row={row}
isCellSelected={isCellSelected}
expanded={row.isExpanded === true}
filterParams={filterParams}
onCellExpand={() => dispatch({ id: row.id, type: 'toggleSubRow', filterParams: filterParams, gridData: gridData, selectedRows: selectedRows })}
/>
)}
<div className="rdg-cell-value">
{!hasChildren && (
<Box className={clsx(getStyleClassName(row, selectedRows, isCellSelected, activeRow), classes.status)}>
<span className={clsx(classes.recordRow, row.icon)}></span>
{row.label}
</Box>
)}
</div>
</>
);
}
},
{
key: 'status',
name: 'Comparison Result',
formatter({ row, isCellSelected }) {
isCellSelected && setActiveRow(row.id);
return (
<Box className={getStyleClassName(row, selectedRows, isCellSelected, activeRow)}>
{row.status}
</Box>
);
}
},
];
useEffect(() => {
let tempRows = gridData;
tempRows.map((row) => {
dispatch({ id: row.id, type: 'applyFilter', filterParams: filterParams, gridData: gridData, selectedRows: selectedRows });
});
}, [filterParams]);
const eventBus = useContext(SchemaDiffEventsContext);
const rowSelection = (row) => {
if (row.ddlData != undefined && row.status != FILTER_NAME.IDENTICAL) {
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, row.ddlData);
} else if (row.status == FILTER_NAME.IDENTICAL) {
let url_params = {
'trans_id': transId,
'source_sid': sourceData.sid,
'source_did': sourceData.did,
'source_scid': row.source_scid,
'target_sid': targetData.sid,
'target_did': targetData.did,
'target_scid': row.target_scid,
'comp_status': row.status,
'source_oid': row.source_oid,
'target_oid': row.target_oid,
'node_type': row.itemType,
};
let baseUrl = url_for('schema_diff.ddl_compare', url_params);
schemaDiffToolContext.api.get(baseUrl).then((res) => {
row.ddlData = {
'SQLdiff': res.data.diff_ddl,
'sourceSQL': res.data.source_ddl,
'targetSQL': res.data.target_ddl
};
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, row.ddlData);
}).catch((err) => {
Notifier.alert(err.message);
});
}
};
function rowKeyGetter(row) {
return row.id;
}
return (
<Box className={classes.root} flexGrow="1" minHeight="0" id="schema-diff-grid">
{
gridData ?
<PgReactDataGrid
id="schema-diff-result-grid"
columns={columns} rows={rows}
className={clsx('big-grid', classes.gridPanel, classes.grid)}
treeDepth={2}
enableRowSelect={true}
defaultColumnOptions={{
resizable: true
}}
headerRowHeight={28}
rowHeight={28}
onRowClick={rowSelection}
enableCellSelect={false}
rowKeyGetter={rowKeyGetter}
direction={'vertical-lr'}
noRowsText={gettext('No difference found')}
noRowsIcon={<InfoIcon className={classes.noRowsIcon} />}
/>
:
<>
{gettext('Loading result grid...')}
</>
}
</Box>
);
}
ResultGridComponent.propTypes = {
gridData: PropTypes.array,
allRowIds: PropTypes.array,
filterParams: PropTypes.array,
selectedRowIds: PropTypes.func,
transId: PropTypes.number,
sourceData: PropTypes.object,
targetData: PropTypes.object,
'sourceData.sid': PropTypes.number,
'sourceData.did': PropTypes.number,
'targetData.sid': PropTypes.number,
'targetData.did': PropTypes.number,
};

View File

@ -0,0 +1,140 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import React, { useContext, useState, useEffect } from 'react';
import { Box } from '@material-ui/core';
import { makeStyles } from '@material-ui/styles';
import { InputSQL } from '../../../../../static/js/components/FormComponents';
import { SchemaDiffEventsContext } from './SchemaDiffComponent';
import { SCHEMA_DIFF_EVENT } from '../SchemaDiffConstants';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
},
table: {
minWidth: 650,
},
summaryContainer: {
flexGrow: 1,
minHeight: 0,
overflow: 'auto',
},
panelTitle: {
borderBottom: '1px solid ' + theme.otherVars.borderColor,
padding: '0.5rem',
},
editorStyle: {
height: '100%'
},
editor: {
height: '100%',
padding: '0.5rem 0.2rem 2rem 0.5rem',
},
editorLabel: {
padding: '0.3rem 0.6rem 0 0.6rem',
},
header: {
padding: '0.5rem',
borderBottom: '1px solid ' + theme.otherVars.borderColor,
},
sqlContainer: {
display: 'flex',
flexDirection: 'row',
padding: '0rem 0rem 0.5rem',
flexGrow: 1,
overflow: 'hidden'
},
sqldata: {
display: 'flex',
flexGrow: 1,
flexDirection: 'column',
padding: '0.2rem 0.5rem',
width: '33.33%',
},
label: {
flexGrow: 1,
}
}));
export function Results() {
const classes = useStyles();
const [sourceSQL, setSourceSQL] = useState(null);
const [targetSQL, setTargetSQL] = useState(null);
const [sqlDiff, setSqlDiff] = useState(null);
const eventBus = useContext(SchemaDiffEventsContext);
useEffect(() => {
eventBus.registerListener(
SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, triggerUpdateResult);
}, []);
const triggerUpdateResult = (resultData) => {
setSourceSQL(resultData.sourceSQL);
setTargetSQL(resultData.targetSQL);
setSqlDiff(resultData.SQLdiff);
};
return (
<>
<Box className={classes.header}>
<span>{gettext('DDL Comparision')}</span>
</Box>
<Box className={classes.sqlContainer}>
<Box className={classes.sqldata}>
<Box className={classes.label}>{gettext('Source')}</Box>
<InputSQL
onLable={true}
value={sourceSQL}
options={{
readOnly: true,
}}
readonly={true}
/>
</Box>
<Box className={classes.sqldata}>
<Box className={classes.label}>{gettext('Target')}</Box>
<InputSQL
onLable={true}
value={targetSQL}
options={{
readOnly: true,
}}
readonly={true}
/>
</Box>
<Box className={classes.sqldata}>
<Box className={classes.label}>{gettext('Difference')}</Box>
<InputSQL
onLable={true}
value={sqlDiff}
options={{
readOnly: true,
}}
readonly={true}
/>
</Box>
</Box>
</>
);
}
Results.propTypes = {
};

View File

@ -0,0 +1,223 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { useState, useRef, useContext, useEffect } from 'react';
import gettext from 'sources/gettext';
import { Box } from '@material-ui/core';
import CompareArrowsRoundedIcon from '@material-ui/icons/CompareArrowsRounded';
import FeaturedPlayListRoundedIcon from '@material-ui/icons/FeaturedPlayListRounded';
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
import { makeStyles } from '@material-ui/styles';
import { DefaultButton, PgButtonGroup, PgIconButton, PrimaryButton } from '../../../../../static/js/components/Buttons';
import { FilterIcon } from '../../../../../static/js/components/ExternalIcon';
import { PgMenu, PgMenuItem, usePgMenuGroup } from '../../../../../static/js/components/Menu';
import { FILTER_NAME, MENUS, MENUS_COMPARE_CONSTANT, SCHEMA_DIFF_EVENT } from '../SchemaDiffConstants';
import { SchemaDiffContext, SchemaDiffEventsContext } from './SchemaDiffComponent';
const useStyles = makeStyles((theme) => ({
emptyIcon: {
width: '1.5rem'
},
diff_btn: {
marginRight: '1rem'
},
noactionBtn: {
cursor: 'default',
'&:hover': {
backgroundColor: 'inherit',
cursor: 'default'
}
},
scriptBtn: {
display: 'flex',
justifyContent: 'flex-end',
paddingRight: '0.3rem',
[theme.breakpoints.down('sm')]: {
paddingTop: '0.3rem',
flexGrow: 1,
},
},
filterBtn: {
[theme.breakpoints.down('sm')]: {
paddingTop: '0.3rem',
flexGrow: 1,
}
},
compareBtn: {
display: 'flex',
flexGrow: 1,
justifyContent: 'flex-start',
paddingLeft: '1.5rem',
[theme.breakpoints.down('sm')]: {
paddingTop: '0.3rem',
},
}
}));
export function SchemaDiffButtonComponent({ sourceData, targetData, selectedRowIds, rows, compareParams, filterParams = [FILTER_NAME.DIFFERENT, FILTER_NAME.SOURCE_ONLY, FILTER_NAME.TARGET_ONLY] }) {
const classes = useStyles();
const filterRef = useRef(null);
const compareRef = useRef(null);
const eventBus = useContext(SchemaDiffEventsContext);
const schemaDiffCtx = useContext(SchemaDiffContext);
const [selectedFilters, setSelectedFilters] = useState(filterParams);
const [selectedCompare, setSelectedCompare] = useState([]);
const [isDisableCompare, setIsDisableCompare] = useState(true);
const { openMenuName, toggleMenu, onMenuClose } = usePgMenuGroup();
useEffect(() => {
let isDisableComp = true;
if (sourceData.sid != null && sourceData.did != null && targetData.sid != null && targetData.did != null) {
isDisableComp = false;
}
setIsDisableCompare(isDisableComp);
}, [sourceData, targetData]);
useEffect(() => {
let prefCompareOptions = [];
if (!_.isUndefined(compareParams)) {
compareParams.ignoreOwner && prefCompareOptions.push(MENUS_COMPARE_CONSTANT.COMPARE_IGNORE_OWNER);
compareParams.ignoreWhitespaces && prefCompareOptions.push(MENUS_COMPARE_CONSTANT.COMPARE_IGNORE_WHITESPACE);
setSelectedCompare(prefCompareOptions);
} else {
schemaDiffCtx?.preferences_schema_diff?.ignore_owner && prefCompareOptions.push(MENUS_COMPARE_CONSTANT.COMPARE_IGNORE_OWNER);
schemaDiffCtx?.preferences_schema_diff?.ignore_whitespaces && prefCompareOptions.push(MENUS_COMPARE_CONSTANT.COMPARE_IGNORE_WHITESPACE);
setSelectedCompare(prefCompareOptions);
}
}, [schemaDiffCtx.preferences_schema_diff]);
const selectFilterOption = (option) => {
let newOptions = [];
setSelectedFilters((prev) => {
let newSelectdOptions = [...prev];
let removeIndex = newSelectdOptions.indexOf(option);
if (prev.includes(option)) {
newSelectdOptions.splice(removeIndex, 1);
} else {
newSelectdOptions.push(option);
}
newOptions = [...newSelectdOptions];
return newSelectdOptions;
});
let filterParam = newOptions;
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_FILTER, { filterParams: filterParam });
};
const selectCompareOption = (option) => {
setSelectedCompare((prev) => {
let newSelectdOptions = [...prev];
let removeIndex = newSelectdOptions.indexOf(option);
if (prev.includes(option)) {
newSelectdOptions.splice(removeIndex, 1);
} else {
newSelectdOptions.push(option);
}
return newSelectdOptions;
});
};
const compareDiff = () => {
let compareParam = {
'ignoreOwner': selectedCompare.includes(MENUS_COMPARE_CONSTANT.COMPARE_IGNORE_OWNER) ? 1 : 0,
'ignoreWhitespaces': selectedCompare.includes(MENUS_COMPARE_CONSTANT.COMPARE_IGNORE_WHITESPACE) ? 1 : 0,
};
let filterParam = selectedFilters;
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_COMPARE_DIFF, { sourceData, targetData, compareParams: compareParam, filterParams: filterParam });
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, {
sourceSQL: null,
targetSQL: null,
SQLdiff: null,
});
};
const generateScript = () => {
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_GENERATE_SCRIPT, { sid: targetData.sid, did: targetData.did, selectedIds: selectedRowIds, rows: rows });
};
return (
<>
<Box className={classes.compareBtn}>
<PgButtonGroup size="small" disabled={isDisableCompare}>
<PrimaryButton startIcon={<CompareArrowsRoundedIcon />}
onClick={compareDiff}>{gettext('Compare')}</PrimaryButton>
<PgIconButton title={gettext('Compare')} icon={<KeyboardArrowDownIcon />} color={'primary'} splitButton
name={MENUS.COMPARE} ref={compareRef} onClick={toggleMenu} ></PgIconButton>
</PgButtonGroup>
</Box>
<Box className={classes.scriptBtn}>
<PgButtonGroup size="small" disabled={selectedRowIds?.length > 0 ? false : true}>
<DefaultButton startIcon={<FeaturedPlayListRoundedIcon />} onClick={generateScript}>{gettext('Generate Script')}</DefaultButton>
</PgButtonGroup>
</Box>
<Box className={classes.filterBtn}>
<PgButtonGroup size="small" disabled={isDisableCompare} style={{ paddingRight: '0.3rem' }}>
<DefaultButton startIcon={<FilterIcon />} className={classes.noactionBtn}
>{gettext('Filter')}</DefaultButton>
<PgIconButton title={gettext('File')} icon={<KeyboardArrowDownIcon />} splitButton
name={MENUS.FILTER} ref={filterRef} onClick={toggleMenu} ></PgIconButton>
</PgButtonGroup>
</Box>
<PgMenu
anchorRef={compareRef}
open={openMenuName == MENUS.COMPARE}
onClose={onMenuClose}
label={gettext('Compare')}
>
<PgMenuItem onClick={() => { selectCompareOption(MENUS_COMPARE_CONSTANT.COMPARE_IGNORE_OWNER); }}>
{selectedCompare.includes(MENUS_COMPARE_CONSTANT.COMPARE_IGNORE_OWNER) ? <CheckRoundedIcon /> : <span className={classes.emptyIcon}></span>}{gettext('Ignore owner')}
</PgMenuItem>
<PgMenuItem onClick={() => { selectCompareOption(MENUS_COMPARE_CONSTANT.COMPARE_IGNORE_WHITESPACE); }}>
{selectedCompare.includes(MENUS_COMPARE_CONSTANT.COMPARE_IGNORE_WHITESPACE) ? <CheckRoundedIcon /> : <span className={classes.emptyIcon}></span>}{gettext('Ignore whitespace')}
</PgMenuItem>
</PgMenu>
<PgMenu
anchorRef={filterRef}
open={openMenuName == MENUS.FILTER}
onClose={onMenuClose}
label={gettext('Filter')}
>
<PgMenuItem onClick={() => { selectFilterOption(FILTER_NAME.IDENTICAL); }}>
{selectedFilters.includes(FILTER_NAME.IDENTICAL) ? <CheckRoundedIcon /> : <span className={classes.emptyIcon}></span>} {gettext(FILTER_NAME.IDENTICAL)}
</PgMenuItem>
<PgMenuItem onClick={() => { selectFilterOption(FILTER_NAME.DIFFERENT); }}>
{selectedFilters.includes(FILTER_NAME.DIFFERENT) ? <CheckRoundedIcon /> : <span className={classes.emptyIcon}></span>} {gettext(FILTER_NAME.DIFFERENT)}
</PgMenuItem>
<PgMenuItem onClick={() => { selectFilterOption(FILTER_NAME.SOURCE_ONLY); }}>
{selectedFilters.includes(FILTER_NAME.SOURCE_ONLY) ? <CheckRoundedIcon /> : <span className={classes.emptyIcon}></span>} {gettext(FILTER_NAME.SOURCE_ONLY)}
</PgMenuItem>
<PgMenuItem onClick={() => { selectFilterOption(FILTER_NAME.TARGET_ONLY); }}>
{selectedFilters.includes(FILTER_NAME.TARGET_ONLY) ? <CheckRoundedIcon /> : <span className={classes.emptyIcon}></span>} {gettext(FILTER_NAME.TARGET_ONLY)}
</PgMenuItem>
</PgMenu>
</>
);
}
SchemaDiffButtonComponent.propTypes = {
sourceData: PropTypes.object,
targetData: PropTypes.object,
selectedRowIds: PropTypes.array,
rows: PropTypes.array,
compareParams: PropTypes.object,
filterParams: PropTypes.array
};

View File

@ -0,0 +1,812 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { useContext, useEffect, useState } from 'react';
import { Box, Grid } from '@material-ui/core';
import InfoRoundedIcon from '@material-ui/icons/InfoRounded';
import HelpIcon from '@material-ui/icons/HelpRounded';
import { makeStyles } from '@material-ui/styles';
import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
import Loader from 'sources/components/Loader';
import pgWindow from 'sources/window';
import { PgButtonGroup, PgIconButton } from '../../../../../static/js/components/Buttons';
import Notifier from '../../../../../static/js/helpers/Notifier';
import ConnectServerContent from '../../../../../static/js/Dialogs/ConnectServerContent';
import { generateScript } from '../../../../sqleditor/static/js/show_query_tool';
import { FILTER_NAME, SCHEMA_DIFF_EVENT, TYPE } from '../SchemaDiffConstants';
import { InputComponent } from './InputComponent';
import { SchemaDiffButtonComponent } from './SchemaDiffButtonComponent';
import { SchemaDiffContext, SchemaDiffEventsContext } from './SchemaDiffComponent';
import { ResultGridComponent } from './ResultGridComponent';
const useStyles = makeStyles(() => ({
table: {
minWidth: 650,
},
summaryContainer: {
flexGrow: 1,
minHeight: 0,
overflow: 'auto',
},
note: {
marginTop: '1.2rem',
textAlign: 'center',
},
helpBtn: {
display: 'flex',
flexDirection: 'row-reverse',
paddingRight: '0.3rem'
},
compareComp: {
flexGrow: 1,
},
diffBtn: {
display: 'flex',
justifyContent: 'flex-end'
}
}));
function generateFinalScript(script_array, scriptHeader, script_body) {
_.each(Object.keys(script_array).reverse(), function (s) {
if (script_array[s].length > 0) {
script_body += script_array[s].join('\n') + '\n\n';
}
});
return `${scriptHeader} BEGIN; \n ${script_body} END;`;
}
function checkAndGetSchemaQuery(data, script_array) {
/* Check whether the selected object belongs to source only schema
if yes then we will have to add create schema statement before creating any other object.*/
if (!_.isUndefined(data.source_schema_name) && !_.isNull(data.source_schema_name)) {
let schema_query = '\nCREATE SCHEMA IF NOT EXISTS ' + data.source_schema_name + ';\n';
if (script_array[data.dependLevel].indexOf(schema_query) == -1) {
script_array[data.dependLevel].push(schema_query);
}
}
}
function getGenerateScriptData(rows, selectedIds, script_array) {
for (let selRowVal of rows) {
if (selectedIds.includes(`${selRowVal.id}`)) {
let data = selRowVal;
if (!_.isUndefined(data.diff_ddl)) {
if (!(data.dependLevel in script_array)) script_array[data.dependLevel] = [];
checkAndGetSchemaQuery(data, script_array);
script_array[data.dependLevel].push(data.diff_ddl);
}
}
}
}
function raiseErrorOnFail(alertTitle, xhr) {
try {
if (_.isUndefined(xhr.response.data)) {
Notifier.alert(alertTitle, gettext('Unable to get the response text.'));
} else {
let err = JSON.parse(xhr.response.data);
Notifier.alert(alertTitle, err.errormsg);
}
} catch (e) {
Notifier.alert(alertTitle, gettext(e.message));
}
}
const onHelpClick=()=>{
let url = url_for('help.static', {'filename': 'schema_diff.html'});
window.open(url, 'pgadmin_help');
};
export function SchemaDiffCompare({ params }) {
const classes = useStyles();
const schemaDiffToolContext = useContext(SchemaDiffContext);
const eventBus = useContext(SchemaDiffEventsContext);
const [showResultGrid, setShowResultGrid] = useState(false);
const [selectedSourceSid, setSelectedSourceSid] = useState(null);
const [selectedTargetSid, setSelectedTargetSid] = useState(null);
const [sourceDatabaseList, setSourceDatabaseList] = useState([]);
const [targetDatabaseList, setTargetDatabaseList] = useState([]);
const [selectedSourceDid, setSelectedSourceDid] = useState(null);
const [selectedTargetDid, setSelectedTargetDid] = useState(null);
const [sourceSchemaList, setSourceSchemaList] = useState([]);
const [targetSchemaList, setTargetSchemaList] = useState([]);
const [selectedSourceScid, setSelectedSourceScid] = useState(null);
const [selectedTargetScid, setSelectedTargetScid] = useState(null);
const [sourceGroupServerList, setSourceGroupServerList] = useState([]);
const [gridData, setGridData] = useState([]);
const [allRowIdList, setAllRowIdList] = useState([]);
const [filterOptions, setFilterOptions] = useState([]);
const [compareOptions, setCompareOptions] = useState(undefined);
const [selectedRowIds, setSelectedRowIds] = useState([]);
const [loaderText, setLoaderText] = useState(null);
const [apiResult, setApiResult] = useState([]);
const [rowDep, setRowDep] = useState({});
const [isInit, setIsInit] = useState(true);
useEffect(() => {
schemaDiffToolContext.api.get(url_for('schema_diff.servers')).then((res) => {
let groupedOptions = [];
_.forIn(res.data.data, (val, _key) => {
if (val.lenght == 0) {
return;
}
groupedOptions.push({
label: _key,
options: val
});
});
setSourceGroupServerList(groupedOptions);
}).catch((err) => {
Notifier.alert(err.message);
});
}, []);
useEffect(() => {
// Register all eventes for debugger.
eventBus.registerListener(
SCHEMA_DIFF_EVENT.TRIGGER_SELECT_SERVER, triggerSelectServer);
eventBus.registerListener(
SCHEMA_DIFF_EVENT.TRIGGER_SELECT_DATABASE, triggerSelectDatabase);
eventBus.registerListener(
SCHEMA_DIFF_EVENT.TRIGGER_SELECT_SCHEMA, triggerSelectSchema);
eventBus.registerListener(
SCHEMA_DIFF_EVENT.TRIGGER_COMPARE_DIFF, triggerCompareDiff);
eventBus.registerListener(
SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_FILTER, triggerChangeFilter);
eventBus.registerListener(
SCHEMA_DIFF_EVENT.TRIGGER_GENERATE_SCRIPT, triggerGenerateScript);
}, []);
function checkAndSetSourceData(diff_type, selectedOption) {
if(selectedOption == null) {
setSelectedRowIds([]);
setGridData([]);
if (diff_type == TYPE.SOURCE) {
setSelectedSourceSid(null);
setSelectedSourceDid(null);
setSelectedSourceScid(null);
} else {
setSelectedTargetSid(null);
setSelectedTargetDid(null);
setSelectedTargetScid(null);
}
}
}
function setSourceTargetSid(diff_type, selectedOption) {
if (diff_type == TYPE.SOURCE) {
setSelectedSourceSid(selectedOption);
} else {
setSelectedTargetSid(selectedOption);
}
}
const triggerSelectServer = ({ selectedOption, diff_type, serverList }) => {
checkAndSetSourceData(diff_type, selectedOption);
for (const group of serverList) {
for (const opt of group.options) {
if (opt.value == selectedOption) {
if (!opt.connected) {
connectServer(selectedOption, diff_type, null, serverList);
break;
} else {
setSourceTargetSid(diff_type, selectedOption);
getDatabaseList(selectedOption, diff_type);
}
}
}
}
setSourceGroupServerList(serverList);
};
const triggerSelectDatabase = ({ selectedServer, selectedDB, diff_type, databaseList }) => {
if(selectedDB == null) {
setGridData([]);
}
if (databaseList) {
for (const opt of databaseList) {
if (opt.value == selectedDB) {
if (!opt.connected) {
connectDatabase(selectedServer, selectedDB, diff_type, databaseList);
break;
} else {
getSchemaList(selectedServer, selectedDB, diff_type);
break;
}
}
}
if (diff_type == TYPE.SOURCE) {
setSelectedSourceDid(selectedDB);
setSourceDatabaseList(databaseList);
} else {
setSelectedTargetDid(selectedDB);
setTargetDatabaseList(databaseList);
}
}
};
const triggerSelectSchema = ({ selectedSC, diff_type }) => {
if (diff_type == TYPE.SOURCE) {
setSelectedSourceScid(selectedSC);
} else {
setSelectedTargetScid(selectedSC);
}
};
const triggerCompareDiff = ({ sourceData, targetData, compareParams, filterParams }) => {
setGridData([]);
setIsInit(false);
if (JSON.stringify(sourceData) === JSON.stringify(targetData)) {
Notifier.alert(gettext('Selection Error'),
gettext('Please select the different source and target.'));
} else {
getCompareStatus();
let schemaDiffPollInterval = setInterval(getCompareStatus, 1000);
setLoaderText('Comparing objects... (this may take a few minutes)...');
let url_params = {
'trans_id': params.transId,
'source_sid': sourceData['sid'],
'source_did': sourceData['did'],
'target_sid': targetData['sid'],
'target_did': targetData['did'],
'ignore_owner': compareParams['ignoreOwner'],
'ignore_whitespaces': compareParams['ignoreWhitespaces'],
};
let baseUrl = url_for('schema_diff.compare_database', url_params);
if (sourceData['scid'] != null && targetData['scid'] != null) {
url_params['source_scid'] = sourceData['scid'];
url_params['target_scid'] = targetData['scid'];
baseUrl = url_for('schema_diff.compare_schema', url_params);
}
setCompareOptions(compareParams);
schemaDiffToolContext.api.get(baseUrl).then((res) => {
setShowResultGrid(true);
setLoaderText(null);
clearInterval(schemaDiffPollInterval);
setFilterOptions(filterParams);
getResultGridData(res.data.data, filterParams);
}).catch((err) => {
setLoaderText(null);
setShowResultGrid(false);
Notifier.error(gettext(err.message));
});
}
};
const triggerChangeFilter = ({ filterParams }) => {
setFilterOptions(filterParams);
};
const triggerGenerateScript = ({ sid, did, selectedIds, rows }) => {
setLoaderText(gettext('Generating script...'));
let generatedScript = undefined, scriptHeader;
scriptHeader = gettext('-- This script was generated by the Schema Diff utility in pgAdmin 4. \n');
scriptHeader += gettext('-- For the circular dependencies, the order in which Schema Diff writes the objects is not very sophisticated \n');
scriptHeader += gettext('-- and may require manual changes to the script to ensure changes are applied in the correct order.\n');
scriptHeader += gettext('-- Please report an issue for any failure with the reproduction steps. \n');
if (selectedIds.length > 0) {
let script_array = { 1: [], 2: [], 3: [], 4: [], 5: [] },
script_body = '';
getGenerateScriptData(rows, selectedIds, script_array);
generatedScript = generateFinalScript(script_array, scriptHeader, script_body);
openQueryTool({ sid: sid, did: did, generatedScript: generatedScript, scriptHeader: scriptHeader });
} else {
openQueryTool({ sid: sid, did: did, scriptHeader: scriptHeader });
}
};
function openQueryTool({ sid, did, generatedScript, scriptHeader }) {
let baseServerUrl = url_for('schema_diff.get_server', { 'sid': sid, 'did': did });
schemaDiffToolContext.api({
url: baseServerUrl,
method: 'GET',
dataType: 'json',
contentType: 'application/json',
})
.then(function (res) {
let data = res.data.data;
let serverData = {};
if (data) {
let sqlId = `schema${params.transId}`;
serverData['sgid'] = data.gid;
serverData['sid'] = data.sid;
serverData['stype'] = data.type;
serverData['server'] = data.name;
serverData['user'] = data.user;
serverData['did'] = did;
serverData['database'] = data.database;
serverData['sql_id'] = sqlId;
if (_.isUndefined(generatedScript)) {
generatedScript = scriptHeader + 'BEGIN;' + '\n' + '' + '\n' + 'END;';
}
localStorage.setItem(sqlId, generatedScript);
generateScript(serverData, pgWindow.pgAdmin.Tools.SQLEditor);
setLoaderText(null);
}
})
.catch(function (xhr) {
setLoaderText(null);
raiseErrorOnFail(gettext('Generate script error'), xhr);
});
}
function generateGridData(record, tempData, allRowIds, filterParams) {
if (record.group_name in tempData && record.label in tempData[record.group_name]['children']) {
let chidId = record.id;
allRowIds.push(`${chidId}`);
tempData[record.group_name]['children'][record.label]['children'].push({
'id': chidId,
'parentId': tempData[record.group_name]['children'][record.label].id,
'label': record.title,
'status': record.status,
'isVisible': filterParams.includes(record.status) ? true : false,
'icon': `icon-${record.type}`,
'isExpanded': false,
'selected': false,
'oid': record.oid,
'itemType': record.type,
'source_oid': record.source_oid,
'target_oid': record.target_oid,
'source_scid': record.source_scid,
'target_scid': record.target_scid,
'dependenciesOid': record.dependencies.map(({ oid }) => oid),
'dependencies': record.dependencies,
'dependencieRowIds': [],
'ddlData': {
'SQLdiff': record.diff_ddl,
'sourceSQL': record.source_ddl,
'targetSQL': record.target_ddl
}
});
} else if (record.group_name in tempData) {
let chidId = Math.floor(Math.random() * 1000000);
allRowIds.push(`${chidId}`);
let subChildId = record.id;
allRowIds.push(`${subChildId}`);
tempData[record.group_name]['children'][record.label] = {
'id': chidId,
'parentId': tempData[record.group_name]['id'],
'label': record.label,
'identicalCount': 0,
'differentCount': 0,
'sourceOnlyCount': 0,
'targetOnlyCount': 0,
'icon': `icon-coll-${record.type}`,
'isExpanded': false,
'selected': false,
'children': [{
'id': subChildId,
'parentId': chidId,
'label': record.title,
'status': record.status,
'isVisible': filterParams.includes(record.status) ? true : false,
'icon': `icon-${record.type}`,
'isExpanded': false,
'selected': false,
'oid': record.oid,
'itemType': record.type,
'source_oid': record.source_oid,
'target_oid': record.target_oid,
'source_scid': record.source_scid,
'target_scid': record.target_scid,
'dependenciesOid': record.dependencies.map(({ oid }) => oid),
'dependencies': record.dependencies,
'dependencieRowIds': [],
'ddlData': {
'SQLdiff': record.diff_ddl,
'sourceSQL': record.source_ddl,
'targetSQL': record.target_ddl
}
}]
};
} else {
let label = record.label;
let _id = Math.floor(Math.random() * 100000);
let _subChildId = Math.floor(Math.random() * 100000);
allRowIds.push(`${_id}`);
allRowIds.push(`${_subChildId}`);
tempData[record.group_name] = {
'id': _id,
'label': record.group_name,
'icon': record.group_name == 'Database Objects' ? 'icon-coll-database' : 'icon-schema',
'groupType': record.group_name,
'isExpanded': false,
'selected': false,
'children': {}
};
let ch_id = record.id;
allRowIds.push(`${ch_id}`);
tempData[record.group_name]['children'][label] = {
'id': _subChildId,
'parentId': _id,
'label': record.label,
'identicalCount': 0,
'differentCount': 0,
'sourceOnlyCount': 0,
'targetOnlyCount': 0,
'selected': false,
'icon': `icon-coll-${record.type}`,
'isExpanded': false,
'children': [{
'id': ch_id,
'parentId': _subChildId,
'label': record.title,
'status': record.status,
'selected': false,
'itemType': record.type,
'isVisible': filterParams.includes(record.status) ? true : false,
'icon': `icon-${record.type}`,
'isExpanded': false,
'oid': record.oid,
'source_oid': record.source_oid,
'target_oid': record.target_oid,
'source_scid': record.source_scid,
'target_scid': record.target_scid,
'dependenciesOid': record.dependencies.map(({ oid }) => oid),
'dependencies': record.dependencies,
'dependencieRowIds': [],
'ddlData': {
'SQLdiff': record.diff_ddl,
'sourceSQL': record.source_ddl,
'targetSQL': record.target_ddl
}
}]
};
}
}
function getResultGridData(gridDataList, filterParams) {
let tempData = {};
let allRowIds = [];
setApiResult(gridDataList);
gridDataList.map((record) => {
generateGridData(record, tempData, allRowIds, filterParams);
});
let keyList = Object.keys(tempData);
let temp = [];
let rowDependencies = {};
for (let i = 0; i < keyList.length; i++) {
tempData[keyList[i]]['children'] = Object.values(tempData[keyList[i]]['children']);
let subChildList = [];
tempData[keyList[i]]['children'].map((ch) => ch.children.map(({ id }) => subChildList.push(`${id}`)));
tempData[keyList[i]]['metadata'] = {
isRoot: true,
children: tempData[keyList[i]]['children'].map(({ id }) => `${id}`),
subChildren: subChildList,
};
tempData[keyList[i]]['children'].map((child) => {
child['metadata'] = {
parentId: tempData[keyList[i]].id,
children: tempData[keyList[i]]['children'].map(({ id }) => `${id}`),
subChildren: child.children.map(({ id }) => `${id}`),
dependencies: [],
};
child.children.map((ch) => {
if (ch.dependenciesOid.length > 0) {
tempData[keyList[i]]['children'].map((el) => {
el.children.map((data) => {
if (ch.dependenciesOid.includes(data.oid)) {
ch.dependencieRowIds.push(`${data.id}`);
}
});
});
}
ch['metadata'] = {
parentId: child.id,
rootId: tempData[keyList[i]].id,
children: child.children.map(({ id }) => `${id}`),
};
child['metadata']['dependencies'].push(...ch.dependencieRowIds);
});
});
temp.push(tempData[keyList[i]]);
}
setRowDep(rowDependencies);
setShowResultGrid(true);
setGridData(temp);
setAllRowIdList([...new Set(allRowIds)]);
}
const getCompareStatus = () => {
let url_params = { 'trans_id': params.transId };
schemaDiffToolContext.api.get(url_for('schema_diff.poll', url_params)).then((res) => {
let msg = res.data.data.compare_msg;
if (res.data.data.diff_percentage != 100) {
msg = msg + gettext(` (this may take a few minutes)... ${res.data.data.diff_percentage} %`);
setLoaderText(msg);
}
})
.catch((err) => {
Notifier.error(gettext(err.message));
});
};
const connectDatabase = (sid, selectedDB, diff_type, databaseList) => {
schemaDiffToolContext.api({
method: 'POST',
url: url_for('schema_diff.connect_database', { 'sid': sid, 'did': selectedDB }),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
}).then((res) => {
let dbList = databaseList;
for (const opt of dbList) {
if (opt.value == selectedDB) {
opt.connected = true;
opt.image = res.data.data.icon || 'pg-icon-database';
getSchemaList(sid, selectedDB, diff_type);
if (diff_type == TYPE.SOURCE) {
setSelectedSourceDid(selectedDB);
setSourceDatabaseList(dbList);
} else {
setSelectedTargetDid(selectedDB);
setTargetDatabaseList(dbList);
}
break;
}
}
}).catch((error) => {
Notifier.error(gettext(`Error in connect database ${error.response.data}`));
});
};
const connectServer = (sid, diff_type, formData = null, serverList = []) => {
try {
schemaDiffToolContext.api({
method: 'POST',
url: url_for('schema_diff.connect_server', { 'sid': sid }),
data: formData,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
}).then((res) => {
for (const group of serverList) {
for (const opt of group.options) {
if (opt.value == sid) {
opt.connected = true;
opt.image = res.data.data.icon || 'icon-pg';
break;
}
}
}
if (diff_type == TYPE.SOURCE) {
setSelectedSourceSid(sid);
} else {
setSelectedTargetSid(sid);
}
setSourceGroupServerList(serverList);
getDatabaseList(sid, diff_type);
}).catch((error) => {
showConnectServer(error.response?.data.result, sid, diff_type, serverList);
});
} catch (error) {
Notifier.error(gettext(`Error in connect server ${error.response.data}` ));
}
};
function getDatabaseList(sid, diff_type) {
schemaDiffToolContext.api.get(
url_for('schema_diff.databases', { 'sid': sid })
).then((res) => {
res.data.data.map((opt) => {
if (opt.is_maintenance_db) {
if (diff_type == TYPE.SOURCE) {
setSelectedSourceDid(opt.value);
} else {
setSelectedTargetDid(opt.value);
}
getSchemaList(sid, opt.value, diff_type);
}
});
if (diff_type == TYPE.SOURCE) {
setSourceDatabaseList(res.data.data);
} else {
setTargetDatabaseList(res.data.data);
}
});
}
function getSchemaList(sid, did, diff_type) {
schemaDiffToolContext.api.get(
url_for('schema_diff.schemas', { 'sid': sid, 'did': did })
).then((res) => {
if (diff_type == TYPE.SOURCE) {
setSourceSchemaList(res.data.data);
} else {
setTargetSchemaList(res.data.data);
}
});
}
function showConnectServer(result, sid, diff_type, serverList) {
schemaDiffToolContext.modal.showModal(gettext('Connect to server'), (closeModal) => {
return (
<ConnectServerContent
closeModal={() => {
closeModal();
}}
data={result}
onOK={(formData) => {
connectServer(sid, diff_type, formData, serverList);
}}
/>
);
});
}
function getFilterParams() {
let opt = [];
if(isInit && filterOptions.length == 0) {
opt = [FILTER_NAME.DIFFERENT, FILTER_NAME.SOURCE_ONLY, FILTER_NAME.TARGET_ONLY];
} else if(filterOptions.length > 0 ) {
opt = filterOptions;
}
return opt;
}
return (
<>
<Loader message={loaderText} style={{fontWeight: 900}}></Loader>
<Box id='compare-container-schema-diff'>
<Grid
container
direction="row"
alignItems="center"
key={_.uniqueId('c')}
>
<Grid item lg={7} md={7} sm={10} xs={10} key={_.uniqueId('c')}>
<InputComponent
label={gettext('Select Source')}
serverList={sourceGroupServerList}
databaseList={sourceDatabaseList}
schemaList={sourceSchemaList}
selectedSid={selectedSourceSid}
selectedDid={selectedSourceDid}
selectedScid={selectedSourceScid}
diff_type={TYPE.SOURCE}
></InputComponent>
</Grid>
<Grid item lg={5} md={5} sm={2} xs={2} key={_.uniqueId('c')} className={classes.helpBtn}>
<PgButtonGroup size="small">
<PgIconButton data-test='schema-diff-help' title={gettext('Help')} icon={<HelpIcon />} onClick={onHelpClick} />
</PgButtonGroup>
</Grid>
</Grid>
<Grid
container
direction="row"
alignItems="center"
key={_.uniqueId('c')}
>
<Grid item lg={7} md={7} sm={10} xs={10} key={_.uniqueId('c')}>
<InputComponent
label={gettext('Select Target')}
serverList={sourceGroupServerList}
databaseList={targetDatabaseList}
schemaList={targetSchemaList}
selectedSid={selectedTargetSid}
selectedDid={selectedTargetDid}
selectedScid={selectedTargetScid}
diff_type={TYPE.TARGET}
></InputComponent>
</Grid>
<Grid item lg={5} md={5} sm={12} xs={12} key={_.uniqueId('c')} className={classes.diffBtn}>
<SchemaDiffButtonComponent
sourceData={{
'sid': selectedSourceSid,
'did': selectedSourceDid,
'scid': selectedSourceScid,
}}
selectedRowIds={selectedRowIds}
rows={apiResult}
targetData={{
'sid': selectedTargetSid,
'did': selectedTargetDid,
'scid': selectedTargetScid,
}}
filterParams={getFilterParams()}
compareParams={compareOptions}
></SchemaDiffButtonComponent>
</Grid>
</Grid>
</Box>
{showResultGrid && gridData.length > 0 && selectedTargetDid && selectedSourceDid ?
<ResultGridComponent
gridData={gridData}
allRowIds={allRowIdList}
filterParams={filterOptions}
selectedRowIds={(rows) => { setSelectedRowIds(rows); }}
rowDependencies={rowDep}
transId={params.transId}
sourceData={{
'sid': selectedSourceSid,
'did': selectedSourceDid,
'scid': selectedSourceScid,
}}
targetData={{
'sid': selectedTargetSid,
'did': selectedTargetDid,
'scid': selectedTargetScid,
}}
></ResultGridComponent>
:
<Box className={classes.note}>
<InfoRoundedIcon style={{ fontSize: '1.2rem' }} />
{gettext(' Source and Target database server must be of the same major version.')}<br />
<strong>{gettext(' Database Compare:')}</strong>
{gettext(' Select the server and database for the source and target and Click')} <strong>{gettext('Compare.')}</strong>
<br />
<strong>{gettext('Schema Compare:')}</strong>
{gettext(' Select the server, database and schema for the source and target and Click')} <strong>{gettext('Compare.')}</strong>
<br />
<strong>{gettext('Note:')}</strong> {gettext('The dependencies will not be resolved in the Schema comparison.')}
</Box>
}
</>
);
}
SchemaDiffCompare.propTypes = {
params: PropTypes.object,
'params.transId': PropTypes.number,
};

View File

@ -0,0 +1,124 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React, { createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import {DividerBox} from 'rc-dock';
import url_for from 'sources/url_for';
import { Box, makeStyles } from '@material-ui/core';
import { Results } from './Results';
import { SchemaDiffCompare } from './SchemaDiffCompare';
import EventBus from '../../../../../static/js/helpers/EventBus';
import getApiInstance from '../../../../../static/js/api_instance';
import { useModal } from '../../../../../static/js/helpers/ModalProvider';
export const SchemaDiffEventsContext = createContext();
export const SchemaDiffContext = createContext();
const useStyles = makeStyles((theme) => ({
resetRoot: {
padding: '2px 4px',
display: 'flex',
alignItems: 'center',
gap: '4px',
backgroundColor: theme.otherVars.editorToolbarBg,
flexWrap: 'wrap',
...theme.mixins.panelBorder.bottom,
'& #id-schema-diff': {
overflow: 'auto'
},
'& #id-results': {
overflow: 'auto'
}
},
resultPanle: {
backgroundColor: theme.palette.default.main,
zIndex: 5,
border: '1px solid ' + theme.otherVars.borderColor,
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
height: '50%',
minHeight: 150,
overflow: 'hidden',
},
comparePanel:{
overflow: 'hidden',
border: '1px solid ' + theme.otherVars.borderColor,
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
minHeight: 150,
height: '50%',
}
}));
export default function SchemaDiffComponent({params}) {
const classes = useStyles();
const eventBus = useRef(new EventBus());
const containerRef = React.useRef(null);
const api = getApiInstance();
const modal = useModal();
const [schemaDiffState, setSchemaDiffState] = useState({
preferences: null
});
const schemaDiffContextValue = useMemo(()=> ({
api: api,
modal: modal,
preferences_schema_diff: schemaDiffState.preferences
}), [schemaDiffState.preferences]);
registerUnload();
useEffect(() => {
reflectPreferences();
params.pgAdmin.Browser.onPreferencesChange('schema_diff', function () {
reflectPreferences();
});
}, []);
const reflectPreferences = useCallback(() => {
setSchemaDiffState({
preferences: params.pgAdmin.Browser.get_preferences_for_module('schema_diff')
});
}, []);
function registerUnload() {
window.addEventListener('unload', ()=>{
api.delete(url_for('schema_diff.close', {
trans_id: params.transId
}));
});
}
return (
<SchemaDiffContext.Provider value={schemaDiffContextValue}>
<SchemaDiffEventsContext.Provider value={eventBus.current}>
<Box display="flex" flexDirection="column" flexGrow="1" tabIndex="0" style={{overflowY: 'auto', minHeight: 80}}>
<DividerBox mode='vertical' style={{flexGrow: 1}}>
<div className={classes.comparePanel} id="schema-diff-compare-container" ref={containerRef}><SchemaDiffCompare params={params} /></div>
<div className={classes.resultPanle} id="schema-diff-result-container">
<Results />
</div>
</DividerBox>
</Box>
</SchemaDiffEventsContext.Provider>
</SchemaDiffContext.Provider>
);
}
SchemaDiffComponent.propTypes = {
params: PropTypes.object
};

View File

@ -0,0 +1,22 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import pgAdmin from 'sources/pgadmin';
import pgBrowser from 'top/browser/static/js/browser';
import SchemaDiff from './SchemaDiffModule';
if (!pgAdmin.Tools) {
pgAdmin.Tools = {};
}
pgAdmin.Tools.SchemaDiff = SchemaDiff.getInstance(pgAdmin, pgBrowser);
module.exports = {
SchemaDiff: SchemaDiff,
};

View File

@ -1,543 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import $ from 'jquery';
import Backbone from 'backbone';
import Backform from 'pgadmin.backform';
import gettext from 'sources/gettext';
import clipboard from 'sources/selection/clipboard';
var formatNode = function (opt) {
if (!opt.id) {
return opt.text;
}
var optimage = $(opt.element).data('image');
if (!optimage) {
return opt.text;
} else {
return $('<span></span>').append(
$('<span></span>', {
class: 'wcTabIcon ' + optimage,
})
).append($('<span></span>').text(opt.text));
}
};
let SchemaDiffSqlControl =
Backform.SqlFieldControl.extend({
defaults: {
label: '',
extraClasses: [], // Add default control height
helpMessage: null,
maxlength: 4096,
rows: undefined,
copyRequired: false,
},
template: _.template([
'<% if (copyRequired) { %><button class="btn btn-secondary ddl-copy d-none">' + gettext('Copy') + '</button> <% } %>',
'<div class="pgadmin-controls pg-el-9 pg-el-12 sql_field_layout <%=extraClasses.join(\' \')%>">',
' <textarea ',
' class="<%=Backform.controlClassName%> " name="<%=name%>" aria-label="<%=name%>"',
' maxlength="<%=maxlength%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%>',
' rows=<%=rows%>',
' <%=required ? "required" : ""%>><%-value%></textarea>',
' <% if (helpMessage && helpMessage.length) { %>',
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
' <% } %>',
'</div>',
].join('\n')),
initialize: function() {
Backform.TextareaControl.prototype.initialize.apply(this, arguments);
this.sqlCtrl = null;
_.bindAll(this, 'onFocus', 'onBlur', 'refreshTextArea', 'copyData');
},
render: function() {
let obj = Backform.SqlFieldControl.prototype.render.apply(this, arguments);
obj.sqlCtrl.setOption('readOnly', true);
if(this.$el.find('.ddl-copy')) this.$el.find('.ddl-copy').on('click', this.copyData);
return obj;
},
copyData() {
event.stopPropagation();
clipboard.copyTextToClipboard(this.model.get('diff_ddl'));
this.$el.find('.ddl-copy').text(gettext('Copied!'));
var self = this;
setTimeout(function() {
let $copy = self.$el.find('.ddl-copy');
if (!$copy.hasClass('d-none')) $copy.addClass('d-none');
$copy.text(gettext('Copy'));
}, 3000);
return false;
},
onFocus: function() {
let $ctrl = this.$el.find('.pgadmin-controls').first(),
$copy = this.$el.find('.ddl-copy');
if (!$ctrl.hasClass('focused')) $ctrl.addClass('focused');
if ($copy.hasClass('d-none')) $copy.removeClass('d-none');
},
});
let SchemaDiffSelect2Control =
Backform.Select2Control.extend({
defaults: _.extend(Backform.Select2Control.prototype.defaults, {
url: undefined,
transform: undefined,
url_with_id: false,
select2: {
allowClear: true,
placeholder: gettext('Select an item...'),
width: 'style',
templateResult: formatNode,
templateSelection: formatNode,
},
controlsClassName: 'pgadmin-controls pg-el-sm-11 pg-el-12',
}),
className: function() {
return 'pgadmin-controls pg-el-sm-4';
},
events: {
'focus select': 'clearInvalid',
'keydown :input': 'processTab',
'select2:select': 'onSelect',
'select2:selecting': 'beforeSelect',
'select2:clear': 'onChange',
},
template: _.template([
'<% if(label == false) {} else {%>',
' <label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
'<% }%>',
'<div class="<%=controlsClassName%>">',
' <select class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>"',
' name="<%=name%>" aria-label="<%=name%>" value="<%-value%>" <%=disabled ? "disabled" : ""%>',
' <%=required ? "required" : ""%><%= select2.multiple ? " multiple>" : ">" %>',
' <%=select2.first_empty ? " <option></option>" : ""%>',
' <% for (var i=0; i < options.length; i++) {%>',
' <% if (options[i].group) { %>',
' <% var group = options[i].group; %>',
' <% if (options[i].optval) { %> <% var option_length = options[i].optval.length; %>',
' <optgroup label="<%=group%>">',
' <% for (var subindex=0; subindex < option_length; subindex++) {%>',
' <% var option = options[i].optval[subindex]; %>',
' <option ',
' <% if (option.image) { %> data-image=<%=option.image%> <%}%>',
' <% if (option.connected) { %> data-connected=connected <%}%>',
' value=<%- formatter.fromRaw(option.value) %>',
' <% if (option.selected) {%>selected="selected"<%} else {%>',
' <% if (!select2.multiple && option.value === rawValue) {%>selected="selected"<%}%>',
' <% if (select2.multiple && rawValue && rawValue.indexOf(option.value) != -1){%>selected="selected" data-index="rawValue.indexOf(option.value)"<%}%>',
' <%}%>',
' <%= disabled ? "disabled" : ""%>><%-option.label%></option>',
' <%}%>',
' </optgroup>',
' <%}%>',
' <%} else {%>',
' <% var option = options[i]; %>',
' <option ',
' <% if (option.image) { %> data-image=<%=option.image%> <%}%>',
' <% if (option.connected) { %> data-connected=connected <%}%>',
' value=<%- formatter.fromRaw(option.value) %>',
' <% if (option.selected) {%>selected="selected"<%} else {%>',
' <% if (!select2.multiple && option.value === rawValue) {%>selected="selected"<%}%>',
' <% if (select2.multiple && rawValue && rawValue.indexOf(option.value) != -1){%>selected="selected" data-index="rawValue.indexOf(option.value)"<%}%>',
' <%}%>',
' <%= disabled ? "disabled" : ""%>><%-option.label%></option>',
' <%}%>',
' <%}%>',
' </select>',
' <% if (helpMessage && helpMessage.length) { %>',
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
' <% } %>',
'</div>',
].join('\n')),
beforeSelect: function() {
var selVal = arguments[0].params.args.data.id;
if(this.field.get('connect') && this.$el.find('option[value="'+selVal+'"]').attr('data-connected') !== 'connected') {
this.field.get('connect').apply(this, [selVal, this.changeIcon.bind(this)]);
} else {
$(this.$sel).trigger('change');
setTimeout(function(){ this.onChange.apply(this); }.bind(this), 200);
}
},
changeIcon: function(data) {
let span = this.$el.find('.select2-selection .select2-selection__rendered span.wcTabIcon'),
selSpan = this.$el.find('option:selected');
if (span.hasClass('icon-server-not-connected') || span.hasClass('icon-shared-server-not-connected')) {
let icon = (data.icon) ? data.icon : 'icon-pg';
span.removeClass('icon-server-not-connected');
span.addClass(icon);
span.attr('data-connected', 'connected');
selSpan.data().image = icon;
selSpan.attr('data-connected', 'connected');
this.onChange.apply(this);
}
else if (span.hasClass('icon-database-not-connected')) {
let icon = (data.icon) ? data.icon : 'pg-icon-database';
span.removeClass('icon-database-not-connected');
span.addClass(icon);
span.attr('data-connected', 'connected');
selSpan.removeClass('icon-database-not-connected');
selSpan.data().image = icon;
selSpan.attr('data-connected', 'connected');
this.onChange.apply(this);
}
},
onChange: function() {
var model = this.model,
attrArr = this.field.get('name').split('.'),
name = attrArr.shift(),
path = attrArr.join('.'),
value = this.getValueFromDOM(),
changes = {},
that = this;
if (this.model.errorModel instanceof Backbone.Model) {
if (_.isEmpty(path)) {
this.model.errorModel.unset(name);
} else {
var nestedError = this.model.errorModel.get(name);
if (nestedError) {
this.keyPathSetter(nestedError, path, null);
this.model.errorModel.set(name, nestedError);
}
}
}
changes[name] = _.isEmpty(path) ? value : _.clone(model.get(name)) || {};
if (!_.isEmpty(path)) that.keyPathSetter(changes[name], path, value);
that.stopListening(that.model, 'change:' + name, that.render);
model.set(changes);
that.listenTo(that.model, 'change:' + name, that.render);
},
render: function() {
/*
* Initialization from the original control.
*/
this.fetchData();
return Backform.Select2Control.prototype.render.apply(this, arguments);
},
fetchData: function() {
/*
* We're about to fetch the options required for this control.
*/
var self = this,
url = self.field.get('url'),
m = self.model;
url = _.isFunction(url) ? url.apply(m) : url;
if (url && self.field.get('deps')) {
url = url.replace('sid', m.get(self.field.get('deps')[0]));
}
// Hmm - we found the url option.
// That means - we needs to fetch the options from that node.
if (url) {
var data;
m.trigger('pgadmin:view:fetching', m, self.field);
$.ajax({
async: false,
url: url,
})
.done(function(res) {
/*
* We will cache this data for short period of time for avoiding
* same calls.
*/
data = res.data;
})
.fail(function() {
m.trigger('pgadmin:view:fetch:error', m, self.field);
});
m.trigger('pgadmin:view:fetched', m, self.field);
// To fetch only options from cache, we do not need time from 'at'
// attribute but only options.
//
/*
* Transform the data
*/
var transform = this.field.get('transform') || self.defaults.transform;
if (transform && _.isFunction(transform)) {
// We will transform the data later, when rendering.
// It will allow us to generate different data based on the
// dependencies.
self.field.set('options', transform.bind(self, data));
} else {
self.field.set('options', data);
}
}
},
});
let SchemaDiffHeaderView = Backform.Form.extend({
label: '',
className: function() {
return 'pg-el-sm-12 pg-el-md-12 pg-el-lg-12 pg-el-12';
},
tabPanelClassName: function() {
return Backform.tabClassName;
},
tabIndex: 0,
initialize: function(opts) {
this.label = opts.label;
Backform.Form.prototype.initialize.apply(this, arguments);
},
template: _.template(`
<div class="row pgadmin-control-group">
<div class="col-1 control-label">` + gettext('Select Source') + `</div>
<div class="col-6 source row"></div>
</div>
<div class="row pgadmin-control-group">
<div class="col-1 control-label">` + gettext('Select Target') + `</div>
<div class="col-6 target row"></div>
<div class="col-5 target-buttons">
<div class="action-btns d-flex">
<div class="btn-group mr-auto" role="group" aria-label="">
<button class="btn btn-primary"><span class="pg-font-icon icon-compare sql-icon-lg"></span>&nbsp;` + gettext('Compare') + `</button>
<button id="btn-ignore-dropdown" type="button" class="btn btn-primary-icon dropdown-toggle dropdown-toggle-split mr-auto"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="ignore"
title=""
tabindex="0">
</button>` +
[
'<ul class="dropdown-menu ignore">',
'<li>',
'<a class="dropdown-item" id="btn-ignore-owner" href="#" tabindex="0">',
'<i class="fa fa-check visibility-hidden" aria-hidden="true"></i>',
'<span> ' + gettext('Ignore owner') + ' </span>',
'</a>',
'</li>',
'<li>',
'<a class="dropdown-item" id="btn-ignore-whitespaces" href="#" tabindex="0">',
'<i class="fa fa-check visibility-hidden" aria-hidden="true"></i>',
'<span> ' + gettext('Ignore whitespace') + ' </span>',
'</a>',
'</li>',
'</ul>',
].join('\n') + `</div>
<button id="generate-script" class="btn btn-primary-icon mr-1" disabled><i class="fa fa-file-code sql-icon-lg"></i>&nbsp;` + gettext('Generate Script') + `</button>
<div class="btn-group mr-1" role="group" aria-label="">
<button id="btn-filter" type="button" class="btn btn-primary-icon"
title=""
tabindex="0"
style="pointer-events: none;">
<i class="fa fa-filter sql-icon-lg" aria-hidden="true"></i>&nbsp;` + gettext('Filter') + `
</button>
<button id="btn-filter-dropdown" type="button" class="btn btn-primary-icon dropdown-toggle dropdown-toggle-split"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="filter"
title=""
tabindex="0">
</button>` +
[
'<ul class="dropdown-menu filter">',
'<li>',
'<a class="dropdown-item" id="btn-identical" href="#" tabindex="0">',
'<i class="identical fa fa-check visibility-hidden" aria-hidden="true"></i>',
'<span> ' + gettext('Identical') + ' </span>',
'</a>',
'</li>',
'<li>',
'<a class="dropdown-item" id="btn-differentt" href="#" tabindex="0">',
'<i class="different fa fa-check" aria-hidden="true"></i>',
'<span> ' + gettext('Different') + ' </span>',
'</a>',
'</li>',
'<li>',
'<a class="dropdown-item" id="btn-source-only" href="#" tabindex="0">',
'<i class="source-only fa fa-check" aria-hidden="true"></i>',
'<span> ' + gettext('Source Only') + ' </span>',
'</a>',
'</li>',
'<li>',
'<a class="dropdown-item" id="btn-target-only" href="#" tabindex="0">',
'<i class="target-only fa fa-check" aria-hidden="true"></i>',
'<span> ' + gettext('Target Only') + ' </span>',
'</a>',
'</li>',
'</ul>',
'</div>',
'</div>',
'</div>',
'</div>',
].join('\n')
),
render: function() {
this.cleanup();
var controls = this.controls,
m = this.model,
self = this,
idx = (this.tabIndex * 100);
this.$el.empty();
$(this.template()).appendTo(this.$el);
this.fields.each(function(f) {
var cntr = new(f.get('control'))({
field: f,
model: m,
dialog: self,
tabIndex: idx,
});
if (f.get('group') && f.get('group') == 'source') {
self.$el.find('.source').append(cntr.render().$el);
}
else {
self.$el.find('.target').append(cntr.render().$el);
}
controls.push(cntr);
});
return this;
},
remove: function(opts) {
if (opts && opts.data) {
if (this.model) {
if (this.model.reset) {
this.model.reset({
validate: false,
silent: true,
stop: true,
});
}
this.model.clear({
validate: false,
silent: true,
stop: true,
});
delete(this.model);
}
if (this.errorModel) {
this.errorModel.clear({
validate: false,
silent: true,
stop: true,
});
delete(this.errorModel);
}
}
this.cleanup();
Backform.Form.prototype.remove.apply(this, arguments);
},
});
let SchemaDiffFooterView = Backform.Form.extend({
className: function() {
return 'set-group pg-el-12';
},
tabPanelClassName: function() {
return Backform.tabClassName;
},
legendClass: 'badge',
contentClass: Backform.accordianContentClassName,
template: {
'content': _.template(`
<div class="pg-el-sm-12 row <%=contentClass%>">
<div class="pg-el-sm-4 ddl-source">` + gettext('Source') + `</div>
<div class="pg-el-sm-4 ddl-target">` + gettext('Target') + `</div>
<div class="pg-el-sm-4 ddl-diff">` + gettext('Difference') + `
</div>
</div>
</div>
`),
},
initialize: function(opts) {
this.label = opts.label;
Backform.Form.prototype.initialize.apply(this, arguments);
},
render: function() {
this.cleanup();
let m = this.model,
$el = this.$el,
tmpl = this.template,
controls = this.controls,
data = {
'className': _.result(this, 'className'),
'legendClass': _.result(this, 'legendClass'),
'contentClass': _.result(this, 'contentClass'),
'collapse': _.result(this, 'collapse'),
},
idx = (this.tabIndex * 100);
this.$el.empty();
let el = $((tmpl['content'])(data)).appendTo($el);
this.fields.each(function(f) {
let cntr = new(f.get('control'))({
field: f,
model: m,
dialog: self,
tabIndex: idx,
name: f.get('name'),
});
if (f.get('group') && f.get('group') == 'ddl-source') {
el.find('.ddl-source').append(cntr.render().$el);
}
else if (f.get('group') && f.get('group') == 'ddl-target') {
el.find('.ddl-target').append(cntr.render().$el);
}
else {
el.find('.ddl-diff').append(cntr.render().$el);
}
controls.push(cntr);
});
$('div.CodeMirror div textarea').attr('aria-label', 'textarea');
let $diff_sc = this.$el.find('.source_ddl'),
$diff_tr = this.$el.find('.target_ddl'),
$diff = this.$el.find('.diff_ddl'),
footer_height = this.$el.parent().height() - 50;
$diff_sc.height(footer_height);
$diff_sc.css({
'height': footer_height + 'px',
});
$diff_tr.height(footer_height);
$diff_tr.css({
'height': footer_height + 'px',
});
$diff.height(footer_height);
$diff.css({
'height': footer_height + 'px',
});
return this;
},
});
export {
SchemaDiffSelect2Control,
SchemaDiffHeaderView,
SchemaDiffFooterView,
SchemaDiffSqlControl,
};

View File

@ -1,171 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define('pgadmin.schemadiff', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'sources/pgadmin', 'sources/csrf', 'pgadmin.alertifyjs', 'sources/utils', 'pgadmin.browser.node',
], function(
gettext, url_for, $, _, pgAdmin, csrfToken, Alertify, commonUtils,
) {
var wcDocker = window.wcDocker,
pgBrowser = pgAdmin.Browser;
/* Return back, this has been called more than once */
if (pgBrowser.SchemaDiff)
return pgBrowser.SchemaDiff;
// Create an Object Restore of pgBrowser class
pgBrowser.SchemaDiff = {
init: function() {
if (this.initialized)
return;
this.initialized = true;
csrfToken.setPGCSRFToken(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
// Define the nodes on which the menus to be appear
var menus = [{
name: 'schema_diff',
module: this,
applies: ['tools'],
callback: 'show_schema_diff_tool',
priority: 1,
label: gettext('Schema Diff'),
enable: true,
below: true,
}];
pgBrowser.add_menus(menus);
// Creating a new pgBrowser frame to show the data.
var schemaDiffFrameType = new pgBrowser.Frame({
name: 'frm_schemadiff',
showTitle: true,
isCloseable: true,
isPrivate: true,
url: 'about:blank',
});
let self = this;
/* Cache may take time to load for the first time
* Keep trying till available
*/
let cacheIntervalId = setInterval(function() {
if(pgBrowser.preference_version() > 0) {
self.preferences = pgBrowser.get_preferences_for_module('schema_diff');
clearInterval(cacheIntervalId);
}
},0);
pgBrowser.onPreferencesChange('schema_diff', function() {
self.preferences = pgBrowser.get_preferences_for_module('schema_diff');
});
// Load the newly created frame
schemaDiffFrameType.load(pgBrowser.docker);
return this;
},
// Callback to draw schema diff for objects
show_schema_diff_tool: function() {
var self = this,
baseUrl = url_for('schema_diff.initialize', null);
$.ajax({
url: baseUrl,
method: 'GET',
dataType: 'json',
contentType: 'application/json',
})
.done(function(res) {
self.trans_id = res.data.schemaDiffTransId;
res.data.panel_title = gettext('Schema Diff'); //TODO: Set the panel title
// TODO: Following function is used to test the fetching of the
// databases this should be moved to server selection event later.
self.launch_schema_diff(res.data);
})
.fail(function(xhr) {
self.raise_error_on_fail(gettext('Schema Diff initialize error') , xhr);
});
},
launch_schema_diff: function(data) {
var panel_title = data.panel_title,
trans_id = data.schemaDiffTransId,
panel_tooltip = '';
var url_params = {
'trans_id': trans_id,
'editor_title': panel_title,
},
baseUrl = url_for('schema_diff.panel', url_params);
var browser_preferences = pgBrowser.get_preferences_for_module('browser');
var open_new_tab = browser_preferences.new_browser_tab_open;
if (open_new_tab && open_new_tab.includes('schema_diff')) {
window.open(baseUrl, '_blank');
// Send the signal to runtime, so that proper zoom level will be set.
setTimeout(function() {
pgBrowser.send_signal_to_runtime('Runtime new window opened');
}, 500);
} else {
var propertiesPanel = pgBrowser.docker.findPanels('properties'),
schemaDiffPanel = pgBrowser.docker.addPanel('frm_schemadiff', wcDocker.DOCK.STACKED, propertiesPanel[0]);
commonUtils.registerDetachEvent(schemaDiffPanel);
// Rename schema diff tab
schemaDiffPanel.on(wcDocker.EVENT.RENAME, function(panel_data) {
Alertify.prompt('', panel_data.$titleText[0].textContent,
// We will execute this function when user clicks on the OK button
function(evt, value) {
if(value) {
// Remove the leading and trailing white spaces.
value = value.trim();
schemaDiffPanel.title('<span>'+ _.escape(value) +'</span>');
}
},
// We will execute this function when user clicks on the Cancel
// button. Do nothing just close it.
function(evt) { evt.cancel = false; }
).set({'title': gettext('Rename Panel')});
});
// Set panel title and icon
schemaDiffPanel.title('<span title="'+panel_tooltip+'">'+panel_title+'</span>');
schemaDiffPanel.icon('pg-font-icon icon-compare');
schemaDiffPanel.focus();
var openSchemaDiffURL = function(j) {
// add spinner element
$(j).data('embeddedFrame').$container.append(pgBrowser.SchemaDiff.spinner_el);
setTimeout(function() {
var frameInitialized = $(j).data('frameInitialized');
if (frameInitialized) {
var frame = $(j).data('embeddedFrame');
if (frame) {
frame.openURL(baseUrl);
frame.$container.find('.pg-sp-container').delay(1000).hide(1);
}
} else {
openSchemaDiffURL(j);
}
}, 100);
};
openSchemaDiffURL(schemaDiffPanel);
}
},
};
return pgBrowser.SchemaDiff;
});

View File

@ -1,243 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
function handleDependencies() {
event.stopPropagation();
let isChecked = event.target.checked || (event.target.checked === undefined &&
event.target.className && event.target.className.indexOf('unchecked') == -1);
let isHeaderSelected = false;
if (event.target.id !== undefined) isHeaderSelected = event.target.id.includes('header-selector');
if (this.gridContext && this.gridContext.rowIndex && _.isUndefined(this.gridContext.row.rows)) {
// Single Row Selection
let rowData = this.grid.getData().getItem(this.gridContext.rowIndex);
this.gridContext = {};
if (rowData.status) {
let depRows = this.selectDependencies(rowData, isChecked);
this.selectedRowCount = this.grid.getSelectedRows().length;
if (isChecked && depRows.length > 0)
this.grid.setSelectedRows(depRows);
else if (!isChecked)
this.grid.setSelectedRows(this.grid.getSelectedRows().filter(x => !depRows.includes(x)));
this.ddlCompare(rowData);
}
} else if((this.gridContext && this.gridContext.row && !_.isUndefined(this.gridContext.row.rows)) ||
this.selectedRowCount != this.grid.getSelectedRows().length) {
// Group / All Rows Selection
this.selectedRowCount = this.grid.getSelectedRows().length;
if (this.gridContext.row && this.gridContext.row.__group) {
let context = this.gridContext;
this.gridContext = {};
this.selectDependenciesForGroup(isChecked, context);
} else {
this.gridContext = {};
this.selectDependenciesForAll(isChecked, isHeaderSelected);
}
}
if (this.grid.getSelectedRows().length > 0) {
this.header.$el.find('button#generate-script').removeAttr('disabled');
} else {
this.header.$el.find('button#generate-script').attr('disabled', true);
}
}
function selectDependenciesForGroup(isChecked, context) {
let self = this,
finalRows = [];
if (!isChecked) {
_.each(context.row.rows, function(row) {
if (row && row.status && row.status.toLowerCase() != 'identical' ) {
let d = self.selectDependencies(row, isChecked);
finalRows = finalRows.concat(d);
}
});
}
else {
_.each(self.grid.getSelectedRows(), function(row) {
let data = self.grid.getData().getItem(row);
if (data.status && data.status.toLowerCase() != 'identical') {
finalRows = finalRows.concat(self.selectDependencies(data, isChecked));
}
});
}
finalRows = [...new Set(finalRows)];
if (isChecked)
self.grid.setSelectedRows(finalRows);
else {
let filterRows = [];
filterRows = self.grid.getSelectedRows().filter(x => !finalRows.includes(x));
self.selectedRowCount = filterRows.length;
self.grid.setSelectedRows(filterRows);
}
}
function selectDependenciesForAll(isChecked, isHeaderSelected) {
let self = this,
finalRows = [];
if(!isChecked && isHeaderSelected) {
self.dataView.getItems().map(function(el) {
el.dependentCount = [];
el.dependLevel = 1;
});
self.selectedRowCount = 0;
return;
}
_.each(self.grid.getSelectedRows(), function(row) {
let data = self.grid.getData().getItem(row);
if (data.status) {
finalRows = finalRows.concat(self.selectDependencies(data, isChecked));
}
});
finalRows = [...new Set(finalRows)];
if (isChecked && finalRows.length > 0)
self.grid.setSelectedRows(finalRows);
else if (!isChecked) {
let filterRows = [];
filterRows = self.grid.getSelectedRows().filter(x => !finalRows.includes(x));
self.selectedRowCount = filterRows.length;
self.grid.setSelectedRows(filterRows);
}
}
function selectDependencies(data, isChecked) {
let self = this,
rows = [],
setDependencies = undefined,
setOrigDependencies = undefined,
finalRows = [];
if (!data.dependLevel || !isChecked) data.dependLevel = 1;
if (!data.dependentCount || !isChecked) data.dependentCount = [];
if (data.status && data.status.toLowerCase() == 'identical') {
self.selectedRowCount = self.grid.getSelectedRows().length;
return [];
}
setDependencies = function(rowData, dependencies, is_checked) {
// Special handling for extension, if extension is present in the
// dependency list then iterate and select only extension node.
let extensions = dependencies.filter(item => item.type == 'extension');
if (extensions.length > 0) dependencies = extensions;
_.each(dependencies, function(dependency) {
if (dependency.length == 0) return;
let dependencyData = [];
dependencyData = self.dataView.getItems().filter(item => item.type == dependency.type && item.oid == dependency.oid);
if (dependencyData.length > 0) {
dependencyData = dependencyData[0];
if (!dependencyData.dependentCount) dependencyData.dependentCount = [];
let groupData = [];
if (dependencyData.status && dependencyData.status.toLowerCase() != 'identical') {
groupData = self.dataView.getGroups().find(
(item) => { if (dependencyData.group_name == item.groupingKey) return item.groups; }
);
if (groupData && groupData.groups) {
groupData = groupData.groups.find(
(item) => { return item.groupingKey == dependencyData.group_name + ':|:' + dependencyData.type; }
);
if (groupData && groupData.collapsed == 1)
self.dataView.expandGroup(dependencyData.group_name + ':|:' + dependencyData.type);
}
if (is_checked || _.isUndefined(is_checked)) {
dependencyData.dependLevel = rowData.dependLevel + 1;
if (dependencyData.dependentCount.indexOf(rowData.oid) === -1)
dependencyData.dependentCount.push(rowData.oid);
rows[rows.length] = dependencyData;
} else {
dependencyData.dependentCount.splice(dependencyData.dependentCount.indexOf(rowData.oid), 1);
if (dependencyData.dependentCount.length == 0) {
rows[rows.length] = dependencyData;
dependencyData.dependLevel = 1;
}
}
}
if (Object.keys(dependencyData.dependencies).length > 0) {
if (dependencyData.dependentCount.indexOf(rowData.oid) !== -1 ) {
let depCirRows = dependencyData.dependencies.filter(x => x.oid !== rowData.oid);
if (!dependencyData.orig_dependencies)
dependencyData.orig_dependencies = Object.assign([], dependencyData.dependencies);
dependencyData.dependencies = depCirRows;
}
setDependencies(dependencyData, dependencyData.dependencies, is_checked);
}
}
});
};
setDependencies(data, data.dependencies, isChecked);
setOrigDependencies = function(dependencies) {
_.each(dependencies, function(dependency) {
if (dependency.length == 0) return;
let dependencyData = [];
dependencyData = self.dataView.getItems().filter(item => item.type == dependency.type && item.oid == dependency.oid);
if (dependencyData.length > 0) {
dependencyData = dependencyData[0];
if (dependencyData.orig_dependencies && Object.keys(dependencyData.orig_dependencies).length > 0) {
if (!dependencyData.dependentCount) dependencyData.dependentCount = [];
if (dependencyData.status && dependencyData.status.toLowerCase() != 'identical') {
dependencyData.dependencies = dependencyData.orig_dependencies;
dependencyData.orig_dependencies = [];
}
if (dependencyData.dependencies.length > 0) {
setOrigDependencies(dependencyData.dependencies);
}
}
}
});
};
setOrigDependencies(data.dependencies);
if (isChecked) finalRows = self.grid.getSelectedRows();
_.each(rows, function(row) {
let r = self.grid.getData().getRowByItem(row);
if(!_.isUndefined(r) && finalRows.indexOf(r) === -1 ) {
finalRows.push(self.grid.getData().getRowByItem(row));
}
});
self.selectedRowCount = finalRows.length;
return finalRows;
}
export {
handleDependencies,
selectDependenciesForGroup,
selectDependenciesForAll,
selectDependencies,
};

View File

@ -1,38 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
define([
'sources/url_for', 'jquery',
'sources/pgadmin', 'pgadmin.tools.schema_diff_ui',
], function(
url_for, $, pgAdmin, SchemaDiffUIModule
) {
var pgTools = pgAdmin.Tools = pgAdmin.Tools || {};
var SchemaDiffUI = SchemaDiffUIModule.default;
/* Return back, this has been called more than once */
if (pgTools.SchemaDiffHook)
return pgTools.SchemaDiffHook;
pgTools.SchemaDiffHook = {
load: function(trans_id) {
window.onbeforeunload = function() {
$.ajax({
url: url_for('schema_diff.close', {'trans_id': trans_id}),
method: 'DELETE',
});
};
let schemaUi = new SchemaDiffUI($('#schema-diff-container'), trans_id);
schemaUi.render();
},
};
return pgTools.SchemaDiffHook;
});

View File

@ -1,967 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import url_for from 'sources/url_for';
import $ from 'jquery';
import gettext from 'sources/gettext';
import Alertify from 'pgadmin.alertifyjs';
import Backbone from 'backbone';
import Slick from 'sources/../bundle/slickgrid';
import pgAdmin from 'sources/pgadmin';
import {setPGCSRFToken} from 'sources/csrf';
import 'pgadmin.tools.sqleditor';
import pgWindow from 'sources/window';
import _ from 'underscore';
import Notify from '../../../../static/js/helpers/Notifier';
import { showSchemaDiffServerPassword } from '../../../../static/js/Dialogs/index';
import { SchemaDiffSelect2Control, SchemaDiffHeaderView,
SchemaDiffFooterView, SchemaDiffSqlControl} from './schema_diff.backform';
import { handleDependencies, selectDependenciesForGroup,
selectDependenciesForAll, selectDependencies } from './schema_diff_dependency';
import { generateScript } from '../../../sqleditor/static/js/show_query_tool';
var wcDocker = window.wcDocker;
export default class SchemaDiffUI {
constructor(container, trans_id) {
var self = this;
this.$container = container;
this.header = null;
this.trans_id = trans_id;
this.filters = ['Identical', 'Different', 'Source Only', 'Target Only'];
this.sel_filters = ['Different', 'Source Only', 'Target Only'];
this.ignore_filters = ['owner', 'whitespaces'];
this.ignore_whitespaces = 0;
this.ignore_owner = 0;
this.dataView = null;
this.grid = null;
this.selection = {};
this.model = new Backbone.Model({
source_sid: undefined,
source_did: undefined,
source_scid: undefined,
target_sid: undefined,
target_did: undefined,
target_scid: undefined,
source_ddl: undefined,
target_ddl: undefined,
diff_ddl: undefined,
});
setPGCSRFToken(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
this.docker = new wcDocker(
this.$container, {
allowContextMenu: false,
allowCollapse: false,
loadingClass: 'pg-sp-icon',
themePath: url_for('static', {
'filename': 'css',
}),
theme: 'webcabin.overrides.css',
}
);
this.header_panel = new pgAdmin.Browser.Panel({
name: 'schema_diff_header_panel',
showTitle: false,
isCloseable: false,
isPrivate: true,
content: '<div id="schema-diff-header" class="pg-el-container" el="sm"></div><div id="schema-diff-grid" class="pg-el-container" el="sm"></div>',
elContainer: true,
});
this.footer_panel = new pgAdmin.Browser.Panel({
name: 'schema_diff_footer_panel',
title: gettext('DDL Comparison'),
isCloseable: false,
isPrivate: true,
height: '60',
content: `<div id="schema-diff-ddl-comp" class="pg-el-container" el="sm">
<div id="ddl_comp_fetching_data" class="pg-sp-container schema-diff-busy-fetching d-none">
<div class="pg-sp-content">
<div class="row">
<div class="col-12 pg-sp-icon"></div>
</div>
<div class="row"><div class="col-12 pg-sp-text">` + gettext('Comparing objects...') + `</div></div>
</div>
</div></div>`,
});
this.header_panel.load(this.docker);
this.footer_panel.load(this.docker);
this.panel_obj = this.docker.addPanel('schema_diff_header_panel', wcDocker.DOCK.TOP, {w:'95%', h:'50%'});
this.footer_panel_obj = this.docker.addPanel('schema_diff_footer_panel', wcDocker.DOCK.BOTTOM, this.panel_obj, {w:'95%', h:'50%'});
self.footer_panel_obj.on(wcDocker.EVENT.VISIBILITY_CHANGED, function() {
setTimeout(function() {
this.resize_grid();
}.bind(self), 200);
});
self.footer_panel_obj.on(wcDocker.EVENT.RESIZE_ENDED, function() {
setTimeout(function() {
this.resize_panels();
}.bind(self), 200);
});
}
raise_error_on_fail(alert_title, xhr) {
try {
if (_.isUndefined(xhr.responseText)) {
Notify.alert(alert_title, gettext('Unable to get the response text.'));
} else {
var err = JSON.parse(xhr.responseText);
Notify.alert(alert_title, err.errormsg);
}
} catch (e) {
Notify.alert(alert_title, gettext(e.message));
}
}
resize_panels() {
let $src_ddl = $('#schema-diff-ddl-comp .source_ddl'),
$tar_ddl = $('#schema-diff-ddl-comp .target_ddl'),
$diff_ddl = $('#schema-diff-ddl-comp .diff_ddl'),
footer_height = $('#schema-diff-ddl-comp').height() - 50;
$src_ddl.height(footer_height);
$src_ddl.css({
'height': footer_height + 'px',
});
$tar_ddl.height(footer_height);
$tar_ddl.css({
'height': footer_height + 'px',
});
$diff_ddl.height(footer_height);
$diff_ddl.css({
'height': footer_height + 'px',
});
this.resize_grid();
}
compare_schemas() {
var self = this,
url_params = self.model.toJSON();
if (url_params['source_sid'] == '' || _.isUndefined(url_params['source_sid']) ||
url_params['source_did'] == '' || _.isUndefined(url_params['source_did']) ||
url_params['target_sid'] == '' || _.isUndefined(url_params['target_sid']) ||
url_params['target_did'] == '' || _.isUndefined(url_params['target_did'])
) {
Notify.alert(gettext('Selection Error'), gettext('Please select source and target.'));
return false;
}
// Check if user has selected the same options for comparison on the GUI
let opts = [['source_sid', 'target_sid'], ['source_did', 'target_did'], ['source_scid', 'target_scid']];
let isSameOptsSelected = true;
for (let opt of opts) {
if (url_params[opt[0]] && url_params[opt[1]] &&
(parseInt(url_params[opt[0]]) !== parseInt(url_params[opt[1]]))) {
isSameOptsSelected = false;
}
}
if (isSameOptsSelected) {
Notify.alert(gettext('Selection Error'), gettext('Please select the different source and target.'));
return false;
}
this.selection = JSON.parse(JSON.stringify(url_params));
url_params['trans_id'] = self.trans_id;
url_params['ignore_owner'] = self.ignore_owner;
url_params['ignore_whitespaces'] = self.ignore_whitespaces;
_.each(url_params, function(key, val) {
url_params[key] = parseInt(val, 10);
});
var baseUrl = url_for('schema_diff.compare_database', url_params);
// If compare two schema then change the base url
if (url_params['source_scid'] != '' && !_.isUndefined(url_params['source_scid']) &&
url_params['target_scid'] != '' && !_.isUndefined(url_params['target_scid'])) {
baseUrl = url_for('schema_diff.compare_schema', url_params);
}
self.model.set({
'source_ddl': undefined,
'target_ddl': undefined,
'diff_ddl': undefined,
});
self.render_grid([]);
self.footer.render();
self.startDiffPoller();
return $.ajax({
url: baseUrl,
method: 'GET',
dataType: 'json',
contentType: 'application/json',
})
.done(function (res) {
self.stopDiffPoller();
self.render_grid(res.data);
})
.fail(function (xhr) {
self.raise_error_on_fail(gettext('Schema compare error'), xhr);
self.stopDiffPoller();
});
}
generate_script() {
var self = this,
baseServerUrl = url_for('schema_diff.get_server', {'sid': self.selection['target_sid'],
'did': self.selection['target_did']}),
sel_rows = self.grid ? self.grid.getSelectedRows() : [],
url_params = self.selection,
generated_script = undefined,
open_query_tool,
script_header;
script_header = gettext('-- This script was generated by the Schema Diff utility in pgAdmin 4. \n');
script_header += gettext('-- For the circular dependencies, the order in which Schema Diff writes the objects is not very sophisticated \n');
script_header += gettext('-- and may require manual changes to the script to ensure changes are applied in the correct order.\n');
script_header += gettext('-- Please report an issue for any failure with the reproduction steps. \n');
_.each(url_params, function(key, val) {
url_params[key] = parseInt(val, 10);
});
$('#diff_fetching_data').removeClass('d-none');
$('#diff_fetching_data').find('.schema-diff-busy-text').text('Generating script...');
open_query_tool = function get_server_details() {
$.ajax({
url: baseServerUrl,
method: 'GET',
dataType: 'json',
contentType: 'application/json',
})
.done(function (res) {
let data = res.data;
let server_data = {};
if (data) {
let sqlId = `schema${self.trans_id}`;
server_data['sgid'] = data.gid;
server_data['sid'] = data.sid;
server_data['stype'] = data.type;
server_data['server'] = data.name;
server_data['user'] = data.user;
server_data['did'] = self.model.get('target_did');
server_data['database'] = data.database;
server_data['sql_id'] = sqlId;
if (_.isUndefined(generated_script)) {
generated_script = script_header + 'BEGIN;' + '\n' + self.model.get('diff_ddl') + '\n' + 'END;';
}
localStorage.setItem(sqlId, generated_script);
generateScript(server_data, pgWindow.pgAdmin.Tools.SQLEditor, Alertify);
}
$('#diff_fetching_data').find('.schema-diff-busy-text').text('');
$('#diff_fetching_data').addClass('d-none');
})
.fail(function (xhr) {
self.raise_error_on_fail(gettext('Generate script error'), xhr);
$('#diff_fetching_data').find('.schema-diff-busy-text').text('');
$('#diff_fetching_data').addClass('d-none');
});
};
if (sel_rows.length > 0) {
let script_array = {1: [], 2: [], 3: [], 4: [], 5: []},
script_body = '';
for (let sel_row_val of sel_rows) {
let data = self.grid.getData().getItem(sel_row_val);
if(!_.isUndefined(data.diff_ddl)) {
if (!(data.dependLevel in script_array)) script_array[data.dependLevel] = [];
// Check whether the selected object belongs to source only schema
// if yes then we will have to add create schema statement before
// creating any other object.
if (!_.isUndefined(data.source_schema_name) && !_.isNull(data.source_schema_name)) {
let schema_query = '\nCREATE SCHEMA IF NOT EXISTS ' + data.source_schema_name + ';\n';
if (script_array[data.dependLevel].indexOf(schema_query) == -1) {
script_array[data.dependLevel].push(schema_query);
}
}
script_array[data.dependLevel].push(data.diff_ddl);
}
}
_.each(Object.keys(script_array).reverse(), function(s) {
if (script_array[s].length > 0) {
script_body += script_array[s].join('\n') + '\n\n';
}
});
generated_script = script_header + 'BEGIN;' + '\n' + script_body + 'END;';
open_query_tool();
} else if (!_.isUndefined(self.model.get('diff_ddl'))) {
open_query_tool();
}
return false;
}
check_empty_diff() {
var self = this;
this.panel_obj.$container.find('#schema-diff-grid .slick-viewport .pg-panel-message').remove();
if (self.dataView.getFilteredItems().length == 0) {
let msg = gettext('No difference found');
// Make the height to 0px to avoid extra scroll bar, height will be calculated automatically.
this.panel_obj.$container.find('#schema-diff-grid .slick-viewport .grid-canvas')[0].style.height = '0px';
this.panel_obj.$container.find('#schema-diff-grid .slick-viewport'
).prepend('<div class="pg-panel-message">'+ msg +'</div>');
}
}
render_grid(data) {
var self = this;
var grid;
if (self.grid) {
// Only render the data
self.render_grid_data(data);
self.check_empty_diff();
return;
}
// Checkbox Column
var checkboxSelector = new Slick.CheckboxSelectColumn({
cssClass: 'slick-cell-checkboxsel',
minWidth: 30,
});
// Format Schema object title with appropriate icon
var formatColumnTitle = function (row, cell, value, columnDef, dataContext) {
let icon = 'icon-' + dataContext.type;
return '<i class="ml-2 wcTabIcon '+ icon +'"></i><span>' + value + '</span>';
};
// Grid Columns
var grid_width = (self.grid_width - 47) / 2 ;
var columns = [
checkboxSelector.getColumnDefinition(),
{id: 'title', name: gettext('Objects'), field: 'title', minWidth: grid_width, formatter: formatColumnTitle},
{id: 'status', name: gettext('Comparison Result'), field: 'status', minWidth: grid_width},
{id: 'label', name: gettext('Objects'), field: 'label', width: 0, minWidth: 0, maxWidth: 0,
cssClass: 'really-hidden', headerCssClass: 'really-hidden'},
{id: 'type', name: gettext('Objects'), field: 'type', width: 0, minWidth: 0, maxWidth: 0,
cssClass: 'really-hidden', headerCssClass: 'really-hidden'},
{id: 'id', name: 'id', field: 'id', width: 0, minWidth: 0, maxWidth: 0,
cssClass: 'really-hidden', headerCssClass: 'really-hidden' },
];
// Grid Options
var options = {
enableCellNavigation: true,
enableColumnReorder: false,
enableRowSelection: true,
};
// Grouping by Schema Object
self.groupBySchemaObject = function() {
self.dataView.setGrouping([{
getter: 'group_name',
formatter: function (g) {
let icon = 'icon-schema';
if (g.rows[0].group_name == 'Database Objects'){
icon = 'icon-coll-database';
}
return '<i class="wcTabIcon '+ icon +'"></i><span>' + _.escape(g.rows[0].group_name);
},
aggregateCollapsed: true,
lazyTotalsCalculation: true,
}, {
getter: 'type',
formatter: function (g) {
let icon = 'icon-coll-' + g.value;
let identical=0, different=0, source_only=0, target_only=0;
for (let row_val of g.rows) {
if (row_val['status'] == self.filters[0]) identical++;
else if (row_val['status'] == self.filters[1]) different++;
else if (row_val['status'] == self.filters[2]) source_only++;
else if (row_val['status'] == self.filters[3]) target_only++;
}
return '<i class="wcTabIcon '+ icon +'"></i><span>' + g.rows[0].label + ' - ' + gettext('Identical') + ': <strong>' + identical + '</strong>&nbsp;&nbsp;' + gettext('Different') + ': <strong>' + different + '</strong>&nbsp;&nbsp;' + gettext('Source Only') + ': <strong>' + source_only + '</strong>&nbsp;&nbsp;' + gettext('Target Only') + ': <strong>' + target_only + '</strong></span>';
},
aggregateCollapsed: true,
collapsed: true,
lazyTotalsCalculation: true,
}]);
};
var groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider({ checkboxSelect: true,
checkboxSelectPlugin: checkboxSelector });
// Dataview for grid
self.dataView = new Slick.Data.DataView({
groupItemMetadataProvider: groupItemMetadataProvider,
inlineFilters: false,
});
// Wire up model events to drive the grid
self.dataView.onRowCountChanged.subscribe(function () {
grid.updateRowCount();
grid.render();
self.accessibility_error();
});
self.dataView.onRowsChanged.subscribe(function (e, args) {
grid.invalidateRows(args.rows);
grid.render();
self.accessibility_error();
});
// Change Row css on the basis of item status
self.dataView.getItemMetadata = function(row) {
let item = self.dataView.getItem(row),
group_item = groupItemMetadataProvider.getGroupRowMetadata(item);
if (item.__group) {
return group_item;
}
if(item.status === 'Different') {
return { cssClasses: 'different' };
} else if (item.status === 'Source Only') {
return { cssClasses: 'source' };
} else if (item.status === 'Target Only') {
return { cssClasses: 'target' };
}
return null;
};
// Grid filter
self.filter = function (item) {
let self_local = this;
if (self_local.sel_filters.indexOf(item.status) !== -1) return true;
return false;
};
let $data_grid = $('#schema-diff-grid');
grid = this.grid = new Slick.Grid($data_grid, self.dataView, columns, options);
$('label[for='+ columns[0].name.split('\'')[1] +']').append('<span style="display:none">checkbox</span>');
grid.registerPlugin(groupItemMetadataProvider);
grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false}));
grid.registerPlugin(checkboxSelector);
self.dataView.syncGridSelection(grid, true, true);
grid.onMouseEnter.subscribe(function (evt) {
var cell = grid.getCellFromEvent(evt);
self.gridContext = {};
self.gridContext.rowIndex = cell.row;
self.gridContext.row = grid.getDataItem(cell.row);
}.bind(self));
grid.onMouseLeave.subscribe(function () {
self.gridContext = {};
});
grid.onClick.subscribe(function(e, args) {
if (args.row) {
data = args.grid.getData().getItem(args.row);
if (data.status) this.ddlCompare(data);
}
}.bind(self));
grid.onSelectedRowsChanged.subscribe(self.handleDependencies.bind(this));
self.model.on('change:diff_ddl', function(event) {
self.handleDependencies.bind(event, self);
});
$('#schema-diff-grid').on('keyup', function() {
if ((event.keyCode == 38 || event.keyCode ==40) && this.grid.getActiveCell().row) {
data = this.grid.getData().getItem(this.grid.getActiveCell().row);
this.ddlCompare(data);
}
}.bind(self));
self.render_grid_data(data);
}
render_grid_data(data) {
var self = this;
self.grid.setSelectedRows([]);
self.selected_row_count = self.grid.getSelectedRows().length;
data.sort((a, b) => (a.label > b.label) ? 1 : (a.label === b.label) ? ((a.title > b.title) ? 1 : -1) : -1);
self.dataView.beginUpdate();
self.dataView.setItems(data);
self.dataView.setFilter(self.filter.bind(self));
self.groupBySchemaObject();
self.dataView.endUpdate();
self.dataView.refresh();
self.resize_grid();
self.accessibility_error();
}
accessibility_error() {
$('.slick-viewport').scroll(function() {
setTimeout(function() {
$('span.slick-column-name label').append('<span style="display:none">checkbox</span>');
$('div.slick-cell.l0 label').each(function(inx, el) {
$(el).append('<span style="display:none">checkbox</span>');
});
}, 0);
});
}
handle_generate_button(){
if (this.grid.getSelectedRows().length > 0 || (this.model.get('diff_ddl') != '' && !_.isUndefined(this.model.get('diff_ddl')))) {
this.header.$el.find('button#generate-script').removeAttr('disabled');
} else {
this.header.$el.find('button#generate-script').attr('disabled', true);
}
}
resize_grid() {
let $data_grid = $('#schema-diff-grid'),
grid_height = (this.panel_obj.height() > 0) ? this.panel_obj.height() - 100 : this.grid_height - 100;
$data_grid.height(grid_height);
$data_grid.css({
'height': grid_height + 'px',
});
if (this.grid) this.grid.resizeCanvas();
}
getCompareStatus() {
var self = this,
url_params = {'trans_id': self.trans_id},
baseUrl = url_for('schema_diff.poll', url_params);
$.ajax({
url: baseUrl,
method: 'GET',
dataType: 'json',
contentType: 'application/json',
})
.done(function (res) {
let msg = _.escape(res.data.compare_msg);
if (res.data.diff_percentage != 100) {
msg = msg + gettext(' (this may take a few minutes)...');
}
msg = msg + '<br>' + gettext('%s completed.', res.data.diff_percentage + '%');
$('#diff_fetching_data').find('.schema-diff-busy-text').html(msg);
})
.fail(function (xhr) {
self.raise_error_on_fail(gettext('Poll error'), xhr);
self.stopDiffPoller('fail');
});
}
startDiffPoller() {
$('#ddl_comp_fetching_data').addClass('d-none');
$('#diff_fetching_data').removeClass('d-none');
/* Execute once for the first time as setInterval will not do */
this.getCompareStatus();
this.diff_poller_int_id = setInterval(this.getCompareStatus.bind(this), 1000);
}
stopDiffPoller(status) {
clearInterval(this.diff_poller_int_id);
// The last polling for comparison
if (status !== 'fail') this.getCompareStatus();
$('#diff_fetching_data').find('.schema-diff-busy-text').text('');
$('#diff_fetching_data').addClass('d-none');
}
ddlCompare(data) {
var self = this,
node_type = data.type,
source_oid = data.oid,
target_oid;
self.model.set({
'source_ddl': undefined,
'target_ddl': undefined,
'diff_ddl': undefined,
}, {silent: true});
if(data.status && data.status.toLowerCase() == 'identical') {
var url_params = self.selection;
target_oid = data.target_oid;
url_params['trans_id'] = self.trans_id;
url_params['source_scid'] = data.source_scid;
url_params['target_scid'] = data.target_scid;
url_params['source_oid'] = source_oid;
url_params['target_oid'] = target_oid;
url_params['comp_status'] = data.status;
url_params['node_type'] = node_type;
_.each(url_params, function(key, val) {
url_params[key] = parseInt(val, 10);
});
$('#ddl_comp_fetching_data').removeClass('d-none');
var baseUrl = url_for('schema_diff.ddl_compare', url_params);
self.model.url = baseUrl;
self.model.fetch({
success: function() {
self.footer.render();
$('#ddl_comp_fetching_data').addClass('d-none');
},
error: function() {
self.footer.render();
$('#ddl_comp_fetching_data').addClass('d-none');
},
});
} else {
self.model.set({
'source_ddl': data.source_ddl,
'target_ddl': data.target_ddl,
'diff_ddl': data.diff_ddl,
}, {silent: true});
self.footer.render();
}
}
transformFunc(data) {
let group_template_options = [];
for (let key in data) {
if (data.hasOwnProperty(key)) {
group_template_options.push({'group': key, 'optval': data[key]});
}
}
return group_template_options;
}
render() {
let self = this;
let panel = self.docker.findPanels('schema_diff_header_panel')[0];
var header = panel.$container.find('#schema-diff-header');
self.header = new SchemaDiffHeaderView({
el: header,
model: this.model,
fields: [{
name: 'source_sid', label: false,
control: SchemaDiffSelect2Control,
transform: function(data) {
return self.transformFunc(data);
},
url: url_for('schema_diff.servers'),
select2: {
allowClear: true,
placeholder: gettext('Select server...'),
},
connect: function() {
self.connect_server(arguments[0], arguments[1]);
},
group: 'source',
disabled: function() {
return false;
},
}, {
name: 'source_did',
group: 'source',
deps: ['source_sid'],
control: SchemaDiffSelect2Control,
url: function() {
if (this.get('source_sid'))
return url_for('schema_diff.databases', {'sid': this.get('source_sid')});
return false;
},
select2: {
allowClear: true,
placeholder: gettext('Select database...'),
},
disabled: function(m) {
let self_local = this;
if (!_.isUndefined(m.get('source_sid')) && !_.isNull(m.get('source_sid'))
&& m.get('source_sid') !== '') {
setTimeout(function() {
for (let opt_val of self_local.options) {
if (opt_val.is_maintenance_db) {
m.set('source_did', opt_val.value);
}
}
}, 10);
return false;
}
setTimeout(function() {
m.set('source_did', undefined);
}, 10);
return true;
},
connect: function() {
self.connect_database(this.model.get('source_sid'), arguments[0], arguments[1]);
},
}, {
name: 'source_scid',
control: SchemaDiffSelect2Control,
group: 'source',
deps: ['source_sid', 'source_did'],
url: function() {
if (this.get('source_sid') && this.get('source_did'))
return url_for('schema_diff.schemas', {'sid': this.get('source_sid'), 'did': this.get('source_did')});
return false;
},
select2: {
allowClear: true,
placeholder: gettext('Select schema...'),
},
disabled: function(m) {
if (!_.isUndefined(m.get('source_did')) && !_.isNull(m.get('source_did'))
&& m.get('source_did') !== '') {
return false;
}
setTimeout(function() {
m.set('source_scid', undefined);
}, 10);
return true;
},
}, {
name: 'target_sid', label: false,
control: SchemaDiffSelect2Control,
transform: function(data) {
return self.transformFunc(data);
},
group: 'target',
url: url_for('schema_diff.servers'),
select2: {
allowClear: true,
placeholder: gettext('Select server...'),
},
disabled: function() {
return false;
},
connect: function() {
self.connect_server(arguments[0], arguments[1]);
},
}, {
name: 'target_did',
control: SchemaDiffSelect2Control,
group: 'target',
deps: ['target_sid'],
url: function() {
if (this.get('target_sid'))
return url_for('schema_diff.databases', {'sid': this.get('target_sid')});
return false;
},
select2: {
allowClear: true,
placeholder: gettext('Select database...'),
},
disabled: function(m) {
let self_local = this;
if (!_.isUndefined(m.get('target_sid')) && !_.isNull(m.get('target_sid'))
&& m.get('target_sid') !== '') {
setTimeout(function() {
for (let opt_val of self_local.options) {
if (opt_val.is_maintenance_db) {
m.set('target_did', opt_val.value);
}
}
}, 10);
return false;
}
setTimeout(function() {
m.set('target_did', undefined);
}, 10);
return true;
},
connect: function() {
self.connect_database(this.model.get('target_sid'), arguments[0], arguments[1]);
},
}, {
name: 'target_scid',
control: SchemaDiffSelect2Control,
group: 'target',
deps: ['target_sid', 'target_did'],
url: function() {
if (this.get('target_sid') && this.get('target_did'))
return url_for('schema_diff.schemas', {'sid': this.get('target_sid'), 'did': this.get('target_did')});
return false;
},
select2: {
allowClear: true,
placeholder: gettext('Select schema...'),
},
disabled: function(m) {
if (!_.isUndefined(m.get('target_did')) && !_.isNull(m.get('target_did'))
&& m.get('target_did') !== '') {
return false;
}
setTimeout(function() {
m.set('target_scid', undefined);
}, 10);
return true;
},
}],
});
self.footer = new SchemaDiffFooterView({
model: this.model,
fields: [{
name: 'source_ddl', label: false,
control: SchemaDiffSqlControl,
group: 'ddl-source',
}, {
name: 'target_ddl', label: false,
control: SchemaDiffSqlControl,
group: 'ddl-target',
}, {
name: 'diff_ddl', label: false,
control: SchemaDiffSqlControl,
group: 'ddl-diff', copyRequired: true,
}],
});
self.header.render();
self.header.$el.find('button.btn-primary').on('click', self.compare_schemas.bind(self));
self.header.$el.find('button#generate-script').on('click', self.generate_script.bind(self));
self.header.$el.find('ul.filter a.dropdown-item').on('click', self.refresh_filters.bind(self));
self.header.$el.find('ul.ignore a.dropdown-item').on('click', self.refresh_ignore_settings.bind(self));
/* Set the default value for 'ignore owner' and 'ignore whitespace' */
let pref = pgWindow.pgAdmin.Browser.get_preferences_for_module('schema_diff');
if (pref.ignore_owner) self.header.$el.find('ul.ignore a.dropdown-item#btn-ignore-owner').click();
if (pref.ignore_whitespaces) self.header.$el.find('ul.ignore a.dropdown-item#btn-ignore-whitespaces').click();
let footer_panel = self.docker.findPanels('schema_diff_footer_panel')[0],
header_panel = self.docker.findPanels('schema_diff_header_panel')[0];
footer_panel.$container.find('#schema-diff-ddl-comp').append(self.footer.render().$el);
$('div.CodeMirror div textarea').attr('aria-label', 'textarea');
header_panel.$container.find('#schema-diff-grid').append(`<div class='obj_properties container-fluid'>
<div class='pg-panel-message'>` + gettext('<strong>Database Compare:</strong> Select the server and database for the source and target and Click <strong>Compare</strong>.') +
gettext('</br><strong>Schema Compare:</strong> Select the server, database and schema for the source and target and Click <strong>Compare</strong>.') +
gettext('</br><strong>Note:</strong> The dependencies will not be resolved in the Schema comparison.') + '</div></div>');
self.grid_width = $('#schema-diff-grid').width();
self.grid_height = this.panel_obj.height();
}
refresh_filters(event) {
let self = this;
_.each(self.filters, function(filter) {
let index = self.sel_filters.indexOf(filter);
let filter_class = '.' + filter.replace(' ', '-').toLowerCase();
if ($(event.currentTarget).find(filter_class).length == 1) {
if ($(filter_class).hasClass('visibility-hidden') === true) {
$(filter_class).removeClass('visibility-hidden');
if (index === -1) self.sel_filters.push(filter);
} else {
$(filter_class).addClass('visibility-hidden');
if(index !== -1 ) self.sel_filters.splice(index, 1);
}
}
});
// Check whether comparison data is loaded or not
if(!_.isUndefined(self.dataView) && !_.isNull(self.dataView)) {
// Refresh the grid
self.dataView.refresh();
self.check_empty_diff();
}
}
refresh_ignore_settings(event) {
let self = this,
element = $(event.currentTarget).find('.fa-check');
if (element.length == 1) {
if (element.hasClass('visibility-hidden') === true) {
element.removeClass('visibility-hidden');
if (event.currentTarget.id === 'btn-ignore-owner') self.ignore_owner = 1;
if (event.currentTarget.id === 'btn-ignore-whitespaces') self.ignore_whitespaces = 1;
} else {
element.addClass('visibility-hidden');
if (event.currentTarget.id === 'btn-ignore-owner') self.ignore_owner = 0;
if (event.currentTarget.id === 'btn-ignore-whitespaces') self.ignore_whitespaces = 0;
}
}
}
connect_database(server_id, db_id, callback) {
var url = url_for('schema_diff.connect_database', {'sid': server_id, 'did': db_id});
$.post(url)
.done(function(res) {
if (res.success && res.data) {
callback(res.data);
}
})
.fail(function(xhr, error) {
Notify.pgNotifier(error, xhr, gettext('Failed to connect the database.'));
});
}
connect_server(server_id, callback) {
let self = this;
var onFailure = function(
xhr, status, error, sid, err_callback
) {
Notify.pgNotifier('error', xhr, error, function(msg) {
setTimeout(function() {
showSchemaDiffServerPassword(
self.docker,
gettext('Connect to Server'),
msg,
sid,
err_callback,
onSuccess,
onFailure
);
}, 100);
});
},
onSuccess = function(res, suc_callback) {
if (res && res.data) {
// We're not reconnecting
suc_callback(res.data);
}
};
var url = url_for('schema_diff.connect_server', {'sid': server_id});
$.post(url)
.done(function(res) {
if (res.success == 1) {
return onSuccess(res, callback);
}
})
.fail(function(xhr, status, error) {
return onFailure(
xhr, status, error, server_id, callback
);
});
}
}
SchemaDiffUI.prototype.handleDependencies = handleDependencies;
SchemaDiffUI.prototype.selectDependenciesForGroup = selectDependenciesForGroup;
SchemaDiffUI.prototype.selectDependenciesForAll = selectDependenciesForAll;
SchemaDiffUI.prototype.selectDependencies = selectDependencies;

View File

@ -1,145 +0,0 @@
#schema-diff-container {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: $color-gray-light;
}
#schema-diff-grid .slick-header .slick-header-columns {
background: $color-bg;
height: 40px;
border-bottom: none;
}
#schema-diff-grid .slick-header .slick-header-column.ui-state-default {
padding: 4px 0 3px 6px;
border-bottom: $panel-border;
border-right: $panel-border;
}
#schema-diff-grid {
font-family: $font-family-primary;
font-size: $tree-font-size;
.ui-widget-content {
background-color: $input-bg;
color: $input-color;
}
.ui-state-default {
color: $color-fg;
}
}
.slick-row:hover .slick-cell{
border-top: $table-hover-border;
border-bottom: $table-hover-border;
background-color: $table-hover-bg-color;
}
#schema-diff-grid .slick-header .slick-header-column.selected {
background-color: $color-primary;
color: $color-primary-fg;
}
.slick-row .slick-cell {
border-bottom: $panel-border;
border-right: $panel-border;
z-index: 0;
}
#schema-diff-grid .slick-row .slick-cell.l0.r0.selected {
background-color: inherit;
}
#schema-diff-grid .slick-row > .slick-cell.selected {
background-color: $table-hover-bg-color !important;
border-top: $table-hover-border;
border-bottom: $table-hover-border;
}
#schema-diff-grid div.slick-header.ui-state-default {
background: $color-bg;
border-bottom: none;
border-right: none;
border-top: $panel-border;
}
#schema-diff-grid .different {
background-color: $schemadiff-diff-row-color !important;
color: $schema-diff-color-fg;
}
#schema-diff-grid .source {
background-color: $schemadiff-source-row-color;
color: $schema-diff-color-fg;
}
#schema-diff-grid .target {
background-color: $schemadiff-target-row-color !important;
color: $schema-diff-color-fg;
}
#schema-diff-grid .slick-row.active {
background-color: $table-bg-selected !important;
color: $schema-diff-color-fg;
}
#schema-diff-ddl-comp {
height: 100%;
bottom: 10px;
background-color: $color-bg !important;
overflow-y: hidden;
}
#schema-diff-grid .slick-group-select-checkbox {
width: 13px;
height: 13px;
margin-right: 1rem;
vertical-align: middle;
display: inline-block;
}
.slick-group-toggle.collapsed::before {
font-family: $font-family-icon;
content: "\f054";
font-size: 0.6rem;
border: none;
font-weight: 900;
}
.slick-group-toggle.expanded::before {
font-family: $font-family-icon;
content: "\f078";
font-size: 0.6rem;
margin-left: 0rem;
font-weight: 900;
}
#schema-diff-ddl-comp .badge .caret::before {
font-family: $font-family-icon;
content: "\f078";
font-size: 0.7rem;
margin-left: 0rem;
font-weight: 900;
}
.slick-group {
color: $input-color !important;
}
.slick-group:hover {
color: $schema-diff-color-fg !important;
}
.slick-group.active {
color: $schema-diff-color-fg !important;
}
.select2-selection__placeholder {
color: $select2-placeholder !important;
}
#btn-ignore-dropdown {
color: $btn-primary-color !important;
background-color: $color-primary !important;
border-color: $color-primary !important;
}

View File

@ -2,14 +2,10 @@
{% block init_script %} {% block init_script %}
try { try {
require( require(
['sources/generated/slickgrid', 'sources/generated/codemirror', 'sources/generated/browser_nodes'], ['sources/generated/codemirror', 'sources/generated/browser_nodes', 'sources/generated/schema_diff'],
function() { function() {
require(['sources/generated/schema_diff'], function(pgSchemaDiffHook) { var pgSchemaDiff = window.pgAdmin.Tools.SchemaDiff;
var pgSchemaDiffHook = pgSchemaDiffHook || pgAdmin.Tools.SchemaDiffHook; pgSchemaDiff.load(document.getElementById('schema-diff-main-container'),{{trans_id}});
pgSchemaDiffHook.load({{trans_id}});
}, function() {
console.log(arguments);
});
}, },
function() { function() {
console.log(arguments); console.log(arguments);
@ -19,18 +15,28 @@ try {
} }
{% endblock %} {% endblock %}
{% block css_link %} {% block css_link %}
<style>
#schema-diff-main-container {
display: flex;
flex-direction: column;
height: 100%;
}
#schema-diff-main-container:not(:empty) + .pg-sp-container {
display: none;
}
</style>
<link type="text/css" rel="stylesheet" href="{{ url_for('browser.browser_css')}}"/> <link type="text/css" rel="stylesheet" href="{{ url_for('browser.browser_css')}}"/>
{% endblock %} {% endblock %}
{% block title %}{{editor_title}}{% endblock %} {% block title %}{{editor_title}}{% endblock %}
{% block body %} {% block body %}
<div id="schema-diff-container"> <div id="schema-diff-main-container" tabindex="0">
<div id="diff_fetching_data" class="pg-sp-container schema-diff-busy-fetching d-none"> <div class="pg-sp-container">
<div class="pg-sp-content"> <div class="pg-sp-content">
<div class="row"> <div class="row">
<div class="col-12 pg-sp-icon"></div> <div class="col-12 pg-sp-icon"></div>
</div> </div>
<div class="row"><div class="col-12 pg-sp-text schema-diff-busy-text"></div></div>
</div>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}

View File

@ -130,7 +130,7 @@ const finaliseData = (nodeData, datum)=> {
datum.icon = 'icon-' + datum.type; datum.icon = 'icon-' + datum.type;
/* finalise path */ /* finalise path */
[datum.path, datum.id_path] = translateSearchObjectsPath(nodeData, datum.path, datum.catalog_level); [datum.path, datum.id_path] = translateSearchObjectsPath(nodeData, datum.path, datum.catalog_level);
/* id is required by slickgrid dataview */ /* id is required by dataview */
datum.id = datum.id_path ? datum.id_path.join('.') : _.uniqueId(datum.name); datum.id = datum.id_path ? datum.id_path.join('.') : _.uniqueId(datum.name);
datum.other_info = datum.other_info ? _.escape(datum.other_info) : datum.other_info; datum.other_info = datum.other_info ? _.escape(datum.other_info) : datum.other_info;

View File

@ -15,7 +15,6 @@ import * as commonUtils from 'sources/utils';
import $ from 'jquery'; import $ from 'jquery';
import url_for from 'sources/url_for'; import url_for from 'sources/url_for';
import _ from 'lodash'; import _ from 'lodash';
import alertify from 'pgadmin.alertifyjs';
import pgWindow from 'sources/window'; import pgWindow from 'sources/window';
import pgAdmin from 'sources/pgadmin'; import pgAdmin from 'sources/pgadmin';
import pgBrowser from 'pgadmin.browser'; import pgBrowser from 'pgadmin.browser';
@ -27,6 +26,7 @@ import ReactDOM from 'react-dom';
import QueryToolComponent from './components/QueryToolComponent'; import QueryToolComponent from './components/QueryToolComponent';
import ModalProvider from '../../../../static/js/helpers/ModalProvider'; import ModalProvider from '../../../../static/js/helpers/ModalProvider';
import Theme from '../../../../static/js/Theme'; import Theme from '../../../../static/js/Theme';
import { showRenamePanel } from '../../../../static/js/Dialogs';
const wcDocker = window.wcDocker; const wcDocker = window.wcDocker;
@ -242,38 +242,19 @@ export default class SQLEditor {
} }
onPanelRename(queryToolPanel, panelData, is_query_tool) { onPanelRename(queryToolPanel, panelData, is_query_tool) {
var temp_title = panelData.$titleText[0].textContent;
var is_dirty_editor = queryToolPanel.is_dirty_editor ? queryToolPanel.is_dirty_editor : false;
var title = queryToolPanel.is_dirty_editor ? panelData.$titleText[0].textContent.replace(/.$/, '') : temp_title;
alertify.prompt('', title,
// We will execute this function when user clicks on the OK button
function(evt, value) {
// Remove the leading and trailing white spaces.
value = value.trim();
if(value) {
var is_file = false;
if(panelData.$titleText[0].innerHTML.includes('File - ')) {
is_file = true;
}
var selected_item = pgBrowser.tree.selected();
var panel_titles = '';
if(is_query_tool) { let preferences = pgBrowser.get_preferences_for_module('browser');
panel_titles = panelTitleFunc.getPanelTitle(pgBrowser, selected_item, value); let temp_title = panelData.$titleText[0].textContent;
} else { let is_dirty_editor = queryToolPanel.is_dirty_editor ? queryToolPanel.is_dirty_editor : false;
panel_titles = showViewData.generateViewDataTitle(pgBrowser, selected_item, value); let title = queryToolPanel.is_dirty_editor ? panelData.$titleText[0].textContent.replace(/.$/, '') : temp_title;
}
// Set title to the selected tab. let qtdata = {
if (is_dirty_editor) { is_query_tool: is_query_tool,
panel_titles = panel_titles + ' *'; is_file: panelData.$titleText[0].innerHTML.includes('File - '),
} is_dirty_editor: is_dirty_editor
panelTitleFunc.setQueryToolDockerTitle(queryToolPanel, is_query_tool, _.unescape(panel_titles), is_file); };
}
}, showRenamePanel(title, preferences, queryToolPanel, 'querytool', qtdata);
// We will execute this function when user clicks on the Cancel
// button. Do nothing just close it.
function(evt) { evt.cancel = false; }
).set({'title': gettext('Rename Panel')});
} }

View File

@ -32,7 +32,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
2) Properties Tab (BackFrom) 2) Properties Tab (BackFrom)
3) Dependents Tab (BackGrid) 3) Dependents Tab (BackGrid)
4) SQL Tab (Code Mirror) 4) SQL Tab (Code Mirror)
5) Query Tool (SlickGrid) 5) Query Tool (Result Grid)
""" """
scenarios = [ scenarios = [
@ -189,7 +189,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
def _check_xss_in_query_tool(self): def _check_xss_in_query_tool(self):
print( print(
"\n\tChecking the SlickGrid cell for XSS vulnerabilities", "\n\tChecking the Result Grid cell for XSS vulnerabilities",
file=sys.stderr, end="" file=sys.stderr, end=""
) )
self.page.fill_codemirror_area_with( self.page.fill_codemirror_area_with(
@ -205,7 +205,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
self._check_escaped_characters( self._check_escaped_characters(
source_code, source_code,
'&lt;img src="x" onerror="console.log(1)"&gt;', '&lt;img src="x" onerror="console.log(1)"&gt;',
"Query tool (SlickGrid)" "Query tool (Result Grid)"
) )
def _check_xss_in_query_tool_history(self): def _check_xss_in_query_tool_history(self):
@ -255,7 +255,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
def _check_xss_view_data(self): def _check_xss_view_data(self):
print( print(
"\n\tChecking the SlickGrid cell for XSS vulnerabilities", "\n\tChecking the Result Grid cell for XSS vulnerabilities",
file=sys.stderr, end="" file=sys.stderr, end=""
) )
@ -268,7 +268,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
self._check_escaped_characters( self._check_escaped_characters(
source_code, source_code,
self.check_xss_chars_set2, self.check_xss_chars_set2,
"View Data (SlickGrid)" "View Data (Result Grid)"
) )
def _check_xss_in_explain_module(self): def _check_xss_in_explain_module(self):

View File

@ -15,9 +15,10 @@ beforeAll(function () {
jasmine.getEnv().allowRespy(true); jasmine.getEnv().allowRespy(true);
window.addEventListener('error', e => { window.addEventListener('error', e => {
console.log(e.message);
if(e.message === 'ResizeObserver loop completed with undelivered notifications.' || if(e.message === 'ResizeObserver loop completed with undelivered notifications.' ||
e.message === 'ResizeObserver loop limit exceeded') { e.message === 'ResizeObserver loop limit exceeded') {
e.stopImmediatePropagation(); e.stopPropagation();
} }
}); });
}); });

View File

@ -0,0 +1,111 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import $ from 'jquery';
window.jQuery = window.$ = $;
import 'wcdocker';
import '../helper/enzyme.helper';
import React from 'react';
import { createMount } from '@material-ui/core/test-utils';
import jasmineEnzyme from 'jasmine-enzyme';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios/index';
// import pgAdmin from 'sources/pgadmin';
import pgWindow from 'sources/window';
import url_for from 'sources/url_for';
import { messages } from '../fake_messages';
import { TreeFake } from '../tree/tree_fake';
import Theme from '../../../pgadmin/static/js/Theme';
import SchemaDiffComponent from '../../../pgadmin/tools/schema_diff/static/js/components/SchemaDiffComponent';
import SchemaDiff from '../../../pgadmin/tools/schema_diff/static/js/SchemaDiffModule';
describe('Schema Diff Component', () => {
let mount;
let mountDOM;
let tree;
let params;
let networkMock;
let schemaDiffInstance;
/* Use createMount so that material ui components gets the required context */
/* https://material-ui.com/guides/testing/#api */
beforeAll(() => {
mount = createMount();
});
beforeEach(() => {
jasmineEnzyme();
// Element for mount wcDocker panel
mountDOM = $('<div class="dockerContainer">');
$(document.body).append(mountDOM);
$(document.body).append($('<div id="debugger-main-container">'));
/* messages used by validators */
pgWindow.pgAdmin.Browser = pgWindow.pgAdmin.Browser || {};
pgWindow.pgAdmin.Browser.messages = pgWindow.pgAdmin.Browser.messages || messages;
pgWindow.pgAdmin.Browser.utils = pgWindow.pgAdmin.Browser.utils || {};
schemaDiffInstance = new SchemaDiff(pgWindow.pgAdmin, pgWindow.pgAdmin.Browser);
// nodeInfo = { parent: {} };
// eslint-disable-next-line
let docker = new wcDocker(
'.dockerContainer', {
allowContextMenu: false,
allowCollapse: false,
loadingClass: 'pg-sp-icon',
});
tree = new TreeFake();
pgWindow.pgAdmin.Browser.tree = tree;
pgWindow.pgAdmin.Browser.docker = docker;
params = {
transId: 1234,
schemaDiff: schemaDiffInstance,
};
networkMock = new MockAdapter(axios);
});
it('SchemaDiff Init', () => {
networkMock.onGet(url_for('schema_diff.servers')).reply(200,
{'success':1,
'errormsg':'',
'info':'',
'result':null,
'data':{
'Servers':[
{'value':1,'label':'PostgreSQL 12','image':'icon-pg','_id':1,'connected':true},
{'value':2,'label':'PostgreSQL 10','image':'icon-server-not-connected','_id':2,'connected':false},
{'value':3,'label':'PostgreSQL 11','image':'icon-server-not-connected','_id':3,'connected':false},
{'value':4,'label':'PostgreSQL 13','image':'icon-server-not-connected','_id':4,'connected':false},
{'value':5,'label':'PostgreSQL 9.6','image':'icon-server-not-connected','_id':5,'connected':false},
{'value':8,'label':'test1234yo','image':'icon-server-not-connected','_id':8,'connected':false}
]
}
}
);
mount(
<Theme>
<SchemaDiffComponent
params={{ transId: params.transId, pgAdmin: pgWindow.pgAdmin }}
>
</SchemaDiffComponent>
</Theme>
);
});
});

View File

@ -1,343 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
define([
'sources/selection/active_cell_capture',
'sources/selection/range_selection_helper',
], function (ActiveCellCapture, RangeSelectionHelper) {
describe('ActiveCellCapture', function () {
var grid, activeCellPlugin, getSelectedRangesSpy, setSelectedRangesSpy;
var onHeaderClickFunction;
var onClickFunction;
var onColumnsResizedFunction;
var onHeaderMouseEnterFunction;
var onHeaderMouseLeaveFunction;
var Slick = window.Slick;
beforeEach(function () {
getSelectedRangesSpy = jasmine.createSpy('getSelectedRangesSpy');
setSelectedRangesSpy = jasmine.createSpy('setSelectedRangesSpy');
grid = {
getSelectionModel: function () {
return {
getSelectedRanges: getSelectedRangesSpy,
setSelectedRanges: setSelectedRangesSpy,
};
},
getColumns: function () {
return [
{id: 'row-header-column'},
{id: 'column-1'},
{id: 'column-2'},
];
},
onDragEnd: jasmine.createSpyObj('onDragEnd', ['subscribe']),
onActiveCellChanged: jasmine.createSpyObj('onActiveCellChanged', ['subscribe']),
onHeaderClick: jasmine.createSpy('onHeaderClick'),
onClick: jasmine.createSpy('onClick'),
onKeyDown: jasmine.createSpyObj('onKeyDown', ['subscribe']),
onColumnsResized: jasmine.createSpyObj('onColumnsResized', ['subscribe']),
onHeaderMouseEnter: jasmine.createSpyObj('onHeaderMouseEnter', ['subscribe']),
onHeaderMouseLeave: jasmine.createSpyObj('onHeaderMouseLeave', ['subscribe']),
getDataLength: function () { return 10; },
setActiveCell: jasmine.createSpy('setActiveCell'),
getActiveCell: jasmine.createSpy('getActiveCell'),
resetActiveCell: jasmine.createSpy('resetActiveCell'),
};
activeCellPlugin = new ActiveCellCapture();
grid.onHeaderClick.subscribe =
jasmine.createSpy('subscribe').and.callFake(function (callback) {
onHeaderClickFunction = callback.bind(activeCellPlugin);
});
grid.onClick.subscribe =
jasmine.createSpy('subscribe').and.callFake(function (callback) {
onClickFunction = callback.bind(activeCellPlugin);
});
grid.onColumnsResized.subscribe =
jasmine.createSpy('subscribe').and.callFake(function (callback) {
onColumnsResizedFunction = callback.bind(activeCellPlugin);
});
grid.onHeaderMouseEnter.subscribe =
jasmine.createSpy('subscribe').and.callFake(function (callback) {
onHeaderMouseEnterFunction = callback.bind(activeCellPlugin);
});
grid.onHeaderMouseLeave.subscribe =
jasmine.createSpy('subscribe').and.callFake(function (callback) {
onHeaderMouseLeaveFunction = callback.bind(activeCellPlugin);
});
activeCellPlugin.init(grid);
});
describe('onHeaderClickHandler', function () {
describe('when no ranges are selected', function () {
beforeEach(function () {
getSelectedRangesSpy.and.returnValue([]);
onHeaderClickFunction({}, {column: {pos: 1}});
grid.getActiveCell.and.returnValue(null);
});
it('should set the active cell', function () {
expect(grid.setActiveCell).toHaveBeenCalledWith(0, 2);
});
it('should not change the selected ranges', function () {
expect(setSelectedRangesSpy).not.toHaveBeenCalled();
});
});
describe('when only one column is already selected', function () {
beforeEach(function () {
getSelectedRangesSpy.and.returnValue([RangeSelectionHelper.rangeForColumn(grid, 2)]);
grid.getActiveCell.and.returnValue({cell: 2, row: 0});
});
describe('when a different column is clicked', function () {
beforeEach(function () {
onHeaderClickFunction({}, {column: {pos: 4}});
});
it('should set the active cell to the newly clicked columns top cell', function () {
expect(grid.setActiveCell).toHaveBeenCalledWith(0, 5);
});
});
describe('when the same column is clicked', function () {
beforeEach(function () {
onHeaderClickFunction({}, {column: {pos: 1}});
});
it('should reset the active cell', function () {
expect(grid.resetActiveCell).toHaveBeenCalled();
});
});
describe('when mouse is inside the header row', function () {
beforeEach(function () {
onHeaderMouseEnterFunction({}, {});
});
describe('when user finishes resizing the selected column', function () {
var eventSpy;
beforeEach(function () {
eventSpy = jasmine.createSpyObj('event', ['stopPropagation']);
onColumnsResizedFunction({}, {grid: grid});
onHeaderClickFunction(eventSpy, {column: {pos: 1}});
});
it('should not deselect the current selected column', function () {
expect(grid.setActiveCell).not.toHaveBeenCalled();
expect(grid.resetActiveCell).not.toHaveBeenCalled();
});
it('should prevent further event propagation', function () {
expect(eventSpy.stopPropagation).toHaveBeenCalled();
});
describe('when the user clicks the resized column header', function () {
beforeEach(function () {
eventSpy.stopPropagation.calls.reset();
onHeaderClickFunction(eventSpy, {column: {pos: 2}});
});
it('should change the active cell', function () {
expect(grid.setActiveCell).toHaveBeenCalledWith(0, 3);
expect(grid.resetActiveCell).not.toHaveBeenCalled();
});
it('should allow further event propagation', function () {
expect(eventSpy.stopPropagation).not.toHaveBeenCalled();
});
});
});
});
describe('when mouse is outside the header row', function () {
beforeEach(function () {
onHeaderMouseEnterFunction({}, {});
onHeaderMouseLeaveFunction({}, {});
});
describe('when user finishes resizing the selected column', function () {
beforeEach(function () {
onColumnsResizedFunction({}, {grid: grid});
});
it('should not deselect the current selected column', function () {
expect(grid.setActiveCell).not.toHaveBeenCalled();
expect(grid.resetActiveCell).not.toHaveBeenCalled();
});
describe('when the user clicks another column header', function () {
var eventSpy;
beforeEach(function () {
eventSpy = jasmine.createSpyObj('event', ['stopPropagation']);
onHeaderMouseEnterFunction({}, {});
onHeaderClickFunction(eventSpy, {column: {pos: 3}});
});
it('should change the active cell', function () {
expect(grid.setActiveCell).toHaveBeenCalledWith(0, 4);
expect(grid.resetActiveCell).not.toHaveBeenCalled();
});
it('should allow further event propagation', function () {
expect(eventSpy.stopPropagation).not.toHaveBeenCalled();
});
});
});
});
});
describe('when three non-consecutive columns are selected', function () {
beforeEach(function () {
getSelectedRangesSpy.and.returnValue([
RangeSelectionHelper.rangeForColumn(grid, 10),
RangeSelectionHelper.rangeForColumn(grid, 6),
RangeSelectionHelper.rangeForColumn(grid, 22),
]);
grid.getActiveCell.and.returnValue({cell: 22, row: 0});
});
describe('when the third column is clicked (thereby deselecting it)', function () {
beforeEach(function () {
onHeaderClickFunction({}, {column: {pos: 21}});
});
it('should set the active cell to the second column', function () {
expect(grid.setActiveCell).toHaveBeenCalledWith(0, 6);
});
});
describe('when the second column is clicked (thereby deselecting it)', function () {
beforeEach(function () {
onHeaderClickFunction({}, {column: {pos: 5}});
});
it('should not set the active cell', function () {
expect(grid.setActiveCell).not.toHaveBeenCalled();
});
});
});
});
describe('onClick', function () {
describe('when the target is a random cell in the grid', function () {
it('should not change the active cell', function () {
onClickFunction({}, {row: 1, cell: 2});
grid.getActiveCell.and.returnValue(null);
expect(grid.setActiveCell).not.toHaveBeenCalled();
});
});
describe('when the target is the row header', function () {
describe('when no rows are selected', function () {
beforeEach(function () {
getSelectedRangesSpy.and.returnValue([]);
onClickFunction({}, {row: 1, cell: 0});
grid.getActiveCell.and.returnValue(null);
});
it('changes the active cell', function () {
expect(grid.setActiveCell).toHaveBeenCalledWith(1, 1);
});
it('should not change the selected ranges', function () {
expect(setSelectedRangesSpy).not.toHaveBeenCalled();
});
});
describe('when there is one cell selected', function () {
beforeEach(function () {
grid.getActiveCell.and.returnValue({row: 4, cell: 5});
getSelectedRangesSpy.and.returnValue([
new Slick.Range(4, 5),
]);
});
it('sets the active cell', function () {
onClickFunction({}, {row: 4, cell: 0});
expect(grid.setActiveCell).toHaveBeenCalledWith(4, 1);
});
});
describe('when there is one row selected', function () {
beforeEach(function () {
grid.getActiveCell.and.returnValue({row: 3, cell: 1});
getSelectedRangesSpy.and.returnValue([
RangeSelectionHelper.rangeForRow(grid, 3),
]);
});
it('resets the selected row', function () {
onClickFunction({}, {row: 3, cell: 0});
expect(grid.resetActiveCell).toHaveBeenCalled();
});
describe('when we select a different row', function () {
it('should change the active cell', function () {
onClickFunction({}, {row: 9, cell: 0});
expect(grid.setActiveCell).toHaveBeenCalledWith(9, 1);
});
});
});
describe('when there are 2 rows selected', function () {
beforeEach(function () {
grid.getActiveCell.and.returnValue({row: 3, cell: 1});
getSelectedRangesSpy.and.returnValue([
RangeSelectionHelper.rangeForRow(grid, 5),
RangeSelectionHelper.rangeForRow(grid, 3),
]);
});
describe('when the last selected row is clicked again', function () {
it('should change the active cell to the first selected row', function () {
onClickFunction({}, {row: 3, cell: 0});
expect(grid.setActiveCell).toHaveBeenCalledWith(5, 1);
});
});
describe('when the first selected row is clicked again', function () {
it('should not change the active cell', function () {
onClickFunction({}, {row: 5, cell: 0});
expect(grid.setActiveCell).not.toHaveBeenCalled();
});
});
});
describe('and the editable new row', function () {
beforeEach(function () {
onClickFunction({}, {row: 10, cell: 0});
});
it('does not select the row', function () {
expect(grid.setActiveCell).not.toHaveBeenCalled();
});
});
});
});
});
});

View File

@ -1,414 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import $ from 'jquery';
import Slick from 'slickgrid';
import 'slickgrid.grid';
import ColumnSelector from 'sources/selection/column_selector';
import ActiveCellCapture from 'sources/selection/active_cell_capture';
import 'sources/selection/grid_selector';
import 'slickgrid.plugins/slick.cellrangeselector';
import XCellSelectionModel from 'sources/selection/xcell_selection_model';
describe('ColumnSelector', function () {
var container, data, columns, options;
var SlickGrid = Slick.Grid;
var KEY = {
RIGHT: 39,
LEFT: 37,
UP: 38,
DOWN: 40,
};
beforeEach(function () {
container = $('<div></div>');
container.height(9999);
container.width(9999);
data = [{
'some-column-name': 'first value',
'second column': 'second value',
'third column': 'nonselectable value',
}, {
'some-column-name': 'row 1 - first value',
'second column': 'row 1 - second value',
'third column': 'row 1 - nonselectable value',
}];
columns = [
{
id: 'row-header-column',
name: 'row header column name',
selectable: false,
display_name: 'row header column name',
column_type: 'text',
},
{
id: '1',
name: 'some-column-name',
pos: 0,
display_name: 'some-column-name',
column_type: 'text',
},
{
id: '2',
name: 'second column',
pos: 1,
display_name: 'second column',
column_type: 'json',
},
{
id: 'third-column-id',
name: 'third column',
pos: 2,
display_name: 'third column',
column_type: 'text',
},
{
name: 'some-non-selectable-column',
selectable: false,
pos: 3,
display_name: 'some-non-selectable-column',
column_type: 'numeric',
},
];
});
it('displays the name of the column', function () {
setupGrid(columns);
expect($(container.find('.slick-header-columns .slick-column-name')[1]).text())
.toContain('some-column-name');
expect($(container.find('.slick-header-columns .slick-column-name')[1]).text())
.toContain('text');
expect($(container.find('.slick-header-columns .slick-column-name')[2]).text())
.toContain('second column');
expect($(container.find('.slick-header-columns .slick-column-name')[2]).text())
.toContain('json');
});
it('preserves the other attributes of column definitions', function () {
var columnSelector = new ColumnSelector();
var selectableColumns = columnSelector.getColumnDefinitions(columns);
expect(selectableColumns[1].id).toEqual('1');
});
describe('with ActiveCellCapture, CellSelectionModel, and GridSelector: selecting columns', function () {
var grid, cellSelectionModel;
beforeEach(function () {
var columnSelector = new ColumnSelector();
columns = columnSelector.getColumnDefinitions(columns);
data = [];
for (var i = 0; i < 10; i++) {
data.push({
'some-column-name': 'some-value-' + i,
'second column': 'second value ' + i,
'third column': 'third value ' + i,
'fourth column': 'fourth value ' + i,
});
}
grid = new SlickGrid(container, data, columns);
grid.registerPlugin(new ActiveCellCapture());
cellSelectionModel = new XCellSelectionModel();
grid.setSelectionModel(cellSelectionModel);
grid.registerPlugin(columnSelector);
grid.invalidate();
$('body').append(container);
});
afterEach(function () {
$('body').find(container).remove();
});
let selectColumnAction = ()=> {
container.find('.slick-header-column:contains(some-column-name)').click();
var selectedRanges = cellSelectionModel.getSelectedRanges();
expectOnlyTheFirstColumnToBeSelected(selectedRanges);
};
describe('when the user clicks a column header', function () {
it('selects the column', function () {
selectColumnAction();
});
it('toggles a selected class to the header cell', function () {
container.find('.slick-header-column:contains(second column)').click();
expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected'))
.toEqual(true);
container.find('.slick-header-column:contains(second column)').click();
expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected'))
.toEqual(false);
});
});
describe('when the user clicks an additional column header', function () {
beforeEach(function () {
container.find('.slick-header-column:contains(some-column-name)').click();
container.find('.slick-header-column:contains(second column)').click();
});
it('selects additional columns', function () {
var selectedRanges = cellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(2);
var column1 = selectedRanges[0];
expect(column1.fromCell).toEqual(1);
expect(column1.toCell).toEqual(1);
var column2 = selectedRanges[1];
expect(column2.fromCell).toEqual(2);
expect(column2.toCell).toEqual(2);
});
describe('and presses shift + right-arrow', function () {
beforeEach(function () {
pressShiftArrow(KEY.RIGHT);
});
it('keeps the last column selected', function () {
expect(cellSelectionModel.getSelectedRanges().length).toEqual(1);
});
it('grows the selection to the right', function () {
var selectedRange = cellSelectionModel.getSelectedRanges()[0];
expect(selectedRange.fromCell).toEqual(2);
expect(selectedRange.toCell).toEqual(3);
expect(selectedRange.fromRow).toEqual(0);
expect(selectedRange.toRow).toEqual(9);
});
it('keeps selected class on columns 2 and 3', function () {
expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected'))
.toEqual(true);
expect($(container.find('.slick-header-column:contains(third column)')).hasClass('selected'))
.toEqual(true);
expect($(container.find('.slick-header-column:contains(some-column-name)')).hasClass('selected'))
.toEqual(false);
});
});
describe('when the user deselects the last selected column header', function () {
beforeEach(function () {
container.find('.slick-header-column:contains(second column)').click();
});
describe('and presses shift + right-arrow', function () {
it('first and second columns are selected', function () {
pressShiftArrow(KEY.RIGHT);
var selectedRanges = cellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(1);
expect(selectedRanges[0].fromCell).toEqual(1);
expect(selectedRanges[0].toCell).toEqual(2);
expect(selectedRanges[0].fromRow).toEqual(0);
expect(selectedRanges[0].toRow).toEqual(9);
});
});
});
});
describe('when the user clicks a column header description', function () {
it('selects the column', function () {
container.find('.slick-header-columns span.column-description:contains(some-column-name)').click();
var selectedRanges = cellSelectionModel.getSelectedRanges();
expectOnlyTheFirstColumnToBeSelected(selectedRanges);
});
it('toggles a selected class to the header cell', function () {
container.find('.slick-header-column span.column-description:contains(second column)').click();
expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected'))
.toEqual(true);
container.find('.slick-header-column span.column-description:contains(second column)').click();
expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected'))
.toEqual(false);
});
});
describe('when a row is selected', function () {
beforeEach(function () {
var selectedRanges = [new Slick.Range(0, 0, 0, 1)];
cellSelectionModel.setSelectedRanges(selectedRanges);
});
it('deselects the row', function () {
container.find('.slick-header-column')[1].click();
var selectedRanges = cellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(1);
var column = selectedRanges[0];
expect(column.fromCell).toEqual(1);
expect(column.toCell).toEqual(1);
expect(column.fromRow).toEqual(0);
expect(column.toRow).toEqual(9);
});
});
describe('clicking a second time', function () {
beforeEach(function () {
container.find('.slick-header-column')[1].click();
});
it('deselects the column', function () {
container.find('.slick-header-column')[1].click();
var selectedRanges = cellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(0);
});
});
describe('when the column is not selectable', function () {
it('does not select the column', function () {
$(container.find('.slick-header-column:contains(some-non-selectable-column)')).trigger('click');
var selectedRanges = cellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(0);
});
});
describe('when a non-column range was already selected', function () {
beforeEach(function () {
var selectedRanges = [new Slick.Range(0, 0, 2, 0)];
cellSelectionModel.setSelectedRanges(selectedRanges);
});
it('deselects the non-column range', function () {
selectColumnAction();
});
});
describe('when a column is selected', function () {
beforeEach(function () {
container.find('.slick-header-column:contains(some-column-name)').click();
});
describe('when the user click a cell on the current range', function () {
beforeEach(function () {
container.find('.slick-cell.l1.r1')[1].click();
});
it('column is deselected', function () {
var selectedRanges = cellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(1);
var column = selectedRanges[0];
expect(column.fromCell).toEqual(1);
expect(column.toCell).toEqual(1);
expect(column.fromRow).toEqual(1);
expect(column.toRow).toEqual(1);
});
it('keep select class on column header', function () {
expect($(container.find('.slick-header-column:contains(some-column-name)')).hasClass('selected'))
.toBeTruthy();
});
});
describe('when the user click a cell outside the current range', function () {
beforeEach(function () {
container.find('.slick-cell.l2.r2')[2].click();
});
it('column is deselected', function () {
var selectedRanges = cellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(1);
var column = selectedRanges[0];
expect(column.fromCell).toEqual(2);
expect(column.toCell).toEqual(2);
expect(column.fromRow).toEqual(2);
expect(column.toRow).toEqual(2);
});
it('remove select class on \'some-column-name\' column header', function () {
expect($(container.find('.slick-header-column:contains(some-column-name)')).hasClass('selected'))
.toBeFalsy();
expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected'))
.toBeTruthy();
});
});
describe('when the user click in a row header', function () {
beforeEach(function () {
var selectedRanges = [new Slick.Range(1, 1, 1, 3)];
cellSelectionModel.setSelectedRanges(selectedRanges);
});
it('column is deselected', function () {
var selectedRanges = cellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(1);
var column = selectedRanges[0];
expect(column.fromCell).toEqual(1);
expect(column.toCell).toEqual(3);
expect(column.fromRow).toEqual(1);
expect(column.toRow).toEqual(1);
});
it('no column should have the class \'selected\'', function () {
expect($(container.find('.slick-header-column:contains(some-column-name)')).hasClass('selected'))
.toBeTruthy();
});
});
});
});
function setupGrid(gridColumns) {
var columnSelector = new ColumnSelector();
gridColumns = columnSelector.getColumnDefinitions(gridColumns);
var grid = new SlickGrid(container, data, gridColumns, options);
var cellSelectionModel = new XCellSelectionModel();
grid.setSelectionModel(cellSelectionModel);
grid.registerPlugin(columnSelector);
grid.invalidate();
}
function expectOnlyTheFirstColumnToBeSelected(selectedRanges) {
var row = selectedRanges[0];
expect(selectedRanges.length).toEqual(1);
expect(row.fromCell).toEqual(1);
expect(row.toCell).toEqual(1);
expect(row.fromRow).toEqual(0);
expect(row.toRow).toEqual(9);
}
function pressShiftArrow(keyCode) {
var pressEvent = new $.Event('keydown');
pressEvent.shiftKey = true;
pressEvent.ctrlKey = false;
pressEvent.altKey = false;
pressEvent.which = keyCode;
$(container.find('.grid-canvas')).trigger(pressEvent);
}
});

View File

@ -1,150 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import $ from 'jquery';
import Slick from 'slickgrid';
import 'slickgrid.grid';
import clipboard from '../../../pgadmin/static/js/selection/clipboard';
import copyData from '../../../pgadmin/static/js/selection/copy_data';
import RangeSelectionHelper from 'sources/selection/range_selection_helper';
import XCellSelectionModel from 'sources/selection/xcell_selection_model';
describe('copyData', function () {
var grid, sqlEditor, gridContainer, buttonPasteRow, buttonCopyWithHeader;
var SlickGrid;
beforeEach(function () {
SlickGrid = Slick.Grid;
var data = [{'id': 1, 'brand':'leopord', 'size':12, '__temp_PK': '123'},
{'id': 2, 'brand':'lion', 'size':13, '__temp_PK': '456'},
{'id': 3, 'brand':'puma', 'size':9, '__temp_PK': '789'}],
dataView = new Slick.Data.DataView();
var CSVOptions = {'quoting': 'strings', 'quote_char': '"', 'field_separator': ','};
var columns = [
{
id: 'row-header-column',
name: 'row header column name',
selectable: false,
display_name: 'row header column name',
column_type: 'text',
},
{
name: 'id',
field: 'id',
pos: 0,
label: 'id<br> numeric',
cell: 'number',
can_edit: false,
type: 'numeric',
}, {
name: 'brand',
field: 'brand',
pos: 1,
label: 'flavor<br> character varying',
cell: 'string',
can_edit: false,
type: 'character varying',
}, {
name: 'size',
field: 'size',
pos: 2,
label: 'size<br> numeric',
cell: 'number',
can_edit: false,
type: 'numeric',
},
];
gridContainer = $('<div id="grid"></div>');
$('body').append(gridContainer);
buttonPasteRow = $('<button id="btn-paste-row" disabled></button>');
buttonCopyWithHeader = $('<button class="copy-with-header visibility-hidden"></button>');
$('body').append(buttonPasteRow);
$('body').append(buttonCopyWithHeader);
grid = new SlickGrid('#grid', dataView, columns, {});
grid.CSVOptions = CSVOptions;
dataView.setItems(data, '__temp_PK');
grid.setSelectionModel(new XCellSelectionModel());
sqlEditor = {slickgrid: grid};
});
afterEach(function() {
grid.destroy();
gridContainer.remove();
buttonPasteRow.remove();
buttonCopyWithHeader.remove();
});
describe('when rows are selected', function () {
beforeEach(function () {
grid.getSelectionModel().setSelectedRanges([
RangeSelectionHelper.rangeForRow(grid, 0),
RangeSelectionHelper.rangeForRow(grid, 2)]
);
});
it('copies them', function () {
spyOn(clipboard, 'copyTextToClipboard').and.callThrough();
copyData.apply(sqlEditor);
expect(sqlEditor.copied_rows.length).toEqual(2);
expect(clipboard.copyTextToClipboard).toHaveBeenCalled();
expect(clipboard.copyTextToClipboard.calls.mostRecent().args[0]).toContain('1,"leopord",12');
expect(clipboard.copyTextToClipboard.calls.mostRecent().args[0]).toContain('3,"puma",9');
});
describe('when the user can edit the grid', function () {
it('enables the paste row button', function () {
copyData.apply(_.extend({can_edit: true}, sqlEditor));
expect($('#btn-paste-row').prop('disabled')).toEqual(false);
});
});
});
describe('when a column is selected', function () {
beforeEach(function () {
var firstDataColumn = RangeSelectionHelper.rangeForColumn(grid, 1);
grid.getSelectionModel().setSelectedRanges([firstDataColumn]);
});
it('copies text to the clipboard', function () {
spyOn(clipboard, 'copyTextToClipboard');
copyData.apply(sqlEditor);
expect(clipboard.copyTextToClipboard).toHaveBeenCalled();
var copyArg = clipboard.copyTextToClipboard.calls.mostRecent().args[0];
var rowStrings = copyArg.split('\n');
expect(rowStrings[0]).toEqual('1');
expect(rowStrings[1]).toEqual('2');
expect(rowStrings[2]).toEqual('3');
});
it('sets copied_rows to empty', function () {
copyData.apply(sqlEditor);
expect(sqlEditor.copied_rows.length).toEqual(0);
});
describe('when the user can edit the grid', function () {
beforeEach(function () {
copyData.apply(_.extend({can_edit: true}, sqlEditor));
});
it('disables the paste row button', function () {
expect($('#btn-paste-row').prop('disabled')).toEqual(true);
});
});
});
});

View File

@ -1,128 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import $ from 'jquery';
import Slick from 'slickgrid';
import 'slickgrid.grid';
import GridSelector from 'sources/selection/grid_selector';
import XCellSelectionModel from 'sources/selection/xcell_selection_model';
describe('GridSelector', function () {
var container, data, columns, gridSelector, xCellSelectionModel;
var SlickGrid;
beforeEach(function () {
SlickGrid = Slick.Grid;
container = $('<div></div>');
container.height(9999);
columns = [{
id: '1',
name: 'some-column-name',
pos: 0,
}, {
id: '2',
name: 'second column',
pos: 1,
}];
gridSelector = new GridSelector();
columns = gridSelector.getColumnDefinitions(columns);
data = [];
for (var i = 0; i < 10; i++) {
data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i});
}
var grid = new SlickGrid(container, data, columns);
xCellSelectionModel = new XCellSelectionModel();
grid.setSelectionModel(xCellSelectionModel);
grid.registerPlugin(gridSelector);
grid.invalidate();
$('body').append(container);
});
afterEach(function () {
$('body').find(container).remove();
});
it('renders an additional column on the left for selecting rows', function () {
expect(columns.length).toEqual(3);
var leftmostColumn = columns[0];
expect(leftmostColumn.id).toEqual('row-header-column');
});
it('renders a button for selecting all the cells', function () {
expect(container.find('[title=\'Select/Deselect All\']').length).toEqual(1);
});
describe('when the cell for the select/deselect all is clicked', function () {
it('selects the whole grid', function () {
container.find('[title=\'Select/Deselect All\']').parent().trigger('click');
var selectedRanges = xCellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(1);
var selectedRange = selectedRanges[0];
expect(selectedRange.fromCell).toEqual(1);
expect(selectedRange.toCell).toEqual(2);
expect(selectedRange.fromRow).toEqual(0);
expect(selectedRange.toRow).toEqual(9);
});
it('adds selected class', function () {
container.find('[title=\'Select/Deselect All\']').parent().trigger('click');
expect($(container.find('[data-id=\'select-all\']')).hasClass('selected')).toBeTruthy();
});
});
describe('when the select all button in the corner gets selected', function () {
it('selects all the cells', function () {
container.find('[title=\'Select/Deselect All\']').trigger('click');
var selectedRanges = xCellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(1);
var selectedRange = selectedRanges[0];
expect(selectedRange.fromCell).toEqual(1);
expect(selectedRange.toCell).toEqual(2);
expect(selectedRange.fromRow).toEqual(0);
expect(selectedRange.toRow).toEqual(9);
});
describe('when the select all button in the corner gets deselected', function () {
beforeEach(function () {
container.find('[title=\'Select/Deselect All\']').trigger('click');
});
it('deselects all the cells', function () {
container.find('[title=\'Select/Deselect All\']').trigger('click');
var selectedRanges = xCellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(0);
});
});
describe('and then the underlying selection changes', function () {
beforeEach(function () {
container.find('[title=\'Select/Deselect All\']').trigger('click');
});
it('removes the selected class', function () {
container.find('[title=\'Select/Deselect All\']').parent().trigger('click');
expect($(container.find('[data-id=\'select-all\']')).hasClass('selected')).toBeFalsy();
});
});
});
});

View File

@ -1,189 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import rangeBoundaryNavigator from 'sources/selection/range_boundary_navigator';
import Slick from 'slickgrid';
describe('RangeBoundaryNavigator', function () {
describe('#getUnion', function () {
describe('when the ranges completely overlap', function () {
it('returns a list with that range', function () {
var ranges = [[1, 4], [1, 4], [1, 4]];
var union = rangeBoundaryNavigator.getUnion(ranges);
expect(union).toEqual([[1, 4]]);
});
});
describe('when the ranges all overlap partially or touch', function () {
it('returns one long range', function () {
var rangeBounds = [[3, 6], [1, 4], [7, 14]];
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
expect(union).toEqual([[1, 14]]);
});
it('returns them in order from lowest to highest', function () {
var rangeBounds = [[3, 6], [2, 3], [10, 12]];
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
expect(union).toEqual([[2, 6], [10, 12]]);
});
describe('when one range completely overlaps another', function() {
it('returns them in order from lowest to highest', function () {
var rangeBounds = [[9, 14], [2, 3], [11, 13]];
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
expect(union).toEqual([[2, 3], [9, 14]]);
});
});
describe('when one range is a subset of another', function () {
it('returns the larger range', function () {
var rangeBounds = [[2, 6], [1, 14], [8, 10]];
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
expect(union).toEqual([[1, 14]]);
});
});
});
describe('when the ranges do not touch', function () {
it('returns them in order from lowest to highest', function () {
var rangeBounds = [[3, 6], [1, 1], [8, 10]];
var union = rangeBoundaryNavigator.getUnion(rangeBounds);
expect(union).toEqual([[1, 1], [3, 6], [8, 10]]);
});
});
});
describe('#mapDimensionBoundaryUnion', function () {
it('returns a list of the results of the callback', function () {
var rangeBounds = [[0, 1], [3, 3]];
var callback = function () {
return 'hello';
};
var result = rangeBoundaryNavigator.mapDimensionBoundaryUnion(rangeBounds, callback);
expect(result).toEqual(['hello', 'hello', 'hello']);
});
it('calls the callback with each index in the dimension', function () {
var rangeBounds = [[0, 1], [3, 3]];
var callback = jasmine.createSpy('callbackSpy');
rangeBoundaryNavigator.mapDimensionBoundaryUnion(rangeBounds, callback);
expect(callback.calls.allArgs()).toEqual([[0], [1], [3]]);
});
});
describe('#mapOver2DArray', function () {
var data, rowCollector, processCell;
beforeEach(function () {
data = [[0, 1, 2, 3], [2, 2, 2, 2], [4, 5, 6, 7]];
processCell = function (rowIndex, columnIndex) {
return data[rowIndex][columnIndex];
};
rowCollector = function (rowData) {
return JSON.stringify(rowData);
};
});
it('calls the callback for each item in the ranges', function () {
var rowRanges = [[0, 0], [2, 2]];
var colRanges = [[0, 3]];
var selectionResult = rangeBoundaryNavigator.mapOver2DArray(rowRanges, colRanges, processCell, rowCollector);
expect(selectionResult).toEqual(['[0,1,2,3]', '[4,5,6,7]']);
});
describe('when the ranges are out of order/duplicated', function () {
var rowRanges, colRanges;
beforeEach(function () {
rowRanges = [[2, 2], [2, 2], [0, 0]];
colRanges = [[0, 3]];
});
it('uses the union of the ranges', function () {
spyOn(rangeBoundaryNavigator, 'getUnion').and.callThrough();
var selectionResult = rangeBoundaryNavigator.mapOver2DArray(rowRanges, colRanges, processCell, rowCollector);
expect(rangeBoundaryNavigator.getUnion).toHaveBeenCalledWith(rowRanges);
expect(rangeBoundaryNavigator.getUnion).toHaveBeenCalledWith(colRanges);
expect(selectionResult).toEqual(['[0,1,2,3]', '[4,5,6,7]']);
});
});
});
describe('#rangesToCsv', function () {
var data, columnDefinitions, ranges, CSVOptions;
beforeEach(function () {
data = [{'id':1, 'animal':'leopard', 'size':'12'},
{'id':2, 'animal':'lion', 'size':'13'},
{'id':3, 'animal':'cougar', 'size':'9'},
{'id':4, 'animal':'tiger', 'size':'10'}];
columnDefinitions = [{name: 'id', field: 'id', pos: 0, cell:'number'},
{name: 'animal', field: 'animal', pos: 1, cell:'string'},
{name: 'size', field: 'size', pos: 2, cell:'string'}];
ranges = [new Slick.Range(0, 0, 0, 2), new Slick.Range(3, 0, 3, 2)];
CSVOptions = [{'quoting': 'all', 'quote_char': '"', 'field_separator': ','},
{'quoting': 'strings', 'quote_char': '"', 'field_separator': ';'},
{'quoting': 'strings', 'quote_char': '\'', 'field_separator': '|'},
{'quoting': 'none', 'quote_char': '"', 'field_separator': '\t'}];
});
it('returns csv for the provided ranges for CSV options quoting All with char " with field separator ,', function () {
var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, ranges, CSVOptions[0]);
expect(csvResult).toEqual('"1","leopard","12"\n"4","tiger","10"');
});
describe('when no cells are selected for CSV options quoting Strings with char " with field separator ;', function () {
it('should return an empty string', function () {
var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, [], CSVOptions[1]);
expect(csvResult).toEqual('');
});
});
describe('when there is an extra column with checkboxes', function () {
beforeEach(function () {
columnDefinitions = [{name: 'not-a-data-column'},
{name: 'id', field: 'id', pos: 0, cell:'number'},
{name: 'animal', field: 'animal', pos: 1, cell:'string'},
{name: 'size', field: 'size',pos: 2, cell:'string'}];
ranges = [new Slick.Range(0, 0, 0, 3), new Slick.Range(3, 0, 3, 3)];
});
it('returns csv for the columns with data for CSV options quoting Strings with char \' with field separator |', function () {
var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, ranges, CSVOptions[2]);
expect(csvResult).toEqual('1|\'leopard\'|\'12\'\n4|\'tiger\'|\'10\'');
});
describe('when no cells are selected for CSV options quoting none with field separator tab', function () {
it('should return an empty string', function () {
var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, [], CSVOptions[3]);
expect(csvResult).toEqual('');
});
});
});
});
});

View File

@ -1,100 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import $ from 'jquery';
import Slick from 'slickgrid';
import 'slickgrid.grid';
import RangeSelectionHelper from 'sources/selection/range_selection_helper';
describe('RangeSelectionHelper utility functions', function () {
var grid;
beforeEach(function () {
var container, data, columns, options;
container = $('<div></div>');
container.height(9999);
columns = [{
id: '1',
name: 'some-column-name',
pos: 0,
}, {
id: 'second-column-id',
name: 'second column',
pos: 1,
}];
data = [];
for (var i = 0; i < 10; i++) {
data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i});
}
grid = new Slick.Grid(container, data, columns, options);
grid.invalidate();
});
describe('#getIndexesOfCompleteRows', function () {
describe('when selected ranges are not rows', function () {
it('returns an empty array', function () {
var rowlessRanges = [RangeSelectionHelper.rangeForColumn(grid, 1)];
expect(RangeSelectionHelper.getIndexesOfCompleteRows(grid, rowlessRanges))
.toEqual([]);
});
});
describe('when selected range', function () {
describe('is a single row', function () {
it('returns an array with one index', function () {
var singleRowRange = [RangeSelectionHelper.rangeForRow(grid, 1)];
expect(RangeSelectionHelper.getIndexesOfCompleteRows(grid, singleRowRange))
.toEqual([1]);
});
});
describe('is multiple rows', function () {
it('returns an array of each row\'s index', function () {
var multipleRowRange = [
RangeSelectionHelper.rangeForRow(grid, 0),
RangeSelectionHelper.rangeForRow(grid, 3),
RangeSelectionHelper.rangeForRow(grid, 2),
];
var indexesOfCompleteRows = RangeSelectionHelper.getIndexesOfCompleteRows(grid, multipleRowRange);
indexesOfCompleteRows.sort();
expect(indexesOfCompleteRows).toEqual([0, 2, 3]);
});
});
describe('contains a multi row selection', function () {
it('returns an array of each individual row\'s index', function () {
var multipleRowRange = [
new Slick.Range(3, 0, 5, 1),
];
var indexesOfCompleteRows = RangeSelectionHelper.getIndexesOfCompleteRows(grid, multipleRowRange);
indexesOfCompleteRows.sort();
expect(indexesOfCompleteRows).toEqual([3, 4, 5]);
});
describe('and also contains a selection that is not a row', function () {
it('returns an array of only the complete rows\' indexes', function () {
var multipleRowRange = [
new Slick.Range(8, 1, 9, 1),
new Slick.Range(3, 0, 5, 1),
];
var indexesOfCompleteRows = RangeSelectionHelper.getIndexesOfCompleteRows(grid, multipleRowRange);
indexesOfCompleteRows.sort();
expect(indexesOfCompleteRows).toEqual([3, 4, 5]);
});
});
});
});
});
});

View File

@ -1,328 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import $ from 'jquery';
import Slick from 'slickgrid';
import 'slickgrid.grid';
import RowSelector from 'sources/selection/row_selector';
import ActiveCellCapture from 'sources/selection/active_cell_capture';
import XCellSelectionModel from 'sources/selection/xcell_selection_model';
describe('RowSelector', function () {
var KEY = {
RIGHT: 39,
LEFT: 37,
UP: 38,
DOWN: 40,
};
var container, dataView, columnDefinitions, grid, cellSelectionModel;
var SlickGrid = Slick.Grid;
beforeEach(function () {
container = $('<div></div>');
container.height(9999);
container.width(9999);
columnDefinitions = [{
id: '1',
name: 'some-column-name',
selectable: true,
pos: 0,
}, {
id: '2',
name: 'second column',
selectable: true,
pos: 1,
}];
dataView = new Slick.Data.DataView();
var rowSelector = new RowSelector();
var data = [];
for (var i = 0; i < 10; i++) {
data.push({'some-column-name':'some-value-' + i, 'second column':'second value ' + i});
}
columnDefinitions = rowSelector.getColumnDefinitions(columnDefinitions);
dataView.setItems(data, 'some-column-name');
grid = new SlickGrid(container, dataView, columnDefinitions);
grid.registerPlugin(new ActiveCellCapture());
cellSelectionModel = new XCellSelectionModel();
grid.setSelectionModel(cellSelectionModel);
grid.registerPlugin(rowSelector);
grid.invalidate();
$('body').append(container);
});
afterEach(function () {
$('body').find(container).remove();
});
it('renders an additional column on the left', function () {
expect(columnDefinitions.length).toEqual(3);
var leftmostColumn = columnDefinitions[0];
expect(leftmostColumn.id).toEqual('row-header-column');
expect(leftmostColumn.name).toEqual('');
expect(leftmostColumn.selectable).toEqual(false);
});
it('renders a span on the leftmost column', function () {
expect(container.find('.slick-row').length).toEqual(10);
expect(container.find('.slick-row .slick-cell:first-child span[data-cell-type="row-header-selector"]').length).toEqual(10);
});
it('preserves the other attributes of column definitions', function () {
expect(columnDefinitions[1].id).toEqual('1');
expect(columnDefinitions[1].selectable).toEqual(true);
});
describe('selecting rows', function () {
describe('when the user clicks a row header span', function () {
it('selects the row', function () {
container.find('.slick-row .slick-cell:first-child span[data-cell-type="row-header-selector"]')[0].click();
var selectedRanges = cellSelectionModel.getSelectedRanges();
expectOnlyTheFirstRowToBeSelected(selectedRanges);
});
it('add selected class to parent of the span', function () {
container.find('.slick-row .slick-cell:first-child span[data-cell-type="row-header-selector"]')[5].click();
expect($(container.find('.slick-row .slick-cell:first-child ')[5])
.hasClass('selected')).toBeTruthy();
});
});
describe('when the user clicks a row header', function () {
beforeEach(function () {
container.find('.slick-row .slick-cell:first-child')[1].click();
});
it('selects the row', function () {
var selectedRanges = cellSelectionModel.getSelectedRanges();
var row = selectedRanges[0];
expect(selectedRanges.length).toEqual(1);
expect(row.fromCell).toEqual(1);
expect(row.toCell).toEqual(2);
expect(row.fromRow).toEqual(1);
expect(row.toRow).toEqual(1);
});
it('add selected class to parent of the span', function () {
expect($(container.find('.slick-row .slick-cell:first-child ')[1])
.hasClass('selected')).toBeTruthy();
});
describe('when the user clicks again the same row header', function () {
it('add selected class to parent of the span', function () {
container.find('.slick-row .slick-cell:first-child span[data-cell-type="row-header-selector"]')[1].click();
expect($(container.find('.slick-row .slick-cell:first-child ')[1])
.hasClass('selected')).toBeFalsy();
});
});
describe('and presses shift + down-arrow', function () {
beforeEach(function () {
pressShiftArrow(KEY.DOWN);
});
it('keeps the last row selected', function () {
expect(cellSelectionModel.getSelectedRanges().length).toEqual(1);
});
it('grows the selection down', function () {
var selectedRanges = cellSelectionModel.getSelectedRanges();
var row = selectedRanges[0];
expect(selectedRanges.length).toEqual(1);
expect(row.fromCell).toEqual(1);
expect(row.toCell).toEqual(2);
expect(row.fromRow).toEqual(1);
expect(row.toRow).toEqual(2);
});
it('keeps selected class on rows 1 and 2', function () {
expect($(container.find('.slick-row .slick-cell:first-child ')[0])
.hasClass('selected')).toBeFalsy();
expect($(container.find('.slick-row .slick-cell:first-child ')[1])
.hasClass('selected')).toBeTruthy();
expect($(container.find('.slick-row .slick-cell:first-child ')[2])
.hasClass('selected')).toBeTruthy();
expect($(container.find('.slick-row .slick-cell:first-child ')[3])
.hasClass('selected')).toBeFalsy();
});
});
describe('when the user clicks a cell on the current range', function () {
beforeEach(function () {
container.find('.slick-cell.l1.r1')[5].click();
});
it('row gets deselected', function () {
var selectedRanges = cellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(1);
var newSelection = selectedRanges[0];
expect(newSelection.fromCell).toEqual(1);
expect(newSelection.fromRow).toEqual(5);
expect(newSelection.toCell).toEqual(1);
expect(newSelection.toRow).toEqual(5);
});
it('keep select class on row header', function () {
expect($(container.find('.slick-cell.l0.r0')[5]).hasClass('selected'))
.toBeTruthy();
});
});
describe('when the user clicks a cell outside the current range', function () {
beforeEach(function () {
container.find('.slick-cell.l2.r2')[2].click();
});
it('row gets deselected', function () {
var selectedRanges = cellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(1);
var newSelection = selectedRanges[0];
expect(newSelection.fromCell).toEqual(2);
expect(newSelection.fromRow).toEqual(2);
expect(newSelection.toCell).toEqual(2);
expect(newSelection.toRow).toEqual(2);
});
it('remove select class on "some-column-name" column header', function () {
expect($(container.find('.slick-cell.l0.r0')[5]).hasClass('selected'))
.toBeFalsy();
expect($(container.find('.slick-cell.l0.r0')[2]).hasClass('selected'))
.toBeTruthy();
});
});
describe('when the user has a column selected', function () {
beforeEach(function () {
var selectedRanges = [new Slick.Range(0, 1, 9, 1)];
cellSelectionModel.setSelectedRanges(selectedRanges);
});
it('no row should have the class "selected"', function () {
expect($(container.find('.slick-cell.l0.r0')[0]).hasClass('selected'))
.toBeFalsy();
expect($(container.find('.slick-cell.l0.r0')[1]).hasClass('selected'))
.toBeFalsy();
expect($(container.find('.slick-cell.l0.r0')[2]).hasClass('selected'))
.toBeFalsy();
expect($(container.find('.slick-cell.l0.r0')[3]).hasClass('selected'))
.toBeFalsy();
expect($(container.find('.slick-cell.l0.r0')[4]).hasClass('selected'))
.toBeFalsy();
expect($(container.find('.slick-cell.l0.r0')[5]).hasClass('selected'))
.toBeFalsy();
});
});
});
describe('when the user clicks multiple row headers', function () {
it('selects another row', function () {
container.find('.slick-row .slick-cell:first-child')[4].click();
container.find('.slick-row .slick-cell:first-child')[0].click();
var selectedRanges = cellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(2);
var row1 = selectedRanges[0];
expect(row1.fromRow).toEqual(4);
expect(row1.toRow).toEqual(4);
var row2 = selectedRanges[1];
expect(row2.fromRow).toEqual(0);
expect(row2.toRow).toEqual(0);
});
});
describe('when a column was already selected', function () {
beforeEach(function () {
var selectedRanges = [new Slick.Range(0, 0, 0, 1)];
cellSelectionModel.setSelectedRanges(selectedRanges);
});
it('deselects the column', function () {
container.find('.slick-row .slick-cell:first-child')[0].click();
var selectedRanges = cellSelectionModel.getSelectedRanges();
expectOnlyTheFirstRowToBeSelected(selectedRanges);
});
});
describe('when the row is deselected through setSelectedRanges', function () {
beforeEach(function () {
container.find('.slick-row .slick-cell:first-child')[4].click();
});
it('should remove the selected class', function () {
cellSelectionModel.setSelectedRanges([]);
expect($(container.find('.slick-row .slick-cell:first-child span[data-cell-type="row-header-selector"]')[4])
.hasClass('selected')).toBeFalsy();
});
});
describe('click a second time', function () {
beforeEach(function () {
container.find('.slick-row .slick-cell:first-child')[1].click();
});
it('removes the selected class', function () {
container.find('.slick-row .slick-cell:first-child')[1].click();
expect($(container.find('.slick-row .slick-cell:first-child span[data-cell-type="row-header-selector"]')[1])
.hasClass('selected')).toBeFalsy();
});
it('unselects the row', function () {
container.find('.slick-row .slick-cell:first-child')[1].click();
var selectedRanges = cellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(0);
});
});
});
function pressShiftArrow(keyCode) {
var pressEvent = new $.Event('keydown');
pressEvent.shiftKey = true;
pressEvent.ctrlKey = false;
pressEvent.altKey = false;
pressEvent.which = keyCode;
$(container.find('.grid-canvas')).trigger(pressEvent);
}
function expectOnlyTheFirstRowToBeSelected(selectedRanges) {
var row = selectedRanges[0];
expect(selectedRanges.length).toEqual(1);
expect(row.fromCell).toEqual(1);
expect(row.toCell).toEqual(2);
expect(row.fromRow).toEqual(0);
expect(row.toRow).toEqual(0);
}
});

View File

@ -1,253 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import $ from 'jquery';
import 'slickgrid.grid';
import Slick from 'slickgrid';
import SetStagedRows from 'sources/selection/set_staged_rows';
describe('set_staged_rows', function () {
var sqlEditorObj, gridSpy, deleteButton, copyButton, selectionSpy;
beforeEach(function () {
var data = [{'a pk column': 'one', 'some column': 'two', '__temp_PK': '123'},
{'a pk column': 'three', 'some column': 'four', '__temp_PK': '456'},
{'a pk column': 'five', 'some column': 'six', '__temp_PK': '789'},
{'a pk column': 'seven', 'some column': 'eight', '__temp_PK': '432'}],
dataView = new Slick.Data.DataView();
dataView.setItems(data, '__temp_PK');
gridSpy = jasmine.createSpyObj('gridSpy', ['getData', 'getCellNode', 'getColumns']);
gridSpy.getData.and.returnValue(dataView);
gridSpy.getColumns.and.returnValue([
{
name: 'a pk column',
field: 'a pk column',
pos: 0,
selectable: true,
}, {
name: 'some column',
field: 'some column',
pos: 1,
selectable: true,
},
]);
selectionSpy = jasmine.createSpyObj('selectionSpy', ['setSelectedRows', 'getSelectedRanges']);
deleteButton = $('<button id="btn-delete-row"></button>');
copyButton = $('<button id="btn-copy-row"></button>');
sqlEditorObj = {
grid: gridSpy,
editor: {
handler: {
data_store: {
staged_rows: {'456': {}},
},
can_edit: false,
},
},
keys: null,
selection: selectionSpy,
columns: [
{
name: 'a pk column',
field: 'a pk column',
pos: 0,
},
{
name: 'some column',
field: 'some column',
pos: 1,
},
],
client_primary_key: '__temp_PK',
};
$('body').append(deleteButton);
$('body').append(copyButton);
deleteButton.prop('disabled', true);
copyButton.prop('disabled', true);
selectionSpy = jasmine.createSpyObj('selectionSpy', [
'setSelectedRows',
'getSelectedRanges',
]);
});
afterEach(function () {
copyButton.remove();
deleteButton.remove();
});
describe('when no full rows are selected', function () {
describe('when nothing is selected', function () {
beforeEach(function () {
selectionSpy.getSelectedRanges.and.returnValue([]);
sqlEditorObj.selection = selectionSpy;
SetStagedRows.call(sqlEditorObj, {}, {});
});
it('should disable the delete row button', function () {
expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
});
it('should disable the copy row button', function () {
expect($('#btn-copy-row').prop('disabled')).toBeTruthy();
});
it('should clear staged rows', function () {
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
});
});
describe('when there is a selection', function () {
beforeEach(function () {
var range = {
fromCell: 0,
toCell: 0,
fromRow: 1,
toRow: 1,
};
selectionSpy.getSelectedRanges.and.returnValue([range]);
sqlEditorObj.selection = selectionSpy;
SetStagedRows.call(sqlEditorObj, {}, {});
});
it('should disable the delete row button', function () {
expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
});
it('should disable the copy row button', function () {
expect($('#btn-copy-row').prop('disabled')).toBeFalsy();
});
it('should clear staged rows', function () {
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
});
});
});
describe('when 2 full rows are selected', function () {
beforeEach(function () {
var range1 = {
fromCell: 0,
toCell: 1,
fromRow: 1,
toRow: 1,
};
var range2 = {
fromCell: 0,
toCell: 1,
fromRow: 2,
toRow: 2,
};
selectionSpy.getSelectedRanges.and.returnValue([range1, range2]);
sqlEditorObj.selection = selectionSpy;
});
describe('when table does not have primary keys', function () {
it('should enable the copy row button', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect($('#btn-copy-row').prop('disabled')).toBeFalsy();
});
it('should not enable the delete row button', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
});
it('should update staged rows with the __temp_PK value of the new Selected Rows', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({'456': {}, '789': {}});
});
describe('the user can edit', function () {
it('should enable the delete row button', function () {
sqlEditorObj.editor.handler.can_edit = true;
SetStagedRows.call(sqlEditorObj, {}, {});
expect($('#btn-delete-row').prop('disabled')).toBeFalsy();
});
});
});
describe('when table has primary keys', function () {
beforeEach(function () {
sqlEditorObj.keys = {'a pk column': 'varchar'};
sqlEditorObj.editor.handler.data_store.staged_rows = {'456': {'a pk column': 'three'}};
});
describe('selected rows have primary key', function () {
it('should set the staged rows correctly', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual(
{'456': {'a pk column': 'three'}, '789': {'a pk column': 'five'}});
});
it('should not clear selected rows in Cell Selection Model', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalledWith();
});
});
describe('selected rows missing primary key', function () {
beforeEach(function () {
var data = [{'a pk column': 'one', 'some column': 'two', '__temp_PK': '123'},
{'some column': 'four', '__temp_PK': '456'},
{'some column': 'six', '__temp_PK': '789'},
{'a pk column': 'seven', 'some column': 'eight', '__temp_PK': '432'}],
dataView = new Slick.Data.DataView();
dataView.setItems(data, '__temp_PK');
gridSpy.getData.and.returnValue(dataView);
});
it('should clear the staged rows', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
});
it('should clear selected rows in Cell Selection Model', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
expect(sqlEditorObj.selection.setSelectedRows).toHaveBeenCalledWith([]);
});
});
describe('when the selected row is a new row', function () {
var parentDiv;
beforeEach(function () {
var childDiv = $('<div></div>');
parentDiv = $('<div class="new_row"></div>');
parentDiv.append(childDiv);
$('body').append(parentDiv);
gridSpy.getCellNode.and.returnValue(childDiv);
SetStagedRows.call(sqlEditorObj, {}, {});
});
afterEach(function () {
parentDiv.remove();
});
it('should not clear the staged rows', function () {
expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({
'456': {'a pk column': 'three'},
'789': {'a pk column': 'five'},
});
});
it('should not clear selected rows in Cell Selection Model', function () {
expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalled();
});
});
});
});
});

View File

@ -1,518 +0,0 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import 'slickgrid.plugins/slick.cellrangeselector';
import XCellSelectionModel from 'sources/selection/xcell_selection_model';
import 'slickgrid.grid';
import Slick from 'slickgrid';
import $ from 'jquery';
describe('XCellSelectionModel', function () {
var KEY_RIGHT = 39;
var KEY_LEFT = 37;
var KEY_UP = 38;
var KEY_DOWN = 40;
var container, grid;
var SlickGrid = Slick.Grid;
var oldWindowParent = window.parent;
beforeEach(function () {
window.parent = window;
var columns = [{
id: 'row-header-column',
name: 'row header column name',
selectable: false,
}, {
id: '1',
name: 'some-column-name',
field: 'some-column-name',
pos: 0,
}, {
id: 'second-column-id',
name: 'second column',
field: 'second column',
pos: 1,
}, {
id: 'third-column-id',
name: 'third column',
field: 'third column',
pos: 2,
},
];
var data = [];
for (var i = 0; i < 10; i++) {
data.push({
'some-column-name': 'some-value-' + i,
'second column': 'second value ' + i,
'third column': 'third value ' + i,
'fourth column': 'fourth value ' + i,
'__temp_PK': '123' + i,
});
}
container = $('<div></div>');
var dataView = new Slick.Data.DataView();
container.height(9999);
container.width(9999);
dataView.setItems(data, '__temp_PK');
grid = new SlickGrid(container, dataView, columns);
grid.setSelectionModel(new XCellSelectionModel());
$('body').append(container);
});
afterEach(function () {
grid.destroy();
container.remove();
window.parent = oldWindowParent;
});
describe('handleKeyDown', function () {
describe('when we press a random key', function () {
it('should not change the range', function () {
var range = new Slick.Range(1, 2);
grid.setActiveCell(1, 2);
grid.getSelectionModel().setSelectedRanges([range]);
pressKey(72);
expect(grid.getSelectionModel().getSelectedRanges()[0]).toEqual(range);
});
});
describe('when we press an arrow key ', function () {
it('should select the cell to the right', function () {
var range = new Slick.Range(1, 2);
grid.setActiveCell(1, 2);
grid.getSelectionModel().setSelectedRanges([range]);
pressKey(KEY_RIGHT);
expectOneSelectedRange(1, 3, 1, 3);
});
});
describe('when we press shift', function () {
describe('and we press an arrow key', function () {
var scrollColumnIntoViewSpy, scrollRowIntoViewSpy;
beforeEach(function () {
scrollColumnIntoViewSpy = spyOn(grid, 'scrollColumnIntoView');
scrollRowIntoViewSpy = spyOn(grid, 'scrollRowIntoView');
});
describe('the right arrow', function () {
describe('when a cell is selected', function () {
beforeEach(function () {
var range = new Slick.Range(1, 1);
grid.setActiveCell(1, 1);
grid.getSelectionModel().setSelectedRanges([range]);
pressShiftPlusKey(KEY_RIGHT);
});
it('increases the range by one to the right', function () {
expectOneSelectedRange(1, 1, 1, 2);
});
it('should scroll the next column into view', function () {
expect(scrollColumnIntoViewSpy).toHaveBeenCalledWith(2);
expect(scrollRowIntoViewSpy).not.toHaveBeenCalled();
});
it('pressing right again grows the range right', function () {
pressShiftPlusKey(KEY_RIGHT);
expectOneSelectedRange(1, 1, 1, 3);
});
it('then pressing left keeps the original selection', function () {
pressShiftPlusKey(KEY_LEFT);
expectOneSelectedRange(1, 1, 1, 1);
});
});
describe('when a column is selected', function () {
beforeEach(function () {
var range = new Slick.Range(0, 1, 9, 1);
grid.setActiveCell(0, 1);
grid.getSelectionModel().setSelectedRanges([range]);
pressShiftPlusKey(KEY_RIGHT);
});
it('increases the range by one column to the right', function () {
expectOneSelectedRange(0, 1, 9, 2);
});
it('should scroll the next column into view', function () {
expect(scrollColumnIntoViewSpy).toHaveBeenCalledWith(2);
expect(scrollRowIntoViewSpy).not.toHaveBeenCalled();
});
});
});
describe('the left arrow', function () {
describe('when a cell is selected', function () {
beforeEach(function () {
var range = new Slick.Range(1, 3);
grid.setActiveCell(1, 3);
grid.getSelectionModel().setSelectedRanges([range]);
pressShiftPlusKey(KEY_LEFT);
});
it('increases the range by one to the left', function () {
expectOneSelectedRange(1, 2, 1, 3);
});
it('should scroll previous column into view', function () {
expect(scrollColumnIntoViewSpy).toHaveBeenCalledWith(2);
expect(scrollRowIntoViewSpy).not.toHaveBeenCalled();
});
it('pressing left again grows the range the left', function () {
pressShiftPlusKey(KEY_LEFT);
expectOneSelectedRange(1, 1, 1, 3);
});
it('then pressing right keeps the original selection', function () {
pressShiftPlusKey(KEY_RIGHT);
expectOneSelectedRange(1, 3, 1, 3);
});
});
describe('when a column is selected', function () {
beforeEach(function () {
var range = new Slick.Range(0, 2, 9, 2);
grid.setActiveCell(0, 2);
grid.getSelectionModel().setSelectedRanges([range]);
pressShiftPlusKey(KEY_LEFT);
});
it('increases the range by one column to the left', function () {
expectOneSelectedRange(0, 1, 9, 2);
});
it('should scroll previous column into view', function () {
expect(scrollColumnIntoViewSpy).toHaveBeenCalledWith(1);
expect(scrollRowIntoViewSpy).not.toHaveBeenCalled();
});
});
});
describe('the up arrow', function () {
describe('when a cell is selected', function () {
beforeEach(function () {
var range = new Slick.Range(2, 2);
grid.setActiveCell(2, 2);
grid.getSelectionModel().setSelectedRanges([range]);
pressShiftPlusKey(KEY_UP);
});
it('increases the range by one up', function () {
expectOneSelectedRange(1, 2, 2, 2);
});
it('should scroll the row above into view', function () {
expect(scrollRowIntoViewSpy).toHaveBeenCalledWith(1);
expect(scrollColumnIntoViewSpy).not.toHaveBeenCalled();
});
it('pressing up again grows the range up', function () {
pressShiftPlusKey(KEY_UP);
expectOneSelectedRange(0, 2, 2, 2);
});
it('then pressing down keeps the original selection', function () {
pressShiftPlusKey(KEY_DOWN);
expectOneSelectedRange(2, 2, 2, 2);
});
});
describe('when a row is selected', function () {
beforeEach(function () {
var range = new Slick.Range(2, 1, 2, 3);
grid.setActiveCell(2, 1);
grid.getSelectionModel().setSelectedRanges([range]);
pressShiftPlusKey(KEY_UP);
});
it('increases the range by one row up', function () {
expectOneSelectedRange(1, 1, 2, 3);
});
it('should scroll the row above into view', function () {
expect(scrollRowIntoViewSpy).toHaveBeenCalledWith(1);
expect(scrollColumnIntoViewSpy).not.toHaveBeenCalled();
});
});
});
describe('the down arrow', function () {
describe('when a cell is selected', function () {
beforeEach(function () {
var range = new Slick.Range(2, 2);
grid.setActiveCell(2, 2);
grid.getSelectionModel().setSelectedRanges([range]);
pressShiftPlusKey(KEY_DOWN);
});
it('increases the range by one down', function () {
expectOneSelectedRange(2, 2, 3, 2);
});
it('should scroll the row below into view', function () {
expect(scrollRowIntoViewSpy).toHaveBeenCalledWith(3);
expect(scrollColumnIntoViewSpy).not.toHaveBeenCalled();
});
it('pressing down again grows the range down', function () {
pressShiftPlusKey(KEY_DOWN);
expectOneSelectedRange(2, 2, 4, 2);
});
it('then pressing up keeps the original selection', function () {
pressShiftPlusKey(KEY_UP);
expectOneSelectedRange(2, 2, 2, 2);
});
});
describe('when a row is selected', function () {
beforeEach(function () {
var range = new Slick.Range(2, 1, 2, 3);
grid.setActiveCell(2, 1);
grid.getSelectionModel().setSelectedRanges([range]);
pressShiftPlusKey(KEY_DOWN);
});
it('increases the range by one row down', function () {
expectOneSelectedRange(2, 1, 3, 3);
});
it('should scroll the row below into view', function () {
expect(scrollRowIntoViewSpy).toHaveBeenCalledWith(3);
expect(scrollColumnIntoViewSpy).not.toHaveBeenCalled();
});
});
});
describe('rectangular selection works', function () {
it('in the down-and-rightward direction', function () {
var range = new Slick.Range(1, 1);
grid.setActiveCell(1, 1);
grid.getSelectionModel().setSelectedRanges([range]);
pressShiftPlusKey(KEY_DOWN);
pressShiftPlusKey(KEY_DOWN);
pressShiftPlusKey(KEY_DOWN);
pressShiftPlusKey(KEY_RIGHT);
pressShiftPlusKey(KEY_RIGHT);
expectOneSelectedRange(1, 1, 4, 3);
});
it('in the up-and-leftward direction', function () {
var range = new Slick.Range(4, 3);
grid.setActiveCell(4, 3);
grid.getSelectionModel().setSelectedRanges([range]);
pressShiftPlusKey(KEY_UP);
pressShiftPlusKey(KEY_UP);
pressShiftPlusKey(KEY_UP);
pressShiftPlusKey(KEY_LEFT);
pressShiftPlusKey(KEY_LEFT);
expectOneSelectedRange(1, 1, 4, 3);
});
it('in the up-and-rightward direction', function () {
var range = new Slick.Range(4, 1);
grid.setActiveCell(4, 1);
grid.getSelectionModel().setSelectedRanges([range]);
pressShiftPlusKey(KEY_UP);
pressShiftPlusKey(KEY_UP);
pressShiftPlusKey(KEY_UP);
pressShiftPlusKey(KEY_RIGHT);
pressShiftPlusKey(KEY_RIGHT);
expectOneSelectedRange(1, 1, 4, 3);
});
it('in the down-and-leftward direction', function () {
var range = new Slick.Range(1, 3);
grid.setActiveCell(1, 3);
grid.getSelectionModel().setSelectedRanges([range]);
pressShiftPlusKey(KEY_DOWN);
pressShiftPlusKey(KEY_DOWN);
pressShiftPlusKey(KEY_DOWN);
pressShiftPlusKey(KEY_LEFT);
pressShiftPlusKey(KEY_LEFT);
expectOneSelectedRange(1, 1, 4, 3);
});
});
describe('and we are on an edge', function () {
var range;
beforeEach(function () {
range = new Slick.Range(2, 1);
grid.setActiveCell(2, 1);
grid.getSelectionModel().setSelectedRanges([range]);
});
it('we still have the selected range before we arrowed', function () {
pressShiftPlusKey(KEY_LEFT);
expectOneSelectedRange(2, 1, 2, 1);
});
});
});
});
});
describe('when we drag and drop', function () {
var dd;
// We could not find an elegant way to calculate this value
// after changing window size we saw this was a constant value
var offsetLeftColumns = 100;
function cellTopPosition($cell, rowNumber) {
return $(grid.getCanvasNode()).offset().top + $cell[0].scrollHeight * rowNumber;
}
function cellLeftPosition(columnNumber) {
return $(grid.getCanvasNode()).offset().left + offsetLeftColumns * columnNumber;
}
beforeEach(function () {
var initialPosition = {cell: 3, row: 4};
var $cell = $($('.slick-cell.l3')[initialPosition.row]);
var event = {
target: $cell,
isPropagationStopped: jasmine.createSpy('isPropagationStopped').and.returnValue(false),
isImmediatePropagationStopped: jasmine.createSpy('isImmediatePropagationStopped').and.returnValue(false),
stopImmediatePropagation: jasmine.createSpy('stopImmediatePropagation'),
};
dd = {
grid: grid,
startX: cellLeftPosition(initialPosition.cell),
startY: cellTopPosition($cell, initialPosition.row),
};
grid.onDragStart.notify(dd, event, grid);
});
describe('when the drop happens outside of the grid', function () {
beforeEach(function () {
var $cell = $($('.slick-cell.l1')[1]);
var finalPosition = {cell: 1, row: 1};
var event = {
target: $cell,
isPropagationStopped: jasmine.createSpy('isPropagationStopped').and.returnValue(false),
isImmediatePropagationStopped: jasmine.createSpy('isImmediatePropagationStopped').and.returnValue(false),
stopImmediatePropagation: jasmine.createSpy('stopImmediatePropagation'),
pageX: cellLeftPosition(finalPosition.cell),
pageY: cellTopPosition($cell, finalPosition.row),
};
grid.onDrag.notify(dd, event, grid);
$(window).mouseup();
});
it('should call handleDragEnd from CellRangeSelector', function () {
var newRange = grid.getSelectionModel().getSelectedRanges();
expect(newRange.length).toEqual(1);
expect(newRange[0].fromCell).toEqual(1);
expect(newRange[0].toCell).toEqual(3);
expect(newRange[0].fromRow).toEqual(1);
expect(newRange[0].toRow).toEqual(4);
});
});
});
describe('when we mouse up and no drag and drop occured', function () {
beforeEach(function () {
grid.onDragEnd.notify = jasmine.createSpy('notify');
grid.onDragEnd.notify.calls.reset();
$(window).mouseup();
});
it('do not notify onDragEnd', function () {
expect(grid.onDragEnd.notify).not.toHaveBeenCalled();
});
});
describe('setSelectedRows', function () {
beforeEach(function () {
grid.getSelectionModel().setSelectedRanges(
[new Slick.Range(1, 1, 1, 1)]
);
});
describe('when passed an empty array', function () {
beforeEach(function () {
grid.getSelectionModel().setSelectedRows([]);
});
it('clears ranges', function () {
var newRanges = grid.getSelectionModel().getSelectedRanges();
expect(newRanges.length).toEqual(0);
});
});
it('sets ranges corresponding to rows', function () {
grid.getSelectionModel().setSelectedRows([0, 2]);
var selectedRanges = grid.getSelectionModel().getSelectedRanges();
expect(selectedRanges.length).toEqual(2);
expectRangeToMatch(selectedRanges[0], 0, 1, 0, 3);
expectRangeToMatch(selectedRanges[1], 2, 1, 2, 3);
});
});
function pressKey(keyCode) {
var pressEvent = new $.Event('keydown');
pressEvent.which = keyCode;
$(container.find('.grid-canvas')).trigger(pressEvent);
}
function pressShiftPlusKey(keyCode) {
var pressEvent = new $.Event('keydown');
pressEvent.shiftKey = true;
pressEvent.which = keyCode;
$(container.find('.grid-canvas')).trigger(pressEvent);
}
function expectOneSelectedRange(fromRow, fromCell, toRow, toCell) {
var selectedRanges = grid.getSelectionModel().getSelectedRanges();
expect(selectedRanges.length).toEqual(1);
expectRangeToMatch(selectedRanges[0], fromRow, fromCell, toRow, toCell);
}
function expectRangeToMatch(range, fromRow, fromCell, toRow, toCell) {
expect(range.fromRow).toEqual(fromRow);
expect(range.toRow).toEqual(toRow);
expect(range.fromCell).toEqual(fromCell);
expect(range.toCell).toEqual(toCell);
}
});

View File

@ -1,80 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import $ from 'jquery';
import 'slickgrid';
import 'slickgrid.grid';
import XCellSelectionModel from 'sources/selection/xcell_selection_model';
import CellSelector from 'sources/slickgrid/cell_selector';
import RangeSelectionHelper from 'sources/selection/range_selection_helper';
describe('CellSelector', function () {
var container, columns, cellSelector, data, cellSelectionModel, grid;
var Slick = window.Slick;
beforeEach(function () {
container = $('<div></div>');
container.height(9999);
container.width(9999);
columns = [{
name: 'some-column-name',
}, {
name: 'second column',
}];
cellSelector = new CellSelector();
data = [];
for (var i = 0; i < 10; i++) {
data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i});
}
grid = new Slick.Grid(container, data, columns);
cellSelectionModel = new XCellSelectionModel();
grid.setSelectionModel(cellSelectionModel);
grid.registerPlugin(cellSelector);
grid.invalidate();
$('body').append(container);
});
afterEach(function () {
$('body').find(container).remove();
});
describe('when the user clicks or tabs to a cell', function () {
it('sets the selected range to that cell', function () {
var row = 1, column = 0;
$(container.find('.slick-row .slick-cell.l' + column)[row]).trigger('click');
var selectedRanges = cellSelectionModel.getSelectedRanges();
expect(selectedRanges.length).toEqual(1);
expect(selectedRanges[0].fromCell).toEqual(0);
expect(selectedRanges[0].toCell).toEqual(0);
expect(selectedRanges[0].fromRow).toEqual(1);
expect(selectedRanges[0].toRow).toEqual(1);
});
it('deselects previously selected ranges', function () {
var row2Range = RangeSelectionHelper.rangeForRow(grid, 2);
var ranges = RangeSelectionHelper.addRange(cellSelectionModel.getSelectedRanges(),
row2Range);
cellSelectionModel.setSelectedRanges(ranges);
var row = 4, column = 1;
$(container.find('.slick-row .slick-cell.l' + column)[row]).trigger('click');
expect(RangeSelectionHelper.isRangeSelected(cellSelectionModel.getSelectedRanges(), row2Range))
.toEqual(false);
});
});
});

View File

@ -1,125 +0,0 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import HandleQueryOutputKeyboardEvent from 'sources/slickgrid/event_handlers/handle_query_output_keyboard_event';
import clipboard from 'sources/selection/clipboard';
import RangeSelectionHelper from 'sources/selection/range_selection_helper';
import XCellSelectionModel from 'sources/selection/xcell_selection_model';
import Slick from 'slickgrid';
import 'slickgrid.grid';
import $ from 'jquery';
describe('#handleQueryOutputKeyboardEvent', function () {
var event, grid, slickEvent;
var handleQueryOutputKeyboardEvent, buttonCopyWithHeader;
beforeEach(function () {
event = {
shiftKey: false,
ctrlKey: false,
metaKey: false,
which: -1,
keyCode: -1,
preventDefault: jasmine.createSpy('preventDefault'),
};
var data = [{'checkboxColumn': '', 'firstColumn': '0,0-cell-content', 'secondColumn': '0,1-cell-content', '__temp_PK': '123'},
{'checkboxColumn': '', 'firstColumn': '1,0-cell-content', 'secondColumn': '1,1-cell-content', '__temp_PK': '456'},
{'checkboxColumn': '', 'firstColumn': '2,0-cell-content', 'secondColumn': '2,1-cell-content', '__temp_PK': '789'}],
columnDefinitions = [{name: 'checkboxColumn'},
{pos: 1, name: 'firstColumn', field: 'firstColumn'},
{ pos: 2, name: 'secondColumn', field: 'secondColumn'}],
dataView = new Slick.Data.DataView(),
CSVOptions = {'quoting': 'all', 'quote_char': '\'', 'field_separator': ','};
grid = new Slick.Grid($('<div></div>'), dataView, columnDefinitions);
grid.setSelectionModel(new XCellSelectionModel());
grid.CSVOptions = CSVOptions;
dataView.setItems(data, '__temp_PK');
slickEvent = {
grid: grid,
};
buttonCopyWithHeader = $('<button class="copy-with-header visibility-hidden"></button>');
$('body').append(buttonCopyWithHeader);
spyOn(clipboard, 'copyTextToClipboard');
handleQueryOutputKeyboardEvent = HandleQueryOutputKeyboardEvent.bind(window);
});
let selectEntireGridAction = ()=> {
handleQueryOutputKeyboardEvent(event, slickEvent);
expect(RangeSelectionHelper.isEntireGridSelected(grid)).toBeTruthy();
expect(grid.getSelectionModel().getSelectedRanges().length).toEqual(1);
};
let commandAAction = ()=> {
beforeEach(function () {
event.metaKey = true;
event.keyCode = 65;
});
it('selects the entire grid to ranges', function () {
selectEntireGridAction();
});
};
let ctrlAAction = ()=> {
beforeEach(function () {
event.ctrlKey = true;
event.keyCode = 65;
});
it('selects the entire grid to ranges', function () {
selectEntireGridAction();
});
};
describe('when a range is selected', function () {
beforeEach(function () {
grid.getSelectionModel().setSelectedRanges([
RangeSelectionHelper.rangeForRow(grid, 0),
RangeSelectionHelper.rangeForRow(grid, 2),
]);
});
let copyCellContentAction = ()=> {
handleQueryOutputKeyboardEvent(event, slickEvent);
expect(clipboard.copyTextToClipboard).toHaveBeenCalledWith('\'0,0-cell-content\',\'0,1-cell-content\'\n\'2,0-cell-content\',\'2,1-cell-content\'');
};
describe('pressing Command + C', function () {
beforeEach(function () {
event.metaKey = true;
event.keyCode = 67;
});
it('copies the cell content to the clipboard', copyCellContentAction);
});
describe('pressing Ctrl + C', function () {
beforeEach(function () {
event.ctrlKey = true;
event.keyCode = 67;
});
it('copies the cell content to the clipboard', copyCellContentAction);
});
describe('pressing Command + A', commandAAction);
describe('pressing Ctrl + A', ctrlAAction);
});
describe('when no ranges are selected', function () {
describe('pressing Command + A', commandAAction);
describe('pressing Ctrl + A', ctrlAAction);
});
});

View File

@ -56,7 +56,7 @@ const providePlugin = new webpack.ProvidePlugin({
// Reference: https://webpack.js.org/plugins/source-map-dev-tool-plugin/#components/sidebar/sidebar.jsx // Reference: https://webpack.js.org/plugins/source-map-dev-tool-plugin/#components/sidebar/sidebar.jsx
const sourceMapDevToolPlugin = new webpack.SourceMapDevToolPlugin({ const sourceMapDevToolPlugin = new webpack.SourceMapDevToolPlugin({
filename: '[name].js.map', filename: '[name].js.map',
exclude: /(vendor|codemirror|slickgrid|pgadmin\.js|pgadmin.theme|pgadmin.static|style\.js|popper)/, exclude: /(vendor|codemirror|pgadmin\.js|pgadmin.theme|pgadmin.static|style\.js|popper)/,
columns: false, columns: false,
}); });
@ -376,9 +376,8 @@ module.exports = [{
entry: { entry: {
'app.bundle': sourceDir + '/bundle/app.js', 'app.bundle': sourceDir + '/bundle/app.js',
codemirror: sourceDir + '/bundle/codemirror.js', codemirror: sourceDir + '/bundle/codemirror.js',
slickgrid: sourceDir + '/bundle/slickgrid.js',
sqleditor: './pgadmin/tools/sqleditor/static/js/index.js', sqleditor: './pgadmin/tools/sqleditor/static/js/index.js',
schema_diff: './pgadmin/tools/schema_diff/static/js/schema_diff_hook.js', schema_diff: './pgadmin/tools/schema_diff/static/js/index.js',
erd_tool: './pgadmin/tools/erd/static/js/index.js', erd_tool: './pgadmin/tools/erd/static/js/index.js',
psql_tool: './pgadmin/tools/psql/static/js/index.js', psql_tool: './pgadmin/tools/psql/static/js/index.js',
debugger: './pgadmin/tools/debugger/static/js/index.js', debugger: './pgadmin/tools/debugger/static/js/index.js',

View File

@ -72,16 +72,6 @@ var webpackShimConfig = {
'deps': ['jquery'], 'exports': 'jQuery.fn.drag', 'deps': ['jquery'], 'exports': 'jQuery.fn.drag',
}, },
'jquery.ui': {'deps': ['jquery']}, 'jquery.ui': {'deps': ['jquery']},
'slick.pgadmin.formatters': {
'deps': ['slickgrid'],
},
'slick.pgadmin.editors': {
'deps': ['slickgrid'],
},
'slickgrid': {
'deps': ['jquery', 'jquery.ui', 'jquery.event.drag'],
'exports': 'Slick',
},
'alertify': { 'alertify': {
'exports': 'alertify', 'exports': 'alertify',
}, },
@ -132,8 +122,6 @@ var webpackShimConfig = {
'wcdocker': path.join(__dirname, './node_modules/webcabin-docker/Build/wcDocker.min'), 'wcdocker': path.join(__dirname, './node_modules/webcabin-docker/Build/wcDocker.min'),
'alertify': path.join(__dirname, './node_modules/alertifyjs/build/alertify'), 'alertify': path.join(__dirname, './node_modules/alertifyjs/build/alertify'),
'moment': path.join(__dirname, './node_modules/moment/moment'), 'moment': path.join(__dirname, './node_modules/moment/moment'),
'jquery.event.drag': path.join(__dirname, './node_modules/slickgrid/lib/jquery.event.drag-2.3.0'),
'jquery.ui': path.join(__dirname, './node_modules/slickgrid/lib/jquery-ui-1.11.3.min'),
'jqueryui.position': path.join(__dirname, './node_modules/jquery-contextmenu/dist/jquery.ui.position'), 'jqueryui.position': path.join(__dirname, './node_modules/jquery-contextmenu/dist/jquery.ui.position'),
'jquery.contextmenu': path.join(__dirname, './node_modules/jquery-contextmenu/dist/jquery.contextMenu'), 'jquery.contextmenu': path.join(__dirname, './node_modules/jquery-contextmenu/dist/jquery.contextMenu'),
'dropzone': path.join(__dirname, './node_modules/dropzone/dist/dropzone'), 'dropzone': path.join(__dirname, './node_modules/dropzone/dist/dropzone'),
@ -278,8 +266,7 @@ var webpackShimConfig = {
'pgadmin.tools.import_export_servers': path.join(__dirname, './pgadmin/tools/import_export_servers/static/js/'), 'pgadmin.tools.import_export_servers': path.join(__dirname, './pgadmin/tools/import_export_servers/static/js/'),
'pgadmin.tools.maintenance': path.join(__dirname, './pgadmin/tools/maintenance/static/js/maintenance'), 'pgadmin.tools.maintenance': path.join(__dirname, './pgadmin/tools/maintenance/static/js/maintenance'),
'pgadmin.tools.restore': path.join(__dirname, './pgadmin/tools/restore/static/js/restore'), 'pgadmin.tools.restore': path.join(__dirname, './pgadmin/tools/restore/static/js/restore'),
'pgadmin.tools.schema_diff': path.join(__dirname, './pgadmin/tools/schema_diff/static/js/schema_diff'), 'pgadmin.tools.schema_diff': path.join(__dirname, './pgadmin/tools/schema_diff/static/js/'),
'pgadmin.tools.schema_diff_ui': path.join(__dirname, './pgadmin/tools/schema_diff/static/js/schema_diff_ui'),
'pgadmin.tools.search_objects': path.join(__dirname, './pgadmin/tools/search_objects/static/js'), 'pgadmin.tools.search_objects': path.join(__dirname, './pgadmin/tools/search_objects/static/js'),
'pgadmin.tools.erd': path.join(__dirname, './pgadmin/tools/erd/static/js'), 'pgadmin.tools.erd': path.join(__dirname, './pgadmin/tools/erd/static/js'),
'pgadmin.tools.psql_module': path.join(__dirname, './pgadmin/tools/psql/static/js/psql_module'), 'pgadmin.tools.psql_module': path.join(__dirname, './pgadmin/tools/psql/static/js/psql_module'),
@ -287,9 +274,6 @@ var webpackShimConfig = {
'pgadmin.tools.sqleditor': path.join(__dirname, './pgadmin/tools/sqleditor/static/js'), 'pgadmin.tools.sqleditor': path.join(__dirname, './pgadmin/tools/sqleditor/static/js'),
'pgadmin.tools.user_management': path.join(__dirname, './pgadmin/tools/user_management/static/js/user_management'), 'pgadmin.tools.user_management': path.join(__dirname, './pgadmin/tools/user_management/static/js/user_management'),
'pgadmin.user_management.current_user': '/user_management/current_user', 'pgadmin.user_management.current_user': '/user_management/current_user',
'slick.pgadmin.editors': path.join(__dirname, './pgadmin/tools/../static/js/slickgrid/editors'),
'slick.pgadmin.formatters': path.join(__dirname, './pgadmin/tools/../static/js/slickgrid/formatters'),
'slick.pgadmin.plugins': path.join(__dirname, './pgadmin/tools/../static/js/slickgrid/plugins'),
}, },
externals: [ externals: [
'pgadmin.user_management.current_user', 'pgadmin.user_management.current_user',
@ -306,8 +290,7 @@ var webpackShimConfig = {
'pgadmin.browser.error', 'pgadmin.browser.server.privilege', 'pgadmin.browser.error', 'pgadmin.browser.server.privilege',
'pgadmin.browser.server.variable', 'pgadmin.browser.collection', 'pgadmin.browser.node.ui', 'pgadmin.browser.server.variable', 'pgadmin.browser.collection', 'pgadmin.browser.node.ui',
'pgadmin.browser.datamodel', 'pgadmin.browser.menu', 'pgadmin.browser.panel', 'pgadmin', 'pgadmin.browser.datamodel', 'pgadmin.browser.menu', 'pgadmin.browser.panel', 'pgadmin',
'pgadmin.browser.frame', 'slick.pgadmin.editors', 'slick.pgadmin.formatters', 'pgadmin.browser.frame', 'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser',
'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser',
'pgadmin.browser.node', 'pgadmin.browser.node',
'pgadmin.alertifyjs', 'pgadmin.settings', 'pgadmin.preferences', 'pgadmin.sqlfoldcode', 'pgadmin.alertifyjs', 'pgadmin.settings', 'pgadmin.preferences', 'pgadmin.sqlfoldcode',
], ],

View File

@ -13,7 +13,6 @@ const webpack = require('webpack');
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin'); const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
const sourcesDir = path.resolve(__dirname, 'pgadmin/static'); const sourcesDir = path.resolve(__dirname, 'pgadmin/static');
const nodeModulesDir = path.resolve(__dirname, 'node_modules');
const regressionDir = path.resolve(__dirname, 'regression'); const regressionDir = path.resolve(__dirname, 'regression');
module.exports = { module.exports = {
@ -99,62 +98,6 @@ module.exports = {
filename: 'img/[name].[ext]', filename: 'img/[name].[ext]',
}, },
exclude: /vendor/, exclude: /vendor/,
}, {
test: /.*slickgrid[\\\/]+slick\.(?!core)*/,
use:[
{
loader: 'imports-loader',
options: {
type: 'commonjs',
imports: [
'pure|jquery.ui',
'pure|jquery.event.drag',
'pure|slickgrid',
],
},
},
],
}, {
test: /.*slickgrid\.plugins[\\\/]+slick\.cellrangeselector/,
use:[
{
loader: 'imports-loader',
options: {
type: 'commonjs',
imports: [
'pure|jquery.ui',
'pure|jquery.event.drag',
'pure|slickgrid',
],
},
}, {
loader: 'exports-loader',
options: {
type: 'commonjs',
exports: 'single|Slick.CellRangeSelector',
},
},
],
}, {
test: /.*slickgrid[\\\/]+slick\.core.*/,
use:[
{
loader: 'imports-loader',
options: {
type: 'commonjs',
imports: [
'pure|jquery.ui',
'pure|jquery.event.drag',
],
},
}, {
loader: 'exports-loader',
options: {
type: 'commonjs',
exports: 'single|Slick',
},
},
],
}, },
{ {
test: /\.js$|\.jsx$/, test: /\.js$|\.jsx$/,
@ -163,7 +106,7 @@ module.exports = {
options: { esModules: true }, options: { esModules: true },
}, },
enforce: 'post', enforce: 'post',
exclude: /node_modules|slickgrid|plugins|bundle|generated|regression|[Tt]est.js|[Ss]pecs.js|[Ss]pec.js|\.spec\.js$/, exclude: /node_modules|plugins|bundle|generated|regression|[Tt]est.js|[Ss]pecs.js|[Ss]pec.js|\.spec\.js$/,
}, },
], ],
}, },
@ -175,8 +118,6 @@ module.exports = {
'jquery': path.join(__dirname, './node_modules/jquery/dist/jquery'), 'jquery': path.join(__dirname, './node_modules/jquery/dist/jquery'),
'wcdocker': path.join(__dirname, './node_modules/webcabin-docker/Build/wcDocker'), 'wcdocker': path.join(__dirname, './node_modules/webcabin-docker/Build/wcDocker'),
'alertify': path.join(__dirname, './node_modules/alertifyjs/build/alertify'), 'alertify': path.join(__dirname, './node_modules/alertifyjs/build/alertify'),
'jquery.event.drag': path.join(__dirname, './node_modules/slickgrid/lib/jquery.event.drag-2.3.0'),
'jquery.ui': path.join(__dirname, './node_modules/slickgrid/lib/jquery-ui-1.11.3'),
'color-picker': path.join(__dirname, './node_modules/@simonwep/pickr/dist/pickr.min'), 'color-picker': path.join(__dirname, './node_modules/@simonwep/pickr/dist/pickr.min'),
'bignumber': path.join(__dirname, './node_modules/bignumber.js/bignumber'), 'bignumber': path.join(__dirname, './node_modules/bignumber.js/bignumber'),
'bootstrap.datetimepicker': path.join(__dirname, './node_modules/tempusdominus-bootstrap-4/build/js/tempusdominus-bootstrap-4.min'), 'bootstrap.datetimepicker': path.join(__dirname, './node_modules/tempusdominus-bootstrap-4/build/js/tempusdominus-bootstrap-4.min'),
@ -192,9 +133,6 @@ module.exports = {
'pgadmin.browser.messages': regressionDir + '/javascript/fake_messages', 'pgadmin.browser.messages': regressionDir + '/javascript/fake_messages',
'pgadmin.server.supported_servers': regressionDir + '/javascript/fake_supported_servers', 'pgadmin.server.supported_servers': regressionDir + '/javascript/fake_supported_servers',
'pgadmin.browser.endpoints': regressionDir + '/javascript/fake_endpoints', 'pgadmin.browser.endpoints': regressionDir + '/javascript/fake_endpoints',
'slickgrid': nodeModulesDir + '/slickgrid/',
'slickgrid.plugins': nodeModulesDir + '/slickgrid/plugins/',
'slickgrid.grid': nodeModulesDir + '/slickgrid/slick.grid',
'moment': path.join(__dirname, './node_modules/moment/moment'), 'moment': path.join(__dirname, './node_modules/moment/moment'),
'jsoneditor.min': path.join(__dirname, './node_modules/jsoneditor/dist/jsoneditor.min'), 'jsoneditor.min': path.join(__dirname, './node_modules/jsoneditor/dist/jsoneditor.min'),
'browser': path.resolve(__dirname, 'pgadmin/browser/static/js'), 'browser': path.resolve(__dirname, 'pgadmin/browser/static/js'),

View File

@ -7121,14 +7121,14 @@ jquery-contextmenu@^2.6.4, jquery-contextmenu@^2.9.2:
dependencies: dependencies:
jquery "^3.5.0" jquery "^3.5.0"
jquery-ui@>=1.8.0, jquery-ui@^1.13.2: jquery-ui@^1.13.2:
version "1.13.2" version "1.13.2"
resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.13.2.tgz#de03580ae6604773602f8d786ad1abfb75232034" resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.13.2.tgz#de03580ae6604773602f8d786ad1abfb75232034"
integrity sha512-wBZPnqWs5GaYJmo1Jj0k/mrSkzdQzKDwhXNtHKcBdAcKVxMM3KNYFq+iJ2i1rwiG53Z8M4mTn3Qxrm17uH1D4Q== integrity sha512-wBZPnqWs5GaYJmo1Jj0k/mrSkzdQzKDwhXNtHKcBdAcKVxMM3KNYFq+iJ2i1rwiG53Z8M4mTn3Qxrm17uH1D4Q==
dependencies: dependencies:
jquery ">=1.8.0 <4.0.0" jquery ">=1.8.0 <4.0.0"
"jquery@>=1.7.1 <4.0.0", jquery@>=1.8.0, "jquery@>=1.8.0 <4.0.0", jquery@^3.3.1, jquery@^3.5.0, jquery@^3.5.1, jquery@^3.6.0: "jquery@>=1.7.1 <4.0.0", "jquery@>=1.8.0 <4.0.0", jquery@^3.3.1, jquery@^3.5.0, jquery@^3.5.1, jquery@^3.6.0:
version "3.6.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470"
integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw== integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==
@ -10365,13 +10365,6 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0" astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0" is-fullwidth-code-point "^3.0.0"
"slickgrid@git+https://github.com/6pac/SlickGrid.git#2.3.16":
version "2.3.16"
resolved "git+https://github.com/6pac/SlickGrid.git#4f8c6f498d0b82391fdf382beb8ef114ed7408e7"
dependencies:
jquery ">=1.8.0"
jquery-ui ">=1.8.0"
smart-buffer@^4.2.0: smart-buffer@^4.2.0:
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"