mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
committed by
Akshay Joshi
parent
ad59380676
commit
e1942d8c9e
@@ -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')
|
||||
};
|
165
web/pgadmin/tools/schema_diff/static/js/SchemaDiffModule.js
Normal file
165
web/pgadmin/tools/schema_diff/static/js/SchemaDiffModule.js
Normal file
@@ -0,0 +1,165 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import url_for from 'sources/url_for';
|
||||
import pgWindow from 'sources/window';
|
||||
import { registerDetachEvent } from 'sources/utils';
|
||||
|
||||
import { _set_dynamic_tab } from '../../../sqleditor/static/js/show_query_tool';
|
||||
import getApiInstance from '../../../../static/js/api_instance';
|
||||
import Theme from '../../../../static/js/Theme';
|
||||
import ModalProvider from '../../../../static/js/helpers/ModalProvider';
|
||||
import Notify from '../../../../static/js/helpers/Notifier';
|
||||
import SchemaDiffComponent from './components/SchemaDiffComponent';
|
||||
import { showRenamePanel } from '../../../../static/js/Dialogs';
|
||||
|
||||
|
||||
export default class SchemaDiff {
|
||||
static instance;
|
||||
|
||||
static getInstance(...args) {
|
||||
if (!SchemaDiff.instance) {
|
||||
SchemaDiff.instance = new SchemaDiff(...args);
|
||||
}
|
||||
return SchemaDiff.instance;
|
||||
}
|
||||
|
||||
constructor(pgAdmin, pgBrowser) {
|
||||
this.pgAdmin = pgAdmin;
|
||||
this.pgBrowser = pgBrowser;
|
||||
this.wcDocker = window.wcDocker;
|
||||
this.api = getApiInstance();
|
||||
}
|
||||
|
||||
init() {
|
||||
let self = this;
|
||||
if (self.initialized)
|
||||
return;
|
||||
self.initialized = true;
|
||||
// Define the nodes on which the menus to be appear
|
||||
self.pgBrowser.add_menus([{
|
||||
name: 'schema_diff',
|
||||
module: self,
|
||||
applies: ['tools'],
|
||||
callback: 'showSchemaDiffTool',
|
||||
priority: 1,
|
||||
label: gettext('Schema Diff'),
|
||||
enable: true,
|
||||
below: true,
|
||||
}]);
|
||||
|
||||
/* Create and load the new frame required for schema diff panel */
|
||||
self.frame = new self.pgBrowser.Frame({
|
||||
name: 'frm_schemadiff',
|
||||
title: gettext('Schema Diff'),
|
||||
showTitle: true,
|
||||
isCloseable: true,
|
||||
isRenamable: true,
|
||||
isPrivate: true,
|
||||
icon: 'pg-font-icon icon-compare',
|
||||
url: 'about:blank',
|
||||
});
|
||||
|
||||
/* Cache may take time to load for the first time. Keep trying till available */
|
||||
let cacheIntervalId = setInterval(function () {
|
||||
if (self.pgBrowser.preference_version() > 0) {
|
||||
self.preferences = self.pgBrowser.get_preferences_for_module('schema_diff');
|
||||
clearInterval(cacheIntervalId);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
self.pgBrowser.onPreferencesChange('schema_diff', function () {
|
||||
self.preferences = self.pgBrowser.get_preferences_for_module('schema_diff');
|
||||
});
|
||||
|
||||
self.frame.load(self.pgBrowser.docker);
|
||||
}
|
||||
|
||||
showSchemaDiffTool() {
|
||||
let self = this;
|
||||
|
||||
self.api({
|
||||
url: url_for('schema_diff.initialize', null),
|
||||
method: 'GET',
|
||||
})
|
||||
.then(function (res) {
|
||||
self.trans_id = res.data.data.schemaDiffTransId;
|
||||
res.data.data.panel_title = gettext('Schema Diff');
|
||||
self.launchSchemaDiff(res.data.data);
|
||||
})
|
||||
.catch(function (error) {
|
||||
Notify.error(gettext(`Error in schema diff initialize ${error.response.data}`));
|
||||
});
|
||||
}
|
||||
|
||||
launchSchemaDiff(data) {
|
||||
let self = this;
|
||||
let panelTitle = data.panel_title,
|
||||
trans_id = data.schemaDiffTransId,
|
||||
panelTooltip = '';
|
||||
|
||||
let url_params = {
|
||||
'trans_id': trans_id,
|
||||
'editor_title': panelTitle,
|
||||
},
|
||||
baseUrl = url_for('schema_diff.panel', url_params);
|
||||
|
||||
let browserPreferences = this.pgBrowser.get_preferences_for_module('browser');
|
||||
let openInNewTab = browserPreferences.new_browser_tab_open;
|
||||
if (openInNewTab && openInNewTab.includes('schema_diff')) {
|
||||
window.open(baseUrl, '_blank');
|
||||
// Send the signal to runtime, so that proper zoom level will be set.
|
||||
setTimeout(function () {
|
||||
this.pgBrowser.send_signal_to_runtime('Runtime new window opened');
|
||||
}, 500);
|
||||
} else {
|
||||
this.pgBrowser.Events.once(
|
||||
'pgadmin-browser:frame:urlloaded:frm_schemadiff',
|
||||
function (frame) {
|
||||
frame.openURL(baseUrl);
|
||||
});
|
||||
let propertiesPanel = this.pgBrowser.docker.findPanels('properties'),
|
||||
schemaDiffPanel = this.pgBrowser.docker.addPanel('frm_schemadiff', this.wcDocker.DOCK.STACKED, propertiesPanel[0]);
|
||||
|
||||
registerDetachEvent(schemaDiffPanel);
|
||||
|
||||
// Panel Rename event
|
||||
schemaDiffPanel.on(self.wcDocker.EVENT.RENAME, function (panel_data) {
|
||||
self.panel_rename_event(panel_data, schemaDiffPanel, browserPreferences);
|
||||
});
|
||||
|
||||
_set_dynamic_tab(this.pgBrowser, browserPreferences['dynamic_tabs']);
|
||||
// Set panel title and icon
|
||||
schemaDiffPanel.title('<span title="' + panelTooltip + '">' + panelTitle + '</span>');
|
||||
schemaDiffPanel.icon('pg-font-icon icon-compare');
|
||||
schemaDiffPanel.focus();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
panel_rename_event(panel_data, panel) {
|
||||
showRenamePanel(panel_data.$titleText[0].textContent, null, panel);
|
||||
}
|
||||
|
||||
load(container, trans_id) {
|
||||
ReactDOM.render(
|
||||
<Theme>
|
||||
<ModalProvider>
|
||||
<SchemaDiffComponent params={{ transId: trans_id, pgAdmin: pgWindow.pgAdmin }}></SchemaDiffComponent>
|
||||
</ModalProvider>
|
||||
</Theme>,
|
||||
container
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -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,
|
||||
};
|
140
web/pgadmin/tools/schema_diff/static/js/components/Results.jsx
Normal file
140
web/pgadmin/tools/schema_diff/static/js/components/Results.jsx
Normal file
@@ -0,0 +1,140 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import gettext from 'sources/gettext';
|
||||
|
||||
import React, { useContext, useState, useEffect } from 'react';
|
||||
|
||||
import { Box } from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
import { InputSQL } from '../../../../../static/js/components/FormComponents';
|
||||
import { SchemaDiffEventsContext } from './SchemaDiffComponent';
|
||||
import { SCHEMA_DIFF_EVENT } from '../SchemaDiffConstants';
|
||||
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
},
|
||||
table: {
|
||||
minWidth: 650,
|
||||
},
|
||||
summaryContainer: {
|
||||
flexGrow: 1,
|
||||
minHeight: 0,
|
||||
overflow: 'auto',
|
||||
},
|
||||
panelTitle: {
|
||||
borderBottom: '1px solid ' + theme.otherVars.borderColor,
|
||||
padding: '0.5rem',
|
||||
|
||||
},
|
||||
editorStyle: {
|
||||
height: '100%'
|
||||
},
|
||||
editor: {
|
||||
height: '100%',
|
||||
padding: '0.5rem 0.2rem 2rem 0.5rem',
|
||||
},
|
||||
editorLabel: {
|
||||
padding: '0.3rem 0.6rem 0 0.6rem',
|
||||
},
|
||||
header: {
|
||||
padding: '0.5rem',
|
||||
borderBottom: '1px solid ' + theme.otherVars.borderColor,
|
||||
},
|
||||
sqlContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
padding: '0rem 0rem 0.5rem',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden'
|
||||
},
|
||||
sqldata: {
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexDirection: 'column',
|
||||
padding: '0.2rem 0.5rem',
|
||||
width: '33.33%',
|
||||
},
|
||||
label: {
|
||||
flexGrow: 1,
|
||||
}
|
||||
}));
|
||||
|
||||
export function Results() {
|
||||
const classes = useStyles();
|
||||
const [sourceSQL, setSourceSQL] = useState(null);
|
||||
const [targetSQL, setTargetSQL] = useState(null);
|
||||
const [sqlDiff, setSqlDiff] = useState(null);
|
||||
|
||||
const eventBus = useContext(SchemaDiffEventsContext);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.registerListener(
|
||||
SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, triggerUpdateResult);
|
||||
|
||||
}, []);
|
||||
|
||||
const triggerUpdateResult = (resultData) => {
|
||||
setSourceSQL(resultData.sourceSQL);
|
||||
setTargetSQL(resultData.targetSQL);
|
||||
setSqlDiff(resultData.SQLdiff);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box className={classes.header}>
|
||||
<span>{gettext('DDL Comparision')}</span>
|
||||
</Box>
|
||||
<Box className={classes.sqlContainer}>
|
||||
|
||||
<Box className={classes.sqldata}>
|
||||
<Box className={classes.label}>{gettext('Source')}</Box>
|
||||
<InputSQL
|
||||
onLable={true}
|
||||
value={sourceSQL}
|
||||
options={{
|
||||
readOnly: true,
|
||||
}}
|
||||
readonly={true}
|
||||
/>
|
||||
</Box>
|
||||
<Box className={classes.sqldata}>
|
||||
<Box className={classes.label}>{gettext('Target')}</Box>
|
||||
<InputSQL
|
||||
onLable={true}
|
||||
value={targetSQL}
|
||||
options={{
|
||||
readOnly: true,
|
||||
}}
|
||||
readonly={true}
|
||||
/>
|
||||
</Box>
|
||||
<Box className={classes.sqldata}>
|
||||
<Box className={classes.label}>{gettext('Difference')}</Box>
|
||||
<InputSQL
|
||||
onLable={true}
|
||||
value={sqlDiff}
|
||||
options={{
|
||||
readOnly: true,
|
||||
}}
|
||||
readonly={true}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Results.propTypes = {
|
||||
|
||||
};
|
@@ -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
|
||||
};
|
22
web/pgadmin/tools/schema_diff/static/js/index.js
Normal file
22
web/pgadmin/tools/schema_diff/static/js/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import pgBrowser from 'top/browser/static/js/browser';
|
||||
import SchemaDiff from './SchemaDiffModule';
|
||||
|
||||
if (!pgAdmin.Tools) {
|
||||
pgAdmin.Tools = {};
|
||||
}
|
||||
|
||||
pgAdmin.Tools.SchemaDiff = SchemaDiff.getInstance(pgAdmin, pgBrowser);
|
||||
|
||||
module.exports = {
|
||||
SchemaDiff: SchemaDiff,
|
||||
};
|
@@ -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 %}
|
||||
|
Reference in New Issue
Block a user