From f9e167fc7bc877c5331f25f91be34a5070c83f95 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 1 Oct 2022 14:43:25 +0200 Subject: [PATCH 01/24] perspective data pattern --- packages/datalib/src/PerspectiveCache.ts | 3 + packages/datalib/src/PerspectiveDataLoader.ts | 79 +++++++++++++- .../datalib/src/PerspectiveDataPattern.ts | 65 +++++++++++ .../datalib/src/PerspectiveDataProvider.ts | 8 +- packages/datalib/src/PerspectiveTreeNode.ts | 103 +++++++++++++++++- packages/datalib/src/index.ts | 1 + .../src/appobj/DatabaseObjectAppObject.svelte | 6 + .../web/src/designer/DesignerTable.svelte | 5 + .../perspectives/PerspectiveDesigner.svelte | 32 +++++- .../src/perspectives/PerspectiveView.svelte | 23 +++- .../src/utility/usePerspectiveDataPatterns.ts | 77 +++++++++++++ 11 files changed, 385 insertions(+), 17 deletions(-) create mode 100644 packages/datalib/src/PerspectiveDataPattern.ts create mode 100644 packages/web/src/utility/usePerspectiveDataPatterns.ts diff --git a/packages/datalib/src/PerspectiveCache.ts b/packages/datalib/src/PerspectiveCache.ts index d26a6697a..a2d8846f6 100644 --- a/packages/datalib/src/PerspectiveCache.ts +++ b/packages/datalib/src/PerspectiveCache.ts @@ -5,6 +5,7 @@ import _zip from 'lodash/zip'; import _difference from 'lodash/difference'; import debug from 'debug'; import stableStringify from 'json-stable-stringify'; +import { PerspectiveDataPattern } from './PerspectiveDataPattern'; const dbg = debug('dbgate:PerspectiveCache'); @@ -86,6 +87,7 @@ export class PerspectiveCache { constructor() {} tables: { [tableKey: string]: PerspectiveCacheTable } = {}; + dataPatterns: PerspectiveDataPattern[] = []; getTableCache(props: PerspectiveDataLoadProps) { const tableKey = stableStringify( @@ -113,5 +115,6 @@ export class PerspectiveCache { clear() { this.tables = {}; + this.dataPatterns = []; } } diff --git a/packages/datalib/src/PerspectiveDataLoader.ts b/packages/datalib/src/PerspectiveDataLoader.ts index 716b89bd0..580549d56 100644 --- a/packages/datalib/src/PerspectiveDataLoader.ts +++ b/packages/datalib/src/PerspectiveDataLoader.ts @@ -93,8 +93,8 @@ export class PerspectiveDataLoader { })); } - async loadData(props: PerspectiveDataLoadProps) { - const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props; + async loadDataSqlDb(props: PerspectiveDataLoadProps) { + const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition, engineType } = props; if (dataColumns?.length == 0) { return []; @@ -143,7 +143,53 @@ export class PerspectiveDataLoader { return response.rows; } - async loadRowCount(props: PerspectiveDataLoadProps) { + getDocDbLoadOptions(props: PerspectiveDataLoadProps) { + const { pureName } = props; + return { + pureName, + skip: props.range?.offset, + limit: props.range?.limit, + }; + } + + async loadDataDocDb(props: PerspectiveDataLoadProps) { + const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition, engineType } = props; + + if (dataColumns?.length == 0) { + return []; + } + + if (dbg?.enabled) { + dbg( + `LOAD DATA, collection=${props.pureName}, columns=${props.dataColumns?.join(',')}, range=${ + props.range?.offset + },${props.range?.limit}` + ); + } + + const options = this.getDocDbLoadOptions(props); + + const response = await this.apiCall('database-connections/collection-data', { + conid: props.databaseConfig.conid, + database: props.databaseConfig.database, + options, + }); + + if (response.errorMessage) return response; + return response.rows; + } + + async loadData(props: PerspectiveDataLoadProps) { + const { engineType } = props; + switch (engineType) { + case 'sqldb': + return this.loadDataSqlDb(props); + case 'docdb': + return this.loadDataDocDb(props); + } + } + + async loadRowCountSqlDb(props: PerspectiveDataLoadProps) { const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props; const select: Select = { @@ -170,4 +216,31 @@ export class PerspectiveDataLoader { if (response.errorMessage) return response; return response.rows[0]; } + + async loadRowCountDocDb(props: PerspectiveDataLoadProps) { + const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props; + + const options = { + ...this.getDocDbLoadOptions(props), + countDocuments: true, + }; + + const response = await this.apiCall('database-connections/collection-data', { + conid: props.databaseConfig.conid, + database: props.databaseConfig.database, + options, + }); + + return response; + } + + async loadRowCount(props: PerspectiveDataLoadProps) { + const { engineType } = props; + switch (engineType) { + case 'sqldb': + return this.loadRowCountSqlDb(props); + case 'docdb': + return this.loadRowCountDocDb(props); + } + } } diff --git a/packages/datalib/src/PerspectiveDataPattern.ts b/packages/datalib/src/PerspectiveDataPattern.ts new file mode 100644 index 000000000..9540307fc --- /dev/null +++ b/packages/datalib/src/PerspectiveDataPattern.ts @@ -0,0 +1,65 @@ +import { PerspectiveDataLoader } from './PerspectiveDataLoader'; +import { PerspectiveDataLoadProps } from './PerspectiveDataProvider'; +import _isString from 'lodash/isString'; +import _isPlainObject from 'lodash/isPlainObject'; +import _isNumber from 'lodash/isNumber'; +import _isBoolean from 'lodash/isBoolean'; + +export type PerspectiveDataPatternColumnType = 'null' | 'string' | 'number' | 'boolean' | 'object'; + +export interface PerspectiveDataPatternColumn { + name: string; + types: PerspectiveDataPatternColumnType[]; + columns: PerspectiveDataPatternColumn[]; +} + +export interface PerspectiveDataPattern { + conid: string; + database: string; + schemaName: string; + pureName: string; + columns: PerspectiveDataPatternColumn[]; +} + +export type PerspectiveDataPatternDict = { [designerId: string]: PerspectiveDataPattern }; + +function detectValueType(value): PerspectiveDataPatternColumnType { + if (_isString(value)) return 'string'; + if (_isNumber(value)) return 'number'; + if (_isBoolean(value)) return 'boolean'; + if (value == null) return 'null'; +} + +function addObjectToColumns(columns: PerspectiveDataPatternColumn[], row) { + if (_isPlainObject(row)) { + for (const key of Object.keys(row)) { + let column: PerspectiveDataPatternColumn = columns.find(x => x.name == key); + if (!column) { + column = { + name: key, + types: [], + columns: [], + }; + columns.push(column); + } + const type = detectValueType(row[key]); + if (!column.types.includes(type)) { + column.types.push(type); + } + } + } +} + +export function analyseDataPattern( + patternBase: Omit, + rows: any[] +): PerspectiveDataPattern { + const res: PerspectiveDataPattern = { + ...patternBase, + columns: [], + }; + for (const row of rows) { + addObjectToColumns(res.columns, row); + } + return res; +} diff --git a/packages/datalib/src/PerspectiveDataProvider.ts b/packages/datalib/src/PerspectiveDataProvider.ts index 17e3bcac6..e06b223e4 100644 --- a/packages/datalib/src/PerspectiveDataProvider.ts +++ b/packages/datalib/src/PerspectiveDataProvider.ts @@ -4,6 +4,7 @@ import { RangeDefinition } from 'dbgate-types'; import { format } from 'path'; import { PerspectiveBindingGroup, PerspectiveCache } from './PerspectiveCache'; import { PerspectiveDataLoader } from './PerspectiveDataLoader'; +import { PerspectiveDataPatternDict } from './PerspectiveDataPattern'; export const PERSPECTIVE_PAGE_SIZE = 100; @@ -28,10 +29,15 @@ export interface PerspectiveDataLoadProps { range?: RangeDefinition; topCount?: number; condition?: Condition; + engineType: 'sqldb' | 'docdb'; } export class PerspectiveDataProvider { - constructor(public cache: PerspectiveCache, public loader: PerspectiveDataLoader) {} + constructor( + public cache: PerspectiveCache, + public loader: PerspectiveDataLoader, + public dataPatterns: PerspectiveDataPatternDict + ) {} async loadData(props: PerspectiveDataLoadProps): Promise<{ rows: any[]; incomplete: boolean }> { dbg('load data', props); // console.log('LOAD DATA', props); diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index 0e69f32be..d571b8a0f 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -1,4 +1,5 @@ import { + CollectionInfo, ColumnInfo, DatabaseInfo, ForeignKeyInfo, @@ -313,7 +314,7 @@ export abstract class PerspectiveTreeNode { }; } - getOrderBy(table: TableInfo | ViewInfo): PerspectiveDataLoadProps['orderBy'] { + getOrderBy(table: TableInfo | ViewInfo | CollectionInfo): PerspectiveDataLoadProps['orderBy'] { const res = _compact( this.childNodes.map(node => { const sort = this.nodeConfig?.sort?.find(x => x.columnName == node.columnName); @@ -325,11 +326,15 @@ export abstract class PerspectiveTreeNode { } }) ); - return res.length > 0 - ? res - : (table as TableInfo)?.primaryKey?.columns.map(x => ({ columnName: x.columnName, order: 'ASC' })) || [ - { columnName: table?.columns[0].columnName, order: 'ASC' }, - ]; + if (res.length > 0) return res; + const pkColumns = (table as TableInfo)?.primaryKey?.columns.map(x => ({ + columnName: x.columnName, + order: 'ASC' as 'ASC', + })); + if (pkColumns) return pkColumns; + const columns = (table as TableInfo | ViewInfo)?.columns; + if (columns) return [{ columnName: columns[0].columnName, order: 'ASC' }]; + return [{ columnName: '_id', order: 'ASC' }]; } getBaseTables() { @@ -553,6 +558,7 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode { databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.refTable), condition: this.getChildrenCondition(), + engineType: 'sqldb', }; } @@ -715,6 +721,7 @@ export class PerspectiveTableNode extends PerspectiveTreeNode { databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.table), condition: this.getChildrenCondition(), + engineType: 'sqldb', }; } @@ -771,6 +778,88 @@ export class PerspectiveTableNode extends PerspectiveTreeNode { } } +export class PerspectiveCollectionNode extends PerspectiveTreeNode { + constructor( + public collection: CollectionInfo, + dbs: MultipleDatabaseInfo, + config: PerspectiveConfig, + setConfig: ChangePerspectiveConfigFunc, + public dataProvider: PerspectiveDataProvider, + databaseConfig: PerspectiveDatabaseConfig, + parentNode: PerspectiveTreeNode, + designerId: string + ) { + super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId); + } + + getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { + return { + schemaName: this.collection.schemaName, + pureName: this.collection.pureName, + dataColumns: this.getDataLoadColumns(), + databaseConfig: this.databaseConfig, + orderBy: this.getOrderBy(this.collection), + condition: this.getChildrenCondition(), + engineType: 'docdb', + }; + } + + get codeName() { + return this.collection.schemaName + ? `${this.collection.schemaName}:${this.collection.pureName}` + : this.collection.pureName; + } + + get title() { + return this.nodeConfig?.alias || this.collection.pureName; + } + + get isExpandable() { + return true; + } + + generateChildNodes(): PerspectiveTreeNode[] { + return []; + // return getTableChildPerspectiveNodes( + // this.table, + // this.dbs, + // this.config, + // this.setConfig, + // this.dataProvider, + // this.databaseConfig, + // this + // ); + } + + get icon() { + return 'img collection'; + } + + getBaseTableFromThis() { + return this.collection; + } + + get headerTableAttributes() { + return { + schemaName: this.collection.schemaName, + pureName: this.collection.pureName, + conid: this.databaseConfig.conid, + database: this.databaseConfig.database, + }; + } + + get tableCode() { + return `${this.collection.schemaName}|${this.collection.pureName}`; + } + + get namedObject(): NamedObjectInfo { + return { + schemaName: this.collection.schemaName, + pureName: this.collection.pureName, + }; + } +} + // export class PerspectiveViewNode extends PerspectiveTreeNode { // constructor( // public view: ViewInfo, @@ -873,6 +962,7 @@ export class PerspectiveTableReferenceNode extends PerspectiveTableNode { databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.table), condition: this.getChildrenCondition(), + engineType: 'sqldb', }; } @@ -978,6 +1068,7 @@ export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode { databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.table), condition: this.getChildrenCondition(), + engineType: 'sqldb', }; } diff --git a/packages/datalib/src/index.ts b/packages/datalib/src/index.ts index dace2858f..d4626bb57 100644 --- a/packages/datalib/src/index.ts +++ b/packages/datalib/src/index.ts @@ -19,3 +19,4 @@ export * from './PerspectiveDataProvider'; export * from './PerspectiveCache'; export * from './PerspectiveConfig'; export * from './processPerspectiveDefaultColunns'; +export * from './PerspectiveDataPattern'; diff --git a/packages/web/src/appobj/DatabaseObjectAppObject.svelte b/packages/web/src/appobj/DatabaseObjectAppObject.svelte index a6cceab66..47dbbad19 100644 --- a/packages/web/src/appobj/DatabaseObjectAppObject.svelte +++ b/packages/web/src/appobj/DatabaseObjectAppObject.svelte @@ -345,6 +345,12 @@ }, }, }, + { + label: 'Open perspective', + tab: 'PerspectiveTab', + forceNewTab: true, + icon: 'img perspective', + }, { label: 'Export', isExport: true, diff --git a/packages/web/src/designer/DesignerTable.svelte b/packages/web/src/designer/DesignerTable.svelte index a7bab85fe..ad8fa924e 100644 --- a/packages/web/src/designer/DesignerTable.svelte +++ b/packages/web/src/designer/DesignerTable.svelte @@ -238,6 +238,7 @@ class:isGrayed class:isTable={objectTypeField == 'tables'} class:isView={objectTypeField == 'views'} + class:isCollection={objectTypeField == 'collections'} use:moveDrag={settings?.canSelectColumns ? [handleMoveStart, handleMove, handleMoveEnd] : null} use:contextMenu={settings?.canSelectColumns ? createMenu : '__no_menu'} style={getTableColorStyle($currentThemeDefinition, table)} @@ -358,6 +359,10 @@ .header.isView { background: var(--theme-bg-magenta); } + .header.isCollection { + background: var(--theme-bg-red); + } + .header.isGrayed { background: var(--theme-bg-2); } diff --git a/packages/web/src/perspectives/PerspectiveDesigner.svelte b/packages/web/src/perspectives/PerspectiveDesigner.svelte index 7a8b8b1c4..6d7baaa05 100644 --- a/packages/web/src/perspectives/PerspectiveDesigner.svelte +++ b/packages/web/src/perspectives/PerspectiveDesigner.svelte @@ -3,10 +3,12 @@ createPerspectiveNodeConfig, MultipleDatabaseInfo, PerspectiveConfig, + PerspectiveDataPatternDict, perspectiveNodesHaveStructure, PerspectiveTreeNode, switchPerspectiveReferenceDirection, } from 'dbgate-datalib'; + import { CollectionInfo } from 'dbgate-types'; import _ from 'lodash'; import { tick } from 'svelte'; import runCommand from '../commands/runCommand'; @@ -18,6 +20,7 @@ export let config: PerspectiveConfig; export let dbInfos: MultipleDatabaseInfo; + export let dataPatterns: PerspectiveDataPatternDict; export let root: PerspectiveTreeNode; export let conid; @@ -27,7 +30,11 @@ export let onClickTableHeader = null; - function createDesignerModel(config: PerspectiveConfig, dbInfos: MultipleDatabaseInfo) { + function createDesignerModel( + config: PerspectiveConfig, + dbInfos: MultipleDatabaseInfo, + dataPatterns: PerspectiveDataPatternDict + ) { return { ...config, tables: _.compact( @@ -38,11 +45,26 @@ const view = dbInfos?.[node.conid || conid]?.[node.database || database]?.views?.find( x => x.pureName == node.pureName && x.schemaName == node.schemaName ); - if (!table && !view) return null; + let collection: CollectionInfo & { columns?: any[] } = dbInfos?.[node.conid || conid]?.[ + node.database || database + ]?.collections?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName); + + if (collection) { + const pattern = dataPatterns?.[node.designerId]; + if (!pattern) return null; + collection = { + ...collection, + columns: pattern.columns.map(x => ({ + columnName: x.name, + })), + }; + } + + if (!table && !view && !collection) return null; const { designerId } = node; return { - ...(table || view), + ...(table || view || collection), left: node?.position?.x || 0, top: node?.position?.y || 0, alias: node.alias, @@ -55,7 +77,7 @@ function handleChange(value, skipUndoChain, settings) { setConfig(oldValue => { - const newValue = _.isFunction(value) ? value(createDesignerModel(oldValue, dbInfos)) : value; + const newValue = _.isFunction(value) ? value(createDesignerModel(oldValue, dbInfos, dataPatterns)) : value; let isArranged = oldValue.isArranged; if (settings?.isCalledFromArrange) { isArranged = true; @@ -277,6 +299,6 @@ onClickTableHeader, }} referenceComponent={QueryDesignerReference} - value={createDesignerModel(config, dbInfos)} + value={createDesignerModel(config, dbInfos, dataPatterns)} onChange={handleChange} /> diff --git a/packages/web/src/perspectives/PerspectiveView.svelte b/packages/web/src/perspectives/PerspectiveView.svelte index e5d644404..877c08ac7 100644 --- a/packages/web/src/perspectives/PerspectiveView.svelte +++ b/packages/web/src/perspectives/PerspectiveView.svelte @@ -28,6 +28,7 @@ import { ChangePerspectiveConfigFunc, extractPerspectiveDatabases, + PerspectiveCollectionNode, PerspectiveConfig, PerspectiveDataProvider, PerspectiveTableNode, @@ -65,6 +66,7 @@ import { sleep } from '../utility/common'; import FontIcon from '../icons/FontIcon.svelte'; import InlineButton from '../buttons/InlineButton.svelte'; + import { usePerspectiveDataPatterns } from '../utility/usePerspectiveDataPatterns'; const dbg = debug('dbgate:PerspectiveView'); @@ -128,13 +130,17 @@ } $: dbInfos = useMultipleDatabaseInfo(perspectiveDatabases); + $: loader = new PerspectiveDataLoader(apiCall); + $: dataPatterns = usePerspectiveDataPatterns({ conid, database }, config, cache, $dbInfos, loader); $: rootObject = config?.nodes?.find(x => x.designerId == config?.rootDesignerId); $: rootDb = rootObject ? $dbInfos?.[rootObject.conid || conid]?.[rootObject.database || database] : null; $: tableInfo = rootDb?.tables.find(x => x.pureName == rootObject?.pureName && x.schemaName == rootObject?.schemaName); $: viewInfo = rootDb?.views.find(x => x.pureName == rootObject?.pureName && x.schemaName == rootObject?.schemaName); + $: collectionInfo = rootDb?.collections.find( + x => x.pureName == rootObject?.pureName && x.schemaName == rootObject?.schemaName + ); - $: loader = new PerspectiveDataLoader(apiCall); - $: dataProvider = new PerspectiveDataProvider(cache, loader); + $: dataProvider = new PerspectiveDataProvider(cache, loader, $dataPatterns); $: root = tableInfo || viewInfo ? new PerspectiveTableNode( @@ -147,6 +153,17 @@ null, config.rootDesignerId ) + : collectionInfo + ? new PerspectiveCollectionNode( + collectionInfo, + $dbInfos, + config, + setConfig, + dataProvider, + { conid, database }, + null, + config.rootDesignerId + ) : null; $: tempRoot = root?.findNodeByDesignerId(tempRootDesignerId); @@ -158,6 +175,7 @@ // $: console.log('PERSPECTIVE', config); // $: console.log('VIEW ROOT', root); + // $: console.log('dataPatterns', $dataPatterns); @@ -205,6 +223,7 @@ {database} {setConfig} dbInfos={$dbInfos} + dataPatterns={$dataPatterns} {root} onClickTableHeader={designerId => { sleep(100).then(() => { diff --git a/packages/web/src/utility/usePerspectiveDataPatterns.ts b/packages/web/src/utility/usePerspectiveDataPatterns.ts new file mode 100644 index 000000000..fdabd9023 --- /dev/null +++ b/packages/web/src/utility/usePerspectiveDataPatterns.ts @@ -0,0 +1,77 @@ +import { + analyseDataPattern, + MultipleDatabaseInfo, + PerspectiveCache, + PerspectiveConfig, + PerspectiveDatabaseConfig, + PerspectiveDataLoadProps, + PerspectiveDataPattern, + PerspectiveDataPatternDict, +} from 'dbgate-datalib'; +import { PerspectiveDataLoader } from 'dbgate-datalib/lib/PerspectiveDataLoader'; +import { writable, Readable } from 'svelte/store'; + +export async function getPerspectiveDataPatterns( + databaseConfig: PerspectiveDatabaseConfig, + config: PerspectiveConfig, + cache: PerspectiveCache, + dbInfos: MultipleDatabaseInfo, + dataLoader: PerspectiveDataLoader +): Promise { + const res = {}; + + for (const node of config.nodes) { + const conid = node.conid || databaseConfig.conid; + const database = node.database || databaseConfig.database; + const { schemaName, pureName } = node; + + const cached = cache.dataPatterns.find( + x => x.conid == conid && x.database == database && x.schemaName == schemaName && x.pureName == pureName + ); + if (cached) { + res[node.designerId] = cached; + continue; + } + + const db = dbInfos?.[conid]?.[database]; + + if (!db) continue; + + const collection = db.collections?.find(x => x.pureName == pureName && x.schemaName == schemaName); + if (!collection) continue; + + const props: PerspectiveDataLoadProps = { + databaseConfig: { conid, database }, + engineType: 'docdb', + pureName, + orderBy: [], + }; + const rows = await dataLoader.loadData(props); + const pattern = analyseDataPattern( + { + conid, + database, + pureName, + schemaName, + }, + rows + ); + + cache.dataPatterns.push(pattern); + res[node.designerId] = pattern; + } + + return res; +} + +export function usePerspectiveDataPatterns( + databaseConfig: PerspectiveDatabaseConfig, + config: PerspectiveConfig, + cache: PerspectiveCache, + dbInfos: MultipleDatabaseInfo, + dataLoader: PerspectiveDataLoader +): Readable { + const res = writable({}); + getPerspectiveDataPatterns(databaseConfig, config, cache, dbInfos, dataLoader).then(value => res.set(value)); + return res; +} From efe15bf0bb76778e45794b478d6975a445d103cb Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 1 Oct 2022 16:44:34 +0200 Subject: [PATCH 02/24] mongo perspective fixes --- .../datalib/src/PerspectiveDataProvider.ts | 4 +- .../src/processPerspectiveDefaultColunns.ts | 9 +++- packages/web/src/designer/Designer.svelte | 3 +- .../web/src/designer/DesignerTable.svelte | 2 + .../perspectives/PerspectiveDesigner.svelte | 24 +++++------ .../src/perspectives/PerspectiveView.svelte | 2 +- .../src/utility/usePerspectiveDataPatterns.ts | 42 ++++++++++++++++++- 7 files changed, 65 insertions(+), 21 deletions(-) diff --git a/packages/datalib/src/PerspectiveDataProvider.ts b/packages/datalib/src/PerspectiveDataProvider.ts index e06b223e4..6ab36bd1a 100644 --- a/packages/datalib/src/PerspectiveDataProvider.ts +++ b/packages/datalib/src/PerspectiveDataProvider.ts @@ -17,9 +17,9 @@ export interface PerspectiveDatabaseConfig { export interface PerspectiveDataLoadProps { databaseConfig: PerspectiveDatabaseConfig; - schemaName: string; + schemaName?: string; pureName: string; - dataColumns: string[]; + dataColumns?: string[]; orderBy: { columnName: string; order: 'ASC' | 'DESC'; diff --git a/packages/datalib/src/processPerspectiveDefaultColunns.ts b/packages/datalib/src/processPerspectiveDefaultColunns.ts index 060c2890b..cd8e97479 100644 --- a/packages/datalib/src/processPerspectiveDefaultColunns.ts +++ b/packages/datalib/src/processPerspectiveDefaultColunns.ts @@ -1,6 +1,7 @@ import { findForeignKeyForColumn } from 'dbgate-tools'; import { DatabaseInfo, TableInfo, ViewInfo } from 'dbgate-types'; import { createPerspectiveNodeConfig, MultipleDatabaseInfo, PerspectiveConfig } from './PerspectiveConfig'; +import { PerspectiveDataPatternDict } from './PerspectiveDataPattern'; import { PerspectiveTableNode } from './PerspectiveTreeNode'; function getPerspectiveDefaultColumns( @@ -47,6 +48,7 @@ function getPerspectiveDefaultColumns( export function perspectiveNodesHaveStructure( config: PerspectiveConfig, dbInfos: MultipleDatabaseInfo, + dataPatterns: PerspectiveDataPatternDict, conid: string, database: string ) { @@ -56,8 +58,10 @@ export function perspectiveNodesHaveStructure( const table = db.tables.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName); const view = db.views.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName); + const collection = db.collections.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName); - if (!table && !view) return false; + if (!table && !view && !collection) return false; + if (collection && !dataPatterns?.[node.designerId]) return false; } return true; @@ -66,13 +70,14 @@ export function perspectiveNodesHaveStructure( export function shouldProcessPerspectiveDefaultColunns( config: PerspectiveConfig, dbInfos: MultipleDatabaseInfo, + dataPatterns: PerspectiveDataPatternDict, conid: string, database: string ) { const nodesNotProcessed = config.nodes.filter(x => !x.defaultColumnsProcessed); if (nodesNotProcessed.length == 0) return false; - return perspectiveNodesHaveStructure(config, dbInfos, conid, database); + return perspectiveNodesHaveStructure(config, dbInfos, dataPatterns, conid, database); } function processPerspectiveDefaultColunnsStep( diff --git a/packages/web/src/designer/Designer.svelte b/packages/web/src/designer/Designer.svelte index cc4dad1da..2dfabbda4 100644 --- a/packages/web/src/designer/Designer.svelte +++ b/packages/web/src/designer/Designer.svelte @@ -479,7 +479,7 @@ const rect = e.target.getBoundingClientRect(); var json = JSON.parse(data); const { objectTypeField } = json; - if (objectTypeField != 'tables' && objectTypeField != 'views') return; + if (objectTypeField != 'tables' && objectTypeField != 'views' && objectTypeField != 'collections') return; json.designerId = `${json.pureName}-${uuidv1()}`; json.left = e.clientX - rect.left; json.top = e.clientY - rect.top; @@ -941,6 +941,7 @@ .empty { margin: 50px; font-size: 20px; + position: absolute; } .canvas { position: relative; diff --git a/packages/web/src/designer/DesignerTable.svelte b/packages/web/src/designer/DesignerTable.svelte index ad8fa924e..ca3162057 100644 --- a/packages/web/src/designer/DesignerTable.svelte +++ b/packages/web/src/designer/DesignerTable.svelte @@ -213,6 +213,8 @@ !isMultipleTableSelection && [{ divider: true }, createDatabaseObjectMenu({ ...table, conid, database })], ]; } + + // $: console.log('COLUMNS', columns);
{ - const table = dbInfos?.[node.conid || conid]?.[node.database || database]?.tables?.find( + const db = dbInfos?.[node.conid || conid]?.[node.database || database]; + const table = db?.tables?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName); + const view = db?.views?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName); + let collection: CollectionInfo & { columns?: any[] } = db?.collections?.find( x => x.pureName == node.pureName && x.schemaName == node.schemaName ); - const view = dbInfos?.[node.conid || conid]?.[node.database || database]?.views?.find( - x => x.pureName == node.pureName && x.schemaName == node.schemaName - ); - let collection: CollectionInfo & { columns?: any[] } = dbInfos?.[node.conid || conid]?.[ - node.database || database - ]?.collections?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName); if (collection) { const pattern = dataPatterns?.[node.designerId]; if (!pattern) return null; collection = { ...collection, - columns: pattern.columns.map(x => ({ - columnName: x.name, - })), + columns: + pattern?.columns.map(x => ({ + columnName: x.name, + })) || [], }; } @@ -144,11 +142,11 @@ }); } - async function detectAutoArrange(config: PerspectiveConfig, dbInfos, root) { + async function detectAutoArrange(config: PerspectiveConfig, dbInfos, dataPatterns, root) { if ( root && config.nodes.find(x => !x.position) && - perspectiveNodesHaveStructure(config, dbInfos, conid, database) && + perspectiveNodesHaveStructure(config, dbInfos, dataPatterns, conid, database) && config.nodes.every(x => root?.findNodeByDesignerId(x.designerId)) ) { await tick(); @@ -156,7 +154,7 @@ } } - $: detectAutoArrange(config, dbInfos, root); + $: detectAutoArrange(config, dbInfos, dataPatterns, root); // $: console.log('DESIGNER ROOT', root); diff --git a/packages/web/src/perspectives/PerspectiveView.svelte b/packages/web/src/perspectives/PerspectiveView.svelte index 877c08ac7..2caa237ed 100644 --- a/packages/web/src/perspectives/PerspectiveView.svelte +++ b/packages/web/src/perspectives/PerspectiveView.svelte @@ -168,7 +168,7 @@ $: tempRoot = root?.findNodeByDesignerId(tempRootDesignerId); $: { - if (shouldProcessPerspectiveDefaultColunns(config, $dbInfos, conid, database)) { + if (shouldProcessPerspectiveDefaultColunns(config, $dbInfos, $dataPatterns, conid, database)) { setConfig(cfg => processPerspectiveDefaultColunns(cfg, $dbInfos, conid, database)); } } diff --git a/packages/web/src/utility/usePerspectiveDataPatterns.ts b/packages/web/src/utility/usePerspectiveDataPatterns.ts index fdabd9023..525a5dd67 100644 --- a/packages/web/src/utility/usePerspectiveDataPatterns.ts +++ b/packages/web/src/utility/usePerspectiveDataPatterns.ts @@ -11,6 +11,38 @@ import { import { PerspectiveDataLoader } from 'dbgate-datalib/lib/PerspectiveDataLoader'; import { writable, Readable } from 'svelte/store'; +export function getPerspectiveDataPatternsFromCache( + databaseConfig: PerspectiveDatabaseConfig, + config: PerspectiveConfig, + cache: PerspectiveCache, + dbInfos: MultipleDatabaseInfo +): PerspectiveDataPatternDict { + const res = {}; + + for (const node of config.nodes) { + const conid = node.conid || databaseConfig.conid; + const database = node.database || databaseConfig.database; + const { schemaName, pureName } = node; + + const cached = cache.dataPatterns.find( + x => x.conid == conid && x.database == database && x.schemaName == schemaName && x.pureName == pureName + ); + if (cached) { + res[node.designerId] = cached; + continue; + } + + const db = dbInfos?.[conid]?.[database]; + + if (!db) continue; + + const collection = db.collections?.find(x => x.pureName == pureName && x.schemaName == schemaName); + if (!collection) continue; + } + + return res; +} + export async function getPerspectiveDataPatterns( databaseConfig: PerspectiveDatabaseConfig, config: PerspectiveConfig, @@ -45,6 +77,10 @@ export async function getPerspectiveDataPatterns( engineType: 'docdb', pureName, orderBy: [], + range: { + offset: 0, + limit: 10, + }, }; const rows = await dataLoader.loadData(props); const pattern = analyseDataPattern( @@ -71,7 +107,9 @@ export function usePerspectiveDataPatterns( dbInfos: MultipleDatabaseInfo, dataLoader: PerspectiveDataLoader ): Readable { - const res = writable({}); - getPerspectiveDataPatterns(databaseConfig, config, cache, dbInfos, dataLoader).then(value => res.set(value)); + const cached = getPerspectiveDataPatternsFromCache(databaseConfig, config, cache, dbInfos); + const promise = getPerspectiveDataPatterns(databaseConfig, config, cache, dbInfos, dataLoader); + const res = writable(cached); + promise.then(value => res.set(value)); return res; } From b3839def321e158726fef809438abd0286d46ee4 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 1 Oct 2022 17:48:47 +0200 Subject: [PATCH 03/24] mongo perspective stuff - basic skeleton works --- packages/datalib/src/PerspectiveTreeNode.ts | 225 +++++++++++++++++- .../src/processPerspectiveDefaultColunns.ts | 49 +++- .../src/tests/PerspectiveDisplay.test.ts | 5 +- .../src/perspectives/PerspectiveView.svelte | 2 +- 4 files changed, 259 insertions(+), 22 deletions(-) diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index d571b8a0f..c3f4ae79e 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -39,6 +39,7 @@ import { FilterType } from 'dbgate-filterparser/lib/types'; import { Condition, Expression, Select } from 'dbgate-sqltree'; // import { getPerspectiveDefaultColumns } from './getPerspectiveDefaultColumns'; import uuidv1 from 'uuid/v1'; +import { PerspectiveDataPatternColumn } from './PerspectiveDataPattern'; export interface PerspectiveDataLoadPropsWithNode { props: PerspectiveDataLoadProps; @@ -699,6 +700,173 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode { } } +export class PerspectivePatternColumnNode extends PerspectiveTreeNode { + foreignKey: ForeignKeyInfo; + refTable: TableInfo; + + constructor( + public column: PerspectiveDataPatternColumn, + dbs: MultipleDatabaseInfo, + config: PerspectiveConfig, + setConfig: ChangePerspectiveConfigFunc, + dataProvider: PerspectiveDataProvider, + databaseConfig: PerspectiveDatabaseConfig, + parentNode: PerspectiveTreeNode, + designerId: string + ) { + super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId); + } + + // matchChildRow(parentRow: any, childRow: any): boolean { + // if (!this.foreignKey) return false; + // return parentRow[this.foreignKey.columns[0].columnName] == childRow[this.foreignKey.columns[0].refColumnName]; + // } + + // getChildMatchColumns() { + // if (!this.foreignKey) return []; + // return [this.foreignKey.columns[0].columnName]; + // } + + // getParentMatchColumns() { + // if (!this.foreignKey) return []; + // return [this.foreignKey.columns[0].refColumnName]; + // } + + // getParentJoinCondition(alias: string, parentAlias: string): Condition[] { + // if (!this.foreignKey) return []; + // return this.foreignKey.columns.map(column => { + // const res: Condition = { + // conditionType: 'binary', + // operator: '=', + // left: { + // exprType: 'column', + // columnName: column.columnName, + // source: { alias: parentAlias }, + // }, + // right: { + // exprType: 'column', + // columnName: column.refColumnName, + // source: { alias }, + // }, + // }; + // return res; + // }); + // } + + // createReferenceConfigColumns(): PerspectiveReferenceConfig['columns'] { + // return this.foreignKey?.columns?.map(col => ({ + // source: col.columnName, + // target: col.refColumnName, + // })); + // } + + getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { + return null; + } + + get icon() { + return 'img column'; + } + + get codeName() { + return this.column.name; + } + + get columnName() { + return this.column.name; + } + + get fieldName() { + return this.codeName + 'Ref'; + } + + get title() { + return this.column.name; + } + + get isExpandable() { + return !!this.foreignKey; + } + + get isSortable() { + return true; + } + + get filterType(): FilterType { + return 'mongo'; + } + + generateChildNodes(): PerspectiveTreeNode[] { + return []; + // if (!this.foreignKey) return []; + // const tbl = this?.db?.tables?.find( + // x => x.pureName == this.foreignKey?.refTableName && x.schemaName == this.foreignKey?.refSchemaName + // ); + + // return getTableChildPerspectiveNodes( + // tbl, + // this.dbs, + // this.config, + // this.setConfig, + // this.dataProvider, + // this.databaseConfig, + // this + // ); + } + + // get filterInfo(): PerspectiveFilterColumnInfo { + // return { + // columnName: this.columnName, + // filterType: this.filterType, + // pureName: this.column.pureName, + // schemaName: this.column.schemaName, + // foreignKey: this.foreignKey, + // }; + // } + + // parseFilterCondition(source = null): Condition { + // const filter = this.getFilter(); + // if (!filter) return null; + // const condition = parseFilter(filter, this.filterType); + // if (!condition) return null; + // return _cloneDeepWith(condition, (expr: Expression) => { + // if (expr.exprType == 'placeholder') { + // return { + // exprType: 'column', + // columnName: this.column.columnName, + // source, + // }; + // } + // }); + // } + + // get headerTableAttributes() { + // if (this.foreignKey) { + // return { + // schemaName: this.foreignKey.refSchemaName, + // pureName: this.foreignKey.refTableName, + // conid: this.databaseConfig.conid, + // database: this.databaseConfig.database, + // }; + // } + // return null; + // } + + // get tableCode() { + // return `${this.collection.schemaName}|${this.table.pureName}`; + // } + + // get namedObject(): NamedObjectInfo { + // if (this.foreignKey) { + // return { + // schemaName: this.foreignKey.refSchemaName, + // pureName: this.foreignKey.refTableName, + // }; + // } + // return null; + // } +} + export class PerspectiveTableNode extends PerspectiveTreeNode { constructor( public table: TableInfo | ViewInfo, @@ -819,16 +987,16 @@ export class PerspectiveCollectionNode extends PerspectiveTreeNode { } generateChildNodes(): PerspectiveTreeNode[] { - return []; - // return getTableChildPerspectiveNodes( - // this.table, - // this.dbs, - // this.config, - // this.setConfig, - // this.dataProvider, - // this.databaseConfig, - // this - // ); + return getCollectionChildPerspectiveNodes( + this.designerId, + this.collection, + this.dbs, + this.config, + this.setConfig, + this.dataProvider, + this.databaseConfig, + this + ); } get icon() { @@ -1172,6 +1340,43 @@ function findDesignerIdForNode( return node; } +export function getCollectionChildPerspectiveNodes( + designerId: string, + collection: CollectionInfo, + dbs: MultipleDatabaseInfo, + config: PerspectiveConfig, + setConfig: ChangePerspectiveConfigFunc, + dataProvider: PerspectiveDataProvider, + databaseConfig: PerspectiveDatabaseConfig, + parentNode: PerspectiveTreeNode +) { + if (!collection) return []; + const db = parentNode.db; + + const pattern = dataProvider.dataPatterns[designerId]; + if (!pattern) return []; + + const columnNodes = pattern.columns.map(col => + findDesignerIdForNode( + config, + parentNode, + designerId => + new PerspectivePatternColumnNode( + col, + dbs, + config, + setConfig, + dataProvider, + databaseConfig, + parentNode, + designerId + ) + ) + ); + + return columnNodes; +} + export function getTableChildPerspectiveNodes( table: TableInfo | ViewInfo, dbs: MultipleDatabaseInfo, diff --git a/packages/datalib/src/processPerspectiveDefaultColunns.ts b/packages/datalib/src/processPerspectiveDefaultColunns.ts index cd8e97479..2e106acf7 100644 --- a/packages/datalib/src/processPerspectiveDefaultColunns.ts +++ b/packages/datalib/src/processPerspectiveDefaultColunns.ts @@ -1,9 +1,17 @@ import { findForeignKeyForColumn } from 'dbgate-tools'; import { DatabaseInfo, TableInfo, ViewInfo } from 'dbgate-types'; import { createPerspectiveNodeConfig, MultipleDatabaseInfo, PerspectiveConfig } from './PerspectiveConfig'; -import { PerspectiveDataPatternDict } from './PerspectiveDataPattern'; +import { PerspectiveDataPattern, PerspectiveDataPatternDict } from './PerspectiveDataPattern'; import { PerspectiveTableNode } from './PerspectiveTreeNode'; +const namePredicates = [ + x => x.toLowerCase() == 'name', + x => x.toLowerCase() == 'title', + x => x.toLowerCase().includes('name'), + x => x.toLowerCase().includes('title'), + x => x.toLowerCase().includes('subject'), +]; + function getPerspectiveDefaultColumns( table: TableInfo | ViewInfo, db: DatabaseInfo, @@ -11,13 +19,7 @@ function getPerspectiveDefaultColumns( ): [string[], string[]] { const columns = table.columns.map(x => x.columnName); const predicates = [ - x => x.toLowerCase() == 'name', - x => x.toLowerCase() == 'title', - x => x.toLowerCase().includes('name'), - x => x.toLowerCase().includes('title'), - x => x.toLowerCase().includes('subject'), - // x => x.toLowerCase().includes('text'), - // x => x.toLowerCase().includes('desc'), + ...namePredicates, x => table.columns .find(y => y.columnName == x) @@ -45,6 +47,16 @@ function getPerspectiveDefaultColumns( return [[columns[0]], null]; } +function getPerspectiveDefaultCollectionColumns(pattern: PerspectiveDataPattern): string[] { + const columns = pattern.columns.map(x => x.name); + const predicates = [...namePredicates, x => pattern.columns.find(y => y.name == x)?.types?.includes('string')]; + + for (const predicate of predicates) { + const col = columns.find(predicate); + if (col) return [col]; + } +} + export function perspectiveNodesHaveStructure( config: PerspectiveConfig, dbInfos: MultipleDatabaseInfo, @@ -83,6 +95,7 @@ export function shouldProcessPerspectiveDefaultColunns( function processPerspectiveDefaultColunnsStep( config: PerspectiveConfig, dbInfos: MultipleDatabaseInfo, + dataPatterns: PerspectiveDataPatternDict, conid: string, database: string ) { @@ -112,6 +125,7 @@ function processPerspectiveDefaultColunnsStep( const table = db.tables.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName); const view = db.views.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName); + const collection = db.collections.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName); if (table || view) { const treeNode = root.findNodeByDesignerId(node.designerId); @@ -186,6 +200,22 @@ function processPerspectiveDefaultColunnsStep( }; } } + + if (collection) { + const defaultColumns = getPerspectiveDefaultCollectionColumns(dataPatterns?.[node.designerId]); + return { + ...config, + nodes: config.nodes.map(n => + n.designerId == node.designerId + ? { + ...n, + defaultColumnsProcessed: true, + checkedColumns: defaultColumns, + } + : n + ), + }; + } } return null; @@ -204,11 +234,12 @@ function markAllProcessed(config: PerspectiveConfig): PerspectiveConfig { export function processPerspectiveDefaultColunns( config: PerspectiveConfig, dbInfos: MultipleDatabaseInfo, + dataPatterns: PerspectiveDataPatternDict, conid: string, database: string ): PerspectiveConfig { while (config.nodes.filter(x => !x.defaultColumnsProcessed).length > 0) { - const newConfig = processPerspectiveDefaultColunnsStep(config, dbInfos, conid, database); + const newConfig = processPerspectiveDefaultColunnsStep(config, dbInfos, dataPatterns, conid, database); if (!newConfig) { return markAllProcessed(config); } diff --git a/packages/datalib/src/tests/PerspectiveDisplay.test.ts b/packages/datalib/src/tests/PerspectiveDisplay.test.ts index d2c74f757..f86cc6b89 100644 --- a/packages/datalib/src/tests/PerspectiveDisplay.test.ts +++ b/packages/datalib/src/tests/PerspectiveDisplay.test.ts @@ -13,6 +13,7 @@ test('test flat view', () => { const configColumns = processPerspectiveDefaultColunns( createPerspectiveConfig({ pureName: 'Artist' }), { conid: { db: chinookDbInfo } }, + null, 'conid', 'db' ); @@ -47,7 +48,7 @@ test('test one level nesting', () => { columns: [{ source: 'ArtistId', target: 'ArtistId' }], }); - const configColumns = processPerspectiveDefaultColunns(config, { conid: { db: chinookDbInfo } }, 'conid', 'db'); + const configColumns = processPerspectiveDefaultColunns(config, { conid: { db: chinookDbInfo } }, null, 'conid', 'db'); // const config = createPerspectiveConfig({ pureName: 'Artist' }); // config.nodes[0].checkedColumns = ['Album']; @@ -107,7 +108,7 @@ test('test two level nesting', () => { designerId: '2', columns: [{ source: 'AlbumId', target: 'AlbumId' }], }); - const configColumns = processPerspectiveDefaultColunns(config, { conid: { db: chinookDbInfo } }, 'conid', 'db'); + const configColumns = processPerspectiveDefaultColunns(config, { conid: { db: chinookDbInfo } }, null, 'conid', 'db'); const root = new PerspectiveTableNode( artistTable, diff --git a/packages/web/src/perspectives/PerspectiveView.svelte b/packages/web/src/perspectives/PerspectiveView.svelte index 2caa237ed..db5e47184 100644 --- a/packages/web/src/perspectives/PerspectiveView.svelte +++ b/packages/web/src/perspectives/PerspectiveView.svelte @@ -169,7 +169,7 @@ $: { if (shouldProcessPerspectiveDefaultColunns(config, $dbInfos, $dataPatterns, conid, database)) { - setConfig(cfg => processPerspectiveDefaultColunns(cfg, $dbInfos, conid, database)); + setConfig(cfg => processPerspectiveDefaultColunns(cfg, $dbInfos, $dataPatterns, conid, database)); } } From 08abec7c3e9186fd021dc2b772eff6494e600dd7 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 1 Oct 2022 18:50:54 +0200 Subject: [PATCH 04/24] perspective mongo condition --- packages/datalib/src/PerspectiveCache.ts | 10 ++- packages/datalib/src/PerspectiveDataLoader.ts | 53 ++++++++++++-- .../datalib/src/PerspectiveDataProvider.ts | 3 +- packages/datalib/src/PerspectiveTreeNode.ts | 73 +++++++++++-------- 4 files changed, 101 insertions(+), 38 deletions(-) diff --git a/packages/datalib/src/PerspectiveCache.ts b/packages/datalib/src/PerspectiveCache.ts index a2d8846f6..e794e9c4f 100644 --- a/packages/datalib/src/PerspectiveCache.ts +++ b/packages/datalib/src/PerspectiveCache.ts @@ -91,7 +91,15 @@ export class PerspectiveCache { getTableCache(props: PerspectiveDataLoadProps) { const tableKey = stableStringify( - _pick(props, ['schemaName', 'pureName', 'bindingColumns', 'databaseConfig', 'orderBy', 'condition']) + _pick(props, [ + 'schemaName', + 'pureName', + 'bindingColumns', + 'databaseConfig', + 'orderBy', + 'sqlCondition', + 'mongoCondition', + ]) ); let res = this.tables[tableKey]; diff --git a/packages/datalib/src/PerspectiveDataLoader.ts b/packages/datalib/src/PerspectiveDataLoader.ts index 580549d56..a58e7ea62 100644 --- a/packages/datalib/src/PerspectiveDataLoader.ts +++ b/packages/datalib/src/PerspectiveDataLoader.ts @@ -8,7 +8,15 @@ export class PerspectiveDataLoader { constructor(public apiCall) {} buildCondition(props: PerspectiveDataLoadProps): Condition { - const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props; + const { + schemaName, + pureName, + bindingColumns, + bindingValues, + dataColumns, + orderBy, + sqlCondition: condition, + } = props; const conditions = []; @@ -94,7 +102,16 @@ export class PerspectiveDataLoader { } async loadDataSqlDb(props: PerspectiveDataLoadProps) { - const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition, engineType } = props; + const { + schemaName, + pureName, + bindingColumns, + bindingValues, + dataColumns, + orderBy, + sqlCondition: condition, + engineType, + } = props; if (dataColumns?.length == 0) { return []; @@ -147,13 +164,23 @@ export class PerspectiveDataLoader { const { pureName } = props; return { pureName, + condition: props.mongoCondition, skip: props.range?.offset, limit: props.range?.limit, }; } async loadDataDocDb(props: PerspectiveDataLoadProps) { - const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition, engineType } = props; + const { + schemaName, + pureName, + bindingColumns, + bindingValues, + dataColumns, + orderBy, + sqlCondition: condition, + engineType, + } = props; if (dataColumns?.length == 0) { return []; @@ -190,7 +217,15 @@ export class PerspectiveDataLoader { } async loadRowCountSqlDb(props: PerspectiveDataLoadProps) { - const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props; + const { + schemaName, + pureName, + bindingColumns, + bindingValues, + dataColumns, + orderBy, + sqlCondition: condition, + } = props; const select: Select = { commandType: 'select', @@ -218,7 +253,15 @@ export class PerspectiveDataLoader { } async loadRowCountDocDb(props: PerspectiveDataLoadProps) { - const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props; + const { + schemaName, + pureName, + bindingColumns, + bindingValues, + dataColumns, + orderBy, + sqlCondition: condition, + } = props; const options = { ...this.getDocDbLoadOptions(props), diff --git a/packages/datalib/src/PerspectiveDataProvider.ts b/packages/datalib/src/PerspectiveDataProvider.ts index 6ab36bd1a..b2a6ee115 100644 --- a/packages/datalib/src/PerspectiveDataProvider.ts +++ b/packages/datalib/src/PerspectiveDataProvider.ts @@ -28,7 +28,8 @@ export interface PerspectiveDataLoadProps { bindingValues?: any[][]; range?: RangeDefinition; topCount?: number; - condition?: Condition; + sqlCondition?: Condition; + mongoCondition?: any; engineType: 'sqldb' | 'docdb'; } diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index c3f4ae79e..f86a3d3d2 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -298,7 +298,7 @@ export abstract class PerspectiveTreeNode { ); } - getChildrenCondition(source = null): Condition { + getChildrenSqlCondition(source = null): Condition { const conditions = _compact([ ...this.childNodes.map(x => x.parseFilterCondition(source)), ...this.buildParentFilterConditions(), @@ -315,6 +315,17 @@ export abstract class PerspectiveTreeNode { }; } + getChildrenMongoCondition(source = null): {} { + const conditions = _compact([...this.childNodes.map(x => x.parseFilterCondition(source))]); + if (conditions.length == 0) { + return null; + } + if (conditions.length == 1) { + return conditions[0]; + } + return { $and: conditions }; + } + getOrderBy(table: TableInfo | ViewInfo | CollectionInfo): PerspectiveDataLoadProps['orderBy'] { const res = _compact( this.childNodes.map(node => { @@ -444,7 +455,7 @@ export abstract class PerspectiveTreeNode { conditionType: 'and', conditions: _compact([ ...lastNode.getParentJoinCondition(lastAlias, this.namedObject.pureName), - leafNode.getChildrenCondition({ alias: 'pert_0' }), + leafNode.getChildrenSqlCondition({ alias: 'pert_0' }), ]), }; @@ -558,7 +569,7 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode { dataColumns: this.getDataLoadColumns(), databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.refTable), - condition: this.getChildrenCondition(), + sqlCondition: this.getChildrenSqlCondition(), engineType: 'sqldb', }; } @@ -705,6 +716,7 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { refTable: TableInfo; constructor( + public owner: NamedObjectInfo, public column: PerspectiveDataPatternColumn, dbs: MultipleDatabaseInfo, config: PerspectiveConfig, @@ -814,31 +826,29 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { // ); } - // get filterInfo(): PerspectiveFilterColumnInfo { - // return { - // columnName: this.columnName, - // filterType: this.filterType, - // pureName: this.column.pureName, - // schemaName: this.column.schemaName, - // foreignKey: this.foreignKey, - // }; - // } + get filterInfo(): PerspectiveFilterColumnInfo { + return { + columnName: this.columnName, + filterType: this.filterType, + pureName: this.owner.pureName, + schemaName: this.owner.schemaName, + foreignKey: this.foreignKey, + }; + } - // parseFilterCondition(source = null): Condition { - // const filter = this.getFilter(); - // if (!filter) return null; - // const condition = parseFilter(filter, this.filterType); - // if (!condition) return null; - // return _cloneDeepWith(condition, (expr: Expression) => { - // if (expr.exprType == 'placeholder') { - // return { - // exprType: 'column', - // columnName: this.column.columnName, - // source, - // }; - // } - // }); - // } + parseFilterCondition(source = null): {} { + const filter = this.getFilter(); + if (!filter) return null; + const condition = parseFilter(filter, 'mongo'); + if (!condition) return null; + return _cloneDeepWith(condition, expr => { + if (expr.__placeholder__) { + return { + [this.columnName]: expr.__placeholder__, + }; + } + }); + } // get headerTableAttributes() { // if (this.foreignKey) { @@ -888,7 +898,7 @@ export class PerspectiveTableNode extends PerspectiveTreeNode { dataColumns: this.getDataLoadColumns(), databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.table), - condition: this.getChildrenCondition(), + sqlCondition: this.getChildrenSqlCondition(), engineType: 'sqldb', }; } @@ -967,7 +977,7 @@ export class PerspectiveCollectionNode extends PerspectiveTreeNode { dataColumns: this.getDataLoadColumns(), databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.collection), - condition: this.getChildrenCondition(), + mongoCondition: this.getChildrenMongoCondition(), engineType: 'docdb', }; } @@ -1129,7 +1139,7 @@ export class PerspectiveTableReferenceNode extends PerspectiveTableNode { dataColumns: this.getDataLoadColumns(), databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.table), - condition: this.getChildrenCondition(), + sqlCondition: this.getChildrenSqlCondition(), engineType: 'sqldb', }; } @@ -1235,7 +1245,7 @@ export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode { dataColumns: this.getDataLoadColumns(), databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.table), - condition: this.getChildrenCondition(), + sqlCondition: this.getChildrenSqlCondition(), engineType: 'sqldb', }; } @@ -1362,6 +1372,7 @@ export function getCollectionChildPerspectiveNodes( parentNode, designerId => new PerspectivePatternColumnNode( + collection, col, dbs, config, From da5dd7ac62d9b4926e10d3798e154cf464bb068e Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 1 Oct 2022 19:18:18 +0200 Subject: [PATCH 05/24] perspective mongo sort --- packages/datalib/src/PerspectiveDataLoader.ts | 59 ++++++++++++------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/packages/datalib/src/PerspectiveDataLoader.ts b/packages/datalib/src/PerspectiveDataLoader.ts index a58e7ea62..d8875ed89 100644 --- a/packages/datalib/src/PerspectiveDataLoader.ts +++ b/packages/datalib/src/PerspectiveDataLoader.ts @@ -1,27 +1,20 @@ import { Condition, Expression, Select } from 'dbgate-sqltree'; import { PerspectiveDataLoadProps } from './PerspectiveDataProvider'; import debug from 'debug'; +import _zipObject from 'lodash/zipObject'; const dbg = debug('dbgate:PerspectiveDataLoader'); export class PerspectiveDataLoader { constructor(public apiCall) {} - buildCondition(props: PerspectiveDataLoadProps): Condition { - const { - schemaName, - pureName, - bindingColumns, - bindingValues, - dataColumns, - orderBy, - sqlCondition: condition, - } = props; + buildSqlCondition(props: PerspectiveDataLoadProps): Condition { + const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, sqlCondition } = props; const conditions = []; - if (condition) { - conditions.push(condition); + if (sqlCondition) { + conditions.push(sqlCondition); } if (bindingColumns?.length == 1) { @@ -46,6 +39,24 @@ export class PerspectiveDataLoader { : null; } + buildMongoCondition(props: PerspectiveDataLoadProps): {} { + const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, mongoCondition } = props; + + const conditions = []; + + if (mongoCondition) { + conditions.push(mongoCondition); + } + + if (bindingColumns?.length == 1) { + conditions.push({ + [bindingColumns[0]]: { $in: bindingValues.map(x => x[0]) }, + }); + } + + return conditions.length == 1 ? conditions[0] : conditions.length > 0 ? { $and: conditions } : null; + } + async loadGrouping(props: PerspectiveDataLoadProps) { const { schemaName, pureName, bindingColumns, bindingValues, dataColumns } = props; @@ -79,7 +90,7 @@ export class PerspectiveDataLoader { }, ...bindingColumnExpressions, ], - where: this.buildCondition(props), + where: this.buildSqlCondition(props), }; select.groupBy = bindingColumnExpressions; @@ -139,7 +150,7 @@ export class PerspectiveDataLoader { }, })), range: props.range, - where: this.buildCondition(props), + where: this.buildSqlCondition(props), }; if (dbg?.enabled) { @@ -160,14 +171,22 @@ export class PerspectiveDataLoader { return response.rows; } - getDocDbLoadOptions(props: PerspectiveDataLoadProps) { + getDocDbLoadOptions(props: PerspectiveDataLoadProps, useSort: boolean) { const { pureName } = props; - return { + const res: any = { pureName, - condition: props.mongoCondition, + condition: this.buildMongoCondition(props), skip: props.range?.offset, limit: props.range?.limit, }; + if (useSort && props.orderBy?.length > 0) { + res.sort = _zipObject( + props.orderBy.map(col => col.columnName), + props.orderBy.map(col => (col.order == 'DESC' ? -1 : 1)) + ); + } + + return res; } async loadDataDocDb(props: PerspectiveDataLoadProps) { @@ -194,7 +213,7 @@ export class PerspectiveDataLoader { ); } - const options = this.getDocDbLoadOptions(props); + const options = this.getDocDbLoadOptions(props, true); const response = await this.apiCall('database-connections/collection-data', { conid: props.databaseConfig.conid, @@ -239,7 +258,7 @@ export class PerspectiveDataLoader { alias: 'count', }, ], - where: this.buildCondition(props), + where: this.buildSqlCondition(props), }; const response = await this.apiCall('database-connections/sql-select', { @@ -264,7 +283,7 @@ export class PerspectiveDataLoader { } = props; const options = { - ...this.getDocDbLoadOptions(props), + ...this.getDocDbLoadOptions(props, false), countDocuments: true, }; From f60e1190c8a4a9cc03d3c04cc7df550a4efa3843 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 2 Oct 2022 09:44:52 +0200 Subject: [PATCH 06/24] perspectives: mongo join works --- packages/datalib/src/PerspectiveDataLoader.ts | 52 +++- packages/datalib/src/PerspectiveTreeNode.ts | 227 ++++++++++-------- packages/tools/src/structureTools.ts | 14 +- .../src/perspectives/PerspectiveView.svelte | 16 +- .../dbgate-plugin-mongo/src/backend/driver.js | 4 + 5 files changed, 196 insertions(+), 117 deletions(-) diff --git a/packages/datalib/src/PerspectiveDataLoader.ts b/packages/datalib/src/PerspectiveDataLoader.ts index d8875ed89..e1e82f984 100644 --- a/packages/datalib/src/PerspectiveDataLoader.ts +++ b/packages/datalib/src/PerspectiveDataLoader.ts @@ -57,8 +57,8 @@ export class PerspectiveDataLoader { return conditions.length == 1 ? conditions[0] : conditions.length > 0 ? { $and: conditions } : null; } - async loadGrouping(props: PerspectiveDataLoadProps) { - const { schemaName, pureName, bindingColumns, bindingValues, dataColumns } = props; + async loadGroupingSqlDb(props: PerspectiveDataLoadProps) { + const { schemaName, pureName, bindingColumns } = props; const bindingColumnExpressions = bindingColumns.map( columnName => @@ -96,7 +96,7 @@ export class PerspectiveDataLoader { select.groupBy = bindingColumnExpressions; if (dbg?.enabled) { - dbg(`LOAD COUNTS, table=${props.pureName}, columns=${props.dataColumns?.join(',')}`); + dbg(`LOAD COUNTS, table=${props.pureName}, columns=${bindingColumns?.join(',')}`); } const response = await this.apiCall('database-connections/sql-select', { @@ -112,6 +112,52 @@ export class PerspectiveDataLoader { })); } + async loadGroupingDocDb(props: PerspectiveDataLoadProps) { + const { schemaName, pureName, bindingColumns } = props; + + const aggregate = [ + { $match: this.buildMongoCondition(props) }, + { + $group: { + _id: _zipObject( + bindingColumns, + bindingColumns.map(col => '$' + col) + ), + count: { $sum: 1 }, + }, + }, + ]; + + if (dbg?.enabled) { + dbg(`LOAD COUNTS, table=${props.pureName}, columns=${bindingColumns?.join(',')}`); + } + + const response = await this.apiCall('database-connections/collection-data', { + conid: props.databaseConfig.conid, + database: props.databaseConfig.database, + options: { + pureName, + aggregate, + }, + }); + + if (response.errorMessage) return response; + return response.rows.map(row => ({ + ...row._id, + _perspective_group_size_: parseInt(row.count), + })); + } + + async loadGrouping(props: PerspectiveDataLoadProps) { + const { engineType } = props; + switch (engineType) { + case 'sqldb': + return this.loadGroupingSqlDb(props); + case 'docdb': + return this.loadGroupingDocDb(props); + } + } + async loadDataSqlDb(props: PerspectiveDataLoadProps) { const { schemaName, diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index f86a3d3d2..746f54b93 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -8,7 +8,7 @@ import { TableInfo, ViewInfo, } from 'dbgate-types'; -import { equalFullName } from 'dbgate-tools'; +import { equalFullName, isCollectionInfo, isTableInfo, isViewInfo } from 'dbgate-tools'; import { ChangePerspectiveConfigFunc, createPerspectiveNodeConfig, @@ -879,7 +879,7 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { export class PerspectiveTableNode extends PerspectiveTreeNode { constructor( - public table: TableInfo | ViewInfo, + public table: TableInfo | ViewInfo | CollectionInfo, dbs: MultipleDatabaseInfo, config: PerspectiveConfig, setConfig: ChangePerspectiveConfigFunc, @@ -892,14 +892,16 @@ export class PerspectiveTableNode extends PerspectiveTreeNode { } getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { + const isMongo = isCollectionInfo(this.table); return { schemaName: this.table.schemaName, pureName: this.table.pureName, dataColumns: this.getDataLoadColumns(), databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.table), - sqlCondition: this.getChildrenSqlCondition(), - engineType: 'sqldb', + sqlCondition: isMongo ? null : this.getChildrenSqlCondition(), + mongoCondition: isMongo ? this.getChildrenMongoCondition() : null, + engineType: isMongo ? 'docdb' : 'sqldb', }; } @@ -956,87 +958,87 @@ export class PerspectiveTableNode extends PerspectiveTreeNode { } } -export class PerspectiveCollectionNode extends PerspectiveTreeNode { - constructor( - public collection: CollectionInfo, - dbs: MultipleDatabaseInfo, - config: PerspectiveConfig, - setConfig: ChangePerspectiveConfigFunc, - public dataProvider: PerspectiveDataProvider, - databaseConfig: PerspectiveDatabaseConfig, - parentNode: PerspectiveTreeNode, - designerId: string - ) { - super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId); - } +// export class PerspectiveCollectionNode extends PerspectiveTreeNode { +// constructor( +// public collection: CollectionInfo, +// dbs: MultipleDatabaseInfo, +// config: PerspectiveConfig, +// setConfig: ChangePerspectiveConfigFunc, +// public dataProvider: PerspectiveDataProvider, +// databaseConfig: PerspectiveDatabaseConfig, +// parentNode: PerspectiveTreeNode, +// designerId: string +// ) { +// super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId); +// } - getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { - return { - schemaName: this.collection.schemaName, - pureName: this.collection.pureName, - dataColumns: this.getDataLoadColumns(), - databaseConfig: this.databaseConfig, - orderBy: this.getOrderBy(this.collection), - mongoCondition: this.getChildrenMongoCondition(), - engineType: 'docdb', - }; - } +// getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { +// return { +// schemaName: this.collection.schemaName, +// pureName: this.collection.pureName, +// dataColumns: this.getDataLoadColumns(), +// databaseConfig: this.databaseConfig, +// orderBy: this.getOrderBy(this.collection), +// mongoCondition: this.getChildrenMongoCondition(), +// engineType: 'docdb', +// }; +// } - get codeName() { - return this.collection.schemaName - ? `${this.collection.schemaName}:${this.collection.pureName}` - : this.collection.pureName; - } +// get codeName() { +// return this.collection.schemaName +// ? `${this.collection.schemaName}:${this.collection.pureName}` +// : this.collection.pureName; +// } - get title() { - return this.nodeConfig?.alias || this.collection.pureName; - } +// get title() { +// return this.nodeConfig?.alias || this.collection.pureName; +// } - get isExpandable() { - return true; - } +// get isExpandable() { +// return true; +// } - generateChildNodes(): PerspectiveTreeNode[] { - return getCollectionChildPerspectiveNodes( - this.designerId, - this.collection, - this.dbs, - this.config, - this.setConfig, - this.dataProvider, - this.databaseConfig, - this - ); - } +// generateChildNodes(): PerspectiveTreeNode[] { +// return getCollectionChildPerspectiveNodes( +// this.designerId, +// this.collection, +// this.dbs, +// this.config, +// this.setConfig, +// this.dataProvider, +// this.databaseConfig, +// this +// ); +// } - get icon() { - return 'img collection'; - } +// get icon() { +// return 'img collection'; +// } - getBaseTableFromThis() { - return this.collection; - } +// getBaseTableFromThis() { +// return this.collection; +// } - get headerTableAttributes() { - return { - schemaName: this.collection.schemaName, - pureName: this.collection.pureName, - conid: this.databaseConfig.conid, - database: this.databaseConfig.database, - }; - } +// get headerTableAttributes() { +// return { +// schemaName: this.collection.schemaName, +// pureName: this.collection.pureName, +// conid: this.databaseConfig.conid, +// database: this.databaseConfig.database, +// }; +// } - get tableCode() { - return `${this.collection.schemaName}|${this.collection.pureName}`; - } +// get tableCode() { +// return `${this.collection.schemaName}|${this.collection.pureName}`; +// } - get namedObject(): NamedObjectInfo { - return { - schemaName: this.collection.schemaName, - pureName: this.collection.pureName, - }; - } -} +// get namedObject(): NamedObjectInfo { +// return { +// schemaName: this.collection.schemaName, +// pureName: this.collection.pureName, +// }; +// } +// } // export class PerspectiveViewNode extends PerspectiveTreeNode { // constructor( @@ -1202,7 +1204,7 @@ export class PerspectiveTableReferenceNode extends PerspectiveTableNode { export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode { constructor( public customJoin: PerspectiveCustomJoinConfig, - table: TableInfo | ViewInfo, + table: TableInfo | ViewInfo | CollectionInfo, dbs: MultipleDatabaseInfo, config: PerspectiveConfig, setConfig: ChangePerspectiveConfigFunc, @@ -1234,6 +1236,8 @@ export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode { getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { // console.log('CUSTOM JOIN', this.customJoin); // console.log('this.getDataLoadColumns()', this.getDataLoadColumns()); + const isMongo = isCollectionInfo(this.table); + return { schemaName: this.table.schemaName, pureName: this.table.pureName, @@ -1245,8 +1249,9 @@ export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode { dataColumns: this.getDataLoadColumns(), databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.table), - sqlCondition: this.getChildrenSqlCondition(), - engineType: 'sqldb', + sqlCondition: isMongo ? null : this.getChildrenSqlCondition(), + mongoCondition: isMongo ? this.getChildrenMongoCondition() : null, + engineType: isMongo ? 'docdb' : 'sqldb', }; } @@ -1389,7 +1394,7 @@ export function getCollectionChildPerspectiveNodes( } export function getTableChildPerspectiveNodes( - table: TableInfo | ViewInfo, + table: TableInfo | ViewInfo | CollectionInfo, dbs: MultipleDatabaseInfo, config: PerspectiveConfig, setConfig: ChangePerspectiveConfigFunc, @@ -1400,25 +1405,48 @@ export function getTableChildPerspectiveNodes( if (!table) return []; const db = parentNode.db; - const columnNodes = table.columns.map(col => - findDesignerIdForNode( - config, - parentNode, - designerId => - new PerspectiveTableColumnNode( - col, - table, - dbs, - config, - setConfig, - dataProvider, - databaseConfig, - parentNode, - designerId - ) - ) - ); + const pattern = dataProvider.dataPatterns[parentNode.designerId]; + const tableOrView = isTableInfo(table) || isViewInfo(table) ? table : null; + + const columnNodes = + tableOrView?.columns?.map(col => + findDesignerIdForNode( + config, + parentNode, + designerId => + new PerspectiveTableColumnNode( + col, + tableOrView, + dbs, + config, + setConfig, + dataProvider, + databaseConfig, + parentNode, + designerId + ) + ) + ) || + pattern?.columns?.map(col => + findDesignerIdForNode( + config, + parentNode, + designerId => + new PerspectivePatternColumnNode( + table, + col, + dbs, + config, + setConfig, + dataProvider, + databaseConfig, + parentNode, + designerId + ) + ) + ) || + []; // if (!columnNodes.find(x => x.isChecked)) { // const circularColumns = columnNodes.filter(x => x.isCircular).map(x => x.columnName); // const defaultColumns = getPerspectiveDefaultColumns(table, db, circularColumns); @@ -1480,6 +1508,7 @@ export function getTableChildPerspectiveNodes( const db = dbs?.[newConfig.conid]?.[newConfig.database]; const table = db?.tables?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName); const view = db?.views?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName); + const collection = db?.collections?.find(x => x.pureName == node.pureName && x.schemaName == node.schemaName); const join: PerspectiveCustomJoinConfig = { refNodeDesignerId: node.designerId, @@ -1496,11 +1525,11 @@ export function getTableChildPerspectiveNodes( : ref.columns.map(col => ({ baseColumnName: col.target, refColumnName: col.source })), }; - if (table || view) { + if (table || view || collection) { customs.push( new PerspectiveCustomJoinTreeNode( join, - table || view, + table || view || collection, dbs, config, setConfig, diff --git a/packages/tools/src/structureTools.ts b/packages/tools/src/structureTools.ts index f3d083514..660c5779c 100644 --- a/packages/tools/src/structureTools.ts +++ b/packages/tools/src/structureTools.ts @@ -1,4 +1,4 @@ -import { DatabaseInfo, TableInfo, ApplicationDefinition } from 'dbgate-types'; +import { DatabaseInfo, TableInfo, ApplicationDefinition, ViewInfo, CollectionInfo } from 'dbgate-types'; import _flatten from 'lodash/flatten'; export function addTableDependencies(db: DatabaseInfo): DatabaseInfo { @@ -118,3 +118,15 @@ export function isTableColumnUnique(table: TableInfo, column: string) { } return false; } + +export function isTableInfo(obj: { objectTypeField?: string }): obj is TableInfo { + return obj.objectTypeField == 'tables'; +} + +export function isViewInfo(obj: { objectTypeField?: string }): obj is ViewInfo { + return obj.objectTypeField == 'views'; +} + +export function isCollectionInfo(obj: { objectTypeField?: string }): obj is CollectionInfo { + return obj.objectTypeField == 'collections'; +} diff --git a/packages/web/src/perspectives/PerspectiveView.svelte b/packages/web/src/perspectives/PerspectiveView.svelte index db5e47184..a260f296b 100644 --- a/packages/web/src/perspectives/PerspectiveView.svelte +++ b/packages/web/src/perspectives/PerspectiveView.svelte @@ -28,7 +28,6 @@ import { ChangePerspectiveConfigFunc, extractPerspectiveDatabases, - PerspectiveCollectionNode, PerspectiveConfig, PerspectiveDataProvider, PerspectiveTableNode, @@ -142,20 +141,9 @@ $: dataProvider = new PerspectiveDataProvider(cache, loader, $dataPatterns); $: root = - tableInfo || viewInfo + tableInfo || viewInfo || collectionInfo ? new PerspectiveTableNode( - tableInfo || viewInfo, - $dbInfos, - config, - setConfig, - dataProvider, - { conid, database }, - null, - config.rootDesignerId - ) - : collectionInfo - ? new PerspectiveCollectionNode( - collectionInfo, + tableInfo || viewInfo || collectionInfo, $dbInfos, config, setConfig, diff --git a/plugins/dbgate-plugin-mongo/src/backend/driver.js b/plugins/dbgate-plugin-mongo/src/backend/driver.js index 756c6a5a1..a120145de 100644 --- a/plugins/dbgate-plugin-mongo/src/backend/driver.js +++ b/plugins/dbgate-plugin-mongo/src/backend/driver.js @@ -207,6 +207,10 @@ const driver = { if (options.countDocuments) { const count = await collection.countDocuments(convertObjectId(options.condition) || {}); return { count }; + } else if (options.aggregate) { + let cursor = await collection.aggregate(options.aggregate); + const rows = await cursor.toArray(); + return { rows: rows.map(transformMongoData) }; } else { // console.log('options.condition', JSON.stringify(options.condition, undefined, 2)); let cursor = await collection.find(convertObjectId(options.condition) || {}); From ccb52e9b58648e7aa6bf1c3e5cb6cba6b5066ef8 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 2 Oct 2022 10:19:01 +0200 Subject: [PATCH 07/24] fix --- .../datalib/src/PerspectiveDataPattern.ts | 15 ++++- packages/datalib/src/PerspectiveTreeNode.ts | 62 +++++++------------ 2 files changed, 34 insertions(+), 43 deletions(-) diff --git a/packages/datalib/src/PerspectiveDataPattern.ts b/packages/datalib/src/PerspectiveDataPattern.ts index 9540307fc..e61b0f6f8 100644 --- a/packages/datalib/src/PerspectiveDataPattern.ts +++ b/packages/datalib/src/PerspectiveDataPattern.ts @@ -4,8 +4,9 @@ import _isString from 'lodash/isString'; import _isPlainObject from 'lodash/isPlainObject'; import _isNumber from 'lodash/isNumber'; import _isBoolean from 'lodash/isBoolean'; +import _isArray from 'lodash/isArray'; -export type PerspectiveDataPatternColumnType = 'null' | 'string' | 'number' | 'boolean' | 'object'; +export type PerspectiveDataPatternColumnType = 'null' | 'string' | 'number' | 'boolean' | 'json'; export interface PerspectiveDataPatternColumn { name: string; @@ -27,6 +28,7 @@ function detectValueType(value): PerspectiveDataPatternColumnType { if (_isString(value)) return 'string'; if (_isNumber(value)) return 'number'; if (_isBoolean(value)) return 'boolean'; + if (_isPlainObject(value) || _isArray(value)) return 'json'; if (value == null) return 'null'; } @@ -42,10 +44,19 @@ function addObjectToColumns(columns: PerspectiveDataPatternColumn[], row) { }; columns.push(column); } - const type = detectValueType(row[key]); + const value = row[key]; + const type = detectValueType(value); if (!column.types.includes(type)) { column.types.push(type); } + if (_isPlainObject(value)) { + addObjectToColumns(column.columns, value); + } + if (_isArray(value)) { + for (const item of value) { + addObjectToColumns(column.columns, item); + } + } } } } diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index 746f54b93..692d2921f 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -729,6 +729,10 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId); } + get isChildColumn() { + return this.parentNode instanceof PerspectivePatternColumnNode; + } + // matchChildRow(parentRow: any, childRow: any): boolean { // if (!this.foreignKey) return false; // return parentRow[this.foreignKey.columns[0].columnName] == childRow[this.foreignKey.columns[0].refColumnName]; @@ -797,7 +801,7 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { } get isExpandable() { - return !!this.foreignKey; + return this.column.columns.length > 0; } get isSortable() { @@ -809,6 +813,20 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { } generateChildNodes(): PerspectiveTreeNode[] { + return this.column.columns.map( + column => + new PerspectivePatternColumnNode( + this.owner, + column, + this.dbs, + this.config, + this.setConfig, + this.dataProvider, + this.databaseConfig, + this, + null + ) + ); return []; // if (!this.foreignKey) return []; // const tbl = this?.db?.tables?.find( @@ -1237,7 +1255,7 @@ export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode { // console.log('CUSTOM JOIN', this.customJoin); // console.log('this.getDataLoadColumns()', this.getDataLoadColumns()); const isMongo = isCollectionInfo(this.table); - + return { schemaName: this.table.schemaName, pureName: this.table.pureName, @@ -1355,44 +1373,6 @@ function findDesignerIdForNode( return node; } -export function getCollectionChildPerspectiveNodes( - designerId: string, - collection: CollectionInfo, - dbs: MultipleDatabaseInfo, - config: PerspectiveConfig, - setConfig: ChangePerspectiveConfigFunc, - dataProvider: PerspectiveDataProvider, - databaseConfig: PerspectiveDatabaseConfig, - parentNode: PerspectiveTreeNode -) { - if (!collection) return []; - const db = parentNode.db; - - const pattern = dataProvider.dataPatterns[designerId]; - if (!pattern) return []; - - const columnNodes = pattern.columns.map(col => - findDesignerIdForNode( - config, - parentNode, - designerId => - new PerspectivePatternColumnNode( - collection, - col, - dbs, - config, - setConfig, - dataProvider, - databaseConfig, - parentNode, - designerId - ) - ) - ); - - return columnNodes; -} - export function getTableChildPerspectiveNodes( table: TableInfo | ViewInfo | CollectionInfo, dbs: MultipleDatabaseInfo, @@ -1405,7 +1385,7 @@ export function getTableChildPerspectiveNodes( if (!table) return []; const db = parentNode.db; - const pattern = dataProvider.dataPatterns[parentNode.designerId]; + const pattern = dataProvider?.dataPatterns?.[parentNode.designerId]; const tableOrView = isTableInfo(table) || isViewInfo(table) ? table : null; From 8b511a053260b0532db58ad856c24a0a7a4681ea Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 2 Oct 2022 10:19:53 +0200 Subject: [PATCH 08/24] removed commented code --- packages/datalib/src/PerspectiveTreeNode.ts | 169 -------------------- 1 file changed, 169 deletions(-) diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index 692d2921f..00e8ec422 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -975,146 +975,6 @@ export class PerspectiveTableNode extends PerspectiveTreeNode { }; } } - -// export class PerspectiveCollectionNode extends PerspectiveTreeNode { -// constructor( -// public collection: CollectionInfo, -// dbs: MultipleDatabaseInfo, -// config: PerspectiveConfig, -// setConfig: ChangePerspectiveConfigFunc, -// public dataProvider: PerspectiveDataProvider, -// databaseConfig: PerspectiveDatabaseConfig, -// parentNode: PerspectiveTreeNode, -// designerId: string -// ) { -// super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId); -// } - -// getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { -// return { -// schemaName: this.collection.schemaName, -// pureName: this.collection.pureName, -// dataColumns: this.getDataLoadColumns(), -// databaseConfig: this.databaseConfig, -// orderBy: this.getOrderBy(this.collection), -// mongoCondition: this.getChildrenMongoCondition(), -// engineType: 'docdb', -// }; -// } - -// get codeName() { -// return this.collection.schemaName -// ? `${this.collection.schemaName}:${this.collection.pureName}` -// : this.collection.pureName; -// } - -// get title() { -// return this.nodeConfig?.alias || this.collection.pureName; -// } - -// get isExpandable() { -// return true; -// } - -// generateChildNodes(): PerspectiveTreeNode[] { -// return getCollectionChildPerspectiveNodes( -// this.designerId, -// this.collection, -// this.dbs, -// this.config, -// this.setConfig, -// this.dataProvider, -// this.databaseConfig, -// this -// ); -// } - -// get icon() { -// return 'img collection'; -// } - -// getBaseTableFromThis() { -// return this.collection; -// } - -// get headerTableAttributes() { -// return { -// schemaName: this.collection.schemaName, -// pureName: this.collection.pureName, -// conid: this.databaseConfig.conid, -// database: this.databaseConfig.database, -// }; -// } - -// get tableCode() { -// return `${this.collection.schemaName}|${this.collection.pureName}`; -// } - -// get namedObject(): NamedObjectInfo { -// return { -// schemaName: this.collection.schemaName, -// pureName: this.collection.pureName, -// }; -// } -// } - -// export class PerspectiveViewNode extends PerspectiveTreeNode { -// constructor( -// public view: ViewInfo, -// dbs: MultipleDatabaseInfo, -// config: PerspectiveConfig, -// setConfig: ChangePerspectiveConfigFunc, -// public dataProvider: PerspectiveDataProvider, -// databaseConfig: PerspectiveDatabaseConfig, -// parentNode: PerspectiveTreeNode -// ) { -// super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig); -// } - -// getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { -// return { -// schemaName: this.view.schemaName, -// pureName: this.view.pureName, -// dataColumns: this.getDataLoadColumns(), -// databaseConfig: this.databaseConfig, -// orderBy: this.getOrderBy(this.view), -// condition: this.getChildrenCondition(), -// }; -// } - -// get codeName() { -// return this.view.schemaName ? `${this.view.schemaName}:${this.view.pureName}` : this.view.pureName; -// } - -// get title() { -// return this.view.pureName; -// } - -// get isExpandable() { -// return true; -// } - -// get childNodes(): PerspectiveTreeNode[] { -// return getTableChildPerspectiveNodes( -// this.view, -// this.dbs, -// this.config, -// this.setConfig, -// this.dataProvider, -// this.databaseConfig, -// this -// ); -// } - -// get icon() { -// return 'img table'; -// } - -// getBaseTableFromThis() { -// return this.view; -// } -// } - export class PerspectiveTableReferenceNode extends PerspectiveTableNode { constructor( public foreignKey: ForeignKeyInfo, @@ -1526,34 +1386,5 @@ export function getTableChildPerspectiveNodes( res.push(..._sortBy(customs, 'title')); - // const customs = []; - // for (const join of config.customJoins || []) { - // if (join.baseUniqueName == parentColumn.uniqueName) { - // const newConfig = { ...databaseConfig }; - // if (join.conid) newConfig.conid = join.conid; - // if (join.database) newConfig.database = join.database; - // const db = dbs?.[newConfig.conid]?.[newConfig.database]; - // const table = db?.tables?.find(x => x.pureName == join.refTableName && x.schemaName == join.refSchemaName); - // const view = db?.views?.find(x => x.pureName == join.refTableName && x.schemaName == join.refSchemaName); - - // if (table || view) { - // customs.push( - // new PerspectiveCustomJoinTreeNode( - // join, - // table || view, - // dbs, - // config, - // setConfig, - // dataProvider, - // newConfig, - // parentColumn, - // null - // ) - // ); - // } - // } - // } - // res.push(..._sortBy(customs, 'title')); - return res; } From d647d30258b8818871da11b320181408d6ffa10f Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 2 Oct 2022 11:30:05 +0200 Subject: [PATCH 09/24] perspective nosql test --- .../datalib/src/PerspectiveDataPattern.ts | 2 +- packages/datalib/src/PerspectiveDisplay.ts | 7 +-- packages/datalib/src/PerspectiveTreeNode.ts | 35 +++++++++++-- .../src/tests/PerspectiveDisplay.test.ts | 52 +++++++++++++++++++ 4 files changed, 88 insertions(+), 8 deletions(-) diff --git a/packages/datalib/src/PerspectiveDataPattern.ts b/packages/datalib/src/PerspectiveDataPattern.ts index e61b0f6f8..6974ba5c8 100644 --- a/packages/datalib/src/PerspectiveDataPattern.ts +++ b/packages/datalib/src/PerspectiveDataPattern.ts @@ -17,7 +17,7 @@ export interface PerspectiveDataPatternColumn { export interface PerspectiveDataPattern { conid: string; database: string; - schemaName: string; + schemaName?: string; pureName: string; columns: PerspectiveDataPatternColumn[]; } diff --git a/packages/datalib/src/PerspectiveDisplay.ts b/packages/datalib/src/PerspectiveDisplay.ts index 7bf8d3e14..75d7bfa46 100644 --- a/packages/datalib/src/PerspectiveDisplay.ts +++ b/packages/datalib/src/PerspectiveDisplay.ts @@ -126,14 +126,14 @@ export class PerspectiveDisplay { fillColumns(children: PerspectiveTreeNode[], parentNodes: PerspectiveTreeNode[]) { for (const child of children) { - if (child.isCheckedColumn || child.isCheckedNode) { + if (child.generatesHiearchicGridColumn || child.generatesDataGridColumn) { this.processColumn(child, parentNodes); } } } processColumn(node: PerspectiveTreeNode, parentNodes: PerspectiveTreeNode[]) { - if (node.isCheckedColumn) { + if (node.generatesDataGridColumn) { const column = new PerspectiveDisplayColumn(this); column.title = node.columnTitle; column.dataField = node.dataField; @@ -145,7 +145,7 @@ export class PerspectiveDisplay { this.columns.push(column); } - if (node.isExpandable && node.isCheckedNode) { + if (node.generatesHiearchicGridColumn) { const countBefore = this.columns.length; this.fillColumns(node.childNodes, [...parentNodes, node]); @@ -185,6 +185,7 @@ export class PerspectiveDisplay { const subRowCollections = []; for (const node of treeNodes) { + // console.log('sourceRow[node.fieldName]', node.fieldName, sourceRow[node.fieldName]); if (sourceRow[node.fieldName]) { const subrows = { rows: this.collectRows(sourceRow[node.fieldName], node.childNodes), diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index 00e8ec422..b0c8a5e99 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -81,7 +81,7 @@ export abstract class PerspectiveTreeNode { this.parentNodeConfig = parentNode?.nodeConfig; } readonly nodeConfig: PerspectiveNodeConfig; - readonly parentNodeConfig: PerspectiveNodeConfig; + parentNodeConfig: PerspectiveNodeConfig; // defaultChecked: boolean; abstract get title(); abstract get codeName(); @@ -110,6 +110,15 @@ export abstract class PerspectiveTreeNode { get namedObject(): NamedObjectInfo { return null; } + get parentTableNode(): PerspectiveTableNode { + if (this instanceof PerspectiveTableNode) { + return this; + } + if (this.parentNode == null) { + return null; + } + return this.parentNode.parentTableNode; + } abstract getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps; get isRoot() { return this.parentNode == null; @@ -121,6 +130,12 @@ export abstract class PerspectiveTreeNode { get isSortable() { return false; } + get generatesHiearchicGridColumn() { + return this.isExpandable && this.isCheckedNode; + } + get generatesDataGridColumn() { + return this.isCheckedColumn; + } matchChildRow(parentRow: any, childRow: any): boolean { return true; } @@ -273,12 +288,12 @@ export abstract class PerspectiveTreeNode { [field]: isIncluded ? [...(n[field] || []), this.codeName] : (n[field] || []).filter(x => x != this.codeName), }); - const [cfgChanged, nodeCfg] = this.parentNode?.ensureNodeConfig(cfg); + const [cfgChanged, nodeCfg] = this.parentTableNode?.ensureNodeConfig(cfg); return { ...cfgChanged, nodes: cfgChanged.nodes.map(n => - n.designerId == (this.parentNode?.designerId || nodeCfg?.designerId) ? changedFields(n) : n + n.designerId == (this.parentTableNode?.designerId || nodeCfg?.designerId) ? changedFields(n) : n ), }; }); @@ -727,6 +742,7 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { designerId: string ) { super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId); + this.parentNodeConfig = this.parentTableNode?.nodeConfig; } get isChildColumn() { @@ -780,11 +796,22 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { return null; } + get generatesHiearchicGridColumn() { + return !!this.parentTableNode?.nodeConfig?.checkedColumns?.find(x => x.startsWith(this.codeName + '::')); + } + + // get generatesHiearchicGridColumn() { + // // return this.config &&; + // } + get icon() { return 'img column'; } get codeName() { + if (this.parentNode instanceof PerspectivePatternColumnNode) { + return `${this.parentNode.codeName}::${this.column.name}`; + } return this.column.name; } @@ -793,7 +820,7 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { } get fieldName() { - return this.codeName + 'Ref'; + return this.column.name; } get title() { diff --git a/packages/datalib/src/tests/PerspectiveDisplay.test.ts b/packages/datalib/src/tests/PerspectiveDisplay.test.ts index f86cc6b89..dfba7a3aa 100644 --- a/packages/datalib/src/tests/PerspectiveDisplay.test.ts +++ b/packages/datalib/src/tests/PerspectiveDisplay.test.ts @@ -7,6 +7,9 @@ import artistDataFlat from './artistDataFlat'; import artistDataAlbum from './artistDataAlbum'; import artistDataAlbumTrack from './artistDataAlbumTrack'; import { processPerspectiveDefaultColunns } from '../processPerspectiveDefaultColunns'; +import { DatabaseAnalyser, isCollectionInfo } from 'dbgate-tools'; +import { analyseDataPattern } from '../PerspectiveDataPattern'; +import { PerspectiveDataProvider } from '../PerspectiveDataProvider'; test('test flat view', () => { const artistTable = chinookDbInfo.tables.find(x => x.pureName == 'Artist'); @@ -141,3 +144,52 @@ test('test two level nesting', () => { expect(display.rows[2].rowSpans).toEqual([1, 2, 1]); expect(display.rows[2].rowCellSkips).toEqual([true, false, false]); }); + +test('test nosql display', () => { + const collectionInfo = { + objectTypeField: 'collections', + pureName: 'Account', + }; + const dbInfo = { + ...DatabaseAnalyser.createEmptyStructure(), + collections: [collectionInfo], + }; + const accountData = [ + { name: 'jan', email: 'jan@foo.co', follows: [{ name: 'lucie' }, { name: 'petr' }] }, + { name: 'romeo', email: 'romeo@foo.co', follows: [{ name: 'julie' }, { name: 'wiliam' }] }, + ]; + const config = createPerspectiveConfig({ pureName: 'Account' }); + const dataPatterns = { + [config.rootDesignerId]: analyseDataPattern( + { + conid: 'conid', + database: 'db', + pureName: 'Account', + }, + accountData + ), + }; + + const configColumns = processPerspectiveDefaultColunns( + config, + { conid: { db: dbInfo } }, + dataPatterns, + 'conid', + 'db' + ); + const root = new PerspectiveTableNode( + collectionInfo, + { conid: { db: dbInfo } }, + configColumns, + null, + new PerspectiveDataProvider(null, null, dataPatterns), + { conid: 'conid', database: 'db' }, + null, + configColumns.rootDesignerId + ); + const display = new PerspectiveDisplay(root, accountData); + + expect(display.rows.length).toEqual(2); + expect(display.rows[0].rowData).toEqual(['jan']); + expect(display.rows[1].rowData).toEqual(['romeo']); +}); From 7a3c46b691ff02f0f5f1a7ad90409c0fe4a35de6 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 2 Oct 2022 12:01:07 +0200 Subject: [PATCH 10/24] perspective display - mongo nested objects --- packages/datalib/src/PerspectiveDisplay.ts | 31 ++++-- packages/datalib/src/PerspectiveTreeNode.ts | 1 + .../src/tests/PerspectiveDisplay.test.ts | 53 ---------- .../src/tests/PerspectiveDisplayNoSql.test.ts | 98 +++++++++++++++++++ 4 files changed, 124 insertions(+), 59 deletions(-) create mode 100644 packages/datalib/src/tests/PerspectiveDisplayNoSql.test.ts diff --git a/packages/datalib/src/PerspectiveDisplay.ts b/packages/datalib/src/PerspectiveDisplay.ts index 75d7bfa46..e3d16455c 100644 --- a/packages/datalib/src/PerspectiveDisplay.ts +++ b/packages/datalib/src/PerspectiveDisplay.ts @@ -3,6 +3,8 @@ import _max from 'lodash/max'; import _range from 'lodash/max'; import _fill from 'lodash/fill'; import _findIndex from 'lodash/findIndex'; +import _isPlainObject from 'lodash/isPlainObject'; +import _isArray from 'lodash/isArray'; import debug from 'debug'; const dbg = debug('dbgate:PerspectiveDisplay'); @@ -167,13 +169,30 @@ export class PerspectiveDisplay { // return _findIndex(this.columns, x => x.dataNode.designerId == node.designerId); // } + extractArray(value) { + if (_isArray(value)) return value; + if (_isPlainObject(value)) return [value]; + return []; + } + collectRows(sourceRows: any[], nodes: PerspectiveTreeNode[]): CollectedPerspectiveDisplayRow[] { // console.log('********** COLLECT ROWS', sourceRows); - const columnNodes = nodes.filter(x => x.isCheckedColumn); - const treeNodes = nodes.filter(x => x.isCheckedNode); + const columnNodes = nodes.filter(x => x.generatesDataGridColumn); + const treeNodes = nodes.filter(x => x.generatesHiearchicGridColumn); - // console.log('columnNodes', columnNodes); - // console.log('treeNodes', treeNodes); + // console.log( + // 'columnNodes', + // columnNodes.map(x => x.title) + // ); + // console.log( + // 'treeNodes', + // treeNodes.map(x => x.title) + // ); + + // console.log( + // 'nodes', + // nodes.map(x => x.title) + // ); const columnIndexes = columnNodes.map(node => this.findColumnIndexFromNode(node)); @@ -181,14 +200,14 @@ export class PerspectiveDisplay { for (const sourceRow of sourceRows) { // console.log('PROCESS SOURCE', sourceRow); // row.startIndex = startIndex; - const rowData = columnNodes.map(node => sourceRow[node.codeName]); + const rowData = columnNodes.map(node => sourceRow[node.fieldName]); const subRowCollections = []; for (const node of treeNodes) { // console.log('sourceRow[node.fieldName]', node.fieldName, sourceRow[node.fieldName]); if (sourceRow[node.fieldName]) { const subrows = { - rows: this.collectRows(sourceRow[node.fieldName], node.childNodes), + rows: this.collectRows(this.extractArray(sourceRow[node.fieldName]), node.childNodes), }; subRowCollections.push(subrows); } diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index b0c8a5e99..8b4bce444 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -797,6 +797,7 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { } get generatesHiearchicGridColumn() { + // console.log('generatesHiearchicGridColumn', this.parentTableNode?.nodeConfig?.checkedColumns, this.codeName + '::'); return !!this.parentTableNode?.nodeConfig?.checkedColumns?.find(x => x.startsWith(this.codeName + '::')); } diff --git a/packages/datalib/src/tests/PerspectiveDisplay.test.ts b/packages/datalib/src/tests/PerspectiveDisplay.test.ts index dfba7a3aa..82bbedc82 100644 --- a/packages/datalib/src/tests/PerspectiveDisplay.test.ts +++ b/packages/datalib/src/tests/PerspectiveDisplay.test.ts @@ -1,4 +1,3 @@ -import { TableInfo } from 'dbgate-types'; import { PerspectiveDisplay } from '../PerspectiveDisplay'; import { PerspectiveTableNode } from '../PerspectiveTreeNode'; import { chinookDbInfo } from './chinookDbInfo'; @@ -7,9 +6,6 @@ import artistDataFlat from './artistDataFlat'; import artistDataAlbum from './artistDataAlbum'; import artistDataAlbumTrack from './artistDataAlbumTrack'; import { processPerspectiveDefaultColunns } from '../processPerspectiveDefaultColunns'; -import { DatabaseAnalyser, isCollectionInfo } from 'dbgate-tools'; -import { analyseDataPattern } from '../PerspectiveDataPattern'; -import { PerspectiveDataProvider } from '../PerspectiveDataProvider'; test('test flat view', () => { const artistTable = chinookDbInfo.tables.find(x => x.pureName == 'Artist'); @@ -144,52 +140,3 @@ test('test two level nesting', () => { expect(display.rows[2].rowSpans).toEqual([1, 2, 1]); expect(display.rows[2].rowCellSkips).toEqual([true, false, false]); }); - -test('test nosql display', () => { - const collectionInfo = { - objectTypeField: 'collections', - pureName: 'Account', - }; - const dbInfo = { - ...DatabaseAnalyser.createEmptyStructure(), - collections: [collectionInfo], - }; - const accountData = [ - { name: 'jan', email: 'jan@foo.co', follows: [{ name: 'lucie' }, { name: 'petr' }] }, - { name: 'romeo', email: 'romeo@foo.co', follows: [{ name: 'julie' }, { name: 'wiliam' }] }, - ]; - const config = createPerspectiveConfig({ pureName: 'Account' }); - const dataPatterns = { - [config.rootDesignerId]: analyseDataPattern( - { - conid: 'conid', - database: 'db', - pureName: 'Account', - }, - accountData - ), - }; - - const configColumns = processPerspectiveDefaultColunns( - config, - { conid: { db: dbInfo } }, - dataPatterns, - 'conid', - 'db' - ); - const root = new PerspectiveTableNode( - collectionInfo, - { conid: { db: dbInfo } }, - configColumns, - null, - new PerspectiveDataProvider(null, null, dataPatterns), - { conid: 'conid', database: 'db' }, - null, - configColumns.rootDesignerId - ); - const display = new PerspectiveDisplay(root, accountData); - - expect(display.rows.length).toEqual(2); - expect(display.rows[0].rowData).toEqual(['jan']); - expect(display.rows[1].rowData).toEqual(['romeo']); -}); diff --git a/packages/datalib/src/tests/PerspectiveDisplayNoSql.test.ts b/packages/datalib/src/tests/PerspectiveDisplayNoSql.test.ts new file mode 100644 index 000000000..aa08e0f83 --- /dev/null +++ b/packages/datalib/src/tests/PerspectiveDisplayNoSql.test.ts @@ -0,0 +1,98 @@ +import { PerspectiveDisplay } from '../PerspectiveDisplay'; +import { PerspectiveTableNode } from '../PerspectiveTreeNode'; +import { createPerspectiveConfig, PerspectiveNodeConfig } from '../PerspectiveConfig'; +import { processPerspectiveDefaultColunns } from '../processPerspectiveDefaultColunns'; +import { DatabaseAnalyser } from 'dbgate-tools'; +import { analyseDataPattern } from '../PerspectiveDataPattern'; +import { PerspectiveDataProvider } from '../PerspectiveDataProvider'; + +const accountData = [ + { + name: 'jan', + email: 'jan@foo.co', + follows: [{ name: 'lucie' }, { name: 'petr' }], + nested: { email: 'jan@nest.cz' }, + }, + { + name: 'romeo', + email: 'romeo@foo.co', + follows: [{ name: 'julie' }, { name: 'wiliam' }], + nested: { email: 'romeo@nest.cz' }, + }, +]; + +function createDisplay(cfgFunc?: (cfg: PerspectiveNodeConfig) => void) { + const collectionInfo = { + objectTypeField: 'collections', + pureName: 'Account', + }; + const dbInfo = { + ...DatabaseAnalyser.createEmptyStructure(), + collections: [collectionInfo], + }; + const config = createPerspectiveConfig({ pureName: 'Account' }); + const dataPatterns = { + [config.rootDesignerId]: analyseDataPattern( + { + conid: 'conid', + database: 'db', + pureName: 'Account', + }, + accountData + ), + }; + const configColumns = processPerspectiveDefaultColunns( + config, + { conid: { db: dbInfo } }, + dataPatterns, + 'conid', + 'db' + ); + if (cfgFunc) { + cfgFunc(configColumns.nodes[0]); + } + const root = new PerspectiveTableNode( + collectionInfo, + { conid: { db: dbInfo } }, + configColumns, + null, + new PerspectiveDataProvider(null, null, dataPatterns), + { conid: 'conid', database: 'db' }, + null, + configColumns.rootDesignerId + ); + + const display = new PerspectiveDisplay(root, accountData); + + return display; +} + +test('test nosql display', () => { + const display = createDisplay(); + + expect(display.rows.length).toEqual(2); + expect(display.rows[0].rowData).toEqual(['jan']); + expect(display.rows[1].rowData).toEqual(['romeo']); +}); + +test('test nosql nested array display', () => { + const display = createDisplay(cfg => { + cfg.checkedColumns = ['name', 'follows::name']; + }); + + expect(display.rows.length).toEqual(4); + expect(display.rows[0].rowData).toEqual(['jan', 'lucie']); + expect(display.rows[1].rowData).toEqual([undefined, 'petr']); + expect(display.rows[2].rowData).toEqual(['romeo', 'julie']); + expect(display.rows[3].rowData).toEqual([undefined, 'wiliam']); +}); + +test('test nosql nested object', () => { + const display = createDisplay(cfg => { + cfg.checkedColumns = ['name', 'nested::email']; + }); + + expect(display.rows.length).toEqual(2); + expect(display.rows[0].rowData).toEqual(['jan', 'jan@nest.cz']); + expect(display.rows[1].rowData).toEqual(['romeo', 'romeo@nest.cz']); +}); From 1d85a1753335d72e55ba9f4a191adf2382e94746 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 2 Oct 2022 15:43:00 +0200 Subject: [PATCH 11/24] fix --- packages/datalib/src/PerspectiveDisplay.ts | 2 +- packages/datalib/src/PerspectiveTreeNode.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/datalib/src/PerspectiveDisplay.ts b/packages/datalib/src/PerspectiveDisplay.ts index e3d16455c..f7f5521e7 100644 --- a/packages/datalib/src/PerspectiveDisplay.ts +++ b/packages/datalib/src/PerspectiveDisplay.ts @@ -200,7 +200,7 @@ export class PerspectiveDisplay { for (const sourceRow of sourceRows) { // console.log('PROCESS SOURCE', sourceRow); // row.startIndex = startIndex; - const rowData = columnNodes.map(node => sourceRow[node.fieldName]); + const rowData = columnNodes.map(node => sourceRow[node.columnName]); const subRowCollections = []; for (const node of treeNodes) { diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index 8b4bce444..c94cd79c3 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -606,6 +606,7 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode { get fieldName() { return this.codeName + 'Ref'; + // return this.codeName ; } get title() { From 23b345c898783503d9120b003e9a3ffe2a3bf4e5 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 2 Oct 2022 17:48:03 +0200 Subject: [PATCH 12/24] perspective mongo fixes --- packages/datalib/src/PerspectiveCache.ts | 3 ++- packages/datalib/src/PerspectiveDataLoader.ts | 4 ---- packages/datalib/src/PerspectiveDataProvider.ts | 2 ++ packages/datalib/src/PerspectiveTreeNode.ts | 2 ++ 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/datalib/src/PerspectiveCache.ts b/packages/datalib/src/PerspectiveCache.ts index e794e9c4f..e706b8ae4 100644 --- a/packages/datalib/src/PerspectiveCache.ts +++ b/packages/datalib/src/PerspectiveCache.ts @@ -35,6 +35,7 @@ export class PerspectiveCacheTable { pureName: string; bindingColumns?: string[]; dataColumns: string[]; + allColumns?: boolean; loadedAll: boolean; loadedRows: any[] = []; bindingGroups: { [bindingKey: string]: PerspectiveBindingGroup } = {}; @@ -103,7 +104,7 @@ export class PerspectiveCache { ); let res = this.tables[tableKey]; - if (res && _difference(props.dataColumns, res.dataColumns).length > 0) { + if (res && _difference(props.dataColumns, res.dataColumns).length > 0 && !res.allColumns) { dbg('Delete cache because incomplete columns', props.pureName, res.dataColumns); // we have incomplete cache diff --git a/packages/datalib/src/PerspectiveDataLoader.ts b/packages/datalib/src/PerspectiveDataLoader.ts index e1e82f984..0e1a9ee13 100644 --- a/packages/datalib/src/PerspectiveDataLoader.ts +++ b/packages/datalib/src/PerspectiveDataLoader.ts @@ -247,10 +247,6 @@ export class PerspectiveDataLoader { engineType, } = props; - if (dataColumns?.length == 0) { - return []; - } - if (dbg?.enabled) { dbg( `LOAD DATA, collection=${props.pureName}, columns=${props.dataColumns?.join(',')}, range=${ diff --git a/packages/datalib/src/PerspectiveDataProvider.ts b/packages/datalib/src/PerspectiveDataProvider.ts index b2a6ee115..b719d5c25 100644 --- a/packages/datalib/src/PerspectiveDataProvider.ts +++ b/packages/datalib/src/PerspectiveDataProvider.ts @@ -20,6 +20,7 @@ export interface PerspectiveDataLoadProps { schemaName?: string; pureName: string; dataColumns?: string[]; + allColumns?: boolean; orderBy: { columnName: string; order: 'ASC' | 'DESC'; @@ -189,6 +190,7 @@ export class PerspectiveDataProvider { // load missing rows tableCache.dataColumns = props.dataColumns; + tableCache.allColumns = props.allColumns; const nextRows = await this.loader.loadData({ ...props, diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index c94cd79c3..6074d01ac 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -944,6 +944,7 @@ export class PerspectiveTableNode extends PerspectiveTreeNode { schemaName: this.table.schemaName, pureName: this.table.pureName, dataColumns: this.getDataLoadColumns(), + allColumns: isMongo, databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.table), sqlCondition: isMongo ? null : this.getChildrenSqlCondition(), @@ -1154,6 +1155,7 @@ export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode { stableStringify ), dataColumns: this.getDataLoadColumns(), + allColumns: isMongo, databaseConfig: this.databaseConfig, orderBy: this.getOrderBy(this.table), sqlCondition: isMongo ? null : this.getChildrenSqlCondition(), From be0aeeb2c8390b084606eb71419e9e33138ae6ef Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 2 Oct 2022 20:52:22 +0200 Subject: [PATCH 13/24] perspective - pattern for SQL sources --- .../api/src/proc/databaseConnectionProcess.js | 2 +- packages/datalib/src/PerspectiveDataLoader.ts | 43 ++++++++++++++----- .../datalib/src/PerspectiveDataPattern.ts | 18 ++++++++ .../datalib/src/PerspectiveDataProvider.ts | 2 +- packages/datalib/src/PerspectiveTreeNode.ts | 43 ++++++++++++------- .../src/utility/usePerspectiveDataPatterns.ts | 32 +++++++++----- 6 files changed, 102 insertions(+), 38 deletions(-) diff --git a/packages/api/src/proc/databaseConnectionProcess.js b/packages/api/src/proc/databaseConnectionProcess.js index 3cd88c580..88f5ef8d9 100644 --- a/packages/api/src/proc/databaseConnectionProcess.js +++ b/packages/api/src/proc/databaseConnectionProcess.js @@ -177,7 +177,7 @@ async function handleQueryData({ msgid, sql }, skipReadonlyCheck = false) { const res = await driver.query(systemConnection, sql); process.send({ msgtype: 'response', msgid, ...res }); } catch (err) { - process.send({ msgtype: 'response', msgid, errorMessage: err.message }); + process.send({ msgtype: 'response', msgid, errorMessage: err.message || 'Error executing SQL script' }); } } diff --git a/packages/datalib/src/PerspectiveDataLoader.ts b/packages/datalib/src/PerspectiveDataLoader.ts index 0e1a9ee13..1c9df1439 100644 --- a/packages/datalib/src/PerspectiveDataLoader.ts +++ b/packages/datalib/src/PerspectiveDataLoader.ts @@ -2,6 +2,26 @@ import { Condition, Expression, Select } from 'dbgate-sqltree'; import { PerspectiveDataLoadProps } from './PerspectiveDataProvider'; import debug from 'debug'; import _zipObject from 'lodash/zipObject'; +import _mapValues from 'lodash/mapValues'; +import _isArray from 'lodash/isArray'; +import { safeJsonParse } from 'dbgate-tools'; + +function normalizeLoadedRow(row) { + return _mapValues(row, v => safeJsonParse(v) || v); +} + +function normalizeResult(result) { + if (_isArray(result)) { + return result.map(normalizeLoadedRow); + } + if (result.errorMessage) { + return result; + } + return { + ...result, + errorMessage: 'Unspecified error', + }; +} const dbg = debug('dbgate:PerspectiveDataLoader'); @@ -187,14 +207,17 @@ export class PerspectiveDataLoader { }, })), selectAll: !dataColumns, - orderBy: orderBy?.map(({ columnName, order }) => ({ - exprType: 'column', - columnName, - direction: order, - source: { - name: { schemaName, pureName }, - }, - })), + orderBy: + orderBy?.length > 0 + ? orderBy?.map(({ columnName, order }) => ({ + exprType: 'column', + columnName, + direction: order, + source: { + name: { schemaName, pureName }, + }, + })) + : null, range: props.range, where: this.buildSqlCondition(props), }; @@ -271,9 +294,9 @@ export class PerspectiveDataLoader { const { engineType } = props; switch (engineType) { case 'sqldb': - return this.loadDataSqlDb(props); + return normalizeResult(await this.loadDataSqlDb(props)); case 'docdb': - return this.loadDataDocDb(props); + return normalizeResult(await this.loadDataDocDb(props)); } } diff --git a/packages/datalib/src/PerspectiveDataPattern.ts b/packages/datalib/src/PerspectiveDataPattern.ts index 6974ba5c8..d51e20349 100644 --- a/packages/datalib/src/PerspectiveDataPattern.ts +++ b/packages/datalib/src/PerspectiveDataPattern.ts @@ -5,6 +5,7 @@ import _isPlainObject from 'lodash/isPlainObject'; import _isNumber from 'lodash/isNumber'; import _isBoolean from 'lodash/isBoolean'; import _isArray from 'lodash/isArray'; +import { safeJsonParse } from 'dbgate-tools'; export type PerspectiveDataPatternColumnType = 'null' | 'string' | 'number' | 'boolean' | 'json'; @@ -57,6 +58,22 @@ function addObjectToColumns(columns: PerspectiveDataPatternColumn[], row) { addObjectToColumns(column.columns, item); } } + if (_isString(value)) { + const json = safeJsonParse(value); + if (json && (_isPlainObject(json) || _isArray(json))) { + if (!column.types.includes('json')) { + column.types.push('json'); + } + if (_isPlainObject(json)) { + addObjectToColumns(column.columns, json); + } + if (_isArray(json)) { + for (const item of json) { + addObjectToColumns(column.columns, item); + } + } + } + } } } } @@ -69,6 +86,7 @@ export function analyseDataPattern( ...patternBase, columns: [], }; + // console.log('ROWS', rows); for (const row of rows) { addObjectToColumns(res.columns, row); } diff --git a/packages/datalib/src/PerspectiveDataProvider.ts b/packages/datalib/src/PerspectiveDataProvider.ts index b719d5c25..18166bb97 100644 --- a/packages/datalib/src/PerspectiveDataProvider.ts +++ b/packages/datalib/src/PerspectiveDataProvider.ts @@ -1,7 +1,6 @@ import debug from 'debug'; import { Condition } from 'dbgate-sqltree'; import { RangeDefinition } from 'dbgate-types'; -import { format } from 'path'; import { PerspectiveBindingGroup, PerspectiveCache } from './PerspectiveCache'; import { PerspectiveDataLoader } from './PerspectiveDataLoader'; import { PerspectiveDataPatternDict } from './PerspectiveDataPattern'; @@ -10,6 +9,7 @@ export const PERSPECTIVE_PAGE_SIZE = 100; const dbg = debug('dbgate:PerspectiveDataProvider'); + export interface PerspectiveDatabaseConfig { conid: string; database: string; diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index 6074d01ac..dd6bf3910 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -309,6 +309,10 @@ export abstract class PerspectiveTreeNode { ...this.childNodes.map(x => x.childDataColumn), ..._flatten(this.childNodes.filter(x => x.isExpandable && x.isChecked).map(x => x.getChildMatchColumns())), ...this.getParentMatchColumns(), + ...this.childNodes + .filter(x => x instanceof PerspectivePatternColumnNode) + .filter(x => this.nodeConfig?.checkedColumns?.find(y => y.startsWith(x.codeName + '::'))) + .map(x => x.columnName), ]) ); } @@ -1282,21 +1286,30 @@ export function getTableChildPerspectiveNodes( const columnNodes = tableOrView?.columns?.map(col => - findDesignerIdForNode( - config, - parentNode, - designerId => - new PerspectiveTableColumnNode( - col, - tableOrView, - dbs, - config, - setConfig, - dataProvider, - databaseConfig, - parentNode, - designerId - ) + findDesignerIdForNode(config, parentNode, designerId => + pattern?.columns?.find(x => x.name == col.columnName)?.types.includes('json') + ? new PerspectivePatternColumnNode( + table, + pattern?.columns?.find(x => x.name == col.columnName), + dbs, + config, + setConfig, + dataProvider, + databaseConfig, + parentNode, + designerId + ) + : new PerspectiveTableColumnNode( + col, + tableOrView, + dbs, + config, + setConfig, + dataProvider, + databaseConfig, + parentNode, + designerId + ) ) ) || pattern?.columns?.map(col => diff --git a/packages/web/src/utility/usePerspectiveDataPatterns.ts b/packages/web/src/utility/usePerspectiveDataPatterns.ts index 525a5dd67..f29c44cea 100644 --- a/packages/web/src/utility/usePerspectiveDataPatterns.ts +++ b/packages/web/src/utility/usePerspectiveDataPatterns.ts @@ -29,15 +29,7 @@ export function getPerspectiveDataPatternsFromCache( ); if (cached) { res[node.designerId] = cached; - continue; } - - const db = dbInfos?.[conid]?.[database]; - - if (!db) continue; - - const collection = db.collections?.find(x => x.pureName == pureName && x.schemaName == schemaName); - if (!collection) continue; } return res; @@ -69,20 +61,38 @@ export async function getPerspectiveDataPatterns( if (!db) continue; + const table = db.tables?.find(x => x.pureName == pureName && x.schemaName == schemaName); + const view = db.views?.find(x => x.pureName == pureName && x.schemaName == schemaName); const collection = db.collections?.find(x => x.pureName == pureName && x.schemaName == schemaName); - if (!collection) continue; + if (!table && !view && !collection) continue; + + // console.log('LOAD PATTERN FOR', pureName); const props: PerspectiveDataLoadProps = { databaseConfig: { conid, database }, - engineType: 'docdb', + engineType: collection ? 'docdb' : 'sqldb', + schemaName, pureName, - orderBy: [], + orderBy: table?.primaryKey + ? table?.primaryKey.columns.map(x => ({ columnName: x.columnName, order: 'ASC' })) + : table || view + ? [{ columnName: (table || view).columns[0].columnName, order: 'ASC' }] + : null, range: { offset: 0, limit: 10, }, }; + // console.log('LOAD PROPS', props); const rows = await dataLoader.loadData(props); + + if (rows.errorMessage) { + console.error('Error loading pattern for', pureName, ':', rows.errorMessage); + continue; + } + + // console.log('PATTERN ROWS', rows); + const pattern = analyseDataPattern( { conid, From c712005e33ff633c3f31e67f6edac3da1c255693 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 2 Oct 2022 21:00:29 +0200 Subject: [PATCH 14/24] v5.1.5-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a9b0fe531..aaad4c9e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "5.1.4", + "version": "5.1.5-beta.1", "name": "dbgate-all", "workspaces": [ "packages/*", From 585806134982b3cf098851f2a9fafc4720cfcb89 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 2 Oct 2022 21:03:22 +0200 Subject: [PATCH 15/24] v5.1.5-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aaad4c9e4..d1ef2fff7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "5.1.5-beta.1", + "version": "5.1.5-beta.2", "name": "dbgate-all", "workspaces": [ "packages/*", From 18519b55194adf16569995fd71441700cd2ca746 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 2 Oct 2022 21:04:41 +0200 Subject: [PATCH 16/24] v5.1.5-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d1ef2fff7..2ad363109 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "5.1.5-beta.2", + "version": "5.1.5-beta.3", "name": "dbgate-all", "workspaces": [ "packages/*", From 44be1bdd11af3cf00f8bca6c52897f4ec21879e1 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 13 Oct 2022 12:36:48 +0200 Subject: [PATCH 17/24] menu label --- packages/web/src/appobj/DatabaseObjectAppObject.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/appobj/DatabaseObjectAppObject.svelte b/packages/web/src/appobj/DatabaseObjectAppObject.svelte index 2794baf57..375d8e86a 100644 --- a/packages/web/src/appobj/DatabaseObjectAppObject.svelte +++ b/packages/web/src/appobj/DatabaseObjectAppObject.svelte @@ -346,7 +346,7 @@ }, }, { - label: 'Open perspective', + label: 'Design perspective query', tab: 'PerspectiveTab', forceNewTab: true, icon: 'img perspective', From 0028240552be3b418f9c2a62970daa2b6fc7dd5e Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 13 Oct 2022 13:49:48 +0200 Subject: [PATCH 18/24] perspective fix --- packages/datalib/src/PerspectiveConfig.ts | 7 ++++++ .../datalib/src/PerspectiveDataProvider.ts | 9 ++------ packages/datalib/src/PerspectiveTreeNode.ts | 23 ++++++++++++++----- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/datalib/src/PerspectiveConfig.ts b/packages/datalib/src/PerspectiveConfig.ts index 1f8ec5a89..d7af928f6 100644 --- a/packages/datalib/src/PerspectiveConfig.ts +++ b/packages/datalib/src/PerspectiveConfig.ts @@ -7,6 +7,13 @@ import uuidv1 from 'uuid/v1'; // uncheckedColumns: string[]; // } +export type PerspectiveDatabaseEngineType = 'sqldb' | 'docdb'; + +export interface PerspectiveDatabaseConfig { + conid: string; + database: string; +} + export interface PerspectiveCustomJoinConfig { refNodeDesignerId: string; referenceDesignerId: string; diff --git a/packages/datalib/src/PerspectiveDataProvider.ts b/packages/datalib/src/PerspectiveDataProvider.ts index 18166bb97..5c471c55e 100644 --- a/packages/datalib/src/PerspectiveDataProvider.ts +++ b/packages/datalib/src/PerspectiveDataProvider.ts @@ -4,17 +4,12 @@ import { RangeDefinition } from 'dbgate-types'; import { PerspectiveBindingGroup, PerspectiveCache } from './PerspectiveCache'; import { PerspectiveDataLoader } from './PerspectiveDataLoader'; import { PerspectiveDataPatternDict } from './PerspectiveDataPattern'; +import { PerspectiveDatabaseConfig, PerspectiveDatabaseEngineType } from './PerspectiveConfig'; export const PERSPECTIVE_PAGE_SIZE = 100; const dbg = debug('dbgate:PerspectiveDataProvider'); - -export interface PerspectiveDatabaseConfig { - conid: string; - database: string; -} - export interface PerspectiveDataLoadProps { databaseConfig: PerspectiveDatabaseConfig; schemaName?: string; @@ -31,7 +26,7 @@ export interface PerspectiveDataLoadProps { topCount?: number; sqlCondition?: Condition; mongoCondition?: any; - engineType: 'sqldb' | 'docdb'; + engineType: PerspectiveDatabaseEngineType; } export class PerspectiveDataProvider { diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index dd6bf3910..13ca89456 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -15,6 +15,8 @@ import { MultipleDatabaseInfo, PerspectiveConfig, PerspectiveCustomJoinConfig, + PerspectiveDatabaseConfig, + PerspectiveDatabaseEngineType, PerspectiveFilterColumnInfo, PerspectiveNodeConfig, PerspectiveReferenceConfig, @@ -28,11 +30,7 @@ import _uniqBy from 'lodash/uniqBy'; import _sortBy from 'lodash/sortBy'; import _cloneDeepWith from 'lodash/cloneDeepWith'; import _findIndex from 'lodash/findIndex'; -import { - PerspectiveDatabaseConfig, - PerspectiveDataLoadProps, - PerspectiveDataProvider, -} from './PerspectiveDataProvider'; +import { PerspectiveDataLoadProps, PerspectiveDataProvider } from './PerspectiveDataProvider'; import stableStringify from 'json-stable-stringify'; import { getFilterType, parseFilter } from 'dbgate-filterparser'; import { FilterType } from 'dbgate-filterparser/lib/types'; @@ -119,6 +117,9 @@ export abstract class PerspectiveTreeNode { } return this.parentNode.parentTableNode; } + get engineType(): PerspectiveDatabaseEngineType { + return null; + } abstract getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps; get isRoot() { return this.parentNode == null; @@ -426,7 +427,9 @@ export abstract class PerspectiveTreeNode { return ( (this.parentNode?.isRoot || this.parentNode?.supportsParentFilter) && this.parentNode?.databaseConfig?.conid == this.databaseConfig?.conid && - this.parentNode?.databaseConfig?.database == this.databaseConfig?.database + this.parentNode?.databaseConfig?.database == this.databaseConfig?.database && + this.engineType == 'sqldb' && + this.parentNode?.engineType == 'sqldb' ); } @@ -532,6 +535,10 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode { ); } + get engineType() { + return this.parentNode.engineType; + } + matchChildRow(parentRow: any, childRow: any): boolean { if (!this.foreignKey) return false; return parentRow[this.foreignKey.columns[0].columnName] == childRow[this.foreignKey.columns[0].refColumnName]; @@ -942,6 +949,10 @@ export class PerspectiveTableNode extends PerspectiveTreeNode { super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId); } + get engineType(): PerspectiveDatabaseEngineType { + return isCollectionInfo(this.table) ? 'docdb' : 'sqldb'; + } + getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { const isMongo = isCollectionInfo(this.table); return { From 225518df3ea5f71bfe7f5db20aa1c6331e099a22 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 13 Oct 2022 14:09:08 +0200 Subject: [PATCH 19/24] show json icon in perspectives --- packages/datalib/src/PerspectiveDataPattern.ts | 3 ++- packages/datalib/src/PerspectiveTreeNode.ts | 7 +++++++ packages/web/src/designer/ColumnLine.svelte | 5 ++++- packages/web/src/elements/ColumnLabel.svelte | 3 ++- packages/web/src/perspectives/PerspectiveCell.svelte | 2 +- packages/web/src/perspectives/PerspectiveDesigner.svelte | 8 ++++++++ 6 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/datalib/src/PerspectiveDataPattern.ts b/packages/datalib/src/PerspectiveDataPattern.ts index d51e20349..876ffc00a 100644 --- a/packages/datalib/src/PerspectiveDataPattern.ts +++ b/packages/datalib/src/PerspectiveDataPattern.ts @@ -7,7 +7,7 @@ import _isBoolean from 'lodash/isBoolean'; import _isArray from 'lodash/isArray'; import { safeJsonParse } from 'dbgate-tools'; -export type PerspectiveDataPatternColumnType = 'null' | 'string' | 'number' | 'boolean' | 'json'; +export type PerspectiveDataPatternColumnType = 'null' | 'oid' | 'string' | 'number' | 'boolean' | 'json'; export interface PerspectiveDataPatternColumn { name: string; @@ -29,6 +29,7 @@ function detectValueType(value): PerspectiveDataPatternColumnType { if (_isString(value)) return 'string'; if (_isNumber(value)) return 'number'; if (_isBoolean(value)) return 'boolean'; + if (value?.$oid) return 'oid'; if (_isPlainObject(value) || _isArray(value)) return 'json'; if (value == null) return 'null'; } diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index 13ca89456..ae9d8cd9c 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -818,6 +818,9 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { // } get icon() { + if (this.column.types.includes('json')) { + return 'img json'; + } return 'img column'; } @@ -885,6 +888,10 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { } get filterInfo(): PerspectiveFilterColumnInfo { + if (this.isChildColumn) { + return null; + } + return { columnName: this.columnName, filterType: this.filterType, diff --git a/packages/web/src/designer/ColumnLine.svelte b/packages/web/src/designer/ColumnLine.svelte index 096d542d1..4a3284e8a 100644 --- a/packages/web/src/designer/ColumnLine.svelte +++ b/packages/web/src/designer/ColumnLine.svelte @@ -61,6 +61,9 @@ } $: sortOrderProps = settings?.getSortOrderProps ? settings?.getSortOrderProps(designerId, column.columnName) : null; + $: iconOverride = settings?.getColumnIconOverride + ? settings?.getColumnIconOverride(designerId, column.columnName) + : null;
{/if} - + {#if designerColumn?.filter} {/if} diff --git a/packages/web/src/elements/ColumnLabel.svelte b/packages/web/src/elements/ColumnLabel.svelte index abd6b11b7..ffb77ba86 100644 --- a/packages/web/src/elements/ColumnLabel.svelte +++ b/packages/web/src/elements/ColumnLabel.svelte @@ -23,8 +23,9 @@ export let foreignKey; export let conid = undefined; export let database = undefined; + export let iconOverride = undefined; - $: icon = getColumnIcon($$props, forceIcon); + $: icon = iconOverride || getColumnIcon($$props, forceIcon); diff --git a/packages/web/src/perspectives/PerspectiveCell.svelte b/packages/web/src/perspectives/PerspectiveCell.svelte index e4750dfd6..b95168b74 100644 --- a/packages/web/src/perspectives/PerspectiveCell.svelte +++ b/packages/web/src/perspectives/PerspectiveCell.svelte @@ -23,7 +23,7 @@ {:else} (no image) {/if} - {:else if _.isArray(value) || _.isPlainObject(value)} + {:else if !value.$oid && (_.isArray(value) || _.isPlainObject(value))} {:else} diff --git a/packages/web/src/perspectives/PerspectiveDesigner.svelte b/packages/web/src/perspectives/PerspectiveDesigner.svelte index 44639b35e..7fb2fd625 100644 --- a/packages/web/src/perspectives/PerspectiveDesigner.svelte +++ b/packages/web/src/perspectives/PerspectiveDesigner.svelte @@ -241,6 +241,14 @@ const orderIndex = sort.length > 1 ? _.findIndex(sort, x => x.columnName == columnName) : -1; return { order, orderIndex }; }, + getColumnIconOverride: (designerId, columnName) => { + const pattern = dataPatterns?.[designerId]; + const column = pattern?.columns.find(x => x.name == columnName); + if (column?.types?.includes('json')) { + return 'img json'; + } + return null; + }, isColumnFiltered: (designerId, columnName) => { return !!config.nodes.find(x => x.designerId == designerId)?.filters?.[columnName]; }, From bf4eb19ef5d01ac591812728b17592f90e11512e Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 13 Oct 2022 15:01:45 +0200 Subject: [PATCH 20/24] perspective fixes --- packages/datalib/src/PerspectiveTreeNode.ts | 17 +++- .../src/perspectives/PerspectiveTable.svelte | 98 +++++++++++-------- 2 files changed, 71 insertions(+), 44 deletions(-) diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index ae9d8cd9c..f3fdd0704 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -715,6 +715,7 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode { pureName: this.foreignKey.refTableName, conid: this.databaseConfig.conid, database: this.databaseConfig.database, + objectTypeField: this.table.objectTypeField, }; } return null; @@ -743,8 +744,9 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { refTable: TableInfo; constructor( - public owner: NamedObjectInfo, + public table: TableInfo | ViewInfo | CollectionInfo, public column: PerspectiveDataPatternColumn, + public tableColumn: ColumnInfo, dbs: MultipleDatabaseInfo, config: PerspectiveConfig, setConfig: ChangePerspectiveConfigFunc, @@ -848,10 +850,11 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { } get isSortable() { - return true; + return !this.isChildColumn; } get filterType(): FilterType { + if (this.tableColumn) return getFilterType(this.tableColumn.dataType); return 'mongo'; } @@ -859,8 +862,9 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { return this.column.columns.map( column => new PerspectivePatternColumnNode( - this.owner, + this.table, column, + this.tableColumn, this.dbs, this.config, this.setConfig, @@ -895,8 +899,8 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { return { columnName: this.columnName, filterType: this.filterType, - pureName: this.owner.pureName, - schemaName: this.owner.schemaName, + pureName: this.table.pureName, + schemaName: this.table.schemaName, foreignKey: this.foreignKey, }; } @@ -1013,6 +1017,7 @@ export class PerspectiveTableNode extends PerspectiveTreeNode { pureName: this.table.pureName, conid: this.databaseConfig.conid, database: this.databaseConfig.database, + objectTypeField: this.table.objectTypeField, }; } @@ -1309,6 +1314,7 @@ export function getTableChildPerspectiveNodes( ? new PerspectivePatternColumnNode( table, pattern?.columns?.find(x => x.name == col.columnName), + col, dbs, config, setConfig, @@ -1338,6 +1344,7 @@ export function getTableChildPerspectiveNodes( new PerspectivePatternColumnNode( table, col, + null, dbs, config, setConfig, diff --git a/packages/web/src/perspectives/PerspectiveTable.svelte b/packages/web/src/perspectives/PerspectiveTable.svelte index 32a9b97d0..09277e76d 100644 --- a/packages/web/src/perspectives/PerspectiveTable.svelte +++ b/packages/web/src/perspectives/PerspectiveTable.svelte @@ -16,6 +16,7 @@ ChangePerspectiveConfigFunc, PerspectiveConfig, PerspectiveDisplay, + PerspectivePatternColumnNode, PerspectiveTableColumnNode, PerspectiveTreeNode, PERSPECTIVE_PAGE_SIZE, @@ -41,6 +42,24 @@ import { getFilterValueExpression } from 'dbgate-filterparser'; import StatusBarTabItem from '../widgets/StatusBarTabItem.svelte'; + const TABS_BY_FIELD = { + tables: { + text: 'table', + tabComponent: 'TableDataTab', + icon: 'img table', + }, + views: { + text: 'view', + tabComponent: 'ViewDataTab', + icon: 'img view', + }, + collections: { + text: 'collection', + tabComponent: 'CollectionDataTab', + icon: 'img collection', + }, + }; + const dbg = debug('dbgate:PerspectiveTable'); export const activator = createActivator('PerspectiveTable', true, ['Designer']); @@ -210,24 +229,28 @@ const tableNode = root?.findNodeByDesignerId(tableNodeDesignerId); if (tableNode?.headerTableAttributes) { - const { pureName, schemaName, conid, database } = tableNode?.headerTableAttributes; - res.push({ - text: `Open table ${pureName}`, - onClick: () => { - openNewTab({ - title: pureName, - icon: 'img table', - tabComponent: 'TableDataTab', - props: { - schemaName, - pureName, - conid: conid, - database: database, - objectTypeField: 'tables', - }, - }); - }, - }); + const { pureName, schemaName, conid, database, objectTypeField } = tableNode?.headerTableAttributes; + console.log('objectTypeField', objectTypeField); + const tab = TABS_BY_FIELD[objectTypeField]; + if (tab) { + res.push({ + text: `Open ${tab.text} ${pureName}`, + onClick: () => { + openNewTab({ + title: pureName, + icon: tab.icon, + tabComponent: tab.tabComponent, + props: { + schemaName, + pureName, + conid: conid, + database: database, + objectTypeField, + }, + }); + }, + }); + } } const setColumnDisplay = type => { @@ -291,42 +314,39 @@ const value = display.rows[rowIndex].rowData[columnIndex]; const { dataNode } = column; - if (dataNode instanceof PerspectiveTableColumnNode) { + if ( + dataNode.filterInfo && + (dataNode instanceof PerspectiveTableColumnNode || dataNode instanceof PerspectivePatternColumnNode) + ) { const { table } = dataNode; - let tabComponent = null; - let icon = null; - let objectTypeField = null; - if (dataNode.isTable) { - tabComponent = 'TableDataTab'; - icon = 'img table'; - objectTypeField = 'tables'; - } - if (dataNode.isView) { - tabComponent = 'ViewDataTab'; - icon = 'img view'; - objectTypeField = 'views'; - } - if (tabComponent) { + + const tab = TABS_BY_FIELD[table.objectTypeField]; + const filterExpression = getFilterValueExpression( + value, + dataNode instanceof PerspectiveTableColumnNode ? dataNode.column.dataType : null + ); + + if (tab) { res.push({ - text: 'Open filtered table', + text: 'Open filtered grid', onClick: () => { openNewTab( { title: table.pureName, - icon, - tabComponent, + icon: tab.icon, + tabComponent: tab.tabComponent, props: { schemaName: table.schemaName, pureName: table.pureName, conid, database, - objectTypeField, + objectTypeField: table.objectTypeField, }, }, { grid: { filters: { - [dataNode.columnName]: getFilterValueExpression(value, dataNode.column.dataType), + [dataNode.columnName]: filterExpression, }, // isFormView: true, }, @@ -350,7 +370,7 @@ ...n, filters: { ...n.filters, - [dataNode.columnName]: getFilterValueExpression(value, dataNode.column.dataType), + [dataNode.columnName]: filterExpression, }, } : n From 69a87bc076179dcc744ec70c7f9a30b874e6880a Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 13 Oct 2022 15:22:23 +0200 Subject: [PATCH 21/24] perspective expand fix --- packages/datalib/src/PerspectiveTreeNode.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index f3fdd0704..4518c149f 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -108,14 +108,14 @@ export abstract class PerspectiveTreeNode { get namedObject(): NamedObjectInfo { return null; } - get parentTableNode(): PerspectiveTableNode { + get tableNodeOrParent(): PerspectiveTableNode { if (this instanceof PerspectiveTableNode) { return this; } if (this.parentNode == null) { return null; } - return this.parentNode.parentTableNode; + return this.parentNode.tableNodeOrParent; } get engineType(): PerspectiveDatabaseEngineType { return null; @@ -289,14 +289,15 @@ export abstract class PerspectiveTreeNode { [field]: isIncluded ? [...(n[field] || []), this.codeName] : (n[field] || []).filter(x => x != this.codeName), }); - const [cfgChanged, nodeCfg] = this.parentTableNode?.ensureNodeConfig(cfg); + const [cfgChanged, nodeCfg] = this.parentNode?.tableNodeOrParent?.ensureNodeConfig(cfg); - return { + const res = { ...cfgChanged, nodes: cfgChanged.nodes.map(n => - n.designerId == (this.parentTableNode?.designerId || nodeCfg?.designerId) ? changedFields(n) : n + n.designerId == (this.parentNode?.tableNodeOrParent?.designerId || nodeCfg?.designerId) ? changedFields(n) : n ), }; + return res; }); } @@ -756,7 +757,7 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { designerId: string ) { super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId); - this.parentNodeConfig = this.parentTableNode?.nodeConfig; + this.parentNodeConfig = this.tableNodeOrParent?.nodeConfig; } get isChildColumn() { @@ -812,7 +813,7 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { get generatesHiearchicGridColumn() { // console.log('generatesHiearchicGridColumn', this.parentTableNode?.nodeConfig?.checkedColumns, this.codeName + '::'); - return !!this.parentTableNode?.nodeConfig?.checkedColumns?.find(x => x.startsWith(this.codeName + '::')); + return !!this.tableNodeOrParent?.nodeConfig?.checkedColumns?.find(x => x.startsWith(this.codeName + '::')); } // get generatesHiearchicGridColumn() { From 494c3c8e4a3d9969e129d8a983aa74d8cb105a23 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 13 Oct 2022 15:28:50 +0200 Subject: [PATCH 22/24] remap Command+H on mac #390 --- packages/web/src/commands/stdCommands.ts | 2 +- packages/web/src/datagrid/DataGridCore.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web/src/commands/stdCommands.ts b/packages/web/src/commands/stdCommands.ts index d5d7834f8..14961db3c 100644 --- a/packages/web/src/commands/stdCommands.ts +++ b/packages/web/src/commands/stdCommands.ts @@ -605,7 +605,7 @@ export function registerFileCommands({ registerCommand({ id: idPrefix + '.replace', category, - keyText: 'CtrlOrCommand+H', + keyText: isMac() ? 'Alt+Command+F' : 'CtrlOrCommand+H', name: 'Replace', testEnabled: () => getCurrentEditor() != null, onClick: () => getCurrentEditor().replace(), diff --git a/packages/web/src/datagrid/DataGridCore.svelte b/packages/web/src/datagrid/DataGridCore.svelte index 83b7bc7f1..15e4f998b 100644 --- a/packages/web/src/datagrid/DataGridCore.svelte +++ b/packages/web/src/datagrid/DataGridCore.svelte @@ -200,7 +200,7 @@ id: 'dataGrid.hideColumn', category: 'Data grid', name: 'Hide column', - keyText: 'CtrlOrCommand+H', + keyText: isMac() ? 'Alt+Command+F' : 'CtrlOrCommand+H', testEnabled: () => getCurrentDataGrid() != null, onClick: () => getCurrentDataGrid().hideColumn(), }); From 42333a97b8d4eb8b717a133f6ed0ad663000038b Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 13 Oct 2022 15:36:24 +0200 Subject: [PATCH 23/24] v5.1.5-beta.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2ad363109..eddfd78ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "5.1.5-beta.3", + "version": "5.1.5-beta.4", "name": "dbgate-all", "workspaces": [ "packages/*", From 0497f541cb1505087161a4b805c55780f9b71585 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 15 Oct 2022 22:08:57 +0200 Subject: [PATCH 24/24] changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 605c8deae..da78e8708 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ Builds: - linux - application for linux - win - application for Windows +### 5.1.5 +- ADDED: Support perspectives for MongoDB - MongoDB query designer +- ADDED: Show JSON content directly in the overview #395 +- CHANGED: OSX Command H shortcut for hiding window #390 +- ADDED: Uppercase Autocomplete Suggestions #389 +- FIXED: Record view left/right arrows cause start record number to be treated as string #388 +- FIXED: MongoDb ObjectId behaviour not consistent in nested objects #387 +- FIXED: demo.dbgate.org - beta version crash 5.1.5-beta.3 #386 +- ADDED: connect via socket - configurable via environment variables #358 + ### 5.1.4 - ADDED: Drop database commands #384 - ADDED: Customizable Redis key separator #379