db diff report

This commit is contained in:
Jan Prochazka 2021-11-06 12:25:34 +01:00
parent 336edfc93f
commit 17286e0c3e
9 changed files with 136 additions and 9 deletions

View File

@ -29,6 +29,8 @@
"dbgate-query-splitter": "^4.1.1",
"dbgate-sqltree": "^4.1.1",
"dbgate-tools": "^4.1.1",
"diff": "^5.0.0",
"diff2html": "^3.4.13",
"eslint": "^6.8.0",
"express": "^4.17.1",
"express-basic-auth": "^1.2.0",

View File

@ -3,16 +3,27 @@ const connections = require('./connections');
const archive = require('./archive');
const socket = require('../utility/socket');
const { fork } = require('child_process');
const { DatabaseAnalyser, getAlterDatabaseScript, generateDbPairingId, matchPairedObjects } = require('dbgate-tools');
const {
DatabaseAnalyser,
computeDbDiffRows,
getCreateObjectScript,
getAlterDatabaseScript,
generateDbPairingId,
matchPairedObjects,
extendDatabaseInfo,
} = require('dbgate-tools');
const { html, parse } = require('diff2html');
const { handleProcessCommunication } = require('../utility/processComm');
const config = require('./config');
const fs = require('fs-extra');
const exportDbModel = require('../utility/exportDbModel');
const { archivedir, resolveArchiveFolder } = require('../utility/directories');
const { archivedir, resolveArchiveFolder, uploadsdir } = require('../utility/directories');
const path = require('path');
const importDbModel = require('../utility/importDbModel');
const requireEngineDriver = require('../utility/requireEngineDriver');
const generateDeploySql = require('../shell/generateDeploySql');
const { createTwoFilesPatch } = require('diff');
const diff2htmlPage = require('../utility/diff2htmlPage');
module.exports = {
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
@ -285,4 +296,55 @@ module.exports = {
// const res = await this.sendRequest(opened, { msgtype: 'queryData', sql });
// return res;
// },
async getUnifiedDiff({ sourceConid, sourceDatabase, targetConid, targetDatabase }) {
const dbDiffOptions = {
// schemaMode: 'ignore',
};
const sourceDb = generateDbPairingId(
extendDatabaseInfo(await this.structure({ conid: sourceConid, database: sourceDatabase }))
);
const targetDb = generateDbPairingId(
extendDatabaseInfo(await this.structure({ conid: targetConid, database: targetDatabase }))
);
// const sourceConnection = await connections.get({conid:sourceConid})
const connection = await connections.get({ conid: targetConid });
const driver = requireEngineDriver(connection);
const targetDbPaired = matchPairedObjects(sourceDb, targetDb, dbDiffOptions);
const diffRows = computeDbDiffRows(sourceDb, targetDbPaired, dbDiffOptions, driver);
// console.log('sourceDb', sourceDb);
// console.log('targetDb', targetDb);
// console.log('sourceConid, sourceDatabase', sourceConid, sourceDatabase);
let res = '';
for (const row of diffRows) {
// console.log('PAIR', row.source && row.source.pureName, row.target && row.target.pureName);
const unifiedDiff = createTwoFilesPatch(
(row.source && row.source.pureName) || '',
(row.target && row.target.pureName) || '',
getCreateObjectScript(row.source, driver),
getCreateObjectScript(row.target, driver),
'',
''
);
res += unifiedDiff;
}
return res;
},
generateDbDiffReport_meta: 'post',
async generateDbDiffReport({ sourceConid, sourceDatabase, targetConid, targetDatabase }) {
const unifiedDiff = await this.getUnifiedDiff({ sourceConid, sourceDatabase, targetConid, targetDatabase });
const diffJson = parse(unifiedDiff);
// $: diffHtml = html(diffJson, { outputFormat: 'side-by-side', drawFileList: false });
const diffHtml = html(diffJson, {});
const fileName = `${uuidv1()}.html`;
await fs.writeFile(path.join(uploadsdir(), fileName), diff2htmlPage(diffHtml));
return fileName;
},
};

View File

@ -25,4 +25,12 @@ module.exports = {
});
});
},
get_meta: {
method: 'get',
raw: true,
},
get(req, res) {
res.sendFile(path.join(uploadsdir(), req.query.file));
},
};

View File

@ -0,0 +1,8 @@
const diff2htmlCss =
'.d2h-d-none{display:none}.d2h-wrapper{text-align:left}.d2h-file-header{background-color:#f7f7f7;border-bottom:1px solid #d8d8d8;font-family:Source Sans Pro,Helvetica Neue,Helvetica,Arial,sans-serif;height:35px;padding:5px 10px}.d2h-file-header,.d2h-file-stats{display:-webkit-box;display:-ms-flexbox;display:flex}.d2h-file-stats{font-size:14px;margin-left:auto}.d2h-lines-added{border:1px solid #b4e2b4;border-radius:5px 0 0 5px;color:#399839;padding:2px;text-align:right;vertical-align:middle}.d2h-lines-deleted{border:1px solid #e9aeae;border-radius:0 5px 5px 0;color:#c33;margin-left:1px;padding:2px;text-align:left;vertical-align:middle}.d2h-file-name-wrapper{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;font-size:15px;width:100%}.d2h-file-name{overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.d2h-file-wrapper{border:1px solid #ddd;border-radius:3px;margin-bottom:1em}.d2h-file-collapse{-webkit-box-pack:end;-ms-flex-pack:end;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid #ddd;border-radius:3px;cursor:pointer;display:none;font-size:12px;justify-content:flex-end;padding:4px 8px}.d2h-file-collapse.d2h-selected{background-color:#c8e1ff}.d2h-file-collapse-input{margin:0 4px 0 0}.d2h-diff-table{border-collapse:collapse;font-family:Menlo,Consolas,monospace;font-size:13px;width:100%}.d2h-files-diff{width:100%}.d2h-file-diff{overflow-y:hidden}.d2h-file-side-diff{display:inline-block;margin-bottom:-8px;margin-right:-4px;overflow-x:scroll;overflow-y:hidden;width:50%}.d2h-code-line{padding:0 8em}.d2h-code-line,.d2h-code-side-line{display:inline-block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;white-space:nowrap;width:100%}.d2h-code-side-line{padding:0 4.5em}.d2h-code-line-ctn{word-wrap:normal;background:none;display:inline-block;padding:0;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;vertical-align:middle;white-space:pre;width:100%}.d2h-code-line del,.d2h-code-side-line del{background-color:#ffb6ba}.d2h-code-line del,.d2h-code-line ins,.d2h-code-side-line del,.d2h-code-side-line ins{border-radius:.2em;display:inline-block;margin-top:-1px;text-decoration:none;vertical-align:middle}.d2h-code-line ins,.d2h-code-side-line ins{background-color:#97f295;text-align:left}.d2h-code-line-prefix{word-wrap:normal;background:none;display:inline;padding:0;white-space:pre}.line-num1{float:left}.line-num1,.line-num2{-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;padding:0 .5em;text-overflow:ellipsis;width:3.5em}.line-num2{float:right}.d2h-code-linenumber{background-color:#fff;border:solid #eee;border-width:0 1px;-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.3);cursor:pointer;display:inline-block;position:absolute;text-align:right;width:7.5em}.d2h-code-linenumber:after{content:"\200b"}.d2h-code-side-linenumber{background-color:#fff;border:solid #eee;border-width:0 1px;-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.3);cursor:pointer;display:inline-block;overflow:hidden;padding:0 .5em;position:absolute;text-align:right;text-overflow:ellipsis;width:4em}.d2h-code-side-linenumber:after{content:"\200b"}.d2h-code-side-emptyplaceholder,.d2h-emptyplaceholder{background-color:#f1f1f1;border-color:#e1e1e1}.d2h-code-line-prefix,.d2h-code-linenumber,.d2h-code-side-linenumber,.d2h-emptyplaceholder{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.d2h-code-linenumber,.d2h-code-side-linenumber{direction:rtl}.d2h-del{background-color:#fee8e9;border-color:#e9aeae}.d2h-ins{background-color:#dfd;border-color:#b4e2b4}.d2h-info{background-color:#f8fafd;border-color:#d5e4f2;color:rgba(0,0,0,.3)}.d2h-file-diff .d2h-del.d2h-change{background-color:#fdf2d0}.d2h-file-diff .d2h-ins.d2h-change{background-color:#ded}.d2h-file-list-wrapper{margin-bottom:10px}.d2h-file-list-wrapper a{color:#3572b0;text-decoration:none}.d2h-file-list-wrapper a:visited{color:#3572b0}.d2h-file-list-header{text-align:left}.d2h-file-list-title{font-weight:700}.d2h-file-list-line{display:-webkit-box;display:-ms-flexbox;display:flex;text-align:left}.d2h-file-list{display:block;list-style:none;margin:0;padding:0}.d2h-file-list>li{border-bottom:1px solid #ddd;margin:0;padding:5px 10px}.d2h-file-list>li:last-child{border-bottom:none}.d2h-file-switch{cursor:pointer;display:none;font-size:10px}.d2h-icon{fill:currentColor;margin-right:10px;vertical-align:middle}.d2h-deleted{color:#c33}.d2h-added{color:#399839}.d2h-changed{color:#d0b44c}.d2h-moved{color:#3572b0}.d2h-tag{background-color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;font-size:10px;margin-left:5px;padding:0 2px}.d2h-deleted-tag{border:1px solid #c33}.d2h-added-tag{border:1px solid #399839}.d2h-changed-tag{border:1px solid #d0b44c}.d2h-moved-tag{border:1px solid #3572b0}';
function diff2htmlPage(content) {
return `<html><head><style>${diff2htmlCss}</style><body>${content}</body></html>`;
}
module.exports = diff2htmlPage;

View File

@ -1,4 +1,4 @@
import { DbDiffOptions, testEqualColumns, testEqualTables } from 'dbgate-tools';
import { DbDiffOptions, testEqualColumns, testEqualTables } from './diffTools';
import { DatabaseInfo, EngineDriver, TableInfo } from 'dbgate-types';
export function computeDiffRowsCore(sourceList, targetList, testEqual) {
@ -69,7 +69,7 @@ export function computeTableDiffColumns(
}));
}
export function getCreateTableScript(table: TableInfo, driver: EngineDriver) {
export function getCreateObjectScript(table: TableInfo, driver: EngineDriver) {
if (!table || !driver) return '';
const dmp = driver.createDumper();
dmp.createTable(table);

View File

@ -15,3 +15,4 @@ export * from './diffTools';
export * from './schemaEditorTools';
export * from './yamlModelConv';
export * from './stringTools';
export * from './computeDiffRows';

View File

@ -22,7 +22,7 @@
{#if isNative}
<select
value={value || defaultValue}
value={options.find(x => x.value == value) ? value : defaultValue}
{...$$restProps}
on:change={e => {
dispatch('change', e.target['value']);

View File

@ -74,6 +74,7 @@
'icon menu': 'mdi mdi-menu',
'icon add-column': 'mdi mdi-table-column-plus-after',
'icon add-key': 'mdi mdi-key-plus',
'icon report': 'mdi mdi-file-chart',
'img ok': 'mdi mdi-check-circle color-icon-green',
'img ok-inv': 'mdi mdi-check-circle color-icon-inv-green',

View File

@ -1,12 +1,35 @@
<script lang="ts" context="module">
export const matchingProps = [];
const getCurrentEditor = () => getActiveComponent('CompareModelTab');
registerCommand({
id: 'compareModels.reportDiff',
category: 'Compare models',
toolbarName: 'Report',
name: 'Report diff',
icon: 'icon report',
toolbar: true,
isRelatedToTab: true,
onClick: () => getCurrentEditor().showReport(),
testEnabled: () => getCurrentEditor() != null,
});
</script>
<script lang="ts">
import { findEngineDriver, generateDbPairingId, getAlterTableScript, matchPairedObjects } from 'dbgate-tools';
import {
findEngineDriver,
generateDbPairingId,
getAlterTableScript,
matchPairedObjects,
computeDbDiffRows,
computeTableDiffColumns,
getCreateObjectScript,
} from 'dbgate-tools';
import _ from 'lodash';
import { derived, writable } from 'svelte/store';
import registerCommand from '../commands/registerCommand';
import DiffView from '../elements/DiffView.svelte';
import ScrollableTableControl from '../elements/ScrollableTableControl.svelte';
import TabControl from '../elements/TabControl.svelte';
@ -20,13 +43,17 @@
import SqlEditor from '../query/SqlEditor.svelte';
import useEditorData from '../query/useEditorData';
import { extensions } from '../stores';
import { computeDbDiffRows, computeTableDiffColumns, getCreateTableScript } from '../utility/computeDiffRows';
import axiosInstance from '../utility/axiosInstance';
import createActivator, { getActiveComponent } from '../utility/createActivator';
import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
import resolveApi from '../utility/resolveApi';
export let tabid;
let pairIndex = 0;
export const activator = createActivator('CompareModelTab', true);
// let values = writable({
// sourceConid: null,
// sourceDatabase: null,
@ -64,6 +91,24 @@
driver
).sql;
export async function showReport() {
const resp = await axiosInstance.post('database-connections/generate-db-diff-report', {
sourceConid: $values?.sourceConid,
sourceDatabase: $values?.sourceDatabase,
targetConid: $values?.targetConid,
targetDatabase: $values?.targetDatabase,
});
window.open(`${resolveApi()}/uploads/get?file=${resp.data}`, '_blank');
// window.open(
// `${resolveApi()}/database-connections/get-diff-html?sourceConid=` +
// `${$values?.sourceConid}&sourceDatabase=${$values?.sourceDatabase}&` +
// `targetConid=${$values?.targetConid}&targetDatabase=${$values?.targetDatabase}`,
// '_blank'
// );
}
const { editorState, editorValue, setEditorData } = useEditorData({
tabid,
// onInitialData: value => {
@ -162,8 +207,8 @@
<DiffView
leftTitle={diffRows[pairIndex]?.source?.pureName}
rightTitle={diffRows[pairIndex]?.source?.pureName}
leftText={getCreateTableScript(diffRows[pairIndex]?.source, driver)}
rightText={getCreateTableScript(diffRows[pairIndex]?.target, driver)}
leftText={getCreateObjectScript(diffRows[pairIndex]?.source, driver)}
rightText={getCreateObjectScript(diffRows[pairIndex]?.target, driver)}
/>
</svelte:fragment>