Before Width: | Height: | Size: 431 KiB After Width: | Height: | Size: 300 KiB |
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 98 KiB |
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 275 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 199 KiB |
|
@ -19,6 +19,7 @@ New features
|
|||
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 #7622 <https://redmine.postgresql.org/issues/7622>`_ - Port search object dialog to React.
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ module.exports = function (config) {
|
|||
}),
|
||||
],
|
||||
files: [
|
||||
'pgadmin/static/bundle/slickgrid.js',
|
||||
{pattern: 'pgadmin/static/**/*.js', included: false, watched: true},
|
||||
{pattern: 'pgadmin/browser/static/js/**/*.js', included: false, watched: true},
|
||||
{pattern: 'regression/javascript/**/*.js', watched: true},
|
||||
|
@ -50,7 +49,6 @@ module.exports = function (config) {
|
|||
preprocessors: {
|
||||
'pgadmin/**/js/**/*.js?': ['sourcemap'],
|
||||
'regression/javascript/**/*.js': ['webpack', 'sourcemap'],
|
||||
'pgadmin/static/bundle/slickgrid.js': ['webpack', 'sourcemap'],
|
||||
},
|
||||
|
||||
// optionally, configure the reporter
|
||||
|
|
|
@ -163,7 +163,6 @@
|
|||
"react-window": "^1.8.5",
|
||||
"select2": "^4.0.13",
|
||||
"shim-loader": "^1.0.1",
|
||||
"slickgrid": "git+https://github.com/6pac/SlickGrid.git#2.3.16",
|
||||
"snapsvg-cjs": "^0.0.6",
|
||||
"socket.io-client": "^4.0.0",
|
||||
"split.js": "^1.5.10",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
{
|
||||
"dark": {
|
||||
"disp_name": "dark_(Beta)",
|
||||
"cssfile": "pgadmin.theme.dark",
|
||||
|
|
|
@ -18,7 +18,7 @@ define([
|
|||
pgAdmin.Browser.Frame = function(options) {
|
||||
var defaults = [
|
||||
'name', 'title', 'width', 'height', 'showTitle', 'isCloseable',
|
||||
'isPrivate', 'url', 'icon', 'onCreate', 'isLayoutMember',
|
||||
'isPrivate', 'url', 'icon', 'onCreate', 'isLayoutMember', 'isRenamable',
|
||||
];
|
||||
_.extend(this, _.pick(options, defaults));
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
{% block init_script %}
|
||||
try {
|
||||
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() {
|
||||
|
|
|
@ -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;
|
|
@ -14,10 +14,6 @@
|
|||
@import 'node_modules/codemirror/addon/dialog/dialog.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-select-all.css';
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -22,6 +22,7 @@ import ChangePasswordContent from './ChangePasswordContent';
|
|||
import NamedRestoreContent from './NamedRestoreContent';
|
||||
import ChangeOwnershipContent from './ChangeOwnershipContent';
|
||||
import UrlDialogContent from './UrlDialogContent';
|
||||
import RenamePanelContent from './RenamePanelContent';
|
||||
|
||||
function mountDialog(title, getDialogContent, docker=undefined, width, height) {
|
||||
// Register dialog panel
|
||||
|
@ -353,3 +354,29 @@ export function showUrlDialog() {
|
|||
{ isFullScreen: false, isResizeable: true, showFullScreen: true, isFullWidth: true,
|
||||
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>;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -103,6 +103,13 @@ export default function(basicSettings) {
|
|||
textMuted: '#8A8A8A',
|
||||
erdCanvasBg: '#303030',
|
||||
erdGridColor: '#444952',
|
||||
schemaDiff: {
|
||||
diffRowColor: '#807a48',
|
||||
sourceRowColor: '#402025',
|
||||
targetRowColor: '#6b5438',
|
||||
diffColorFg: '#d4d4d4',
|
||||
diffSelectFG: '#d4d4d4'
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -101,6 +101,13 @@ export default function(basicSettings) {
|
|||
textMuted: '#8b9cad',
|
||||
erdCanvasBg: '#010B15',
|
||||
erdGridColor: '#1F2932',
|
||||
schemaDiff: {
|
||||
diffRowColor: '#CFC56E',
|
||||
sourceRowColor: '#EE97A5',
|
||||
targetRowColor: '#FFAD65',
|
||||
diffColorFg: '#FFFFFF',
|
||||
diffSelectFG: '#010B15'
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -121,6 +121,13 @@ export default function(basicSettings) {
|
|||
color: '#FFFFFF',
|
||||
bg: '#880000'
|
||||
},
|
||||
},
|
||||
schemaDiff: {
|
||||
diffRowColor: '#fff9c4',
|
||||
sourceRowColor: '#ffebee',
|
||||
targetRowColor: '#fbe3bf',
|
||||
diffColorFg: '#222',
|
||||
diffSelectFG: '#222'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -37,6 +37,7 @@ const useStyles = makeStyles((theme)=>({
|
|||
},
|
||||
message: {
|
||||
marginLeft: '0.5rem',
|
||||
fontSize: '16px',
|
||||
}
|
||||
}));
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ CustomRow.propTypes = {
|
|||
};
|
||||
|
||||
export default function PgReactDataGrid({gridRef, className, hasSelectColumn=true, onItemEnter, onItemSelect,
|
||||
onItemClick, noRowsText, ...props}) {
|
||||
onItemClick, noRowsText, noRowsIcon,...props}) {
|
||||
const classes = useStyles();
|
||||
let finalClassName = [classes.root];
|
||||
hasSelectColumn && finalClassName.push(classes.hasSelectColumn);
|
||||
|
@ -142,7 +142,7 @@ export default function PgReactDataGrid({gridRef, className, hasSelectColumn=tru
|
|||
components={{
|
||||
sortIcon: CutomSortIcon,
|
||||
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}
|
||||
/>
|
||||
|
@ -158,5 +158,6 @@ PgReactDataGrid.propTypes = {
|
|||
onItemEnter: PropTypes.func,
|
||||
onItemSelect: PropTypes.func,
|
||||
onItemClick: PropTypes.func,
|
||||
noRowsText: PropTypes.string
|
||||
noRowsText: PropTypes.string,
|
||||
noRowsIcon: PropTypes.elementType
|
||||
};
|
||||
|
|
|
@ -67,6 +67,12 @@ const useStyles = makeStyles((theme)=>({
|
|||
'& .dock-tabpane': {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
'& #id-schema-diff': {
|
||||
overflowY: 'auto'
|
||||
},
|
||||
'& #id-results': {
|
||||
overflowY: 'auto'
|
||||
}
|
||||
},
|
||||
'& .dock-tab': {
|
||||
|
|
|
@ -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()]);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
});
|
|
@ -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,
|
||||
};
|
||||
});
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
});
|
|
@ -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(),
|
||||
});
|
||||
};
|
||||
});
|
|
@ -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
|
||||
),
|
||||
]);
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
|
@ -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);
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -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);
|
|
@ -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));
|
|
@ -833,18 +833,6 @@ body {
|
|||
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.unchecked {
|
||||
background: $color-bg;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -34,6 +34,5 @@ $theme-colors: (
|
|||
@import 'jsoneditor.overrides';
|
||||
@import 'pgadmin4-tree.overrides';
|
||||
@import 'pgadmin4-tree/src/css/styles';
|
||||
@import 'slickgrid.overrides';
|
||||
@import 'rc-dock/dist/rc-dock.css';
|
||||
@import '@szhsin/react-menu/dist/index.css';
|
||||
|
|
|
@ -16,7 +16,6 @@ import gettext from 'sources/gettext';
|
|||
import { sprintf, registerDetachEvent } from 'sources/utils';
|
||||
import url_for from 'sources/url_for';
|
||||
import pgWindow from 'sources/window';
|
||||
import alertify from 'pgadmin.alertifyjs';
|
||||
import Kerberos from 'pgadmin.authenticate.kerberos';
|
||||
|
||||
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 DebuggerComponent from './components/DebuggerComponent';
|
||||
import Theme from '../../../../static/js/Theme';
|
||||
import { showRenamePanel } from '../../../../static/js/Dialogs';
|
||||
|
||||
export default class DebuggerModule {
|
||||
static instance;
|
||||
|
@ -713,20 +713,13 @@ export default class DebuggerModule {
|
|||
}
|
||||
|
||||
panel_rename_event(panel_data, panel, treeInfo) {
|
||||
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();
|
||||
let preferences = this.pgBrowser.get_preferences_for_module('browser');
|
||||
var name = getAppropriateLabel(treeInfo);
|
||||
setDebuggerTitle(panel, preferences, name, treeInfo.schema.label, treeInfo.database.label, value, this.pgBrowser);
|
||||
}
|
||||
},
|
||||
// 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') });
|
||||
let name = getAppropriateLabel(treeInfo);
|
||||
let preferences = this.pgBrowser.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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import gettext from 'sources/gettext';
|
|||
import * as commonUtils from 'sources/utils';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import Loader from 'sources/components/Loader';
|
||||
import Alertify from 'pgadmin.alertifyjs';
|
||||
|
||||
import SchemaView from '../../../../../static/js/SchemaView';
|
||||
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 { DebuggerArgumentSchema } from './DebuggerArgs.ui';
|
||||
import { DEBUGGER_ARGS } from '../DebuggerConstants';
|
||||
|
||||
import { showRenamePanel } from '../../../../../static/js/Dialogs';
|
||||
|
||||
|
||||
const useStyles = makeStyles((theme) =>
|
||||
|
@ -677,7 +676,6 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug,
|
|||
}
|
||||
|
||||
function startDebugging() {
|
||||
var self = this;
|
||||
setLoaderText('Starting debugger.');
|
||||
try {
|
||||
/* 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.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();
|
||||
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') });
|
||||
panelRenameEvent(panel_data, panel, treeInfo);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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 (
|
||||
<Box className={classes.root}>
|
||||
<Box className={classes.body}>
|
||||
|
|
|
@ -129,18 +129,7 @@ def panel(trans_id, editor_title):
|
|||
trans_id=trans_id,
|
||||
requirejs=True,
|
||||
basejs=True,
|
||||
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
|
||||
editor_title=editor_title,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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')
|
||||
};
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -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 = {
|
||||
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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,
|
||||
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -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> ` + 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> ` + 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> ` + 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,
|
||||
};
|
|
@ -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;
|
||||
});
|
|
@ -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,
|
||||
};
|
|
@ -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;
|
||||
});
|
|
@ -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> ' + gettext('Different') + ': <strong>' + different + '</strong> ' + gettext('Source Only') + ': <strong>' + source_only + '</strong> ' + 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;
|
|
@ -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;
|
||||
}
|
|
@ -2,14 +2,10 @@
|
|||
{% block init_script %}
|
||||
try {
|
||||
require(
|
||||
['sources/generated/slickgrid', 'sources/generated/codemirror', 'sources/generated/browser_nodes'],
|
||||
['sources/generated/codemirror', 'sources/generated/browser_nodes', 'sources/generated/schema_diff'],
|
||||
function() {
|
||||
require(['sources/generated/schema_diff'], function(pgSchemaDiffHook) {
|
||||
var pgSchemaDiffHook = pgSchemaDiffHook || pgAdmin.Tools.SchemaDiffHook;
|
||||
pgSchemaDiffHook.load({{trans_id}});
|
||||
}, function() {
|
||||
console.log(arguments);
|
||||
});
|
||||
var pgSchemaDiff = window.pgAdmin.Tools.SchemaDiff;
|
||||
pgSchemaDiff.load(document.getElementById('schema-diff-main-container'),{{trans_id}});
|
||||
},
|
||||
function() {
|
||||
console.log(arguments);
|
||||
|
@ -19,18 +15,28 @@ try {
|
|||
}
|
||||
{% endblock %}
|
||||
{% 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')}}"/>
|
||||
{% endblock %}
|
||||
{% block title %}{{editor_title}}{% endblock %}
|
||||
{% block body %}
|
||||
<div id="schema-diff-container">
|
||||
<div id="diff_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 schema-diff-busy-text"></div></div>
|
||||
<div id="schema-diff-main-container" tabindex="0">
|
||||
<div class="pg-sp-container">
|
||||
<div class="pg-sp-content">
|
||||
<div class="row">
|
||||
<div class="col-12 pg-sp-icon"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -130,7 +130,7 @@ const finaliseData = (nodeData, datum)=> {
|
|||
datum.icon = 'icon-' + datum.type;
|
||||
/* finalise path */
|
||||
[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.other_info = datum.other_info ? _.escape(datum.other_info) : datum.other_info;
|
||||
|
@ -423,4 +423,4 @@ export default function SearchObjects({nodeData}) {
|
|||
SearchObjects.propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
nodeData: PropTypes.object,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -15,7 +15,6 @@ import * as commonUtils from 'sources/utils';
|
|||
import $ from 'jquery';
|
||||
import url_for from 'sources/url_for';
|
||||
import _ from 'lodash';
|
||||
import alertify from 'pgadmin.alertifyjs';
|
||||
import pgWindow from 'sources/window';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import pgBrowser from 'pgadmin.browser';
|
||||
|
@ -27,6 +26,7 @@ import ReactDOM from 'react-dom';
|
|||
import QueryToolComponent from './components/QueryToolComponent';
|
||||
import ModalProvider from '../../../../static/js/helpers/ModalProvider';
|
||||
import Theme from '../../../../static/js/Theme';
|
||||
import { showRenamePanel } from '../../../../static/js/Dialogs';
|
||||
|
||||
const wcDocker = window.wcDocker;
|
||||
|
||||
|
@ -242,38 +242,19 @@ export default class SQLEditor {
|
|||
}
|
||||
|
||||
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) {
|
||||
panel_titles = panelTitleFunc.getPanelTitle(pgBrowser, selected_item, value);
|
||||
} else {
|
||||
panel_titles = showViewData.generateViewDataTitle(pgBrowser, selected_item, value);
|
||||
}
|
||||
// Set title to the selected tab.
|
||||
if (is_dirty_editor) {
|
||||
panel_titles = panel_titles + ' *';
|
||||
}
|
||||
panelTitleFunc.setQueryToolDockerTitle(queryToolPanel, is_query_tool, _.unescape(panel_titles), is_file);
|
||||
}
|
||||
},
|
||||
// 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')});
|
||||
let preferences = pgBrowser.get_preferences_for_module('browser');
|
||||
let temp_title = panelData.$titleText[0].textContent;
|
||||
let is_dirty_editor = queryToolPanel.is_dirty_editor ? queryToolPanel.is_dirty_editor : false;
|
||||
let title = queryToolPanel.is_dirty_editor ? panelData.$titleText[0].textContent.replace(/.$/, '') : temp_title;
|
||||
|
||||
let qtdata = {
|
||||
is_query_tool: is_query_tool,
|
||||
is_file: panelData.$titleText[0].innerHTML.includes('File - '),
|
||||
is_dirty_editor: is_dirty_editor
|
||||
};
|
||||
|
||||
showRenamePanel(title, preferences, queryToolPanel, 'querytool', qtdata);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
|
|||
2) Properties Tab (BackFrom)
|
||||
3) Dependents Tab (BackGrid)
|
||||
4) SQL Tab (Code Mirror)
|
||||
5) Query Tool (SlickGrid)
|
||||
5) Query Tool (Result Grid)
|
||||
"""
|
||||
|
||||
scenarios = [
|
||||
|
@ -189,7 +189,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
|
|||
|
||||
def _check_xss_in_query_tool(self):
|
||||
print(
|
||||
"\n\tChecking the SlickGrid cell for XSS vulnerabilities",
|
||||
"\n\tChecking the Result Grid cell for XSS vulnerabilities",
|
||||
file=sys.stderr, end=""
|
||||
)
|
||||
self.page.fill_codemirror_area_with(
|
||||
|
@ -205,7 +205,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
|
|||
self._check_escaped_characters(
|
||||
source_code,
|
||||
'<img src="x" onerror="console.log(1)">',
|
||||
"Query tool (SlickGrid)"
|
||||
"Query tool (Result Grid)"
|
||||
)
|
||||
|
||||
def _check_xss_in_query_tool_history(self):
|
||||
|
@ -255,7 +255,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
|
|||
|
||||
def _check_xss_view_data(self):
|
||||
print(
|
||||
"\n\tChecking the SlickGrid cell for XSS vulnerabilities",
|
||||
"\n\tChecking the Result Grid cell for XSS vulnerabilities",
|
||||
file=sys.stderr, end=""
|
||||
)
|
||||
|
||||
|
@ -268,7 +268,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
|
|||
self._check_escaped_characters(
|
||||
source_code,
|
||||
self.check_xss_chars_set2,
|
||||
"View Data (SlickGrid)"
|
||||
"View Data (Result Grid)"
|
||||
)
|
||||
|
||||
def _check_xss_in_explain_module(self):
|
||||
|
|
|
@ -15,9 +15,10 @@ beforeAll(function () {
|
|||
jasmine.getEnv().allowRespy(true);
|
||||
|
||||
window.addEventListener('error', e => {
|
||||
console.log(e.message);
|
||||
if(e.message === 'ResizeObserver loop completed with undelivered notifications.' ||
|
||||
e.message === 'ResizeObserver loop limit exceeded') {
|
||||
e.stopImmediatePropagation();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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('');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -56,7 +56,7 @@ const providePlugin = new webpack.ProvidePlugin({
|
|||
// Reference: https://webpack.js.org/plugins/source-map-dev-tool-plugin/#components/sidebar/sidebar.jsx
|
||||
const sourceMapDevToolPlugin = new webpack.SourceMapDevToolPlugin({
|
||||
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,
|
||||
});
|
||||
|
||||
|
@ -376,9 +376,8 @@ module.exports = [{
|
|||
entry: {
|
||||
'app.bundle': sourceDir + '/bundle/app.js',
|
||||
codemirror: sourceDir + '/bundle/codemirror.js',
|
||||
slickgrid: sourceDir + '/bundle/slickgrid.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',
|
||||
psql_tool: './pgadmin/tools/psql/static/js/index.js',
|
||||
debugger: './pgadmin/tools/debugger/static/js/index.js',
|
||||
|
|
|
@ -72,16 +72,6 @@ var webpackShimConfig = {
|
|||
'deps': ['jquery'], 'exports': 'jQuery.fn.drag',
|
||||
},
|
||||
'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': {
|
||||
'exports': 'alertify',
|
||||
},
|
||||
|
@ -132,8 +122,6 @@ var webpackShimConfig = {
|
|||
'wcdocker': path.join(__dirname, './node_modules/webcabin-docker/Build/wcDocker.min'),
|
||||
'alertify': path.join(__dirname, './node_modules/alertifyjs/build/alertify'),
|
||||
'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'),
|
||||
'jquery.contextmenu': path.join(__dirname, './node_modules/jquery-contextmenu/dist/jquery.contextMenu'),
|
||||
'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.maintenance': path.join(__dirname, './pgadmin/tools/maintenance/static/js/maintenance'),
|
||||
'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_ui': path.join(__dirname, './pgadmin/tools/schema_diff/static/js/schema_diff_ui'),
|
||||
'pgadmin.tools.schema_diff': path.join(__dirname, './pgadmin/tools/schema_diff/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.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.user_management': path.join(__dirname, './pgadmin/tools/user_management/static/js/user_management'),
|
||||
'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: [
|
||||
'pgadmin.user_management.current_user',
|
||||
|
@ -306,8 +290,7 @@ var webpackShimConfig = {
|
|||
'pgadmin.browser.error', 'pgadmin.browser.server.privilege',
|
||||
'pgadmin.browser.server.variable', 'pgadmin.browser.collection', 'pgadmin.browser.node.ui',
|
||||
'pgadmin.browser.datamodel', 'pgadmin.browser.menu', 'pgadmin.browser.panel', 'pgadmin',
|
||||
'pgadmin.browser.frame', 'slick.pgadmin.editors', 'slick.pgadmin.formatters',
|
||||
'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser',
|
||||
'pgadmin.browser.frame', 'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser',
|
||||
'pgadmin.browser.node',
|
||||
'pgadmin.alertifyjs', 'pgadmin.settings', 'pgadmin.preferences', 'pgadmin.sqlfoldcode',
|
||||
],
|
||||
|
|
|
@ -13,7 +13,6 @@ const webpack = require('webpack');
|
|||
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
|
||||
|
||||
const sourcesDir = path.resolve(__dirname, 'pgadmin/static');
|
||||
const nodeModulesDir = path.resolve(__dirname, 'node_modules');
|
||||
const regressionDir = path.resolve(__dirname, 'regression');
|
||||
|
||||
module.exports = {
|
||||
|
@ -99,62 +98,6 @@ module.exports = {
|
|||
filename: 'img/[name].[ext]',
|
||||
},
|
||||
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$/,
|
||||
|
@ -163,7 +106,7 @@ module.exports = {
|
|||
options: { esModules: true },
|
||||
},
|
||||
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'),
|
||||
'wcdocker': path.join(__dirname, './node_modules/webcabin-docker/Build/wcDocker'),
|
||||
'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'),
|
||||
'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'),
|
||||
|
@ -192,9 +133,6 @@ module.exports = {
|
|||
'pgadmin.browser.messages': regressionDir + '/javascript/fake_messages',
|
||||
'pgadmin.server.supported_servers': regressionDir + '/javascript/fake_supported_servers',
|
||||
'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'),
|
||||
'jsoneditor.min': path.join(__dirname, './node_modules/jsoneditor/dist/jsoneditor.min'),
|
||||
'browser': path.resolve(__dirname, 'pgadmin/browser/static/js'),
|
||||
|
|
|
@ -7121,14 +7121,14 @@ jquery-contextmenu@^2.6.4, jquery-contextmenu@^2.9.2:
|
|||
dependencies:
|
||||
jquery "^3.5.0"
|
||||
|
||||
jquery-ui@>=1.8.0, jquery-ui@^1.13.2:
|
||||
jquery-ui@^1.13.2:
|
||||
version "1.13.2"
|
||||
resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.13.2.tgz#de03580ae6604773602f8d786ad1abfb75232034"
|
||||
integrity sha512-wBZPnqWs5GaYJmo1Jj0k/mrSkzdQzKDwhXNtHKcBdAcKVxMM3KNYFq+iJ2i1rwiG53Z8M4mTn3Qxrm17uH1D4Q==
|
||||
dependencies:
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470"
|
||||
integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==
|
||||
|
@ -10365,13 +10365,6 @@ slice-ansi@^4.0.0:
|
|||
astral-regex "^2.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:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
|
||||
|
|